Browse Source

Merge branch 'master' into shaderpipeline

Significant adaptation was needed and is included in this commit

Also use alloca only a single time in issue_parameters() to reduce stack space
usage
rdb 1 year ago
parent
commit
9d7f5db07e
100 changed files with 1999 additions and 3108 deletions
  1. 91 61
      .github/workflows/ci.yml
  2. 0 39
      .github/workflows/lint.yml
  3. 23 0
      .github/workflows/mypy.yml
  4. 0 77
      .github/workflows/review.yml
  5. 3 1
      .gitignore
  6. 4 0
      BACKERS.md
  7. 3 3
      README.md
  8. 6 1
      cmake/install/Panda3DConfig.cmake
  9. 3 3
      contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py
  10. 1 1
      contrib/src/panda3dtoolsgui/setup.py
  11. 1 4
      contrib/src/sceneeditor/seCameraControl.py
  12. 2 8
      contrib/src/sceneeditor/seManipulation.py
  13. 7 16
      direct/src/actor/Actor.py
  14. 3 6
      direct/src/cluster/ClusterClient.py
  15. 3 7
      direct/src/cluster/ClusterServer.py
  16. 6 14
      direct/src/dcparser/dcClass_ext.cxx
  17. 1 2
      direct/src/dcparser/dcPacker_ext.cxx
  18. 438 312
      direct/src/dcparser/dcParser.cxx.prebuilt
  19. 61 45
      direct/src/dcparser/dcParser.h.prebuilt
  20. 5 1
      direct/src/dcparser/dcParser.yxx
  21. 1 1
      direct/src/directbase/DirectStart.py
  22. 1 1
      direct/src/directbase/TestStart.py
  23. 1 1
      direct/src/directbase/ThreeUpStart.py
  24. 6 2
      direct/src/directdevices/DirectJoybox.py
  25. 35 27
      direct/src/directnotify/DirectNotify.py
  26. 14 10
      direct/src/directnotify/Logger.py
  27. 46 41
      direct/src/directnotify/Notifier.py
  28. 33 23
      direct/src/directnotify/RotatingLog.py
  29. 187 166
      direct/src/directtools/DirectCameraControl.py
  30. 4 3
      direct/src/directtools/DirectGrid.py
  31. 1 3
      direct/src/directtools/DirectLights.py
  32. 204 180
      direct/src/directtools/DirectManipulation.py
  33. 59 39
      direct/src/directtools/DirectSession.py
  34. 60 34
      direct/src/dist/FreezeTool.py
  35. 25 15
      direct/src/dist/commands.py
  36. 3 1
      direct/src/distributed/AsyncRequest.py
  37. 0 38
      direct/src/distributed/CRDataCache.py
  38. 7 0
      direct/src/distributed/CachedDOData.py
  39. 3 0
      direct/src/distributed/ClientRepositoryBase.py
  40. 1 1
      direct/src/distributed/DistributedCartesianGrid.py
  41. 2 2
      direct/src/distributed/DistributedObjectAI.py
  42. 1 1
      direct/src/distributed/DistributedObjectUD.py
  43. 2 8
      direct/src/distributed/DoCollectionManager.py
  44. 5 4
      direct/src/distributed/DoInterestManager.py
  45. 4 2
      direct/src/distributed/GridParent.py
  46. 142 137
      direct/src/distributed/MsgTypes.py
  47. 15 18
      direct/src/distributed/MsgTypesCMU.py
  48. 9 9
      direct/src/distributed/cConnectionRepository.cxx
  49. 12 4
      direct/src/extensions_native/NodePath_extensions.py
  50. 3 1
      direct/src/fsm/ClassicFSM.py
  51. 3 1
      direct/src/fsm/State.py
  52. 4 153
      direct/src/fsm/StatePush.py
  53. 3 1
      direct/src/gui/DirectDialog.py
  54. 1 1
      direct/src/gui/DirectEntry.py
  55. 10 5
      direct/src/gui/DirectGuiBase.py
  56. 1 1
      direct/src/gui/DirectGuiGlobals.py
  57. 1 1
      direct/src/gui/OnscreenText.py
  58. 3 7
      direct/src/interval/FunctionInterval.py
  59. 0 222
      direct/src/interval/IntervalTest.py
  60. 3 4
      direct/src/interval/cInterval_ext.cxx
  61. 1 3
      direct/src/leveleditor/HotKeyUI.py
  62. 1 1
      direct/src/leveleditor/LevelEditorStart.py
  63. 28 27
      direct/src/leveleditor/LevelEditorUIBase.py
  64. 3 0
      direct/src/leveleditor/LevelLoader.py
  65. 2 1
      direct/src/leveleditor/testData.py
  66. 3 1
      direct/src/motiontrail/MotionTrail.py
  67. 1 1
      direct/src/particles/ParticleEffect.py
  68. 1 3
      direct/src/showbase/BulletinBoard.py
  69. 4 3
      direct/src/showbase/ContainerLeakDetector.py
  70. 7 10
      direct/src/showbase/ContainerReport.py
  71. 0 144
      direct/src/showbase/CountedResource.py
  72. 1 1
      direct/src/showbase/DConfig.py
  73. 3 29
      direct/src/showbase/DistancePhasedNode.py
  74. 5 14
      direct/src/showbase/ExceptionVarDump.py
  75. 1 2
      direct/src/showbase/GarbageReport.py
  76. 1 3
      direct/src/showbase/JobManager.py
  77. 11 8
      direct/src/showbase/Loader.py
  78. 4 12
      direct/src/showbase/Messenger.py
  79. 3 11
      direct/src/showbase/ObjectPool.py
  80. 4 2
      direct/src/showbase/ObjectReport.py
  81. 1 3
      direct/src/showbase/OnScreenDebug.py
  82. 53 97
      direct/src/showbase/PythonUtil.py
  83. 22 14
      direct/src/showbase/ShowBase.py
  84. 8 3
      direct/src/showbase/ShowBaseGlobal.py
  85. 2 1
      direct/src/showbase/TkGlobal.py
  86. 1 1
      direct/src/showbase/VerboseImport.py
  87. 2 1
      direct/src/showbase/WxGlobal.py
  88. 2 4
      direct/src/showbase/extend_frozen.c
  89. 8 6
      direct/src/showutil/TexMemWatcher.py
  90. 1 1
      direct/src/showutil/TexViewer.py
  91. 2 2
      direct/src/stdpy/pickle.py
  92. 3 111
      direct/src/stdpy/threading.py
  93. 1 90
      direct/src/stdpy/threading2.py
  94. 2 0
      direct/src/task/MiniTask.py
  95. 105 574
      direct/src/task/Task.py
  96. 90 89
      direct/src/tkpanels/DirectSessionPanel.py
  97. 1 2
      direct/src/tkpanels/FSMInspector.py
  98. 7 7
      direct/src/tkpanels/Inspector.py
  99. 2 24
      direct/src/tkpanels/ParticlePanel.py
  100. 26 33
      direct/src/tkpanels/Placer.py

+ 91 - 61
.github/workflows/ci.yml

@@ -113,23 +113,23 @@ jobs:
         libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
-        python3-setuptools
+        python3-setuptools python3-tk
 
     - name: Cache dependencies (Windows)
       if: runner.os == 'Windows'
       uses: actions/cache@v1
       with:
         path: thirdparty
-        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.13-r1
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.14-r1
     - name: Install dependencies (Windows)
       if: runner.os == 'Windows'
       shell: powershell
       run: |
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
           $wc = New-Object System.Net.WebClient
-          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip")
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip", "thirdparty-tools.zip")
           Expand-Archive -Path thirdparty-tools.zip
-          Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination .
+          Move-Item -Path thirdparty-tools/panda3d-1.10.14/thirdparty -Destination .
         }
 
     - name: ccache (non-Windows)
@@ -180,25 +180,25 @@ jobs:
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
 
-    - name: Setup Python (Python 3.7)
+    - name: Setup Python (Python 3.8)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       with:
-        python-version: '3.7'
-    - name: Configure (Python 3.7)
+        python-version: '3.8'
+    - name: Configure (Python 3.8)
       if: contains(matrix.python, 'YES')
       working-directory: build
       shell: bash
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.7 -DHAVE_PYTHON=YES
+        cmake -DWANT_PYTHON_VERSION=3.8 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.7)
+    - name: Build (Python 3.8)
       if: contains(matrix.python, 'YES')
       # BEGIN A
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
-    - name: Test (Python 3.7)
+    - name: Test (Python 3.8)
       # BEGIN B
       if: contains(matrix.python, 'YES')
       working-directory: build
@@ -207,30 +207,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
 
-    - name: Setup Python (Python 3.8)
+    - name: Setup Python (Python 3.9)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       with:
-        python-version: '3.8'
-    - name: Configure (Python 3.8)
+        python-version: '3.9'
+    - name: Configure (Python 3.9)
       if: contains(matrix.python, 'YES')
       working-directory: build
       shell: bash
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.8 -DHAVE_PYTHON=YES
+        cmake -DWANT_PYTHON_VERSION=3.9 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.8)
+    - name: Build (Python 3.9)
       if: contains(matrix.python, 'YES')
       # BEGIN A
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
-    - name: Test (Python 3.8)
+    - name: Test (Python 3.9)
       # BEGIN B
       if: contains(matrix.python, 'YES')
       working-directory: build
@@ -239,30 +239,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
 
-    - name: Setup Python (Python 3.9)
+    - name: Setup Python (Python 3.10)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       with:
-        python-version: '3.9'
-    - name: Configure (Python 3.9)
+        python-version: '3.10'
+    - name: Configure (Python 3.10)
       if: contains(matrix.python, 'YES')
       working-directory: build
       shell: bash
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.9 -DHAVE_PYTHON=YES
+        cmake -DWANT_PYTHON_VERSION=3.10 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.9)
+    - name: Build (Python 3.10)
       if: contains(matrix.python, 'YES')
       # BEGIN A
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
-    - name: Test (Python 3.9)
+    - name: Test (Python 3.10)
       # BEGIN B
       if: contains(matrix.python, 'YES')
       working-directory: build
@@ -271,30 +271,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
 
-    - name: Setup Python (Python 3.10)
+    - name: Setup Python (Python 3.11)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       with:
-        python-version: '3.10'
-    - name: Configure (Python 3.10)
+        python-version: '3.11'
+    - name: Configure (Python 3.11)
       if: contains(matrix.python, 'YES')
       working-directory: build
       shell: bash
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.10 -DHAVE_PYTHON=YES
+        cmake -DWANT_PYTHON_VERSION=3.11 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.10)
+    - name: Build (Python 3.11)
       if: contains(matrix.python, 'YES')
       # BEGIN A
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
-    - name: Test (Python 3.10)
+    - name: Test (Python 3.11)
       # BEGIN B
       if: contains(matrix.python, 'YES')
       working-directory: build
@@ -303,30 +303,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
 
-    - name: Setup Python (Python 3.11)
+    - name: Setup Python (Python 3.12)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       with:
-        python-version: '3.11'
-    - name: Configure (Python 3.11)
+        python-version: '3.12'
+    - name: Configure (Python 3.12)
       if: contains(matrix.python, 'YES')
       working-directory: build
       shell: bash
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.11 -DHAVE_PYTHON=YES
+        cmake -DWANT_PYTHON_VERSION=3.12 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.11)
+    - name: Build (Python 3.12)
       if: contains(matrix.python, 'YES')
       # BEGIN A
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
-    - name: Test (Python 3.11)
+    - name: Test (Python 3.12)
       # BEGIN B
       if: contains(matrix.python, 'YES')
       working-directory: build
@@ -335,7 +335,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
@@ -375,9 +375,9 @@ jobs:
       shell: powershell
       run: |
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.14/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       run: |
@@ -387,6 +387,20 @@ jobs:
         rmdir panda3d-1.10.14
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
 
+    - name: Set up Python 3.12
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.12'
+    - name: Build Python 3.12
+      shell: bash
+      run: |
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
+    - name: Test Python 3.12
+      shell: bash
+      run: |
+        python -m pip install -r requirements-test.txt
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+
     - name: Set up Python 3.11
       uses: actions/setup-python@v4
       with:
@@ -398,8 +412,8 @@ jobs:
     - name: Test Python 3.11
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        python -m pip install -r requirements-test.txt
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.10
       uses: actions/setup-python@v4
@@ -412,8 +426,8 @@ jobs:
     - name: Test Python 3.10
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        python -m pip install -r requirements-test.txt
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.9
       uses: actions/setup-python@v4
@@ -426,8 +440,8 @@ jobs:
     - name: Test Python 3.9
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        python -m pip install -r requirements-test.txt
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.8
       uses: actions/setup-python@v4
@@ -440,23 +454,39 @@ jobs:
     - name: Test Python 3.8
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        python -m pip install -r requirements-test.txt
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
-    - name: Set up Python 3.7
-      uses: actions/setup-python@v4
-      with:
-        python-version: '3.7'
-    - name: Build Python 3.7
-      shell: bash
+    - name: Make installer
       run: |
-        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
-    - name: Test Python 3.7
+        python makepanda/makepackage.py --verbose --lzma
+
+  emscripten:
+    if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
+    runs-on: ubuntu-22.04
+    steps:
+    - uses: actions/checkout@v1
+
+    - name: Install dependencies
+      run: |
+        sudo apt-get update
+        sudo apt-get install build-essential ninja-build bison flex
+
+    - name: Build Host Interrogate
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        mkdir -p host-build
+        cmake -S . -B host-build -DBUILD_DIRECT=OFF -DBUILD_PANDA=OFF -DBUILD_PANDATOOL=OFF -DBUILD_CONTRIB=OFF -DBUILD_DTOOL=ON -DBUILD_MODELS=OFF -DBUILD_SHARED_LIBS=OFF -DINTERROGATE_PYTHON_INTERFACE=OFF
+        cmake --build host-build --config Standard --parallel 4
+        echo host-build/bin >> $GITHUB_PATH
 
-    - name: Make installer
+    - name: Setup emsdk
+      uses: mymindstorm/setup-emsdk@v14
+      with:
+        version: 3.1.51
+        actions-cache-folder: 'emsdk-cache'
+
+    - name: Build for Emscripten
+      shell: bash
       run: |
-        python makepanda/makepackage.py --verbose --lzma
+        python3 makepanda/makepanda.py --git-commit=${{github.sha}} --target emscripten --outputdir=built --everything --no-python --verbose --threads=4

+ 0 - 39
.github/workflows/lint.yml

@@ -1,39 +0,0 @@
-name: Lint
-on: [pull_request]
-
-jobs:
-  clang-tidy:
-    runs-on: ubuntu-20.04
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        fetch-depth: 2
-    - name: Install clang-tidy
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y clang-tidy build-essential pkg-config libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev libgtk-3-dev libassimp-dev libopenexr-dev
-    - name: Prepare compile_commands.json
-      run: |
-        cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_UNITY_BUILD=OFF -DHAVE_PYTHON=OFF -DINTERROGATE_PYTHON_INTERFACE=OFF
-    - name: Copy prebuilt files
-      run: |
-        for fn in **/*.prebuilt; do
-          echo mkdir -p $(dirname "cmake/$fn");
-          echo cp "$fn" "cmake/${fn%.*}";
-        done
-    - name: Create results directory
-      run: |
-        mkdir clang-tidy-result
-    - name: Analyze
-      run: |
-        git diff -U0 HEAD^ | clang-tidy-diff -p1 -path build -export-fixes clang-tidy-result/fixes.yml
-    - name: Save PR metadata
-      run: |
-        echo ${{ github.event.number }} > clang-tidy-result/pr-id.txt
-        echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt
-        echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt
-    - name: Upload results
-      uses: actions/upload-artifact@v2
-      with:
-        name: clang-tidy-result
-        path: clang-tidy-result/

+ 23 - 0
.github/workflows/mypy.yml

@@ -0,0 +1,23 @@
+name: Run Mypy
+on: [push, pull_request]
+
+jobs:
+  mypy:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ['3.8', '3.11']
+      fail-fast: false
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install mypy==1.4.0
+      - name: Run mypy on direct
+        run: python tests/run_mypy.py

+ 0 - 77
.github/workflows/review.yml

@@ -1,77 +0,0 @@
-name: Post PR Review
-
-on:
-  workflow_run:
-    workflows: ["Lint"]
-    types: [completed]
-
-jobs:
-  clang-tidy-results:
-    # Trigger the job only if the previous (insecure) workflow completed successfully
-    if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
-    runs-on: ubuntu-20.04
-    steps:
-    - name: Download analysis results
-      uses: actions/[email protected]
-      with:
-        script: |
-          let artifacts = await github.actions.listWorkflowRunArtifacts({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              run_id: ${{ github.event.workflow_run.id }},
-          });
-          let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
-              return artifact.name == "clang-tidy-result"
-          })[0];
-          let download = await github.actions.downloadArtifact({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              artifact_id: matchArtifact.id,
-              archive_format: "zip",
-          });
-          let fs = require("fs");
-          fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
-    - name: Set environment variables
-      run: |
-        mkdir clang-tidy-result
-        unzip clang-tidy-result.zip -d clang-tidy-result
-        echo "pr_id=$(cat clang-tidy-result/pr-id.txt)" >> $GITHUB_ENV
-        echo "pr_head_repo=$(cat clang-tidy-result/pr-head-repo.txt)" >> $GITHUB_ENV
-        echo "pr_head_ref=$(cat clang-tidy-result/pr-head-ref.txt)" >> $GITHUB_ENV
-    - uses: actions/checkout@v2
-      with:
-        repository: ${{ env.pr_head_repo }}
-        ref: ${{ env.pr_head_ref }}
-        persist-credentials: false
-    - name: Redownload analysis results
-      uses: actions/[email protected]
-      with:
-        script: |
-          let artifacts = await github.actions.listWorkflowRunArtifacts({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              run_id: ${{github.event.workflow_run.id}},
-          });
-          let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
-              return artifact.name == "clang-tidy-result"
-          })[0];
-          let download = await github.actions.downloadArtifact({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              artifact_id: matchArtifact.id,
-              archive_format: "zip",
-          });
-          let fs = require("fs");
-          fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
-    - name: Extract analysis results
-      run: |
-        mkdir clang-tidy-result
-        unzip clang-tidy-result.zip -d clang-tidy-result
-    - name: Run clang-tidy-pr-comments action
-      uses: platisd/clang-tidy-pr-comments@master
-      with:
-        github_token: ${{ github.token }}
-        clang_tidy_fixes: clang-tidy-result/fixes.yml
-        pull_request_id: ${{ env.pr_id }}
-        request_changes: true
-        suggestions_per_comment: 10

+ 3 - 1
.gitignore

@@ -17,6 +17,7 @@ vgcore.*
 *.save.1
 *.sublime-workspace
 .vscode/
+.idea/
 
 # Temporary build files
 /_vfsimporter.*
@@ -51,9 +52,10 @@ cmake_install.cmake
 install_manifest.txt
 CTestTestfile.cmake
 
-# Windows
+# Operating system
 Thumbs.db
 ehthumbs.db
+.DS_Store
 
 # macOS
 .DS_Store

+ 4 - 0
BACKERS.md

@@ -17,6 +17,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Max Voss
 * Hawkheart
 * Veronica
+* Cody Sevier
+* Marek Alexa
 
 ## Enthusiasts
 
@@ -28,6 +30,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * SureBet
 * Gyedo Jeon
 * GameDev JONI
+* Max Rodriguez
+* Jethro Schoppenhorst
 
 ## Backers
 

+ 3 - 3
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-13/).
+[this page](https://www.panda3d.org/download/sdk-1-10-14/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 
-- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win32.zip
 
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based

+ 6 - 1
cmake/install/Panda3DConfig.cmake

@@ -103,6 +103,11 @@
 #               Panda3D::OpenGLES2::pandagles2
 #
 #
+#   TinyDisplay - Support for software rendering.
+#
+#               Panda3D::TinyDisplay::p3tinydisplay
+#
+#
 #   Vision    - Support for vision processing.
 #
 #               Panda3D::Vision::p3vision
@@ -126,7 +131,7 @@ set(_panda_components
   Bullet ODE
   FFmpeg
   OpenAL FMOD
-  OpenGL DX9 OpenGLES1 OpenGLES2
+  OpenGL DX9 OpenGLES1 OpenGLES2 TinyDisplay
   Vision VRPN
 )
 

+ 3 - 3
contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py

@@ -2650,11 +2650,11 @@ class main(wx.Frame):
                                             for inputFile in inputs:
                                                 if (inputFile != ''):
                                                     inputFilename = inputFile.split('\\')[-1]
-                                                    print "Compare: ", inFile, filename, inputFile, inputFilename
+                                                    print("Compare: ", inFile, filename, inputFile, inputFilename)
                                                     if inputFilename == filename:
                                                         inputTime = os.path.getmtime(inputFile)
                                                         outputTime = os.path.getmtime(inFile)
-                                                        print "Matched: ", (inputTime > outputTime)
+                                                        print("Matched: ", (inputTime > outputTime))
                                                         inputChanged = (inputTime > outputTime)
                                                         break
                                             '''
@@ -2848,7 +2848,7 @@ class main(wx.Frame):
 
         except ValueError:
             return
-        #print self.batchList
+        #print(self.batchList)
 
     def OnBatchItemEdit(self, event):
         selectedItemId = self.batchTree.GetSelections()

+ 1 - 1
contrib/src/panda3dtoolsgui/setup.py

@@ -1,4 +1,4 @@
-from distutils.core import setup
+from setuptools import setup
 import py2exe
 
 setup(console=['Panda3DToolsGUI.py'])

+ 1 - 4
contrib/src/sceneeditor/seCameraControl.py

@@ -281,10 +281,7 @@ class DirectCameraControl(DirectObject):
         angle = getCrankAngle(state.coaCenter)
         deltaAngle = angle - state.lastAngle
         state.lastAngle = angle
-        if base.config.GetBool('temp-hpr-fix',0):
-            self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
-        else:
-            self.camManipRef.setHpr(self.camManipRef, 0, 0, -deltaAngle)
+        self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
         SEditor.camera.setTransform(self.camManipRef, wrt)
         return Task.cont
 

+ 2 - 8
contrib/src/sceneeditor/seManipulation.py

@@ -324,10 +324,7 @@ class DirectManipulationControl(DirectObject):
         if self.rotateAxis == 'x':
             SEditor.widget.setP(SEditor.widget, deltaAngle)
         elif self.rotateAxis == 'y':
-            if base.config.GetBool('temp-hpr-fix',0):
-                SEditor.widget.setR(SEditor.widget, deltaAngle)
-            else:
-                SEditor.widget.setR(SEditor.widget, -deltaAngle)
+            SEditor.widget.setR(SEditor.widget, deltaAngle)
         elif self.rotateAxis == 'z':
             SEditor.widget.setH(SEditor.widget, deltaAngle)
         # Record crank angle for next time around
@@ -456,10 +453,7 @@ class DirectManipulationControl(DirectObject):
         deltaAngle = angle - state.lastAngle
         state.lastAngle = angle
         # Mouse motion edge to edge of display region results in one full turn
-        if base.config.GetBool('temp-hpr-fix',0):
-            relHpr(SEditor.widget, SEditor.camera, 0, 0, -deltaAngle)
-        else:
-            relHpr(SEditor.widget, SEditor.camera, 0, 0, deltaAngle)
+        relHpr(SEditor.widget, SEditor.camera, 0, 0, -deltaAngle)
 
     def scale3D(self, state):
         # Scale the selected node based upon up down mouse motion

+ 7 - 16
direct/src/actor/Actor.py

@@ -280,9 +280,7 @@ class Actor(DirectObject, NodePath):
                         self.setLODNode(node = lodNode)
                         # preserve numerical order for lod's
                         # this will make it easier to set ranges
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             # make a node under the LOD switch
                             # for each lod (just because!)
                             self.addLOD(str(lodName))
@@ -302,9 +300,7 @@ class Actor(DirectObject, NodePath):
                         # it is a single part actor w/LOD
                         self.setLODNode(node = lodNode)
                         # preserve order of LOD's
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             self.addLOD(str(lodName))
                             # pass in dictionary of parts
                             self.loadModel(models[lodName], lodName=lodName,
@@ -323,9 +319,7 @@ class Actor(DirectObject, NodePath):
                         if isinstance(models, dict):
                             if isinstance(models[next(iter(models))], dict):
                                 # then we have a multi-part w/ LOD
-                                sortedKeys = list(models.keys())
-                                sortedKeys.sort()
-                                for lodName in sortedKeys:
+                                for lodName in sorted(models):
                                     # iterate over both dicts
                                     for partName in anims:
                                         self.loadAnims(
@@ -336,9 +330,7 @@ class Actor(DirectObject, NodePath):
                                     self.loadAnims(anims[partName], partName)
                     elif isinstance(models, dict):
                         # then we have single-part w/ LOD
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             self.loadAnims(anims, lodName=lodName)
                     else:
                         # else it is single-part w/o LOD
@@ -603,9 +595,6 @@ class Actor(DirectObject, NodePath):
         return bundles
 
     def __updateSortedLODNames(self):
-        # Cache the sorted LOD names so we don't have to grab them
-        # and sort them every time somebody asks for the list
-        self.__sortedLODNames = list(self.__partBundleDict.keys())
         # Reverse sort the doing a string->int
         def sortKey(x):
             if not str(x).isdigit():
@@ -622,7 +611,9 @@ class Actor(DirectObject, NodePath):
             else:
                 return int(x)
 
-        self.__sortedLODNames.sort(key=sortKey, reverse=True)
+        # Cache the sorted LOD names so we don't have to grab them
+        # and sort them every time somebody asks for the list
+        self.__sortedLODNames = sorted(self.__partBundleDict, key=sortKey, reverse=True)
 
     def getLODNames(self):
         """

+ 3 - 6
direct/src/cluster/ClusterClient.py

@@ -169,12 +169,9 @@ class ClusterClient(DirectObject.DirectObject):
             self.serverList[server].sendNamedMovementDone()
 
     def redoSortedPriorities(self):
-        self.sortedControlMappings = []
-        for key in self.controlMappings:
-            self.sortedControlMappings.append([self.controlPriorities[key],
-                                               key])
-
-        self.sortedControlMappings.sort()
+        self.sortedControlMappings = sorted(
+            [self.controlPriorities[key], key] for key in self.controlMappings
+        )
 
     def moveObject(self, nodePath, object, serverList, offset, hasColor = True):
         self.notify.debug('moving object '+object)

+ 3 - 7
direct/src/cluster/ClusterServer.py

@@ -137,13 +137,9 @@ class ClusterServer(DirectObject.DirectObject):
             self.objectMappings.pop(name)
 
     def redoSortedPriorities(self):
-
-        self.sortedControlMappings = []
-        for key in self.objectMappings:
-            self.sortedControlMappings.append([self.controlPriorities[key],
-                                               key])
-
-        self.sortedControlMappings.sort()
+        self.sortedControlMappings = sorted(
+            [self.controlPriorities[key], key] for key in self.objectMappings
+        )
 
     def addControlMapping(self, objectName, controlledName, offset = None,
                           priority = 0):

+ 6 - 14
direct/src/dcparser/dcClass_ext.cxx

@@ -41,9 +41,7 @@ void Extension<DCClass>::
 set_class_def(PyObject *class_def) {
   PythonClassDefsImpl *defs = do_get_defs();
 
-  Py_XINCREF(class_def);
-  Py_XDECREF(defs->_class_def);
-  defs->_class_def = class_def;
+  Py_XSETREF(defs->_class_def, Py_XNewRef(class_def));
 }
 
 /**
@@ -53,13 +51,11 @@ set_class_def(PyObject *class_def) {
 PyObject *Extension<DCClass>::
 get_class_def() const {
   if (!has_class_def()) {
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
 
   PythonClassDefsImpl *defs = do_get_defs();
-  Py_INCREF(defs->_class_def);
-  return defs->_class_def;
+  return Py_NewRef(defs->_class_def);
 }
 
 /**
@@ -80,9 +76,7 @@ void Extension<DCClass>::
 set_owner_class_def(PyObject *owner_class_def) {
   PythonClassDefsImpl *defs = do_get_defs();
 
-  Py_XINCREF(owner_class_def);
-  Py_XDECREF(defs->_owner_class_def);
-  defs->_owner_class_def = owner_class_def;
+  Py_XSETREF(defs->_owner_class_def, Py_XNewRef(owner_class_def));
 }
 
 /**
@@ -92,13 +86,11 @@ set_owner_class_def(PyObject *owner_class_def) {
 PyObject *Extension<DCClass>::
 get_owner_class_def() const {
   if (!has_owner_class_def()) {
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
 
   PythonClassDefsImpl *defs = do_get_defs();
-  Py_INCREF(defs->_owner_class_def);
-  return defs->_owner_class_def;
+  return Py_NewRef(defs->_owner_class_def);
 }
 
 /**

+ 1 - 2
direct/src/dcparser/dcPacker_ext.cxx

@@ -173,8 +173,7 @@ unpack_object() {
 
   switch (pack_type) {
   case PT_invalid:
-    object = Py_None;
-    Py_INCREF(object);
+    object = Py_NewRef(Py_None);
     _this->unpack_skip();
     break;
 

File diff suppressed because it is too large
+ 438 - 312
direct/src/dcparser/dcParser.cxx.prebuilt


+ 61 - 45
direct/src/dcparser/dcParser.h.prebuilt

@@ -1,8 +1,9 @@
-/* A Bison parser, made by GNU Bison 3.1.  */
+/* A Bison parser, made by GNU Bison 3.8.2.  */
 
 /* Bison interface for Yacc-like parsers in C
 
-   Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+   Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -15,7 +16,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
 /* As a special exception, you may create a larger work that contains
    part or all of the Bison parser skeleton and distribute that work
@@ -30,6 +31,10 @@
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+   especially those whose name start with YY_ or yy_.  They are
+   private implementation details that can be changed or removed.  */
+
 #ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 /* Debug traces.  */
@@ -40,54 +45,63 @@
 extern int dcyydebug;
 #endif
 
-/* Token type.  */
+/* Token kinds.  */
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
   enum yytokentype
   {
-    UNSIGNED_INTEGER = 258,
-    SIGNED_INTEGER = 259,
-    REAL = 260,
-    STRING = 261,
-    IDENTIFIER = 262,
-    HEX_STRING = 263,
-    KEYWORD = 264,
-    KW_DCLASS = 265,
-    KW_STRUCT = 266,
-    KW_FROM = 267,
-    KW_IMPORT = 268,
-    KW_TYPEDEF = 269,
-    KW_KEYWORD = 270,
-    KW_SWITCH = 271,
-    KW_CASE = 272,
-    KW_DEFAULT = 273,
-    KW_BREAK = 274,
-    KW_INT8 = 275,
-    KW_INT16 = 276,
-    KW_INT32 = 277,
-    KW_INT64 = 278,
-    KW_UINT8 = 279,
-    KW_UINT16 = 280,
-    KW_UINT32 = 281,
-    KW_UINT64 = 282,
-    KW_FLOAT64 = 283,
-    KW_STRING = 284,
-    KW_BLOB = 285,
-    KW_BLOB32 = 286,
-    KW_INT8ARRAY = 287,
-    KW_INT16ARRAY = 288,
-    KW_INT32ARRAY = 289,
-    KW_UINT8ARRAY = 290,
-    KW_UINT16ARRAY = 291,
-    KW_UINT32ARRAY = 292,
-    KW_UINT32UINT8ARRAY = 293,
-    KW_CHAR = 294,
-    START_DC = 295,
-    START_PARAMETER_VALUE = 296,
-    START_PARAMETER_DESCRIPTION = 297
+    YYEMPTY = -2,
+    YYEOF = 0,                     /* "end of file"  */
+    YYerror = 256,                 /* error  */
+    YYUNDEF = 257,                 /* "invalid token"  */
+    UNSIGNED_INTEGER = 258,        /* UNSIGNED_INTEGER  */
+    SIGNED_INTEGER = 259,          /* SIGNED_INTEGER  */
+    REAL = 260,                    /* REAL  */
+    STRING = 261,                  /* STRING  */
+    IDENTIFIER = 262,              /* IDENTIFIER  */
+    HEX_STRING = 263,              /* HEX_STRING  */
+    KEYWORD = 264,                 /* KEYWORD  */
+    KW_DCLASS = 265,               /* KW_DCLASS  */
+    KW_STRUCT = 266,               /* KW_STRUCT  */
+    KW_FROM = 267,                 /* KW_FROM  */
+    KW_IMPORT = 268,               /* KW_IMPORT  */
+    KW_TYPEDEF = 269,              /* KW_TYPEDEF  */
+    KW_KEYWORD = 270,              /* KW_KEYWORD  */
+    KW_SWITCH = 271,               /* KW_SWITCH  */
+    KW_CASE = 272,                 /* KW_CASE  */
+    KW_DEFAULT = 273,              /* KW_DEFAULT  */
+    KW_BREAK = 274,                /* KW_BREAK  */
+    KW_INT8 = 275,                 /* KW_INT8  */
+    KW_INT16 = 276,                /* KW_INT16  */
+    KW_INT32 = 277,                /* KW_INT32  */
+    KW_INT64 = 278,                /* KW_INT64  */
+    KW_UINT8 = 279,                /* KW_UINT8  */
+    KW_UINT16 = 280,               /* KW_UINT16  */
+    KW_UINT32 = 281,               /* KW_UINT32  */
+    KW_UINT64 = 282,               /* KW_UINT64  */
+    KW_FLOAT64 = 283,              /* KW_FLOAT64  */
+    KW_STRING = 284,               /* KW_STRING  */
+    KW_BLOB = 285,                 /* KW_BLOB  */
+    KW_BLOB32 = 286,               /* KW_BLOB32  */
+    KW_INT8ARRAY = 287,            /* KW_INT8ARRAY  */
+    KW_INT16ARRAY = 288,           /* KW_INT16ARRAY  */
+    KW_INT32ARRAY = 289,           /* KW_INT32ARRAY  */
+    KW_UINT8ARRAY = 290,           /* KW_UINT8ARRAY  */
+    KW_UINT16ARRAY = 291,          /* KW_UINT16ARRAY  */
+    KW_UINT32ARRAY = 292,          /* KW_UINT32ARRAY  */
+    KW_UINT32UINT8ARRAY = 293,     /* KW_UINT32UINT8ARRAY  */
+    KW_CHAR = 294,                 /* KW_CHAR  */
+    START_DC = 295,                /* START_DC  */
+    START_PARAMETER_VALUE = 296,   /* START_PARAMETER_VALUE  */
+    START_PARAMETER_DESCRIPTION = 297 /* START_PARAMETER_DESCRIPTION  */
   };
+  typedef enum yytokentype yytoken_kind_t;
 #endif
-/* Tokens.  */
+/* Token kinds.  */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define REAL 260
@@ -134,6 +148,8 @@ extern int dcyydebug;
 
 extern YYSTYPE dcyylval;
 
+
 int dcyyparse (void);
 
+
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

+ 5 - 1
direct/src/dcparser/dcParser.yxx

@@ -297,6 +297,7 @@ dclass_or_struct:
 dclass:
         KW_DCLASS optional_name
 {
+  $<u.dclass>$ = current_class;
   current_class = new DCClass(dc_file, $2, false, false);
 }
         dclass_derivation '{' dclass_fields '}'
@@ -401,6 +402,7 @@ dclass_field:
 struct:
         KW_STRUCT optional_name
 {
+  $<u.dclass>$ = current_class;
   current_class = new DCClass(dc_file, $2, true, false);
 }
         struct_derivation '{' struct_fields '}'
@@ -488,6 +490,7 @@ struct_field:
 atomic_field:
         optional_name '('
 {
+  $<u.atomic>$ = current_atomic;
   if (current_class == nullptr) {
     yyerror("Cannot define a method outside of a struct or class.");
     DCClass *temp_class = new DCClass(dc_file, "temp", false, false);  // memory leak.
@@ -1243,12 +1246,13 @@ optional_name:
 switch:
         KW_SWITCH optional_name '(' parameter_or_atomic ')' '{'
 {
+  $<u.dswitch>$ = current_switch;
   current_switch = new DCSwitch($2, $4);
 }
         switch_fields '}'
 {
   $$ = current_switch;
-  current_switch = (DCSwitch *)$<u.parameter>7;
+  current_switch = $<u.dswitch>7;
 }
         ;
 

+ 1 - 1
direct/src/directbase/DirectStart.py

@@ -15,7 +15,7 @@ to and may be replaced by the following code:
    base = ShowBase()
 """
 
-__all__ = []
+__all__ = ()
 
 if __debug__:
     print('Using deprecated DirectStart interface.')

+ 1 - 1
direct/src/directbase/TestStart.py

@@ -6,7 +6,7 @@ from direct.showbase import ShowBase
 base = ShowBase.ShowBase()
 
 # Put an axis in the world:
-base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(base.render)
 
 base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)

+ 1 - 1
direct/src/directbase/ThreeUpStart.py

@@ -8,7 +8,7 @@ from direct.showbase import ThreeUpShow
 base = ThreeUpShow.ThreeUpShow()
 
 # Put an axis in the world:
-base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(base.render)
 
 base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)

+ 6 - 2
direct/src/directdevices/DirectJoybox.py

@@ -41,8 +41,12 @@ class DirectJoybox(DirectObject):
     xyzMultiplier = 1.0
     hprMultiplier = 1.0
 
-    def __init__(self, device = 'CerealBox', nodePath = base.direct.camera,
-                 headingNP = base.direct.camera):
+    def __init__(self, device = 'CerealBox', nodePath = None, headingNP = None):
+        from direct.showbase.ShowBaseGlobal import base
+        if nodePath is None:
+            nodePath = base.direct.camera
+        if headingNP is None:
+            headingNP = base.direct.camera
         # See if device manager has been initialized
         if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()

+ 35 - 27
direct/src/directnotify/DirectNotify.py

@@ -2,6 +2,10 @@
 DirectNotify module: this module contains the DirectNotify class
 """
 
+from __future__ import annotations
+
+from panda3d.core import StreamWriter
+
 from . import Notifier
 from . import Logger
 
@@ -12,39 +16,39 @@ class DirectNotify:
     mulitple notify categories via a dictionary of Notifiers.
     """
 
-    def __init__(self):
+    def __init__(self) -> None:
         """
         DirectNotify class keeps a dictionary of Notfiers
         """
-        self.__categories = {}
+        self.__categories: dict[str, Notifier.Notifier] = {}
         # create a default log file
         self.logger = Logger.Logger()
 
         # This will get filled in later by ShowBase.py with a
         # C++-level StreamWriter object for writing to standard
         # output.
-        self.streamWriter = None
+        self.streamWriter: StreamWriter | None = None
 
-    def __str__(self):
+    def __str__(self) -> str:
         """
         Print handling routine
         """
         return "DirectNotify categories: %s" % (self.__categories)
 
     #getters and setters
-    def getCategories(self):
+    def getCategories(self) -> list[str]:
         """
         Return list of category dictionary keys
         """
         return list(self.__categories.keys())
 
-    def getCategory(self, categoryName):
+    def getCategory(self, categoryName: str) -> Notifier.Notifier | None:
         """getCategory(self, string)
         Return the category with given name if present, None otherwise
         """
         return self.__categories.get(categoryName, None)
 
-    def newCategory(self, categoryName, logger=None):
+    def newCategory(self, categoryName: str, logger: Logger.Logger | None = None) -> Notifier.Notifier:
         """newCategory(self, string)
         Make a new notify category named categoryName. Return new category
         if no such category exists, else return existing category
@@ -52,9 +56,11 @@ class DirectNotify:
         if categoryName not in self.__categories:
             self.__categories[categoryName] = Notifier.Notifier(categoryName, logger)
             self.setDconfigLevel(categoryName)
-        return self.getCategory(categoryName)
+        notifier = self.getCategory(categoryName)
+        assert notifier is not None
+        return notifier
 
-    def setDconfigLevel(self, categoryName):
+    def setDconfigLevel(self, categoryName: str) -> None:
         """
         Check to see if this category has a dconfig variable
         to set the notify severity and then set that level. You cannot
@@ -77,40 +83,42 @@ class DirectNotify:
             level = 'error'
 
         category = self.getCategory(categoryName)
+        assert category is not None, f'failed to find category: {categoryName!r}'
         # Note - this print statement is making it difficult to
         # achieve "no output unless there's an error" operation - Josh
         # print ("Setting DirectNotify category: " + categoryName +
         #        " to severity: " + level)
         if level == "error":
-            category.setWarning(0)
-            category.setInfo(0)
-            category.setDebug(0)
+            category.setWarning(False)
+            category.setInfo(False)
+            category.setDebug(False)
         elif level == "warning":
-            category.setWarning(1)
-            category.setInfo(0)
-            category.setDebug(0)
+            category.setWarning(True)
+            category.setInfo(False)
+            category.setDebug(False)
         elif level == "info":
-            category.setWarning(1)
-            category.setInfo(1)
-            category.setDebug(0)
+            category.setWarning(True)
+            category.setInfo(True)
+            category.setDebug(False)
         elif level == "debug":
-            category.setWarning(1)
-            category.setInfo(1)
-            category.setDebug(1)
+            category.setWarning(True)
+            category.setInfo(True)
+            category.setDebug(True)
         else:
             print("DirectNotify: unknown notify level: " + str(level)
                    + " for category: " + str(categoryName))
 
-    def setDconfigLevels(self):
+    def setDconfigLevels(self) -> None:
         for categoryName in self.getCategories():
             self.setDconfigLevel(categoryName)
 
-    def setVerbose(self):
+    def setVerbose(self) -> None:
         for categoryName in self.getCategories():
             category = self.getCategory(categoryName)
-            category.setWarning(1)
-            category.setInfo(1)
-            category.setDebug(1)
+            assert category is not None
+            category.setWarning(True)
+            category.setInfo(True)
+            category.setDebug(True)
 
     def popupControls(self, tl = None):
         # Don't use a regular import, to prevent ModuleFinder from picking
@@ -119,5 +127,5 @@ class DirectNotify:
         NotifyPanel = importlib.import_module('direct.tkpanels.NotifyPanel')
         NotifyPanel.NotifyPanel(self, tl)
 
-    def giveNotify(self,cls):
+    def giveNotify(self, cls) -> None:
         cls.notify = self.newCategory(cls.__name__)

+ 14 - 10
direct/src/directnotify/Logger.py

@@ -1,27 +1,30 @@
 """Logger module: contains the logger class which creates and writes
    data to log files on disk"""
 
+from __future__ import annotations
+
+import io
 import time
 import math
 
 
 class Logger:
-    def __init__(self, fileName="log"):
+    def __init__(self, fileName: str = "log") -> None:
         """
         Logger constructor
         """
-        self.__timeStamp = 1
+        self.__timeStamp = True
         self.__startTime = 0.0
-        self.__logFile = None
+        self.__logFile: io.TextIOWrapper | None = None
         self.__logFileName = fileName
 
-    def setTimeStamp(self, enable):
+    def setTimeStamp(self, enable: bool) -> None:
         """
         Toggle time stamp printing with log entries on and off
         """
         self.__timeStamp = enable
 
-    def getTimeStamp(self):
+    def getTimeStamp(self) -> bool:
         """
         Return whether or not we are printing time stamps with log entries
         """
@@ -29,24 +32,25 @@ class Logger:
 
     # logging control
 
-    def resetStartTime(self):
+    def resetStartTime(self) -> None:
         """
         Reset the start time of the log file for time stamps
         """
         self.__startTime = time.time()
 
-    def log(self, entryString):
+    def log(self, entryString: str) -> None:
         """log(self, string)
         Print the given string to the log file"""
         if self.__logFile is None:
             self.__openLogFile()
+        assert self.__logFile is not None
         if self.__timeStamp:
             self.__logFile.write(self.__getTimeStamp())
         self.__logFile.write(entryString + '\n')
 
     # logging functions
 
-    def __openLogFile(self):
+    def __openLogFile(self) -> None:
         """
         Open a file for logging error/warning messages
         """
@@ -56,14 +60,14 @@ class Logger:
         logFileName = self.__logFileName + "." + st
         self.__logFile = open(logFileName, "w")
 
-    def __closeLogFile(self):
+    def __closeLogFile(self) -> None:
         """
         Close the error/warning output file
         """
         if self.__logFile is not None:
             self.__logFile.close()
 
-    def __getTimeStamp(self):
+    def __getTimeStamp(self) -> str:
         """
         Return the offset between current time and log file startTime
         """

+ 46 - 41
direct/src/directnotify/Notifier.py

@@ -2,11 +2,16 @@
 Notifier module: contains methods for handling information output
 for the programmer/user
 """
+
+from __future__ import annotations
+
+from .Logger import Logger
 from .LoggerGlobal import defaultLogger
 from direct.showbase import PythonUtil
 from panda3d.core import ConfigVariableBool, NotifyCategory, StreamWriter, Notify
 import time
 import sys
+from typing import NoReturn
 
 
 class NotifierException(Exception):
@@ -20,13 +25,13 @@ class Notifier:
     # messages instead of writing them to the console.  This is
     # particularly useful for integrating the Python notify system
     # with the C++ notify system.
-    streamWriter = None
+    streamWriter: StreamWriter | None = None
     if ConfigVariableBool('notify-integrate', True):
         streamWriter = StreamWriter(Notify.out(), False)
 
     showTime = ConfigVariableBool('notify-timestamp', False)
 
-    def __init__(self, name, logger=None):
+    def __init__(self, name: str, logger: Logger | None = None) -> None:
         """
         Parameters:
             name (str): a string name given to this Notifier instance.
@@ -42,12 +47,12 @@ class Notifier:
             self.__logger = logger
 
         # Global default levels are initialized here
-        self.__info = 1
-        self.__warning = 1
-        self.__debug = 0
-        self.__logging = 0
+        self.__info = True
+        self.__warning = True
+        self.__debug = False
+        self.__logging = False
 
-    def setServerDelta(self, delta, timezone):
+    def setServerDelta(self, delta: float, timezone: int) -> None:
         """
         Call this method on any Notify object to globally change the
         timestamp printed for each line of all Notify objects.
@@ -65,7 +70,7 @@ class Notifier:
 
         self.info("Notify clock adjusted by %s (and timezone adjusted by %s hours) to synchronize with server." % (PythonUtil.formatElapsedSeconds(delta), (time.timezone - timezone) / 3600))
 
-    def getTime(self):
+    def getTime(self) -> str:
         """
         Return the time as a string suitable for printing at the
         head of any notify message
@@ -74,14 +79,14 @@ class Notifier:
         # the task is out of focus on win32.  time.clock doesn't have this problem.
         return time.strftime(":%m-%d-%Y %H:%M:%S ", time.localtime(time.time() + self.serverDelta))
 
-    def getOnlyTime(self):
+    def getOnlyTime(self) -> str:
         """
         Return the time as a string.
         The Only in the name is referring to not showing the date.
         """
         return time.strftime("%H:%M:%S", time.localtime(time.time() + self.serverDelta))
 
-    def __str__(self):
+    def __str__(self) -> str:
         """
         Print handling routine
         """
@@ -89,26 +94,26 @@ class Notifier:
                (self.__name, self.__info, self.__warning, self.__debug, self.__logging)
 
     # Severity funcs
-    def setSeverity(self, severity):
+    def setSeverity(self, severity: int) -> None:
         from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
         if severity >= NSError:
-            self.setWarning(0)
-            self.setInfo(0)
-            self.setDebug(0)
+            self.setWarning(False)
+            self.setInfo(False)
+            self.setDebug(False)
         elif severity == NSWarning:
-            self.setWarning(1)
-            self.setInfo(0)
-            self.setDebug(0)
+            self.setWarning(True)
+            self.setInfo(False)
+            self.setDebug(False)
         elif severity == NSInfo:
-            self.setWarning(1)
-            self.setInfo(1)
-            self.setDebug(0)
+            self.setWarning(True)
+            self.setInfo(True)
+            self.setDebug(False)
         elif severity <= NSDebug:
-            self.setWarning(1)
-            self.setInfo(1)
-            self.setDebug(1)
+            self.setWarning(True)
+            self.setInfo(True)
+            self.setDebug(True)
 
-    def getSeverity(self):
+    def getSeverity(self) -> int:
         from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
         if self.getDebug():
             return NSDebug
@@ -120,7 +125,7 @@ class Notifier:
             return NSError
 
     # error funcs
-    def error(self, errorString, exception=NotifierException):
+    def error(self, errorString: object, exception: type[Exception] = NotifierException) -> NoReturn:
         """
         Raise an exception with given string and optional type:
         Exception: error
@@ -134,7 +139,7 @@ class Notifier:
         raise exception(errorString)
 
     # warning funcs
-    def warning(self, warningString):
+    def warning(self, warningString: object) -> int:
         """
         Issue the warning message if warn flag is on
         """
@@ -148,20 +153,20 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert myNotify.warning("blah")
 
-    def setWarning(self, enable):
+    def setWarning(self, enable: bool) -> None:
         """
         Enable/Disable the printing of warning messages
         """
         self.__warning = enable
 
-    def getWarning(self):
+    def getWarning(self) -> bool:
         """
         Return whether the printing of warning messages is on or off
         """
         return self.__warning
 
     # debug funcs
-    def debug(self, debugString):
+    def debug(self, debugString: object) -> int:
         """
         Issue the debug message if debug flag is on
         """
@@ -175,20 +180,20 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert myNotify.debug("blah")
 
-    def setDebug(self, enable):
+    def setDebug(self, enable: bool) -> None:
         """
         Enable/Disable the printing of debug messages
         """
         self.__debug = enable
 
-    def getDebug(self):
+    def getDebug(self) -> bool:
         """
         Return whether the printing of debug messages is on or off
         """
         return self.__debug
 
     # info funcs
-    def info(self, infoString):
+    def info(self, infoString: object) -> int:
         """
         Print the given informational string, if info flag is on
         """
@@ -202,39 +207,39 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert myNotify.info("blah")
 
-    def getInfo(self):
+    def getInfo(self) -> bool:
         """
         Return whether the printing of info messages is on or off
         """
         return self.__info
 
-    def setInfo(self, enable):
+    def setInfo(self, enable: bool) -> None:
         """
         Enable/Disable informational message  printing
         """
         self.__info = enable
 
     # log funcs
-    def __log(self, logEntry):
+    def __log(self, logEntry: str) -> None:
         """
         Determine whether to send informational message to the logger
         """
         if self.__logging:
             self.__logger.log(logEntry)
 
-    def getLogging(self):
+    def getLogging(self) -> bool:
         """
         Return 1 if logging enabled, 0 otherwise
         """
         return self.__logging
 
-    def setLogging(self, enable):
+    def setLogging(self, enable: bool) -> None:
         """
         Set the logging flag to int (1=on, 0=off)
         """
         self.__logging = enable
 
-    def __print(self, string):
+    def __print(self, string: str) -> None:
         """
         Prints the string to output followed by a newline.
         """
@@ -251,7 +256,7 @@ class Notifier:
         the function call (with parameters).
         """
         #f.f_locals['self'].__init__.im_class.__name__
-        if self.__debug:
+        if __debug__ and self.__debug:
             state = ''
             doId = ''
             if obj is not None:
@@ -285,13 +290,13 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert self.notify.debugStateCall(self)
 
-    def debugCall(self, debugString=''):
+    def debugCall(self, debugString: object = '') -> int:
         """
         If this notify is in debug mode, print the time of the
         call followed by the notifier category and
         the function call (with parameters).
         """
-        if self.__debug:
+        if __debug__ and self.__debug:
             message = str(debugString)
             string = ":%s:%s \"%s\" %s"%(
                 self.getOnlyTime(),

+ 33 - 23
direct/src/directnotify/RotatingLog.py

@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 import os
 import time
+from typing import Iterable
 
 
 class RotatingLog:
@@ -8,7 +11,12 @@ class RotatingLog:
     to a new file if the prior file is too large or after a time interval.
     """
 
-    def __init__(self, path="./log_file", hourInterval=24, megabyteLimit=1024):
+    def __init__(
+        self,
+        path: str = "./log_file",
+        hourInterval: int | None = 24,
+        megabyteLimit: int | None = 1024,
+    ) -> None:
         """
         Args:
             path: a full or partial path with file name.
@@ -28,43 +36,44 @@ class RotatingLog:
         if megabyteLimit is not None:
             self.sizeLimit = megabyteLimit*1024*1024
 
-    def __del__(self):
+    def __del__(self) -> None:
         self.close()
 
-    def close(self):
+    def close(self) -> None:
         if hasattr(self, "file"):
             self.file.flush()
             self.file.close()
             self.closed = self.file.closed
             del self.file
         else:
-            self.closed = 1
+            self.closed = True
 
-    def shouldRotate(self):
+    def shouldRotate(self) -> bool:
         """
         Returns a bool about whether a new log file should
         be created and written to (while at the same time
         stopping output to the old log file and closing it).
         """
         if not hasattr(self, "file"):
-            return 1
+            return True
         if self.timeLimit is not None and time.time() > self.timeLimit:
-            return 1
+            return True
         if self.sizeLimit is not None and self.file.tell() > self.sizeLimit:
-            return 1
-        return 0
+            return True
+        return False
 
-    def filePath(self):
-        dateString=time.strftime("%Y_%m_%d_%H", time.localtime())
+    def filePath(self) -> str:
+        dateString = time.strftime("%Y_%m_%d_%H", time.localtime())
         for i in range(26):
-            path="%s_%s_%s.log"%(self.path, dateString, chr(i+97))
-            if not os.path.exists(path) or os.stat(path)[6] < self.sizeLimit:
+            limit = self.sizeLimit
+            path = "%s_%s_%s.log" % (self.path, dateString, chr(i+97))
+            if limit is None or not os.path.exists(path) or os.stat(path)[6] < limit:
                 return path
         # Hmm, 26 files are full?  throw the rest in z:
         # Maybe we should clear the self.sizeLimit here... maybe.
         return path
 
-    def rotate(self):
+    def rotate(self) -> None:
         """
         Rotate the log now.  You normally shouldn't need to call this.
         See write().
@@ -83,17 +92,17 @@ class RotatingLog:
             self.closed = self.file.closed
             self.mode = self.file.mode
             self.name = self.file.name
-            self.softspace = self.file.softspace
             #self.encoding = self.file.encoding # Python 2.3
             #self.newlines = self.file.newlines # Python 2.3, maybe
 
             if self.timeLimit is not None and time.time() > self.timeLimit:
+                assert self.timeInterval is not None
                 self.timeLimit=time.time()+self.timeInterval
         else:
             # We'll keep writing to the old file, if available.
             print("RotatingLog error: Unable to open new log file \"%s\"." % (path,))
 
-    def write(self, data):
+    def write(self, data: str) -> int | None:
         """
         Write the data to either the current log or a new one,
         depending on the return of shouldRotate() and whether
@@ -105,14 +114,15 @@ class RotatingLog:
             r = self.file.write(data)
             self.file.flush()
             return r
+        return None
 
-    def flush(self):
+    def flush(self) -> None:
         return self.file.flush()
 
-    def fileno(self):
+    def fileno(self) -> int:
         return self.file.fileno()
 
-    def isatty(self):
+    def isatty(self) -> bool:
         return self.file.isatty()
 
     def __next__(self):
@@ -131,14 +141,14 @@ class RotatingLog:
     def xreadlines(self):
         return self.file.xreadlines()
 
-    def seek(self, offset, whence=0):
+    def seek(self, offset: int, whence: int = 0) -> int:
         return self.file.seek(offset, whence)
 
-    def tell(self):
+    def tell(self) -> int:
         return self.file.tell()
 
-    def truncate(self, size):
+    def truncate(self, size: int | None) -> int:
         return self.file.truncate(size)
 
-    def writelines(self, sequence):
+    def writelines(self, sequence: Iterable[str]) -> None:
         return self.file.writelines(sequence)

+ 187 - 166
direct/src/directtools/DirectCameraControl.py

@@ -1,6 +1,7 @@
 import math
 from panda3d.core import BitMask32, Mat4, NodePath, Point3, VBase3, Vec3, Vec4, rad2Deg
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase import ShowBaseGlobal
 from .DirectUtil import CLAMP, useDirectRenderStyle
 from .DirectGeometry import getCrankAngle, getScreenXY
 from . import DirectGlobals as DG
@@ -26,7 +27,7 @@ class DirectCameraControl(DirectObject):
         self.orthoViewRoll = 0.0
         self.lastView = 0
         self.coa = Point3(0, 100, 0)
-        self.coaMarker = base.loader.loadModel('models/misc/sphere')
+        self.coaMarker = ShowBaseGlobal.loader.loadModel('models/misc/sphere')
         self.coaMarker.setName('DirectCameraCOAMarker')
         self.coaMarker.setTransparency(1)
         self.coaMarker.setColor(1, 0, 0, 0)
@@ -37,8 +38,8 @@ class DirectCameraControl(DirectObject):
         self.fLockCOA = 0
         self.nullHitPointCount = 0
         self.cqEntries = []
-        self.coaMarkerRef = base.direct.group.attachNewNode('coaMarkerRef')
-        self.camManipRef = base.direct.group.attachNewNode('camManipRef')
+        self.coaMarkerRef = ShowBaseGlobal.direct.group.attachNewNode('coaMarkerRef')
+        self.camManipRef = ShowBaseGlobal.direct.group.attachNewNode('camManipRef')
         self.switchDirBelowZero = True
         self.manipulateCameraTask = None
         self.manipulateCameraInterval = None
@@ -112,11 +113,6 @@ class DirectCameraControl(DirectObject):
         self.perspCollPlane2 = None # [gjeon] used for new LE
 
     def toggleMarkerVis(self):
-##        if base.direct.cameraControl.coaMarker.isHidden():
-##            base.direct.cameraControl.coaMarker.show()
-##        else:
-##            base.direct.cameraControl.coaMarker.hide()
-
         if self.coaMarker.isHidden():
             self.coaMarker.show()
         else:
@@ -132,11 +128,14 @@ class DirectCameraControl(DirectObject):
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
             # Record time of start of mouse interaction
+            base = ShowBaseGlobal.base
             self.startT = base.clock.getFrameTime()
             self.startF = base.clock.getFrameCount()
             # If the cam is orthogonal, spawn differentTask
-            if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
-               base.direct.camera.getName() != 'persp':
+            direct = ShowBaseGlobal.direct
+            if hasattr(direct, "manipulationControl") and \
+               direct.manipulationControl.fMultiView and \
+               direct.camera.getName() != 'persp':
                 self.spawnOrthoZoom()
             else:
                 # Start manipulation
@@ -167,7 +166,9 @@ class DirectCameraControl(DirectObject):
 
     def mouseFlyStart(self, modifiers):
         # Record undo point
-        # base.direct.pushUndo([base.direct.camera])            # Wasteful use of undo
+        base = ShowBaseGlobal.base
+        direct = ShowBaseGlobal.direct
+        #direct.pushUndo([direct.camera])            # Wasteful use of undo
         if self.useMayaCamControls and modifiers == 4:          # alt is down, use maya controls
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
@@ -176,15 +177,16 @@ class DirectCameraControl(DirectObject):
             self.startF = base.clock.getFrameCount()
             # Start manipulation
             # If the cam is orthogonal, spawn differentTask
-            if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
-               base.direct.camera.getName() != 'persp':
+            if hasattr(direct, "manipulationControl") and \
+               direct.manipulationControl.fMultiView and \
+               direct.camera.getName() != 'persp':
                 self.spawnOrthoTranslate()
             else:
                 self.spawnXZTranslate()
             self.altDown = 1
         elif not self.useMayaCamControls:
             # Where are we in the display region?
-            if ((abs(base.direct.dr.mouseX) < 0.9) and (abs(base.direct.dr.mouseY) < 0.9)):
+            if abs(direct.dr.mouseX) < 0.9 and abs(direct.dr.mouseY) < 0.9:
                 # MOUSE IS IN CENTRAL REGION
                 # Hide the marker for this kind of motion
                 self.coaMarker.hide()
@@ -194,19 +196,18 @@ class DirectCameraControl(DirectObject):
                 # Start manipulation
                 self.spawnXZTranslateOrHPanYZoom()
                 # END MOUSE IN CENTRAL REGION
+            elif abs(direct.dr.mouseX) > 0.9 and abs(direct.dr.mouseY) > 0.9:
+                # Mouse is in corners, spawn roll task
+                self.spawnMouseRollTask()
             else:
-                if ((abs(base.direct.dr.mouseX) > 0.9) and
-                    (abs(base.direct.dr.mouseY) > 0.9)):
-                    # Mouse is in corners, spawn roll task
-                    self.spawnMouseRollTask()
-                else:
-                    # Mouse is in outer frame, spawn mouseRotateTask
-                    self.spawnMouseRotateTask()
+                # Mouse is in outer frame, spawn mouseRotateTask
+                self.spawnMouseRotateTask()
         if not modifiers == 4:
             self.altDown = 0
 
     def mouseFlyStop(self):
         self.__stopManipulateCamera()
+        base = ShowBaseGlobal.base
         stopT = base.clock.getFrameTime()
         deltaT = stopT - self.startT
         stopF = base.clock.getFrameCount()
@@ -215,7 +216,8 @@ class DirectCameraControl(DirectObject):
         # if not self.useMayaCamControls and (deltaT <= 0.25) or (deltaF <= 1):
 
         # Do this when not trying to manipulate camera
-        if not self.altDown and len(base.direct.selected.getSelectedAsList()) == 0:
+        direct = ShowBaseGlobal.direct
+        if not self.altDown and len(direct.selected.getSelectedAsList()) == 0:
             # Check for a hit point based on
             # current mouse position
             # Allow intersection with unpickable objects
@@ -224,13 +226,13 @@ class DirectCameraControl(DirectObject):
             skipFlags = DG.SKIP_HIDDEN | DG.SKIP_BACKFACE
             # Skip camera (and its children), unless control key is pressed
             skipFlags |= DG.SKIP_CAMERA * (1 - base.getControl())
-            self.computeCOA(base.direct.iRay.pickGeom(skipFlags = skipFlags))
+            self.computeCOA(direct.iRay.pickGeom(skipFlags = skipFlags))
             # Record reference point
             self.coaMarkerRef.setPosHprScale(base.cam, 0, 0, 0, 0, 0, 0, 1, 1, 1)
             # Record entries
             self.cqEntries = []
-            for i in range(base.direct.iRay.getNumEntries()):
-                self.cqEntries.append(base.direct.iRay.getEntry(i))
+            for i in range(direct.iRay.getNumEntries()):
+                self.cqEntries.append(direct.iRay.getEntry(i))
         # Show the marker
         self.coaMarker.show()
         # Resize it
@@ -251,7 +253,7 @@ class DirectCameraControl(DirectObject):
         # Spawn the new task
         t = Task.Task(self.XZTranslateOrHPanYZoomTask)
         # For HPanYZoom
-        t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
+        t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
         self.__startManipulateCamera(task = t)
 
     def spawnXZTranslateOrHPPan(self):
@@ -277,7 +279,7 @@ class DirectCameraControl(DirectObject):
         self.__stopManipulateCamera()
         # Spawn new task
         t = Task.Task(self.HPanYZoomTask)
-        t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
+        t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
         self.__startManipulateCamera(task = t)
 
     def spawnOrthoZoom(self):
@@ -294,13 +296,13 @@ class DirectCameraControl(DirectObject):
         self.__startManipulateCamera(func = self.HPPanTask)
 
     def XZTranslateOrHPanYZoomTask(self, state):
-        if base.direct.fShift:
+        if ShowBaseGlobal.direct.fShift:
             return self.XZTranslateTask(state)
         else:
             return self.HPanYZoomTask(state)
 
     def XZTranslateOrHPPanTask(self, state):
-        if base.direct.fShift:
+        if ShowBaseGlobal.direct.fShift:
             # Panning action
             return self.HPPanTask(state)
         else:
@@ -308,43 +310,46 @@ class DirectCameraControl(DirectObject):
             return self.XZTranslateTask(state)
 
     def XZTranslateTask(self, state):
-        coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
-        xlateSF = coaDist / base.direct.dr.near
-        base.direct.camera.setPos(base.direct.camera,
-                             (-0.5 * base.direct.dr.mouseDeltaX *
-                              base.direct.dr.nearWidth *
+        direct = ShowBaseGlobal.direct
+        coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
+        xlateSF = coaDist / direct.dr.near
+        direct.camera.setPos(direct.camera,
+                             (-0.5 * direct.dr.mouseDeltaX *
+                              direct.dr.nearWidth *
                               xlateSF),
                              0.0,
-                             (-0.5 * base.direct.dr.mouseDeltaY *
-                              base.direct.dr.nearHeight *
+                             (-0.5 * direct.dr.mouseDeltaY *
+                              direct.dr.nearHeight *
                               xlateSF))
         return Task.cont
 
     def OrthoTranslateTask(self, state):
         # create ray from the camera to detect 3d position
-        iRay = SelectionRay(base.direct.camera)
-        iRay.collider.setFromLens(base.direct.camNode, base.direct.dr.mouseX, base.direct.dr.mouseY)
+        direct = ShowBaseGlobal.direct
+        iRay = SelectionRay(direct.camera)
+        iRay.collider.setFromLens(direct.camNode, direct.dr.mouseX, direct.dr.mouseY)
         #iRay.collideWithBitMask(1)
         iRay.collideWithBitMask(BitMask32.bit(21))
-        iRay.ct.traverse(base.direct.grid)
+        iRay.ct.traverse(direct.grid)
 
         entry = iRay.getEntry(0)
         hitPt = entry.getSurfacePoint(entry.getFromNodePath())
         iRay.collisionNodePath.removeNode()
         del iRay
         if hasattr(state, 'prevPt'):
-            base.direct.camera.setPos(base.direct.camera, (state.prevPt - hitPt))
+            direct.camera.setPos(direct.camera, (state.prevPt - hitPt))
         state.prevPt = hitPt
         return Task.cont
 
     def HPanYZoomTask(self, state):
         # If the cam is orthogonal, don't rotate or zoom.
-        if (hasattr(base.direct.cam.node(), "getLens") and
-            base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
+        direct = ShowBaseGlobal.direct
+        if (hasattr(direct.cam.node(), "getLens") and
+            direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
             return
 
-        if base.direct.fControl:
-            moveDir = Vec3(self.coaMarker.getPos(base.direct.camera))
+        if direct.fControl:
+            moveDir = Vec3(self.coaMarker.getPos(direct.camera))
             # If marker is behind camera invert vector
             if moveDir[1] < 0.0:
                 moveDir.assign(moveDir * -1)
@@ -353,18 +358,18 @@ class DirectCameraControl(DirectObject):
             moveDir = Vec3(Y_AXIS)
 
         if self.useMayaCamControls: # use maya controls
-            moveDir.assign(moveDir * ((base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY)
+            moveDir.assign(moveDir * ((direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY)
                                     * state.zoomSF))
             hVal = 0.0
         else:
-            moveDir.assign(moveDir * (-1.0 * base.direct.dr.mouseDeltaY *
+            moveDir.assign(moveDir * (-1.0 * direct.dr.mouseDeltaY *
                                         state.zoomSF))
-            if base.direct.dr.mouseDeltaY > 0.0:
+            if direct.dr.mouseDeltaY > 0.0:
                 moveDir.setY(moveDir[1] * 1.0)
 
-            hVal = 0.5 * base.direct.dr.mouseDeltaX * base.direct.dr.fovH
+            hVal = 0.5 * direct.dr.mouseDeltaX * direct.dr.fovH
 
-        base.direct.camera.setPosHpr(base.direct.camera,
+        direct.camera.setPosHpr(direct.camera,
                                 moveDir[0],
                                 moveDir[1],
                                 moveDir[2],
@@ -372,39 +377,42 @@ class DirectCameraControl(DirectObject):
                                 0.0, 0.0)
         if self.lockRoll:
             # flatten roll
-            base.direct.camera.setR(0)
+            direct.camera.setR(0)
 
         return Task.cont
 
     def OrthoZoomTask(self, state):
-        filmSize = base.direct.camNode.getLens().getFilmSize()
-        factor = (base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY) * 0.1
-        x = base.direct.dr.getWidth()
-        y = base.direct.dr.getHeight()
-        base.direct.dr.orthoFactor -= factor
-        if base.direct.dr.orthoFactor < 0:
-            base.direct.dr.orthoFactor = 0.0001
-        base.direct.dr.updateFilmSize(x, y)
+        direct = ShowBaseGlobal.direct
+        filmSize = direct.camNode.getLens().getFilmSize()
+        factor = (direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY) * 0.1
+        x = direct.dr.getWidth()
+        y = direct.dr.getHeight()
+        direct.dr.orthoFactor -= factor
+        if direct.dr.orthoFactor < 0:
+            direct.dr.orthoFactor = 0.0001
+        direct.dr.updateFilmSize(x, y)
         return Task.cont
 
     def HPPanTask(self, state):
-        base.direct.camera.setHpr(base.direct.camera,
-                             (0.5 * base.direct.dr.mouseDeltaX *
-                              base.direct.dr.fovH),
-                             (-0.5 * base.direct.dr.mouseDeltaY *
-                              base.direct.dr.fovV),
+        direct = ShowBaseGlobal.direct
+        direct.camera.setHpr(direct.camera,
+                             (0.5 * direct.dr.mouseDeltaX *
+                              direct.dr.fovH),
+                             (-0.5 * direct.dr.mouseDeltaY *
+                              direct.dr.fovV),
                              0.0)
         return Task.cont
 
     def spawnMouseRotateTask(self):
         # Kill any existing tasks
         self.__stopManipulateCamera()
+        direct = ShowBaseGlobal.direct
         if self.perspCollPlane:
-            iRay = SelectionRay(base.direct.camera)
-            iRay.collider.setFromLens(base.direct.camNode, 0.0, 0.0)
+            iRay = SelectionRay(direct.camera)
+            iRay.collider.setFromLens(direct.camNode, 0.0, 0.0)
             iRay.collideWithBitMask(1)
 
-            if base.direct.camera.getPos().getZ() >=0:
+            if direct.camera.getPos().getZ() >=0:
                 iRay.ct.traverse(self.perspCollPlane)
             else:
                 iRay.ct.traverse(self.perspCollPlane2)
@@ -415,7 +423,7 @@ class DirectCameraControl(DirectObject):
 
                 # create a temp nodePath to get the position
                 np = NodePath('temp')
-                np.setPos(base.direct.camera, hitPt)
+                np.setPos(direct.camera, hitPt)
                 self.coaMarkerPos = np.getPos()
                 np.removeNode()
                 self.coaMarker.setPos(self.coaMarkerPos)
@@ -425,9 +433,9 @@ class DirectCameraControl(DirectObject):
 
         # Set at markers position in render coordinates
         self.camManipRef.setPos(self.coaMarkerPos)
-        self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
+        self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
         t = Task.Task(self.mouseRotateTask)
-        if abs(base.direct.dr.mouseX) > 0.9:
+        if abs(direct.dr.mouseX) > 0.9:
             t.constrainedDir = 'y'
         else:
             t.constrainedDir = 'x'
@@ -435,36 +443,37 @@ class DirectCameraControl(DirectObject):
 
     def mouseRotateTask(self, state):
         # If the cam is orthogonal, don't rotate.
-        if (hasattr(base.direct.cam.node(), "getLens") and
-            base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
+        direct = ShowBaseGlobal.direct
+        if (hasattr(direct.cam.node(), "getLens") and
+            direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
             return
         # If moving outside of center, ignore motion perpendicular to edge
-        if ((state.constrainedDir == 'y') and (abs(base.direct.dr.mouseX) > 0.9)):
+        if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
             deltaX = 0
-            deltaY = base.direct.dr.mouseDeltaY
-        elif ((state.constrainedDir == 'x') and (abs(base.direct.dr.mouseY) > 0.9)):
-            deltaX = base.direct.dr.mouseDeltaX
+            deltaY = direct.dr.mouseDeltaY
+        elif ((state.constrainedDir == 'x') and (abs(direct.dr.mouseY) > 0.9)):
+            deltaX = direct.dr.mouseDeltaX
             deltaY = 0
         else:
-            deltaX = base.direct.dr.mouseDeltaX
-            deltaY = base.direct.dr.mouseDeltaY
-        if base.direct.fShift:
-            base.direct.camera.setHpr(base.direct.camera,
-                                 (deltaX * base.direct.dr.fovH),
-                                 (-deltaY * base.direct.dr.fovV),
+            deltaX = direct.dr.mouseDeltaX
+            deltaY = direct.dr.mouseDeltaY
+        if direct.fShift:
+            direct.camera.setHpr(direct.camera,
+                                 (deltaX * direct.dr.fovH),
+                                 (-deltaY * direct.dr.fovV),
                                  0.0)
             if self.lockRoll:
                 # flatten roll
-                base.direct.camera.setR(0)
+                direct.camera.setR(0)
             self.camManipRef.setPos(self.coaMarkerPos)
-            self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
+            self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
         else:
-            if base.direct.camera.getPos().getZ() >=0 or not self.switchDirBelowZero:
+            if direct.camera.getPos().getZ() >=0 or not self.switchDirBelowZero:
                 dirX = -1
             else:
                 dirX = 1
 
-            wrt = base.direct.camera.getTransform(self.camManipRef)
+            wrt = direct.camera.getTransform(self.camManipRef)
             self.camManipRef.setHpr(self.camManipRef,
                                     (dirX * deltaX * 180.0),
                                     (deltaY * 180.0),
@@ -473,20 +482,21 @@ class DirectCameraControl(DirectObject):
             if self.lockRoll:
                 # flatten roll
                 self.camManipRef.setR(0)
-            base.direct.camera.setTransform(self.camManipRef, wrt)
+            direct.camera.setTransform(self.camManipRef, wrt)
         return Task.cont
 
     def spawnMouseRollTask(self):
         # Kill any existing tasks
         self.__stopManipulateCamera()
         # Set at markers position in render coordinates
+        direct = ShowBaseGlobal.direct
         self.camManipRef.setPos(self.coaMarkerPos)
-        self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
+        self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
         t = Task.Task(self.mouseRollTask)
         t.coaCenter = getScreenXY(self.coaMarker)
         t.lastAngle = getCrankAngle(t.coaCenter)
         # Store the camera/manipRef offset transform
-        t.wrt = base.direct.camera.getTransform(self.camManipRef)
+        t.wrt = direct.camera.getTransform(self.camManipRef)
         self.__startManipulateCamera(task = t)
 
     def mouseRollTask(self, state):
@@ -498,23 +508,23 @@ class DirectCameraControl(DirectObject):
         if self.lockRoll:
             # flatten roll
             self.camManipRef.setR(0)
-        base.direct.camera.setTransform(self.camManipRef, wrt)
+        ShowBaseGlobal.direct.camera.setTransform(self.camManipRef, wrt)
         return Task.cont
 
     def lockCOA(self):
         self.fLockCOA = 1
-        base.direct.message('COA Lock On')
+        ShowBaseGlobal.direct.message('COA Lock On')
 
     def unlockCOA(self):
         self.fLockCOA = 0
-        base.direct.message('COA Lock Off')
+        ShowBaseGlobal.direct.message('COA Lock Off')
 
     def toggleCOALock(self):
         self.fLockCOA = 1 - self.fLockCOA
         if self.fLockCOA:
-            base.direct.message('COA Lock On')
+            ShowBaseGlobal.direct.message('COA Lock On')
         else:
-            base.direct.message('COA Lock Off')
+            ShowBaseGlobal.direct.message('COA Lock Off')
 
     def pickNextCOA(self):
         """ Cycle through collision handler entries """
@@ -524,7 +534,7 @@ class DirectCameraControl(DirectObject):
             self.cqEntries = self.cqEntries[1:] + self.cqEntries[:1]
             # Filter out object's under camera
             nodePath = entry.getIntoNodePath()
-            if base.direct.camera not in nodePath.getAncestors():
+            if ShowBaseGlobal.direct.camera not in nodePath.getAncestors():
                 # Compute new hit point
                 hitPt = entry.getSurfacePoint(entry.getFromNodePath())
                 # Move coa marker to new point
@@ -536,11 +546,11 @@ class DirectCameraControl(DirectObject):
 
     def computeCOA(self, entry):
         coa = Point3(0)
-        dr = base.direct.drList.getCurrentDr()
+        dr = ShowBaseGlobal.direct.drList.getCurrentDr()
         if self.fLockCOA:
             # COA is locked, use existing point
             # Use existing point
-            coa.assign(self.coaMarker.getPos(base.direct.camera))
+            coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
             # Reset hit point count
             self.nullHitPointCount = 0
         elif entry:
@@ -553,7 +563,7 @@ class DirectCameraControl(DirectObject):
             if ((hitPtDist < (1.1 * dr.near)) or
                 (hitPtDist > dr.far)):
                 # Just use existing point
-                coa.assign(self.coaMarker.getPos(base.direct.camera))
+                coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
             # Reset hit point count
             self.nullHitPointCount = 0
         else:
@@ -565,7 +575,7 @@ class DirectCameraControl(DirectObject):
             # MRM: Would be nice to be able to control this
             # At least display it
             dist = pow(10.0, self.nullHitPointCount)
-            base.direct.message('COA Distance: ' + repr(dist))
+            ShowBaseGlobal.direct.message('COA Distance: ' + repr(dist))
             coa.set(0, dist, 0)
         # Compute COA Dist
         coaDist = Vec3(coa - DG.ZERO_POINT).length()
@@ -583,7 +593,7 @@ class DirectCameraControl(DirectObject):
         if ref is None:
             # KEH: use the current display region
             # ref = base.cam
-            ref = base.direct.drList.getCurrentDr().cam
+            ref = ShowBaseGlobal.direct.drList.getCurrentDr().cam
         self.coaMarker.setPos(ref, self.coa)
         pos = self.coaMarker.getPos()
         self.coaMarker.setPosHprScale(pos, Vec3(0), Vec3(1))
@@ -598,10 +608,10 @@ class DirectCameraControl(DirectObject):
 
     def updateCoaMarkerSize(self, coaDist = None):
         if not coaDist:
-            coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
+            coaDist = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
         # Nominal size based on default 30 degree vertical FOV
         # Need to adjust size based on distance and current FOV
-        sf = COA_MARKER_SF * coaDist * (base.direct.drList.getCurrentDr().fovV/30.0)
+        sf = COA_MARKER_SF * coaDist * (ShowBaseGlobal.direct.drList.getCurrentDr().fovV/30.0)
         if sf == 0.0:
             sf = 0.1
         self.coaMarker.setScale(sf)
@@ -619,32 +629,36 @@ class DirectCameraControl(DirectObject):
 
     def homeCam(self):
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
-        base.direct.camera.reparentTo(render)
-        base.direct.camera.clearMat()
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
+        direct.camera.reparentTo(ShowBaseGlobal.base.render)
+        direct.camera.clearMat()
         # Resize coa marker
         self.updateCoaMarkerSize()
 
     def uprightCam(self):
         self.__stopManipulateCamera()
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
         # Pitch camera till upright
-        currH = base.direct.camera.getH()
-        ival = base.direct.camera.hprInterval(CAM_MOVE_DURATION,
-                                              (currH, 0, 0),
-                                              other = render,
-                                              blendType = 'easeInOut',
-                                              name = 'manipulateCamera')
-        self.__startManipulateCamera(ival = ival)
+        currH = direct.camera.getH()
+        ival = direct.camera.hprInterval(CAM_MOVE_DURATION,
+                                         (currH, 0, 0),
+                                         other=ShowBaseGlobal.base.render,
+                                         blendType='easeInOut',
+                                         name='manipulateCamera')
+        self.__startManipulateCamera(ival=ival)
 
     def orbitUprightCam(self):
         self.__stopManipulateCamera()
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
         # Transform camera z axis to render space
+        render = ShowBaseGlobal.base.render
         mCam2Render = Mat4(Mat4.identMat()) # [gjeon] fixed to give required argument
-        mCam2Render.assign(base.direct.camera.getMat(render))
+        mCam2Render.assign(direct.camera.getMat(render))
         zAxis = Vec3(mCam2Render.xformVec(DG.Z_AXIS))
         zAxis.normalize()
         # Compute rotation angle needed to upright cam
@@ -665,8 +679,8 @@ class DirectCameraControl(DirectObject):
         self.camManipRef.setPos(self.coaMarker, Vec3(0))
         self.camManipRef.setHpr(render, rotAngle, 0, 0)
         # Reparent Cam to ref Coordinate system
-        parent = base.direct.camera.getParent()
-        base.direct.camera.wrtReparentTo(self.camManipRef)
+        parent = direct.camera.getParent()
+        direct.camera.wrtReparentTo(self.camManipRef)
         # Rotate ref CS to final orientation
         ival = self.camManipRef.hprInterval(CAM_MOVE_DURATION,
                                             (rotAngle, orbitAngle, 0),
@@ -685,17 +699,18 @@ class DirectCameraControl(DirectObject):
     def centerCamIn(self, t):
         self.__stopManipulateCamera()
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
         # Determine marker location
-        markerToCam = self.coaMarker.getPos(base.direct.camera)
+        markerToCam = self.coaMarker.getPos(direct.camera)
         dist = Vec3(markerToCam - DG.ZERO_POINT).length()
         scaledCenterVec = Y_AXIS * dist
         delta = markerToCam - scaledCenterVec
-        self.camManipRef.setPosHpr(base.direct.camera, Point3(0), Point3(0))
-        ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
-                                              Point3(delta),
-                                              other = self.camManipRef,
-                                              blendType = 'easeInOut')
+        self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
+        ival = direct.camera.posInterval(CAM_MOVE_DURATION,
+                                         Point3(delta),
+                                         other=self.camManipRef,
+                                         blendType='easeInOut')
         ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                         name = 'manipulateCamera')
         self.__startManipulateCamera(ival = ival)
@@ -703,17 +718,18 @@ class DirectCameraControl(DirectObject):
     def zoomCam(self, zoomFactor, t):
         self.__stopManipulateCamera()
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
         # Find a point zoom factor times the current separation
         # of the widget and cam
-        zoomPtToCam = self.coaMarker.getPos(base.direct.camera) * zoomFactor
+        zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
         # Put a target nodePath there
-        self.camManipRef.setPos(base.direct.camera, zoomPtToCam)
+        self.camManipRef.setPos(direct.camera, zoomPtToCam)
         # Move to that point
-        ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
-                                              DG.ZERO_POINT,
-                                              other = self.camManipRef,
-                                              blendType = 'easeInOut')
+        ival = direct.camera.posInterval(CAM_MOVE_DURATION,
+                                         DG.ZERO_POINT,
+                                         other=self.camManipRef,
+                                         blendType='easeInOut')
         ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                         name = 'manipulateCamera')
         self.__startManipulateCamera(ival = ival)
@@ -722,7 +738,8 @@ class DirectCameraControl(DirectObject):
         # Kill any existing tasks
         self.__stopManipulateCamera()
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
         # Calc hprOffset
         hprOffset = VBase3()
         if view == 8:
@@ -751,7 +768,7 @@ class DirectCameraControl(DirectObject):
         self.camManipRef.setPosHpr(self.coaMarker, DG.ZERO_VEC,
                                    hprOffset)
         # Scale center vec by current distance to target
-        offsetDistance = Vec3(base.direct.camera.getPos(self.camManipRef) -
+        offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) -
                               DG.ZERO_POINT).length()
         scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
         # Now put the camManipRef at that point
@@ -760,11 +777,11 @@ class DirectCameraControl(DirectObject):
                                    DG.ZERO_VEC)
         # Record view for next time around
         self.lastView = view
-        ival = base.direct.camera.posHprInterval(CAM_MOVE_DURATION,
-                                                 pos = DG.ZERO_POINT,
-                                                 hpr = VBase3(0, 0, self.orthoViewRoll),
-                                                 other = self.camManipRef,
-                                                 blendType = 'easeInOut')
+        ival = direct.camera.posHprInterval(CAM_MOVE_DURATION,
+                                            pos=DG.ZERO_POINT,
+                                            hpr=VBase3(0, 0, self.orthoViewRoll),
+                                            other=self.camManipRef,
+                                            blendType='easeInOut')
         ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                         name = 'manipulateCamera')
         self.__startManipulateCamera(ival = ival)
@@ -774,15 +791,16 @@ class DirectCameraControl(DirectObject):
         self.__stopManipulateCamera()
 
         # Record undo point
-        base.direct.pushUndo([base.direct.camera])
+        direct = ShowBaseGlobal.direct
+        direct.pushUndo([direct.camera])
 
         # Coincident with widget
         self.camManipRef.setPos(self.coaMarker, DG.ZERO_POINT)
         # But aligned with render space
         self.camManipRef.setHpr(DG.ZERO_POINT)
 
-        parent = base.direct.camera.getParent()
-        base.direct.camera.wrtReparentTo(self.camManipRef)
+        parent = direct.camera.getParent()
+        direct.camera.wrtReparentTo(self.camManipRef)
 
         ival = self.camManipRef.hprInterval(CAM_MOVE_DURATION,
                                             VBase3(degrees, 0, 0),
@@ -792,7 +810,7 @@ class DirectCameraControl(DirectObject):
         self.__startManipulateCamera(ival = ival)
 
     def reparentCam(self, parent):
-        base.direct.camera.wrtReparentTo(parent)
+        ShowBaseGlobal.direct.camera.wrtReparentTo(parent)
         self.updateCoaMarkerSize()
 
     def fitOnWidget(self, nodePath = 'None Given'):
@@ -800,75 +818,78 @@ class DirectCameraControl(DirectObject):
         # stop any ongoing tasks
         self.__stopManipulateCamera()
         # How big is the node?
-        nodeScale = base.direct.widget.scalingNode.getScale(render)
+        direct = ShowBaseGlobal.direct
+        nodeScale = direct.widget.scalingNode.getScale(ShowBaseGlobal.base.render)
         maxScale = max(nodeScale[0], nodeScale[1], nodeScale[2])
-        maxDim = min(base.direct.dr.nearWidth, base.direct.dr.nearHeight)
+        maxDim = min(direct.dr.nearWidth, direct.dr.nearHeight)
 
         # At what distance does the object fill 30% of the screen?
         # Assuming radius of 1 on widget
-        camY = base.direct.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
+        camY = direct.dr.near * (2.0 * maxScale) / (0.3 * maxDim)
 
         # What is the vector through the center of the screen?
         centerVec = Y_AXIS * camY
 
         # Where is the node relative to the viewpoint
-        vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
+        vWidget2Camera = direct.widget.getPos(direct.camera)
 
         # How far do you move the camera to be this distance from the node?
         deltaMove = vWidget2Camera - centerVec
 
         # Move a target there
         try:
-            self.camManipRef.setPos(base.direct.camera, deltaMove)
+            self.camManipRef.setPos(direct.camera, deltaMove)
         except Exception:
             #self.notify.debug
             pass
 
-        parent = base.direct.camera.getParent()
-        base.direct.camera.wrtReparentTo(self.camManipRef)
-        ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
-                                              Point3(0, 0, 0),
-                                              blendType = 'easeInOut')
+        parent = direct.camera.getParent()
+        direct.camera.wrtReparentTo(self.camManipRef)
+        ival = direct.camera.posInterval(CAM_MOVE_DURATION,
+                                         Point3(0, 0, 0),
+                                         blendType='easeInOut')
         ival = Sequence(ival, Func(self.reparentCam, parent),
-                        name = 'manipulateCamera')
-        self.__startManipulateCamera(ival = ival)
+                        name='manipulateCamera')
+        self.__startManipulateCamera(ival=ival)
 
     def moveToFit(self):
         # How big is the active widget?
-        widgetScale = base.direct.widget.scalingNode.getScale(render)
+        direct = ShowBaseGlobal.direct
+        widgetScale = direct.widget.scalingNode.getScale(ShowBaseGlobal.base.render)
         maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
         # At what distance does the widget fill 50% of the screen?
-        camY = ((2 * base.direct.dr.near * (1.5 * maxScale)) /
-                min(base.direct.dr.nearWidth, base.direct.dr.nearHeight))
+        camY = ((2 * direct.dr.near * (1.5 * maxScale)) /
+                min(direct.dr.nearWidth, direct.dr.nearHeight))
         # Find a point this distance along the Y axis
         # MRM: This needs to be generalized to support non uniform frusta
         centerVec = Y_AXIS * camY
         # Before moving, record the relationship between the selected nodes
         # and the widget, so that this can be maintained
-        base.direct.selected.getWrtAll()
+        direct.selected.getWrtAll()
         # Push state onto undo stack
-        base.direct.pushUndo(base.direct.selected)
+        direct.pushUndo(direct.selected)
         # Remove the task to keep the widget attached to the object
         taskMgr.remove('followSelectedNodePath')
         # Spawn a task to keep the selected objects with the widget
         taskMgr.add(self.stickToWidgetTask, 'stickToWidget')
         # Spawn a task to move the widget
-        ival = base.direct.widget.posInterval(CAM_MOVE_DURATION,
-                                              Point3(centerVec),
-                                              other = base.direct.camera,
-                                              blendType = 'easeInOut')
+        ival = direct.widget.posInterval(CAM_MOVE_DURATION,
+                                         Point3(centerVec),
+                                         other=direct.camera,
+                                         blendType='easeInOut')
         ival = Sequence(ival, Func(lambda: taskMgr.remove('stickToWidget')),
                         name = 'moveToFit')
         ival.start()
 
     def stickToWidgetTask(self, state):
         # Move the objects with the widget
-        base.direct.selected.moveWrtWidgetAll()
+        ShowBaseGlobal.direct.selected.moveWrtWidgetAll()
         # Continue
         return Task.cont
 
     def enableMouseFly(self, fKeyEvents = 1):
         # disable C++ fly interface
+        base = ShowBaseGlobal.base
         base.disableMouse()
         # Enable events
         for event in self.actionEvents:
@@ -877,11 +898,11 @@ class DirectCameraControl(DirectObject):
             for event in self.keyEvents:
                 self.accept(event[0], event[1], extraArgs = event[2:])
         # Show marker
-        self.coaMarker.reparentTo(base.direct.group)
+        self.coaMarker.reparentTo(ShowBaseGlobal.direct.group)
 
     def disableMouseFly(self):
         # Hide the marker
-        self.coaMarker.reparentTo(hidden)
+        self.coaMarker.reparentTo(ShowBaseGlobal.hidden)
         # Ignore events
         for event in self.actionEvents:
             self.ignore(event[0])
@@ -890,7 +911,7 @@ class DirectCameraControl(DirectObject):
         # Kill tasks
         self.removeManipulateCameraTask()
         taskMgr.remove('stickToWidget')
-        base.enableMouse()
+        ShowBaseGlobal.base.enableMouse()
 
     def removeManipulateCameraTask(self):
         self.__stopManipulateCamera()

+ 4 - 3
direct/src/directtools/DirectGrid.py

@@ -1,6 +1,7 @@
 import math
 from panda3d.core import NodePath, Point3, VBase4
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase import ShowBaseGlobal
 from .DirectUtil import ROUND_TO, useDirectRenderStyle
 from .DirectGeometry import LineNodePath
 
@@ -14,7 +15,7 @@ class DirectGrid(NodePath, DirectObject):
 
         # Load up grid parts to initialize grid object
         # Polygon used to mark grid plane
-        self.gridBack = base.loader.loadModel('models/misc/gridBack')
+        self.gridBack = ShowBaseGlobal.loader.loadModel('models/misc/gridBack')
         self.gridBack.reparentTo(self)
         self.gridBack.setColor(*planeColor)
 
@@ -36,7 +37,7 @@ class DirectGrid(NodePath, DirectObject):
         self.centerLines.setThickness(3)
 
         # Small marker to hilight snap-to-grid point
-        self.snapMarker = base.loader.loadModel('models/misc/sphere')
+        self.snapMarker = ShowBaseGlobal.loader.loadModel('models/misc/sphere')
         self.snapMarker.node().setName('gridSnapMarker')
         self.snapMarker.reparentTo(self)
         self.snapMarker.setColor(1, 0, 0, 1)
@@ -55,7 +56,7 @@ class DirectGrid(NodePath, DirectObject):
         if parent:
             self.reparentTo(parent)
         else:
-            self.reparentTo(base.direct.group)
+            self.reparentTo(ShowBaseGlobal.direct.group)
 
         self.updateGrid()
         self.fEnabled = 1

+ 1 - 3
direct/src/directtools/DirectLights.py

@@ -66,9 +66,7 @@ class DirectLights(NodePath):
 
     def getNameList(self):
         # Return a sorted list of all lights in the light dict
-        nameList = [x.getName() for x in self.lightDict.values()]
-        nameList.sort()
-        return nameList
+        return sorted(x.getName() for x in self.lightDict.values())
 
     def create(self, ltype):
         ltype = ltype.lower()

File diff suppressed because it is too large
+ 204 - 180
direct/src/directtools/DirectManipulation.py


+ 59 - 39
direct/src/directtools/DirectSession.py

@@ -5,6 +5,7 @@ from panda3d.core import (
     ConfigVariableBool,
     ConfigVariableString,
     CSDefault,
+    GraphicsWindow,
     NodePath,
     Point3,
     TextNode,
@@ -35,19 +36,33 @@ from direct.gui import OnscreenText
 from direct.interval.IntervalGlobal import Func, Sequence
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.MessengerGlobal import messenger
+from direct.showbase import ShowBaseGlobal
+from direct.showbase.ShowBaseGlobal import ShowBase, hidden
 
+import builtins
+
+base: ShowBase
 
 class DirectSession(DirectObject):
 
     # post this to the bboard to make sure DIRECT doesn't turn on
     DIRECTdisablePost = 'disableDIRECT'
 
+    cam: NodePath
+    camera: NodePath
+    oobeCamera: NodePath
+
     def __init__(self):
         # Establish a global pointer to the direct object early on
         # so dependant classes can access it in their code
-        __builtins__["direct"] = base.direct = self
+        global direct, base
+        base = ShowBaseGlobal.base
+        base.direct = self
+        setattr(builtins, 'direct', self)
+        ShowBaseGlobal.direct = self
+
         # These come early since they are used later on
-        self.group = render.attachNewNode('DIRECT')
+        self.group = base.render.attachNewNode('DIRECT')
         self.font = TextNode.getDefaultFont()
         self.fEnabled = 0
         self.fEnabledLight = 0
@@ -57,7 +72,7 @@ class DirectSession(DirectObject):
         self.drList = DisplayRegionList()
         self.iRayList = [x.iRay for x in self.drList]
         self.dr = self.drList[0]
-        self.win = base.win
+        self.win: GraphicsWindow = base.win
         self.camera = base.camera
         self.cam = base.cam
         self.camNode = base.camNode
@@ -70,7 +85,7 @@ class DirectSession(DirectObject):
         self.useObjectHandles()
         self.grid = DirectGrid()
         self.grid.disable()
-        self.lights = DirectLights(base.direct.group)
+        self.lights = DirectLights(self.group)
         # Create some default lights
         self.lights.createDefaultLights()
         # But turn them off
@@ -308,13 +323,16 @@ class DirectSession(DirectObject):
         if base.wantTk:
             from direct.tkpanels import DirectSessionPanel
             self.panel = DirectSessionPanel.DirectSessionPanel(parent = base.tkRoot)
-        try:
+
+        clusterMode: str
+        if hasattr(builtins, 'clusterMode'):
             # Has the clusterMode been set externally (i.e. via the
             # bootstrap application?
-            self.clusterMode = clusterMode
-        except NameError:
+            clusterMode = builtins.clusterMode
+        else:
             # Has the clusterMode been set via a config variable?
-            self.clusterMode = ConfigVariableString("cluster-mode", '').value
+            clusterMode = ConfigVariableString("cluster-mode", '').value
+        self.clusterMode = clusterMode
 
         if self.clusterMode == 'client':
             from direct.cluster.ClusterClient import createClusterClient
@@ -325,7 +343,7 @@ class DirectSession(DirectObject):
         else:
             from direct.cluster.ClusterClient import DummyClusterClient
             self.cluster = DummyClusterClient()
-        __builtins__['cluster'] = self.cluster
+        setattr(builtins, 'cluster', self.cluster)
 
     def addPassThroughKey(self,key):
 
@@ -412,10 +430,10 @@ class DirectSession(DirectObject):
 
         if self.oobeMode:
             # Position a target point to lerp the oobe camera to
-            base.direct.cameraControl.camManipRef.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
+            self.cameraControl.camManipRef.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
             ival = self.oobeCamera.posHprInterval(
                 2.0, pos = Point3(0), hpr = Vec3(0),
-                other = base.direct.cameraControl.camManipRef,
+                other = self.cameraControl.camManipRef,
                 blendType = 'easeInOut')
             ival = Sequence(ival, Func(self.endOOBE), name = 'oobeTransition')
             ival.start()
@@ -432,20 +450,20 @@ class DirectSession(DirectObject):
             # Put camera under new oobe camera
             self.cam.reparentTo(self.oobeCamera)
             # Position a target point to lerp the oobe camera to
-            base.direct.cameraControl.camManipRef.setPos(
+            self.cameraControl.camManipRef.setPos(
                 self.trueCamera, Vec3(-2, -20, 5))
-            base.direct.cameraControl.camManipRef.lookAt(self.trueCamera)
+            self.cameraControl.camManipRef.lookAt(self.trueCamera)
             ival = self.oobeCamera.posHprInterval(
                 2.0, pos = Point3(0), hpr = Vec3(0),
-                other = base.direct.cameraControl.camManipRef,
+                other = self.cameraControl.camManipRef,
                 blendType = 'easeInOut')
             ival = Sequence(ival, Func(self.beginOOBE), name = 'oobeTransition')
             ival.start()
 
     def beginOOBE(self):
         # Make sure we've reached our final destination
-        self.oobeCamera.setPosHpr(base.direct.cameraControl.camManipRef, 0, 0, 0, 0, 0, 0)
-        base.direct.camera = self.oobeCamera
+        self.oobeCamera.setPosHpr(self.cameraControl.camManipRef, 0, 0, 0, 0, 0, 0)
+        self.camera = self.oobeCamera
         self.oobeMode = 1
 
     def endOOBE(self):
@@ -453,7 +471,7 @@ class DirectSession(DirectObject):
         self.oobeCamera.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
         # Disable OOBE mode.
         self.cam.reparentTo(self.trueCamera)
-        base.direct.camera = self.trueCamera
+        self.camera = self.trueCamera
         # Get rid of ancillary node paths
         self.oobeVis.reparentTo(hidden)
         self.oobeCamera.reparentTo(hidden)
@@ -501,7 +519,7 @@ class DirectSession(DirectObject):
     def inputHandler(self, input):
         if not hasattr(self, 'oobeMode') or self.oobeMode == 0:
             # [gjeon] change current camera dr, iRay, mouseWatcher accordingly to support multiple windows
-            if base.direct.manipulationControl.fMultiView:
+            if self.manipulationControl.fMultiView:
                 # handling orphan events
                 if self.fMouse1 and 'mouse1' not in input or\
                    self.fMouse2 and 'mouse2' not in input or\
@@ -518,7 +536,7 @@ class DirectSession(DirectObject):
                     return
 
                 if (self.fMouse1 or self.fMouse2 or self.fMouse3) and\
-                   input[4:7] != base.direct.camera.getName()[:3] and\
+                   input[4:7] != self.camera.getName()[:3] and\
                    input.endswith('-up'):
                     # to handle orphan events
                     return
@@ -551,14 +569,14 @@ class DirectSession(DirectObject):
                     self.cam = NodePath(winCtrl.camNode)
                     self.camNode = winCtrl.camNode
                     if hasattr(winCtrl, 'grid'):
-                        base.direct.grid = winCtrl.grid
-                    base.direct.dr = base.direct.drList[base.camList.index(NodePath(winCtrl.camNode))]
-                    base.direct.iRay = base.direct.dr.iRay
+                        self.grid = winCtrl.grid
+                    self.dr = self.drList[base.camList.index(NodePath(winCtrl.camNode))]
+                    self.iRay = self.dr.iRay
                     base.mouseWatcher = winCtrl.mouseWatcher
                     base.mouseWatcherNode = winCtrl.mouseWatcher.node()
-                    base.direct.dr.mouseUpdate()
+                    self.dr.mouseUpdate()
                     DG.LE_showInOneCam(self.selectedNPReadout, self.camera.getName())
-                    base.direct.widget = base.direct.manipulationControl.widgetList[base.camList.index(NodePath(winCtrl.camNode))]
+                    self.widget = self.manipulationControl.widgetList[base.camList.index(NodePath(winCtrl.camNode))]
 
                 input = input[8:] # get rid of camera prefix
                 if self.fAlt and 'alt' not in input and not input.endswith('-up'):
@@ -683,20 +701,18 @@ class DirectSession(DirectObject):
         if not taskMgr.hasTaskNamed('resizeObjectHandles'):
             dnp = self.selected.last
             if dnp:
-                direct = base.direct
-
                 if self.manipulationControl.fMultiView:
                     for i in range(3):
-                        sf = 30.0 * direct.drList[i].orthoFactor
+                        sf = 30.0 * self.drList[i].orthoFactor
                         self.manipulationControl.widgetList[i].setDirectScalingFactor(sf)
 
                     nodeCamDist = Vec3(dnp.getPos(base.camList[3])).length()
-                    sf = 0.075 * nodeCamDist * math.tan(deg2Rad(direct.drList[3].fovV))
+                    sf = 0.075 * nodeCamDist * math.tan(deg2Rad(self.drList[3].fovV))
                     self.manipulationControl.widgetList[3].setDirectScalingFactor(sf)
 
                 else:
-                    nodeCamDist = Vec3(dnp.getPos(direct.camera)).length()
-                    sf = 0.075 * nodeCamDist * math.tan(deg2Rad(direct.drList.getCurrentDr().fovV))
+                    nodeCamDist = Vec3(dnp.getPos(self.camera)).length()
+                    sf = 0.075 * nodeCamDist * math.tan(deg2Rad(self.drList.getCurrentDr().fovV))
                     self.widget.setDirectScalingFactor(sf)
         return Task.cont
 
@@ -755,7 +771,7 @@ class DirectSession(DirectObject):
             messenger.send('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', [dnp, fMultiSelect, fSelectTag, fLEPane])
 
     def followSelectedNodePathTask(self, state):
-        mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
+        mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(base.render)
         decomposeMatrix(mCoa2Render,
                         self.scale, self.hpr, self.pos,
                         CSDefault)
@@ -874,7 +890,7 @@ class DirectSession(DirectObject):
         if nodePath == 'None Given':
             # If nothing specified, try selected node path
             nodePath = self.selected.last
-        base.direct.select(nodePath)
+        self.select(nodePath)
 
         def fitTask(state, self = self):
             self.cameraControl.fitOnWidget()
@@ -1061,7 +1077,7 @@ class DirectSession(DirectObject):
 
     def useObjectHandles(self):
         self.widget = self.manipulationControl.objectHandles
-        self.widget.reparentTo(base.direct.group)
+        self.widget.reparentTo(self.group)
 
     def hideSelectedNPReadout(self):
         self.selectedNPReadout.reparentTo(hidden)
@@ -1167,14 +1183,14 @@ class DisplayRegionContext(DirectObject):
             self.camLens.setFov(hfov, vfov)
 
     def getWidth(self):
-        prop = base.direct.win.getProperties()
+        prop = ShowBaseGlobal.direct.win.getProperties()
         if prop.hasSize():
             return prop.getXSize()
         else:
             return 640
 
     def getHeight(self):
-        prop = base.direct.win.getProperties()
+        prop = ShowBaseGlobal.direct.win.getProperties()
         if prop.hasSize():
             return prop.getYSize()
         else:
@@ -1208,9 +1224,10 @@ class DisplayRegionContext(DirectObject):
 
         # Values for this frame
         # This ranges from -1 to 1
-        if base.mouseWatcherNode and base.mouseWatcherNode.hasMouse():
-            self.mouseX = base.mouseWatcherNode.getMouseX()
-            self.mouseY = base.mouseWatcherNode.getMouseY()
+        mouseWatcherNode = base.mouseWatcherNode
+        if mouseWatcherNode and mouseWatcherNode.hasMouse():
+            self.mouseX = mouseWatcherNode.getMouseX()
+            self.mouseY = mouseWatcherNode.getMouseY()
             self.mouseX = (self.mouseX-self.originX)*self.scaleX
             self.mouseY = (self.mouseY-self.originY)*self.scaleY
         # Delta percent of window the mouse moved
@@ -1262,6 +1279,9 @@ class DisplayRegionList(DirectObject):
     def __len__(self):
         return len(self.displayRegionList)
 
+    def __iter__(self):
+        return iter(self.displayRegionList)
+
     def updateContext(self):
         self.contextTask(None)
 
@@ -1296,7 +1316,7 @@ class DisplayRegionList(DirectObject):
 
     def getCurrentDr(self):
         if not self.tryToGetCurrentDr:
-            return base.direct.dr
+            return ShowBaseGlobal.direct.dr
         for dr in self.displayRegionList:
             if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
                 dr.mouseY >= -1.0 and dr.mouseY <= 1.0):

+ 60 - 34
direct/src/dist/FreezeTool.py

@@ -5,7 +5,6 @@ import modulefinder
 import sys
 import os
 import marshal
-import imp
 import platform
 import struct
 import io
@@ -13,6 +12,7 @@ import sysconfig
 import zipfile
 import importlib
 import warnings
+from importlib import machinery
 
 from . import pefile
 
@@ -24,6 +24,16 @@ except ImportError:
 
 from panda3d.core import Filename, Multifile, PandaSystem, StringStream
 
+# Old imp constants.
+_PY_SOURCE = 1
+_PY_COMPILED = 2
+_C_EXTENSION = 3
+_PKG_DIRECTORY = 5
+_C_BUILTIN = 6
+_PY_FROZEN = 7
+
+_PKG_NAMESPACE_DIRECTORY = object()
+
 # Check to see if we are running python_d, which implies we have a
 # debug build, and we have to build the module with debug options.
 # This is only relevant on Windows.
@@ -37,7 +47,7 @@ isDebugBuild = (python.lower().endswith('_d'))
 # NB. if encodings are removed, be sure to remove them from the shortcut in
 # deploy-stub.c.
 startupModules = [
-    'imp', 'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
+    'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
     'importlib.util',
 ]
 
@@ -262,10 +272,15 @@ class CompilationEnvironment:
                 self.arch = '-arch x86_64'
             elif proc in ('arm64', 'aarch64'):
                 self.arch = '-arch arm64'
-            self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
-            self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
-            self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
-            self.linkDll = "gcc %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
+            self.compileObjExe = "clang -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
+            self.compileObjDll = "clang -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
+            self.linkExe = "clang %(arch)s -o %(basename)s %(basename)s.o"
+            if '/Python.framework/' in self.PythonIPath:
+                framework_dir = self.PythonIPath.split("/Python.framework/", 1)[0]
+                if framework_dir != "/System/Library/Frameworks":
+                    self.linkExe += " -F " + framework_dir
+            self.linkExe += " -framework Python"
+            self.linkDll = "clang %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
 
         else:
             # Unix
@@ -897,12 +912,11 @@ class Freezer:
 
         # Suffix/extension for Python C extension modules
         if self.platform == PandaSystem.getPlatform():
-            suffixes = imp.get_suffixes()
-
-            # Set extension for Python files to binary mode
-            for i, suffix in enumerate(suffixes):
-                if suffix[2] == imp.PY_SOURCE:
-                    suffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
+            suffixes = (
+                [(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] +
+                [(s, 'rb', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] +
+                [(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
+            )
         else:
             suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
 
@@ -1146,15 +1160,12 @@ class Freezer:
 
         # Walk through the list in sorted order, so we reach parents
         # before children.
-        names = list(self.modules.items())
-        names.sort()
-
         excludeDict = {}
         implicitParentDict = {}
         includes = []
         autoIncludes = []
         origToNewName = {}
-        for newName, mdef in names:
+        for newName, mdef in sorted(self.modules.items()):
             moduleName = mdef.moduleName
             origToNewName[moduleName] = newName
             if mdef.implicit and '.' in newName:
@@ -1319,11 +1330,11 @@ class Freezer:
             ext = mdef.filename.getExtension()
             if ext == 'pyc' or ext == 'pyo':
                 fp = open(pathname, 'rb')
-                stuff = ("", "rb", imp.PY_COMPILED)
+                stuff = ("", "rb", _PY_COMPILED)
                 self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
             else:
-                stuff = ("", "rb", imp.PY_SOURCE)
-                if mdef.text:
+                stuff = ("", "rb", _PY_SOURCE)
+                if mdef.text is not None:
                     fp = io.StringIO(mdef.text)
                 else:
                     fp = open(pathname, 'rb')
@@ -1418,7 +1429,7 @@ class Freezer:
 
     def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
-            data = imp.get_magic() + b'\0\0\0\0\0\0\0\0'
+            data = importlib.util.MAGIC_NUMBER + b'\0\0\0\0\0\0\0\0'
             data += marshal.dumps(code)
 
             stream = StringStream(data)
@@ -1608,7 +1619,7 @@ class Freezer:
             # trouble importing it as a builtin module.  Synthesize a frozen
             # module that loads it as builtin.
             if '.' in moduleName and self.linkExtensionModules:
-                code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize)
+                code = compile('import sys;del sys.modules["%s"];from importlib._bootstrap import _builtin_from_name;_builtin_from_name("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize)
                 code = marshal.dumps(code)
                 mangledName = self.mangleName(moduleName)
                 moduleDefs.append(self.makeModuleDef(mangledName, code))
@@ -1890,9 +1901,19 @@ class Freezer:
             if '.' in moduleName and not self.platform.startswith('android'):
                 if self.platform.startswith("macosx") and not use_console:
                     # We write the Frameworks directory to sys.path[0].
-                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                    direxpr = 'sys.path[0]'
                 else:
-                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                    direxpr = 'os.path.dirname(sys.executable)'
+
+                code = \
+                    f'import sys;' \
+                    f'del sys.modules["{moduleName}"];' \
+                    f'import sys,os;' \
+                    f'from importlib.machinery import ExtensionFileLoader,ModuleSpec;' \
+                    f'from importlib._bootstrap import _load;' \
+                    f'path=os.path.join({direxpr}, "{moduleName}{modext}");' \
+                    f'_load(ModuleSpec(name="{moduleName}", loader=ExtensionFileLoader("{moduleName}", path), origin=path))'
+
                 code = compile(code, moduleName, 'exec', optimize=self.optimize)
                 code = marshal.dumps(code)
                 moduleList.append((moduleName, len(pool), len(code)))
@@ -1927,6 +1948,9 @@ class Freezer:
         if self.platform.startswith('win'):
             # We don't use mmap on Windows.  Align just for good measure.
             blob_align = 32
+        elif self.platform.endswith('_aarch64') or self.platform.endswith('_arm64'):
+            # Most arm64 operating systems are configured with 16 KiB pages.
+            blob_align = 16384
         else:
             # Align to page size, so that it can be mmapped.
             blob_align = 4096
@@ -2403,9 +2427,6 @@ class Freezer:
         return True
 
 
-_PKG_NAMESPACE_DIRECTORY = object()
-
-
 class PandaModuleFinder(modulefinder.ModuleFinder):
 
     def __init__(self, *args, **kw):
@@ -2418,7 +2439,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
         self.builtin_module_names = kw.pop('builtin_module_names', sys.builtin_module_names)
 
-        self.suffixes = kw.pop('suffixes', imp.get_suffixes())
+        self.suffixes = kw.pop('suffixes', (
+            [(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] +
+            [(s, 'r', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] +
+            [(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
+        ))
+
         self.optimize = kw.pop('optimize', -1)
 
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
@@ -2566,7 +2592,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
         suffix, mode, type = file_info
         self.msgin(2, "load_module", fqname, fp and "fp", pathname)
-        if type == imp.PKG_DIRECTORY:
+        if type == _PKG_DIRECTORY:
             m = self.load_package(fqname, pathname)
             self.msgout(2, "load_module ->", m)
             return m
@@ -2577,7 +2603,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             m.__path__ = pathname
             return m
 
-        if type == imp.PY_SOURCE:
+        if type == _PY_SOURCE:
             if fqname in overrideModules:
                 # This module has a custom override.
                 code = overrideModules[fqname]
@@ -2601,7 +2627,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec', optimize=self.optimize)
-        elif type == imp.PY_COMPILED:
+        elif type == _PY_COMPILED:
             if sys.version_info >= (3, 7):
                 try:
                     data = fp.read()
@@ -2755,11 +2781,11 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
         # If we have a custom override for this module, we know we have it.
         if fullname in overrideModules:
-            return (None, '', ('.py', 'r', imp.PY_SOURCE))
+            return (None, '', ('.py', 'r', _PY_SOURCE))
 
         # It's built into the interpreter.
         if fullname in self.builtin_module_names:
-            return (None, None, ('', '', imp.C_BUILTIN))
+            return (None, None, ('', '', _C_BUILTIN))
 
         # If no search path is given, look for a built-in module.
         if path is None:
@@ -2809,7 +2835,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             for suffix, mode, _ in self.suffixes:
                 init = os.path.join(basename, '__init__' + suffix)
                 if self._open_file(init, mode):
-                    return (None, basename, ('', '', imp.PKG_DIRECTORY))
+                    return (None, basename, ('', '', _PKG_DIRECTORY))
 
             # This may be a namespace package.
             if self._dir_exists(basename):
@@ -2821,7 +2847,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             # Only if we're not looking on a particular path, though.
             if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
                 # It's a frozen module.
-                return (None, name, ('', '', imp.PY_FROZEN))
+                return (None, name, ('', '', _PY_FROZEN))
 
         # If we found folders on the path with this module name without an
         # __init__.py file, we should consider this a namespace package.

+ 25 - 15
direct/src/dist/commands.py

@@ -13,7 +13,6 @@ import re
 import shutil
 import stat
 import struct
-import imp
 import string
 import tempfile
 
@@ -55,10 +54,16 @@ def _register_python_loaders():
 
     _register_python_loaders.done = True
 
-    registry = p3d.LoaderFileTypeRegistry.getGlobalPtr()
+    from importlib.metadata import entry_points
 
-    import pkg_resources
-    for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'):
+    eps = entry_points()
+    if isinstance(eps, dict): # Python 3.8 and 3.9
+        loaders = eps.get('panda3d.loaders', ())
+    else:
+        loaders = eps.select(group='panda3d.loaders')
+
+    registry = p3d.LoaderFileTypeRegistry.get_global_ptr()
+    for entry_point in loaders:
         registry.register_deferred_type(entry_point)
 
 
@@ -466,8 +471,10 @@ class build_apps(setuptools.Command):
         if self.bam_model_extensions:
             for ext in self.bam_model_extensions:
                 ext = '.' + ext.lstrip('.')
-                assert ext not in self.file_handlers, \
-                    'Extension {} occurs in both file_handlers and bam_model_extensions!'.format(ext)
+                handler = self.file_handlers.get(ext)
+                if handler != _model_to_bam:
+                    assert handler is None, \
+                        'Extension {} occurs in both file_handlers and bam_model_extensions!'.format(ext)
                 self.file_handlers[ext] = _model_to_bam
 
         tmp = self.default_file_handlers.copy()
@@ -717,6 +724,7 @@ class build_apps(setuptools.Command):
             'CFBundlePackageType': 'APPL',
             'CFBundleSignature': '', #TODO
             'CFBundleExecutable': self.macos_main_app,
+            'NSHighResolutionCapable': 'True',
         }
 
         icon = self.icon_objects.get(
@@ -859,16 +867,12 @@ class build_apps(setuptools.Command):
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             etcdir = os.path.join(libdir, '..', 'etc')
 
-            etcfiles = os.listdir(etcdir)
-            etcfiles.sort(reverse=True)
-            for fn in etcfiles:
+            for fn in sorted(os.listdir(etcdir), reverse=True):
                 if fn.lower().endswith('.prc'):
                     with open(os.path.join(etcdir, fn)) as f:
                         prcstring += f.read()
         else:
-            etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')]
-            etcfiles.sort(reverse=True)
-            for fn in etcfiles:
+            for fn in sorted((i for i in p3dwhl.namelist() if i.endswith('.prc')), reverse=True):
                 with p3dwhl.open(fn) as f:
                     prcstring += f.read().decode('utf8')
 
@@ -1071,7 +1075,7 @@ class build_apps(setuptools.Command):
             freezer_extras.update(freezer.extras)
             freezer_modules.update(freezer.getAllModuleNames())
             for suffix in freezer.mf.suffixes:
-                if suffix[2] == imp.C_EXTENSION:
+                if suffix[2] == 3: # imp.C_EXTENSION:
                     ext_suffixes.add(suffix[0])
 
         for appname, scriptname in self.gui_apps.items():
@@ -1702,7 +1706,7 @@ class bdist_apps(setuptools.Command):
             setattr(self, opt, None)
 
     def finalize_options(self):
-        import pkg_resources
+        from importlib.metadata import entry_points
 
         # We need to massage the inputs a bit in case they came from a
         # setup.cfg file.
@@ -1716,11 +1720,17 @@ class bdist_apps(setuptools.Command):
             self.signing_certificate = os.path.abspath(self.signing_certificate)
             self.signing_private_key = os.path.abspath(self.signing_private_key)
 
+        eps = entry_points()
+        if isinstance(eps, dict): # Python 3.8 and 3.9
+            installer_eps = eps.get('panda3d.bdist_apps.installers', ())
+        else:
+            installer_eps = eps.select(group='panda3d.bdist_apps.installers')
+
         tmp = self.DEFAULT_INSTALLER_FUNCS.copy()
         tmp.update(self.installer_functions)
         tmp.update({
             entrypoint.name: entrypoint.load()
-            for entrypoint in pkg_resources.iter_entry_points('panda3d.bdist_apps.installers')
+            for entrypoint in installer_eps
         })
         self.installer_functions = tmp
 

+ 3 - 1
direct/src/distributed/AsyncRequest.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 #from otp.ai.AIBaseGlobal import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.DirectObject import DirectObject
@@ -39,7 +41,7 @@ class AsyncRequest(DirectObject):
     will be called again when the new self.neededObjects is complete.  You
     may repeat this as necessary.
     """
-    _asyncRequests = {}
+    _asyncRequests: dict[int, AsyncRequest] = {}
 
     notify = DirectNotifyGlobal.directNotify.newCategory('AsyncRequest')
 

+ 0 - 38
direct/src/distributed/CRDataCache.py

@@ -76,41 +76,3 @@ if __debug__:
         def flush(self):
             CachedDOData.flush(self)
             self._flushed = True
-
-    dc = CRDataCache()
-    dc._startMemLeakCheck()
-
-    cd = CachedDOData()
-    cd.foo = 34
-    dc.setCachedData(1, 'testCachedData', cd)
-    del cd
-    cd = CachedDOData()
-    cd.bar = 45
-    dc.setCachedData(1, 'testCachedData2', cd)
-    del cd
-    assert dc.hasCachedData(1)
-    assert dc.hasCachedData(1)
-    assert not dc.hasCachedData(2)
-    # data is dict of dataName->data
-    data = dc.popCachedData(1)
-    assert len(data) == 2
-    assert 'testCachedData' in data
-    assert 'testCachedData2' in data
-    assert data['testCachedData'].foo == 34
-    assert data['testCachedData2'].bar == 45
-    for cd in data.values():
-        cd.flush()
-    del data
-    dc._checkMemLeaks()
-
-    cd = CachedDOData()
-    cd.bar = 1234
-    dc.setCachedData(43, 'testCachedData2', cd)
-    del cd
-    assert dc.hasCachedData(43)
-    dc.flush()
-    dc._checkMemLeaks()
-
-    dc._stopMemLeakCheck()
-    dc.destroy()
-    del dc

+ 7 - 0
direct/src/distributed/CachedDOData.py

@@ -20,3 +20,10 @@ class CachedDOData:
         # override and destroy the cached data
         # cached data is typically created by the DistributedObject and destroyed here
         pass
+
+    # These next two methods tell mypy to allow arbitrary attributes.
+    def __getattribute__(self, name: str):
+        return object.__getattribute__(self, name)
+
+    def __setattr__(self, name: str, value) -> None:
+        object.__setattr__(self, name, value)

+ 3 - 0
direct/src/distributed/ClientRepositoryBase.py

@@ -469,6 +469,9 @@ class ClientRepositoryBase(ConnectionRepository):
                     f"Asked to update non-existent DistObj {doId} and failed to find it")
 
     def __doUpdateOwner(self, doId, di):
+        if not self.hasOwnerView():
+            return False
+
         ovObj = self.doId2ownerView.get(doId)
         if ovObj:
             odg = Datagram(di.getDatagram())

+ 1 - 1
direct/src/distributed/DistributedCartesianGrid.py

@@ -23,7 +23,7 @@ GRID_Z_OFFSET = 0.0
 
 class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
     notify = directNotify.newCategory("DistributedCartesianGrid")
-    notify.setDebug(0)
+    notify.setDebug(False)
 
     VisualizeGrid = ConfigVariableBool("visualize-cartesian-grid", False)
 

+ 2 - 2
direct/src/distributed/DistributedObjectAI.py

@@ -299,7 +299,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # setLocation destroys self._zoneData if we move away to
         # a different zone
         if self._zoneData is None:
-            from otp.ai.AIZoneData import AIZoneData
+            from otp.ai.AIZoneData import AIZoneData  # type: ignore[import]
             self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId)
         return self._zoneData
 
@@ -489,7 +489,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # should have different names.
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

+ 1 - 1
direct/src/distributed/DistributedObjectUD.py

@@ -424,7 +424,7 @@ class DistributedObjectUD(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # should have different names.
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

+ 2 - 8
direct/src/distributed/DoCollectionManager.py

@@ -149,10 +149,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
-        counts = list(count2classes.keys())
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(count2classes, reverse=True):
             count2classes[count].sort()
             for name in count2classes[count]:
                 print('%s %s' % (count, name))
@@ -166,10 +163,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
-        counts = list(count2classes.keys())
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(count2classes, reverse=True):
             count2classes[count].sort()
             for name in count2classes[count]:
                 # print '%s %s' % (count, name)

+ 5 - 4
direct/src/distributed/DoInterestManager.py

@@ -7,6 +7,8 @@ zone, remove interest in that zone.
 p.s. A great deal of this code is just code moved from ClientRepository.py.
 """
 
+from __future__ import annotations
+
 from panda3d.core import ConfigVariableBool
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from direct.showbase import DirectObject
@@ -98,9 +100,9 @@ class DoInterestManager(DirectObject.DirectObject):
     _ContextIdSerialNum = 100
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
 
-    _interests = {}
+    _interests: dict[int, InterestState] = {}
     if __debug__:
-        _debug_interestHistory = []
+        _debug_interestHistory: list[tuple] = []
         _debug_maxDescriptionLen = 40
 
     _SerialGen = SerialNumGen()
@@ -512,8 +514,7 @@ class DoInterestManager(DirectObject.DirectObject):
         datagram = PyDatagram()
         # Add message type
         if isinstance(zoneIdList, list):
-            vzl = list(zoneIdList)
-            vzl.sort()
+            vzl = sorted(zoneIdList)
             uniqueElements(vzl)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint32(contextId)

+ 4 - 2
direct/src/distributed/GridParent.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from panda3d.core import NodePath
 
 #
@@ -13,8 +15,8 @@ from panda3d.core import NodePath
 class GridParent:
 
     # this lets GridParents share CellOrigins
-    GridZone2CellOrigin = {}
-    GridZone2count = {}
+    GridZone2CellOrigin: dict[tuple, NodePath] = {}
+    GridZone2count: dict[tuple, int] = {}
     @staticmethod
     def getCellOrigin(grid, zoneId):
         tup = (grid, zoneId)

+ 142 - 137
direct/src/distributed/MsgTypes.py

@@ -1,143 +1,148 @@
 """MsgTypes module: contains distributed object message types"""
 
+from __future__ import annotations
+
 from direct.showbase.PythonUtil import invertDictLossless
 
-MsgName2Id = {
-    'CLIENT_HELLO':                                  1,
-    'CLIENT_HELLO_RESP':                             2,
-
-    # Sent by the client when it's leaving.
-    'CLIENT_DISCONNECT':                             3,
-
-    # Sent by the server when it is dropping the connection deliberately.
-    'CLIENT_EJECT':                                  4,
-
-    'CLIENT_HEARTBEAT':                              5,
-
-    'CLIENT_OBJECT_SET_FIELD':                       120,
-    'CLIENT_OBJECT_SET_FIELDS':                      121,
-    'CLIENT_OBJECT_LEAVING':                         132,
-    'CLIENT_OBJECT_LEAVING_OWNER':                   161,
-    'CLIENT_ENTER_OBJECT_REQUIRED':                  142,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER':            143,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OWNER':            172,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER':      173,
-
-    'CLIENT_DONE_INTEREST_RESP':                     204,
-
-    'CLIENT_ADD_INTEREST':                           200,
-    'CLIENT_ADD_INTEREST_MULTIPLE':                  201,
-    'CLIENT_REMOVE_INTEREST':                        203,
-    'CLIENT_OBJECT_LOCATION':                        140,
-
-
-    # These are sent internally inside the Astron cluster.
-
-    # Message Director control messages:
-    'CONTROL_CHANNEL':                                  1,
-    'CONTROL_ADD_CHANNEL':                              9000,
-    'CONTROL_REMOVE_CHANNEL':                           9001,
-    'CONTROL_ADD_RANGE':                                9002,
-    'CONTROL_REMOVE_RANGE':                             9003,
-    'CONTROL_ADD_POST_REMOVE':                          9010,
-    'CONTROL_CLEAR_POST_REMOVES':                       9011,
-
-    # State Server control messages:
-    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED':          2000,
-    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER':    2001,
-    'STATESERVER_DELETE_AI_OBJECTS':                    2009,
-    'STATESERVER_OBJECT_GET_FIELD':                     2010,
-    'STATESERVER_OBJECT_GET_FIELD_RESP':                2011,
-    'STATESERVER_OBJECT_GET_FIELDS':                    2012,
-    'STATESERVER_OBJECT_GET_FIELDS_RESP':               2013,
-    'STATESERVER_OBJECT_GET_ALL':                       2014,
-    'STATESERVER_OBJECT_GET_ALL_RESP':                  2015,
-    'STATESERVER_OBJECT_SET_FIELD':                     2020,
-    'STATESERVER_OBJECT_SET_FIELDS':                    2021,
-    'STATESERVER_OBJECT_DELETE_FIELD_RAM':              2030,
-    'STATESERVER_OBJECT_DELETE_FIELDS_RAM':             2031,
-    'STATESERVER_OBJECT_DELETE_RAM':                    2032,
-    'STATESERVER_OBJECT_SET_LOCATION':                          2040,
-    'STATESERVER_OBJECT_CHANGING_LOCATION':                     2041,
-    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED':          2042,
-    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER':    2043,
-    'STATESERVER_OBJECT_GET_LOCATION':                          2044,
-    'STATESERVER_OBJECT_GET_LOCATION_RESP':                     2045,
-    'STATESERVER_OBJECT_SET_AI':                                2050,
-    'STATESERVER_OBJECT_CHANGING_AI':                           2051,
-    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED':                2052,
-    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER':          2053,
-    'STATESERVER_OBJECT_GET_AI':                                2054,
-    'STATESERVER_OBJECT_GET_AI_RESP':                           2055,
-    'STATESERVER_OBJECT_SET_OWNER':                             2060,
-    'STATESERVER_OBJECT_CHANGING_OWNER':                        2061,
-    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED':             2062,
-    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER':       2063,
-    'STATESERVER_OBJECT_GET_OWNER':                             2064,
-    'STATESERVER_OBJECT_GET_OWNER_RESP':                        2065,
-    'STATESERVER_OBJECT_GET_ZONE_OBJECTS':              2100,
-    'STATESERVER_OBJECT_GET_ZONES_OBJECTS':             2102,
-    'STATESERVER_OBJECT_GET_CHILDREN':                  2104,
-    'STATESERVER_OBJECT_GET_ZONE_COUNT':                2110,
-    'STATESERVER_OBJECT_GET_ZONE_COUNT_RESP':           2111,
-    'STATESERVER_OBJECT_GET_ZONES_COUNT':               2112,
-    'STATESERVER_OBJECT_GET_ZONES_COUNT_RESP':          2113,
-    'STATESERVER_OBJECT_GET_CHILD_COUNT':               2114,
-    'STATESERVER_OBJECT_GET_CHILD_COUNT_RESP':          2115,
-    'STATESERVER_OBJECT_DELETE_ZONE':                   2120,
-    'STATESERVER_OBJECT_DELETE_ZONES':                  2122,
-    'STATESERVER_OBJECT_DELETE_CHILDREN':               2124,
-    # DBSS-backed-object messages:
-    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS':        2200,
-    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER':  2201,
-    'DBSS_OBJECT_GET_ACTIVATED':                 2207,
-    'DBSS_OBJECT_GET_ACTIVATED_RESP':            2208,
-    'DBSS_OBJECT_DELETE_FIELD_DISK':             2230,
-    'DBSS_OBJECT_DELETE_FIELDS_DISK':            2231,
-    'DBSS_OBJECT_DELETE_DISK':                   2232,
-
-    # Database Server control messages:
-    'DBSERVER_CREATE_OBJECT':                       3000,
-    'DBSERVER_CREATE_OBJECT_RESP':                  3001,
-    'DBSERVER_OBJECT_GET_FIELD':                    3010,
-    'DBSERVER_OBJECT_GET_FIELD_RESP':               3011,
-    'DBSERVER_OBJECT_GET_FIELDS':                   3012,
-    'DBSERVER_OBJECT_GET_FIELDS_RESP':              3013,
-    'DBSERVER_OBJECT_GET_ALL':                      3014,
-    'DBSERVER_OBJECT_GET_ALL_RESP':                 3015,
-    'DBSERVER_OBJECT_SET_FIELD':                    3020,
-    'DBSERVER_OBJECT_SET_FIELDS':                   3021,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS':          3022,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP':     3023,
-    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS':         3024,
-    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP':    3025,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY':           3026,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY_RESP':      3027,
-    'DBSERVER_OBJECT_DELETE_FIELD':                 3030,
-    'DBSERVER_OBJECT_DELETE_FIELDS':                3031,
-    'DBSERVER_OBJECT_DELETE':                       3032,
-
-    # Client Agent control messages:
-    'CLIENTAGENT_SET_STATE':                        1000,
-    'CLIENTAGENT_SET_CLIENT_ID':                    1001,
-    'CLIENTAGENT_SEND_DATAGRAM':                    1002,
-    'CLIENTAGENT_EJECT':                            1004,
-    'CLIENTAGENT_DROP':                             1005,
-    'CLIENTAGENT_GET_NETWORK_ADDRESS':              1006,
-    'CLIENTAGENT_GET_NETWORK_ADDRESS_RESP':         1007,
-    'CLIENTAGENT_DECLARE_OBJECT':                   1010,
-    'CLIENTAGENT_UNDECLARE_OBJECT':                 1011,
-    'CLIENTAGENT_ADD_SESSION_OBJECT':               1012,
-    'CLIENTAGENT_REMOVE_SESSION_OBJECT':            1013,
-    'CLIENTAGENT_SET_FIELDS_SENDABLE':              1014,
-    'CLIENTAGENT_OPEN_CHANNEL':                     1100,
-    'CLIENTAGENT_CLOSE_CHANNEL':                    1101,
-    'CLIENTAGENT_ADD_POST_REMOVE':                  1110,
-    'CLIENTAGENT_CLEAR_POST_REMOVES':               1111,
-    'CLIENTAGENT_ADD_INTEREST':                     1200,
-    'CLIENTAGENT_ADD_INTEREST_MULTIPLE':            1201,
-    'CLIENTAGENT_REMOVE_INTEREST':                  1203,
-}
+CLIENT_HELLO =                                 1
+CLIENT_HELLO_RESP =                            2
+
+# Sent by the client when it's leaving.
+CLIENT_DISCONNECT =                            3
+
+# Sent by the server when it is dropping the connection deliberately.
+CLIENT_EJECT =                                 4
+
+CLIENT_HEARTBEAT =                             5
+
+CLIENT_OBJECT_SET_FIELD =                      120
+CLIENT_OBJECT_SET_FIELDS =                     121
+CLIENT_OBJECT_LEAVING =                        132
+CLIENT_OBJECT_LEAVING_OWNER =                  161
+CLIENT_ENTER_OBJECT_REQUIRED =                 142
+CLIENT_ENTER_OBJECT_REQUIRED_OTHER =           143
+CLIENT_ENTER_OBJECT_REQUIRED_OWNER =           172
+CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER =     173
+
+CLIENT_DONE_INTEREST_RESP =                    204
+
+CLIENT_ADD_INTEREST =                          200
+CLIENT_ADD_INTEREST_MULTIPLE =                 201
+CLIENT_REMOVE_INTEREST =                       203
+CLIENT_OBJECT_LOCATION =                       140
+
+
+# These are sent internally inside the Astron cluster.
+
+# Message Director control messages:
+CONTROL_CHANNEL =                                 1
+CONTROL_ADD_CHANNEL =                             9000
+CONTROL_REMOVE_CHANNEL =                          9001
+CONTROL_ADD_RANGE =                               9002
+CONTROL_REMOVE_RANGE =                            9003
+CONTROL_ADD_POST_REMOVE =                         9010
+CONTROL_CLEAR_POST_REMOVES =                      9011
+CONTROL_SET_CON_NAME =                            9012
+CONTROL_SET_CON_URL =                             9013
+CONTROL_LOG_MESSAGE =                             9014
+
+# State Server control messages:
+STATESERVER_CREATE_OBJECT_WITH_REQUIRED =         2000
+STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER =   2001
+STATESERVER_DELETE_AI_OBJECTS =                   2009
+STATESERVER_OBJECT_GET_FIELD =                    2010
+STATESERVER_OBJECT_GET_FIELD_RESP =               2011
+STATESERVER_OBJECT_GET_FIELDS =                   2012
+STATESERVER_OBJECT_GET_FIELDS_RESP =              2013
+STATESERVER_OBJECT_GET_ALL =                      2014
+STATESERVER_OBJECT_GET_ALL_RESP =                 2015
+STATESERVER_OBJECT_SET_FIELD =                    2020
+STATESERVER_OBJECT_SET_FIELDS =                   2021
+STATESERVER_OBJECT_DELETE_FIELD_RAM =             2030
+STATESERVER_OBJECT_DELETE_FIELDS_RAM =            2031
+STATESERVER_OBJECT_DELETE_RAM =                   2032
+STATESERVER_OBJECT_SET_LOCATION =                         2040
+STATESERVER_OBJECT_CHANGING_LOCATION =                    2041
+STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED =         2042
+STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER =   2043
+STATESERVER_OBJECT_GET_LOCATION =                         2044
+STATESERVER_OBJECT_GET_LOCATION_RESP =                    2045
+STATESERVER_OBJECT_SET_AI =                               2050
+STATESERVER_OBJECT_CHANGING_AI =                          2051
+STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED =               2052
+STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER =         2053
+STATESERVER_OBJECT_GET_AI =                               2054
+STATESERVER_OBJECT_GET_AI_RESP =                          2055
+STATESERVER_OBJECT_SET_OWNER =                            2060
+STATESERVER_OBJECT_CHANGING_OWNER =                       2061
+STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED =            2062
+STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER =      2063
+STATESERVER_OBJECT_GET_OWNER =                            2064
+STATESERVER_OBJECT_GET_OWNER_RESP =                       2065
+STATESERVER_OBJECT_GET_ZONE_OBJECTS =             2100
+STATESERVER_OBJECT_GET_ZONES_OBJECTS =            2102
+STATESERVER_OBJECT_GET_CHILDREN =                 2104
+STATESERVER_OBJECT_GET_ZONE_COUNT =               2110
+STATESERVER_OBJECT_GET_ZONE_COUNT_RESP =          2111
+STATESERVER_OBJECT_GET_ZONES_COUNT =              2112
+STATESERVER_OBJECT_GET_ZONES_COUNT_RESP =         2113
+STATESERVER_OBJECT_GET_CHILD_COUNT =              2114
+STATESERVER_OBJECT_GET_CHILD_COUNT_RESP =         2115
+STATESERVER_OBJECT_DELETE_ZONE =                  2120
+STATESERVER_OBJECT_DELETE_ZONES =                 2122
+STATESERVER_OBJECT_DELETE_CHILDREN =              2124
+# DBSS-backed-object messages:
+DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS =       2200
+DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER = 2201
+DBSS_OBJECT_GET_ACTIVATED =                2207
+DBSS_OBJECT_GET_ACTIVATED_RESP =           2208
+DBSS_OBJECT_DELETE_FIELD_DISK =            2230
+DBSS_OBJECT_DELETE_FIELDS_DISK =           2231
+DBSS_OBJECT_DELETE_DISK =                  2232
+
+# Database Server control messages:
+DBSERVER_CREATE_OBJECT =                      3000
+DBSERVER_CREATE_OBJECT_RESP =                 3001
+DBSERVER_OBJECT_GET_FIELD =                   3010
+DBSERVER_OBJECT_GET_FIELD_RESP =              3011
+DBSERVER_OBJECT_GET_FIELDS =                  3012
+DBSERVER_OBJECT_GET_FIELDS_RESP =             3013
+DBSERVER_OBJECT_GET_ALL =                     3014
+DBSERVER_OBJECT_GET_ALL_RESP =                3015
+DBSERVER_OBJECT_SET_FIELD =                   3020
+DBSERVER_OBJECT_SET_FIELDS =                  3021
+DBSERVER_OBJECT_SET_FIELD_IF_EQUALS =         3022
+DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP =    3023
+DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS =        3024
+DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP =   3025
+DBSERVER_OBJECT_SET_FIELD_IF_EMPTY =          3026
+DBSERVER_OBJECT_SET_FIELD_IF_EMPTY_RESP =     3027
+DBSERVER_OBJECT_DELETE_FIELD =                3030
+DBSERVER_OBJECT_DELETE_FIELDS =               3031
+DBSERVER_OBJECT_DELETE =                      3032
+
+# Client Agent control messages:
+CLIENTAGENT_SET_STATE =                       1000
+CLIENTAGENT_SET_CLIENT_ID =                   1001
+CLIENTAGENT_SEND_DATAGRAM =                   1002
+CLIENTAGENT_EJECT =                           1004
+CLIENTAGENT_DROP =                            1005
+CLIENTAGENT_GET_NETWORK_ADDRESS =             1006
+CLIENTAGENT_GET_NETWORK_ADDRESS_RESP =        1007
+CLIENTAGENT_DECLARE_OBJECT =                  1010
+CLIENTAGENT_UNDECLARE_OBJECT =                1011
+CLIENTAGENT_ADD_SESSION_OBJECT =              1012
+CLIENTAGENT_REMOVE_SESSION_OBJECT =           1013
+CLIENTAGENT_SET_FIELDS_SENDABLE =             1014
+CLIENTAGENT_OPEN_CHANNEL =                    1100
+CLIENTAGENT_CLOSE_CHANNEL =                   1101
+CLIENTAGENT_ADD_POST_REMOVE =                 1110
+CLIENTAGENT_CLEAR_POST_REMOVES =              1111
+CLIENTAGENT_ADD_INTEREST =                    1200
+CLIENTAGENT_ADD_INTEREST_MULTIPLE =           1201
+CLIENTAGENT_REMOVE_INTEREST =                 1203
+
+MsgName2Id = {name: value for name, value in globals().items() if isinstance(value, int)}
 
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
@@ -146,7 +151,7 @@ MsgId2Names = invertDictLossless(MsgName2Id)
 globals().update(MsgName2Id)
 
 # These messages are ignored when the client is headed to the quiet zone
-QUIET_ZONE_IGNORED_LIST = [
+QUIET_ZONE_IGNORED_LIST: list[int] = [
 
     # We mustn't ignore updates, because some updates for localToon
     # are always important.

+ 15 - 18
direct/src/distributed/MsgTypesCMU.py

@@ -5,25 +5,22 @@ implementation. """
 
 from direct.showbase.PythonUtil import invertDictLossless
 
-MsgName2Id = {
-    'SET_DOID_RANGE_CMU'                      : 9001,
-    'CLIENT_OBJECT_GENERATE_CMU'              : 9002,
-    'OBJECT_GENERATE_CMU'                     : 9003,
-    'OBJECT_UPDATE_FIELD_CMU'                 : 9004,
-    'OBJECT_DISABLE_CMU'                      : 9005,
-    'OBJECT_DELETE_CMU'                       : 9006,
-    'REQUEST_GENERATES_CMU'                   : 9007,
-    'CLIENT_DISCONNECT_CMU'                   : 9008,
-    'CLIENT_SET_INTEREST_CMU'                 : 9009,
-    'OBJECT_SET_ZONE_CMU'                     : 9010,
-    'CLIENT_HEARTBEAT_CMU'                    : 9011,
-    'CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU'  : 9011,
+SET_DOID_RANGE_CMU                      = 9001
+CLIENT_OBJECT_GENERATE_CMU              = 9002
+OBJECT_GENERATE_CMU                     = 9003
+OBJECT_UPDATE_FIELD_CMU                 = 9004
+OBJECT_DISABLE_CMU                      = 9005
+OBJECT_DELETE_CMU                       = 9006
+REQUEST_GENERATES_CMU                   = 9007
+CLIENT_DISCONNECT_CMU                   = 9008
+CLIENT_SET_INTEREST_CMU                 = 9009
+OBJECT_SET_ZONE_CMU                     = 9010
+CLIENT_HEARTBEAT_CMU                    = 9011
+CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU  = 9011
 
-    'CLIENT_OBJECT_UPDATE_FIELD' : 120,  # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
-}
+CLIENT_OBJECT_UPDATE_FIELD = 120  # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
+
+MsgName2Id = {name: value for name, value in globals().items() if isinstance(value, int)}
 
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
-
-# put msg names in module scope, assigned to msg value
-globals().update(MsgName2Id)

+ 9 - 9
direct/src/distributed/cConnectionRepository.cxx

@@ -725,9 +725,9 @@ handle_update_field() {
       // while we call the update method--otherwise, the update method might
       // get into trouble if it tried to delete the object from the doId2do
       // map.
-      Py_INCREF(distobj);
-      invoke_extension(dclass).receive_update(distobj, _di);
-      Py_DECREF(distobj);
+      PyObject *distobj_ref = Py_NewRef(distobj);
+      invoke_extension(dclass).receive_update(distobj_ref, _di);
+      Py_DECREF(distobj_ref);
 
       if (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
@@ -802,12 +802,12 @@ handle_update_field_owner() {
         // raised while we call the update method--otherwise, the update
         // method might get into trouble if it tried to delete the object from
         // the doId2do map.
-        Py_INCREF(distobjOV);
+        PyObject *distobjOV_ref = Py_NewRef(distobjOV);
         // make a copy of the datagram iterator so that we can use the main
         // iterator for the non-owner update
         DatagramIterator _odi(_di);
-        invoke_extension(dclass).receive_update(distobjOV, _odi);
-        Py_DECREF(distobjOV);
+        invoke_extension(dclass).receive_update(distobjOV_ref, _odi);
+        Py_DECREF(distobjOV_ref);
 
         if (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
@@ -846,9 +846,9 @@ handle_update_field_owner() {
         // while we call the update method--otherwise, the update method might
         // get into trouble if it tried to delete the object from the doId2do
         // map.
-        Py_INCREF(distobj);
-        invoke_extension(dclass).receive_update(distobj, _di);
-        Py_DECREF(distobj);
+        PyObject *distobj_ref = Py_NewRef(distobj);
+        invoke_extension(dclass).receive_update(distobj_ref, _di);
+        Py_DECREF(distobj_ref);
 
         if (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)

+ 12 - 4
direct/src/extensions_native/NodePath_extensions.py

@@ -435,7 +435,8 @@ Dtool_funcToMethod(iPosHprScale, NodePath)
 del iPosHprScale
 #####################################################################
 def place(self):
-    base.startDirect(fWantTk = 1)
+    from direct.showbase import ShowBaseGlobal
+    ShowBaseGlobal.base.startDirect(fWantTk = 1)
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     import importlib
@@ -446,7 +447,8 @@ Dtool_funcToMethod(place, NodePath)
 del place
 #####################################################################
 def explore(self):
-    base.startDirect(fWantTk = 1)
+    from direct.showbase import ShowBaseGlobal
+    ShowBaseGlobal.base.startDirect(fWantTk = 1)
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     import importlib
@@ -457,7 +459,8 @@ Dtool_funcToMethod(explore, NodePath)
 del explore
 #####################################################################
 def rgbPanel(self, cb = None):
-    base.startTk()
+    from direct.showbase import ShowBaseGlobal
+    ShowBaseGlobal.base.startTk()
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     import importlib
@@ -468,6 +471,8 @@ Dtool_funcToMethod(rgbPanel, NodePath)
 del rgbPanel
 #####################################################################
 def select(self):
+    from direct.showbase import ShowBaseGlobal
+    base = ShowBaseGlobal.base
     base.startDirect(fWantTk = 0)
     base.direct.select(self)
 
@@ -475,6 +480,8 @@ Dtool_funcToMethod(select, NodePath)
 del select
 #####################################################################
 def deselect(self):
+    from direct.showbase import ShowBaseGlobal
+    base = ShowBaseGlobal.base
     base.startDirect(fWantTk = 0)
     base.direct.deselect(self)
 
@@ -676,7 +683,8 @@ def flattenMultitex(self, stateFrom = None, target = None,
     mr.setAllowTexMat(allowTexMat)
 
     if win is None:
-        win = base.win
+        from direct.showbase import ShowBaseGlobal
+        win = ShowBaseGlobal.base.win
 
     if stateFrom is None:
         mr.scan(self)

+ 3 - 1
direct/src/fsm/ClassicFSM.py

@@ -5,6 +5,8 @@ Note:
     existing code.  New code should use the :mod:`.FSM` module instead.
 """
 
+from __future__ import annotations
+
 __all__ = ['ClassicFSM']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -13,7 +15,7 @@ from direct.showbase.MessengerGlobal import messenger
 import weakref
 
 if __debug__:
-    _debugFsms = {}
+    _debugFsms: dict[str, weakref.ref] = {}
 
     def printDebugFsmList():
         for k in sorted(_debugFsms.keys()):

+ 3 - 1
direct/src/fsm/State.py

@@ -1,5 +1,7 @@
 """State module: contains State class"""
 
+from __future__ import annotations
+
 __all__ = ['State']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -18,7 +20,7 @@ class State(DirectObject):
     # should not cause any leaks.
     if __debug__:
         import weakref
-        States = weakref.WeakKeyDictionary()
+        States: weakref.WeakKeyDictionary[State, int] = weakref.WeakKeyDictionary()
 
         @classmethod
         def replaceMethod(cls, oldFunction, newFunction):

+ 4 - 153
direct/src/fsm/StatePush.py

@@ -1,6 +1,8 @@
 # classes for event-driven programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 
+from __future__ import annotations
+
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
            'EventArgument', ]
 
@@ -46,11 +48,6 @@ class PushesStateChanges:
         for subscriber in self._subscribers:
             subscriber._recvStatePush(self)
 
-if __debug__:
-    psc = PushesStateChanges(0)
-    assert psc.getState() == 0
-    psc.destroy()
-    del psc
 
 class ReceivesStateChanges:
     # base class for objects that subscribe to state changes from PushesStateChanges objects
@@ -83,10 +80,6 @@ class ReceivesStateChanges:
     def _recvStatePush(self, source):
         pass
 
-if __debug__:
-    rsc = ReceivesStateChanges(None)
-    rsc.destroy()
-    del rsc
 
 class StateVar(PushesStateChanges):
     # coder-friendly object that allows values to be set on it and pushes those values
@@ -97,13 +90,6 @@ class StateVar(PushesStateChanges):
     def get(self):
         return PushesStateChanges.getState(self)
 
-if __debug__:
-    sv = StateVar(0)
-    assert sv.get() == 0
-    sv.set(1)
-    assert sv.get() == 1
-    sv.destroy()
-    del sv
 
 class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
     # base class that can be used to create a state-change notification chain
@@ -120,31 +106,6 @@ class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
         # got a state push, apply new state to self
         self._handlePotentialStateChange(source._value)
 
-if __debug__:
-    sv = StateVar(0)
-    assert sv.get() == 0
-    scn = StateChangeNode(sv)
-    assert scn.getState() == 0
-    sv.set(1)
-    assert sv.get() == 1
-    assert scn.getState() == 1
-    scn2 = StateChangeNode(scn)
-    assert scn2.getState() == 1
-    sv.set(2)
-    assert scn2.getState() == 2
-    scn3 = StateChangeNode(scn)
-    assert scn3.getState() == 2
-    sv.set(3)
-    assert scn2.getState() == 3
-    assert scn3.getState() == 3
-    scn3.destroy()
-    scn2.destroy()
-    scn.destroy()
-    sv.destroy()
-    del scn3
-    del scn2
-    del scn
-    del sv
 
 class ReceivesMultipleStateChanges:
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
@@ -179,15 +140,6 @@ class ReceivesMultipleStateChanges:
     def _recvMultiStatePush(self, key, source):
         pass
 
-if __debug__:
-    rsc = ReceivesMultipleStateChanges()
-    sv = StateVar(0)
-    sv2 = StateVar('b')
-    rsc._subscribeTo(sv, 'a')
-    rsc._subscribeTo(sv2, 2)
-    rsc._unsubscribe('a')
-    rsc.destroy()
-    del rsc
 
 class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
     # calls func with provided args whenever arguments' state changes
@@ -249,47 +201,6 @@ class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
             self._func(*self._bakedArgs, **self._bakedKargs)
             PushesStateChanges._handleStateChange(self)
 
-if __debug__:
-    l = []
-    def handler1(value, l=l):
-        l.append(value)
-    assert not l
-    sv = StateVar(0)
-    fc = FunctionCall(handler1, sv)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [0,]
-    sv.set(1)
-    assert l == [0,1,]
-    sv.set(2)
-    assert l == [0,1,2,]
-    fc.destroy()
-    sv.destroy()
-    del fc
-    del sv
-    del handler1
-    del l
-
-    l = []
-    def handler2(value, kDummy=None, kValue=None, l=l):
-        l.append((value, kValue))
-    assert not l
-    sv = StateVar(0)
-    ksv = StateVar('a')
-    fc = FunctionCall(handler2, sv, kValue=ksv)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [(0,'a',),]
-    sv.set(1)
-    assert l == [(0,'a'),(1,'a'),]
-    ksv.set('b')
-    assert l == [(0,'a'),(1,'a'),(1,'b'),]
-    fc.destroy()
-    sv.destroy()
-    del fc
-    del sv
-    del handler2
-    del l
 
 class EnterExit(StateChangeNode):
     # call enterFunc when our state becomes true, exitFunc when it becomes false
@@ -314,33 +225,6 @@ class EnterExit(StateChangeNode):
             self._exitFunc()
         StateChangeNode._handleStateChange(self)
 
-if __debug__:
-    l = []
-    def enter(l=l):
-        l.append(1)
-    def exit(l=l):
-        l.append(0)
-    sv = StateVar(0)
-    ee = EnterExit(sv, enter, exit)
-    sv.set(0)
-    assert not l
-    sv.set(1)
-    assert l == [1,]
-    sv.set(2)
-    assert l == [1,]
-    sv.set(0)
-    assert l == [1,0,]
-    sv.set(True)
-    assert l == [1,0,1,]
-    sv.set(False)
-    assert l == [1,0,1,0,]
-    ee.destroy()
-    sv.destroy()
-    del ee
-    del sv
-    del enter
-    del exit
-    del l
 
 class Pulse(PushesStateChanges):
     # changes state to True then immediately to False whenever sendPulse is called
@@ -351,25 +235,6 @@ class Pulse(PushesStateChanges):
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(False)
 
-if __debug__:
-    l = []
-    def handler(value, l=l):
-        l.append(value)
-    p = Pulse()
-    fc = FunctionCall(handler, p)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [False, ]
-    p.sendPulse()
-    assert l == [False, True, False, ]
-    p.sendPulse()
-    assert l == [False, True, False, True, False, ]
-    fc.destroy()
-    p.destroy()
-    del fc
-    del p
-    del l
-    del handler
 
 class EventPulse(Pulse, DirectObject):
     # sends a True-False "pulse" whenever a specific messenger message is sent
@@ -381,6 +246,7 @@ class EventPulse(Pulse, DirectObject):
         self.ignoreAll()
         Pulse.destroy(self)
 
+
 class EventArgument(PushesStateChanges, DirectObject):
     # tracks a particular argument to a particular messenger event
     def __init__(self, event, index=0):
@@ -396,6 +262,7 @@ class EventArgument(PushesStateChanges, DirectObject):
     def _handleEvent(self, *args):
         self._handlePotentialStateChange(args[self._index])
 
+
 class AttrSetter(StateChangeNode):
     def __init__(self, source, object, attrName):
         self._object = object
@@ -406,19 +273,3 @@ class AttrSetter(StateChangeNode):
     def _handleStateChange(self):
         setattr(self._object, self._attrName, self._value)
         StateChangeNode._handleStateChange(self)
-
-if __debug__:
-    from direct.showbase.PythonUtil import ScratchPad
-    o = ScratchPad()
-    svar = StateVar(0)
-    aset = AttrSetter(svar, o, 'testAttr')
-    assert hasattr(o, 'testAttr')
-    assert o.testAttr == 0
-    svar.set('red')
-    assert o.testAttr == 'red'
-    aset.destroy()
-    svar.destroy()
-    o.destroy()
-    del aset
-    del svar
-    del o

+ 3 - 1
direct/src/gui/DirectDialog.py

@@ -4,6 +4,8 @@ See the :ref:`directdialog` page in the programming manual for a more
 in-depth explanation and an example of how to use this class.
 """
 
+from __future__ import annotations
+
 __all__ = [
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
@@ -45,7 +47,7 @@ def cleanupDialog(uniqueName):
 
 class DirectDialog(DirectFrame):
 
-    AllDialogs = {}
+    AllDialogs: dict[str, DirectDialog] = {}
     PanelIndex = 0
 
     def __init__(self, parent=None, **kw):

+ 1 - 1
direct/src/gui/DirectEntry.py

@@ -28,7 +28,7 @@ class DirectEntry(DirectFrame):
     to keyboard buttons
     """
 
-    directWtext = ConfigVariableBool('direct-wtext', 1)
+    directWtext = ConfigVariableBool('direct-wtext', True)
 
     AllowCapNamePrefixes = ("Al", "Ap", "Ben", "De", "Del", "Della", "Delle", "Der", "Di", "Du",
                             "El", "Fitz", "La", "Las", "Le", "Les", "Lo", "Los",

+ 10 - 5
direct/src/gui/DirectGuiBase.py

@@ -84,6 +84,8 @@ Code overview:
     see if any keywords are left unused.  If so, an error is raised.
 """
 
+from __future__ import annotations
+
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
@@ -227,7 +229,12 @@ class DirectGuiBase(DirectObject.DirectObject):
                         del keywords[name]
                     else:
                         # Use optionDefs value
-                        optionInfo[name] = [default, default, function]
+                        value = default
+                        if isinstance(value, list):
+                            value = list(value)
+                        elif isinstance(value, dict):
+                            value = dict(value)
+                        optionInfo[name] = [default, value, function]
                 elif optionInfo[name][FUNCTION] is None:
                     # Only override function if not defined by derived class
                     optionInfo[name][FUNCTION] = function
@@ -616,9 +623,7 @@ class DirectGuiBase(DirectObject.DirectObject):
 
     def components(self):
         # Return a list of all components.
-        names = list(self.__componentInfo.keys())
-        names.sort()
-        return names
+        return sorted(self.__componentInfo)
 
     def hascomponent(self, component):
         return component in self.__componentInfo
@@ -682,7 +687,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     else:
         inactiveInitState = DGG.DISABLED
 
-    guiDict = {}
+    guiDict: dict[str, DirectGuiWidget] = {}
 
     def __init__(self, parent = None, **kw):
         # Direct gui widgets are node paths

+ 1 - 1
direct/src/gui/DirectGuiGlobals.py

@@ -3,7 +3,7 @@ Global definitions used by Direct Gui Classes and handy constants
 that can be used during widget construction
 """
 
-__all__ = []
+__all__ = ()
 
 from panda3d.core import (
     KeyboardButton,

+ 1 - 1
direct/src/gui/OnscreenText.py

@@ -173,7 +173,7 @@ class OnscreenText(NodePath):
         self.__wordwrap = wordwrap
 
         if decal:
-            textNode.setCardDecal(1)
+            textNode.setCardDecal(True)
 
         if font is None:
             font = DGG.getDefaultFont()

+ 3 - 7
direct/src/interval/FunctionInterval.py

@@ -1,5 +1,7 @@
 """FunctionInterval module: contains the FunctionInterval class"""
 
+from __future__ import annotations
+
 __all__ = ['FunctionInterval', 'EventInterval', 'AcceptInterval', 'IgnoreInterval', 'ParentInterval', 'WrtParentInterval', 'PosInterval', 'HprInterval', 'ScaleInterval', 'PosHprInterval', 'HprScaleInterval', 'PosHprScaleInterval', 'Func', 'Wait']
 
 from panda3d.direct import WaitInterval
@@ -8,12 +10,6 @@ from direct.directnotify.DirectNotifyGlobal import directNotify
 from . import Interval
 
 
-#############################################################
-###                                                       ###
-### See examples of function intervals in IntervalTest.py ###
-###                                                       ###
-#############################################################
-
 class FunctionInterval(Interval.Interval):
     # Name counter
     functionIntervalNum = 1
@@ -23,7 +19,7 @@ class FunctionInterval(Interval.Interval):
     # should not cause any leaks.
     if __debug__:
         import weakref
-        FunctionIntervals = weakref.WeakKeyDictionary()
+        FunctionIntervals: weakref.WeakKeyDictionary[FunctionInterval, int] = weakref.WeakKeyDictionary()
 
         @classmethod
         def replaceMethod(cls, oldFunction, newFunction):

+ 0 - 222
direct/src/interval/IntervalTest.py

@@ -1,222 +0,0 @@
-"""Undocumented Module"""
-
-__all__ = []
-
-
-if __name__ == "__main__":
-    from panda3d.core import Filename, Point3, Vec3
-    from direct.showbase.DirectObject import DirectObject
-    from direct.showbase.ShowBase import ShowBase
-    from direct.actor.Actor import Actor
-    from direct.directutil import Mopath
-    from direct.showbase.MessengerGlobal import messenger
-    from .ActorInterval import ActorInterval
-    from .FunctionInterval import (
-        AcceptInterval,
-        EventInterval,
-        FunctionInterval,
-        IgnoreInterval,
-        PosHprInterval,
-    )
-    from .LerpInterval import LerpPosInterval, LerpHprInterval, LerpPosHprInterval
-    from .MopathInterval import MopathInterval
-    from .SoundInterval import SoundInterval
-    from .MetaInterval import PREVIOUS_END, PREVIOUS_START, TRACK_START, Track
-
-    base = ShowBase()
-
-    boat = base.loader.loadModel('models/misc/smiley')
-    boat.reparentTo(render)
-
-    donald = Actor()
-    donald.loadModel("phase_6/models/char/donald-wheel-1000")
-    donald.loadAnims({"steer":"phase_6/models/char/donald-wheel-wheel"})
-    donald.reparentTo(boat)
-
-    dock = base.loader.loadModel('models/misc/smiley')
-    dock.reparentTo(render)
-
-    sound = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_waterlap.mp3')
-    foghorn = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_foghorn.mp3')
-
-    mp = Mopath.Mopath()
-    mp.loadFile(Filename('phase_6/paths/dd-e-w'))
-
-    # Set up the boat
-    boatMopath = MopathInterval(mp, boat, 'boatpath')
-    boatTrack = Track([boatMopath], 'boattrack')
-    BOAT_START = boatTrack.getIntervalStartTime('boatpath')
-    BOAT_END = boatTrack.getIntervalEndTime('boatpath')
-
-    # This will create an anim interval that is posed every frame
-    donaldSteerInterval = ActorInterval(donald, 'steer')
-    # This will create an anim interval that is started at t = 0 and then
-    # loops for 10 seconds
-    donaldLoopInterval = ActorInterval(donald, 'steer', loop=1, duration = 10.0)
-    donaldSteerTrack = Track([donaldSteerInterval, donaldLoopInterval],
-                             name = 'steerTrack')
-
-    # Make the dock lerp up so that it's up when the boat reaches the end of
-    # its mopath
-    dockLerp = LerpPosHprInterval(dock, 5.0,
-                                  pos=Point3(0, 0, -5),
-                                  hpr=Vec3(0, 0, 0),
-                                  name='dock-lerp')
-    # We need the dock's state to be defined before the lerp
-    dockPos = PosHprInterval(dock, dock.getPos(), dock.getHpr(), 1.0, 'dockpos')
-    dockUpTime = BOAT_END - dockLerp.getDuration()
-    hpr2 = Vec3(90.0, 90.0, 90.0)
-    dockLerp2 = LerpHprInterval(dock, 3.0, hpr2, name='hpr-lerp')
-    dockTrack = Track([dockLerp2, dockPos, dockLerp], 'docktrack')
-    dockTrack.setIntervalStartTime('dock-lerp', dockUpTime)
-    dockTrack.setIntervalStartTime('hpr-lerp', BOAT_START)
-
-    # Start the water sound 5 seconds after the boat starts moving
-    waterStartTime = BOAT_START + 5.0
-    waterSound = SoundInterval(sound, name='watersound')
-    soundTrack = Track([waterSound], 'soundtrack')
-    soundTrack.setIntervalStartTime('watersound', waterStartTime)
-
-    # Throw an event when the water track ends
-    eventTime = soundTrack.getIntervalEndTime('watersound')
-    waterDone = EventInterval('water-is-done')
-    waterEventTrack = Track([waterDone])
-    waterEventTrack.setIntervalStartTime('water-is-done', eventTime)
-
-    def handleWaterDone():
-        print('water is done')
-
-    # Interval can handle its own event
-    messenger.accept('water-is-done', waterDone, handleWaterDone)
-
-    foghornStartTime = BOAT_START + 4.0
-    foghornSound = SoundInterval(foghorn, name='foghorn')
-    soundTrack2 = Track([(foghornStartTime, foghornSound)], 'soundtrack2')
-
-    mtrack = MultiTrack([boatTrack, dockTrack, soundTrack, soundTrack2, waterEventTrack,
-                         donaldSteerTrack])
-    # Print out MultiTrack parameters
-    print(mtrack)
-
-    ### Using lambdas and functions ###
-    # Using a lambda
-    i1 = FunctionInterval(lambda: base.transitions.fadeOut())
-    i2 = FunctionInterval(lambda: base.transitions.fadeIn())
-
-    def caughtIt():
-        print('Caught here-is-an-event')
-
-    class DummyAcceptor(DirectObject):
-        pass
-
-    da = DummyAcceptor()
-    i3 = AcceptInterval(da, 'here-is-an-event', caughtIt)
-
-    i4 = EventInterval('here-is-an-event')
-
-    i5 = IgnoreInterval(da, 'here-is-an-event')
-
-    # Using a function
-    def printDone():
-        print('done')
-
-    i6 = FunctionInterval(printDone)
-
-    # Create track
-    t1 = Track([
-        # Fade out
-        (0.0, i1),
-        # Fade in
-        (2.0, i2),
-        # Accept event
-        (4.0, i3),
-        # Throw it,
-        (5.0, i4),
-        # Ignore event
-        (6.0, i5),
-        # Throw event again and see if ignore worked
-        (7.0, i4),
-        # Print done
-        (8.0, i6)], name = 'demo')
-
-    print(t1)
-
-    ### Specifying interval start times during track construction ###
-    # Interval start time can be specified relative to three different points:
-    # PREVIOUS_END
-    # PREVIOUS_START
-    # TRACK_START
-
-    startTime = 0.0
-    def printStart():
-        global startTime
-        startTime = base.clock.getFrameTime()
-        print('Start')
-
-    def printPreviousStart():
-        global startTime
-        currTime = base.clock.getFrameTime()
-        print('PREVIOUS_END %0.2f' % (currTime - startTime))
-
-    def printPreviousEnd():
-        global startTime
-        currTime = base.clock.getFrameTime()
-        print('PREVIOUS_END %0.2f' % (currTime - startTime))
-
-    def printTrackStart():
-        global startTime
-        currTime = base.clock.getFrameTime()
-        print('TRACK_START %0.2f' % (currTime - startTime))
-
-    def printArguments(a, b, c):
-        print('My args were %d, %d, %d' % (a, b, c))
-
-    i1 = FunctionInterval(printStart)
-    # Just to take time
-    i2 = LerpPosInterval(base.camera, 2.0, Point3(0, 10, 5))
-    # This will be relative to end of camera move
-    i3 = FunctionInterval(printPreviousEnd)
-    # Just to take time
-    i4 = LerpPosInterval(base.camera, 2.0, Point3(0, 0, 5))
-    # This will be relative to the start of the camera move
-    i5 = FunctionInterval(printPreviousStart)
-    # This will be relative to track start
-    i6 = FunctionInterval(printTrackStart)
-    # This will print some arguments
-    # This will be relative to track start
-    i7 = FunctionInterval(printArguments, extraArgs = [1, 10, 100])
-    # Create the track, if you don't specify offset type in tuple it defaults to
-    # relative to TRACK_START (first entry below)
-    t2 = Track([(0.0, i1),                 # i1 start at t = 0, duration = 0.0
-                (1.0, i2, TRACK_START),    # i2 start at t = 1, duration = 2.0
-                (2.0, i3, PREVIOUS_END),   # i3 start at t = 5, duration = 0.0
-                (1.0, i4, PREVIOUS_END),   # i4 start at t = 6, duration = 2.0
-                (3.0, i5, PREVIOUS_START), # i5 start at t = 9, duration = 0.0
-                (10.0, i6, TRACK_START),   # i6 start at t = 10, duration = 0.0
-                (12.0, i7)],               # i7 start at t = 12, duration = 0.0
-               name = 'startTimeDemo')
-
-    print(t2)
-
-    # Play tracks
-    # mtrack.play()
-    # t1.play()
-    # t2.play()
-
-
-    def test(n):
-        lerps = []
-        for i in range(n):
-            lerps.append(LerpPosHprInterval(dock, 5.0,
-                                            pos=Point3(0, 0, -5),
-                                            hpr=Vec3(0, 0, 0),
-                                            startPos=dock.getPos(),
-                                            startHpr=dock.getHpr(),
-                                            name='dock-lerp'))
-            lerps.append(EventInterval("joe"))
-        t = Track(lerps)
-        mt = MultiTrack([t])
-        # return mt
-
-    test(5)
-    base.run()

+ 3 - 4
direct/src/interval/cInterval_ext.cxx

@@ -24,7 +24,7 @@ extern struct Dtool_PyTypedObject Dtool_CInterval;
 /**
  * Yields continuously until the interval is done.
  */
-static PyObject *gen_next(PyObject *self) {
+static PyObject *gen_next_c_interval(PyObject *self) {
   const CInterval *ival;
   if (!Dtool_Call_ExtractThisPointer(self, Dtool_CInterval, (void **)&ival)) {
     return nullptr;
@@ -32,8 +32,7 @@ static PyObject *gen_next(PyObject *self) {
 
   if (ival->get_state() != CInterval::S_final) {
     // Try again next frame.
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
   else {
     PyErr_SetNone(PyExc_StopIteration);
@@ -55,7 +54,7 @@ __await__(PyObject *self) {
   // we call this via Python.
   PyObject *result = PyObject_CallMethod(self, "start", nullptr);
   Py_XDECREF(result);
-  return Dtool_NewGenerator(self, &gen_next);
+  return Dtool_NewGenerator(self, &gen_next_c_interval);
 }
 
 #endif  // HAVE_PYTHON

+ 1 - 3
direct/src/leveleditor/HotKeyUI.py

@@ -116,9 +116,7 @@ class HotKeyPanel(ScrolledPanel):
 
     def updateUI(self):
         vbox = wx.BoxSizer(wx.VERTICAL)
-        keys = list(base.direct.hotKeyMap.keys())
-        keys.sort()
-        for key in keys:
+        for key in sorted(base.direct.hotKeyMap):
             keyDesc = base.direct.hotKeyMap[key]
             itemPanel = wx.Panel(self)
             sizer = wx.BoxSizer(wx.HORIZONTAL)

+ 1 - 1
direct/src/leveleditor/LevelEditorStart.py

@@ -4,7 +4,7 @@ if __name__ == '__main__':
     from direct.showbase.ShowBase import ShowBase
     base = ShowBase()
 
-    base.le = LevelEditor.LevelEditor()
+    base.le = LevelEditor.LevelEditor()  # type: ignore[attr-defined]
     # You should define LevelEditor instance as
     # base.le so it can be reached in global scope
 

+ 28 - 27
direct/src/leveleditor/LevelEditorUIBase.py

@@ -7,6 +7,7 @@ from direct.wxwidgets.WxPandaShell import WxPandaShell
 from direct.wxwidgets.WxSlider import WxSlider
 from direct.directtools.DirectSelection import SelectionRay
 from direct.showbase.MessengerGlobal import messenger
+from direct.showbase import ShowBaseGlobal
 
 #from ViewPort import *
 from . import ObjectGlobals as OG
@@ -88,7 +89,7 @@ class PandaTextDropTarget(wx.TextDropTarget):
             np = NodePath('temp')
             np.setPos(self.view.camera, hitPt)
 
-            if base.direct.manipulationControl.fGridSnap:
+            if ShowBaseGlobal.direct.manipulationControl.fGridSnap:
                 snappedPos = self.view.grid.computeSnapPoint(np.getPos())
                 np.setPos(snappedPos)
 
@@ -98,10 +99,10 @@ class PandaTextDropTarget(wx.TextDropTarget):
 
             # transform newobj to cursor position
             obj = self.editor.objectMgr.findObjectByNodePath(newobj)
-            action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
-            self.editor.actionMgr.push(action)
+            action2 = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
+            self.editor.actionMgr.push(action2)
             np.remove()
-            action()
+            action2()
         iRay.collisionNodePath.removeNode()
         del iRay
 
@@ -250,13 +251,13 @@ class LevelEditorUIBase(WxPandaShell):
         WxPandaShell.createMenu(self)
 
     def onGraphEditor(self, e):
-        if base.direct.selected.last is None:
+        if ShowBaseGlobal.direct.selected.last is None:
             dlg = wx.MessageDialog(None, 'Please select a object first.', 'NOTICE', wx.OK)
             dlg.ShowModal()
             dlg.Destroy()
             self.graphEditorMenuItem.Check(False)
         else:
-            currentObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
+            currentObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
             self.graphEditorUI = GraphEditorUI(self, self.editor, currentObj)
             self.graphEditorUI.Show()
             self.graphEditorMenuItem.Check(True)
@@ -298,7 +299,7 @@ class LevelEditorUIBase(WxPandaShell):
                     degreeUI = CurveDegreeUI(self, -1, 'Curve Degree')
                     degreeUI.ShowModal()
                     degreeUI.Destroy()
-                    base.direct.manipulationControl.disableManipulation()
+                    ShowBaseGlobal.direct.manipulationControl.disableManipulation()
                     self.editCurveMenuItem.Check(False)
 
     def onEditCurve(self, e):
@@ -313,15 +314,15 @@ class LevelEditorUIBase(WxPandaShell):
                 self.createCurveMenuItem.Check(False)
                 self.onEditCurve(None)
             else:
-                if base.direct.selected.last is None:
+                if ShowBaseGlobal.direct.selected.last is None:
                     dlg = wx.MessageDialog(None, 'Please select a curve first.', 'NOTICE', wx.OK)
                     dlg.ShowModal()
                     dlg.Destroy()
                     self.editCurveMenuItem.Check(False)
-                if base.direct.selected.last is not None:
-                    base.direct.manipulationControl.enableManipulation()
+                if ShowBaseGlobal.direct.selected.last is not None:
+                    ShowBaseGlobal.direct.manipulationControl.enableManipulation()
                     self.createCurveMenuItem.Check(False)
-                    self.curveObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
+                    self.curveObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
                     if self.curveObj[OG.OBJ_DEF].name == '__Curve__':
                         self.editor.mode = self.editor.EDIT_CURVE_MODE
                         self.editor.updateStatusReadout('Please press ENTER to end the curve editing.')
@@ -339,8 +340,8 @@ class LevelEditorUIBase(WxPandaShell):
 
     def updateMenu(self):
         hotKeyDict = {}
-        for hotKey in base.direct.hotKeyMap.keys():
-            desc = base.direct.hotKeyMap[hotKey]
+        for hotKey in ShowBaseGlobal.direct.hotKeyMap.keys():
+            desc = ShowBaseGlobal.direct.hotKeyMap[hotKey]
             hotKeyDict[desc[1]] = hotKey
 
         for id in self.MENU_TEXTS.keys():
@@ -401,16 +402,16 @@ class LevelEditorUIBase(WxPandaShell):
         else:
             mpos = evt.GetPosition()
 
-        base.direct.fMouse3 = 0
+        ShowBaseGlobal.direct.fMouse3 = 0
         self.PopupMenu(self.contextMenu, mpos)
 
     def onKeyDownEvent(self, evt):
         if evt.GetKeyCode() == wx.WXK_ALT:
-            base.direct.fAlt = 1
+            ShowBaseGlobal.direct.fAlt = 1
         elif evt.GetKeyCode() == wx.WXK_CONTROL:
-            base.direct.fControl = 1
+            ShowBaseGlobal.direct.fControl = 1
         elif evt.GetKeyCode() == wx.WXK_SHIFT:
-            base.direct.fShift = 1
+            ShowBaseGlobal.direct.fShift = 1
         elif evt.GetKeyCode() == wx.WXK_UP:
             messenger.send('arrow_up')
         elif evt.GetKeyCode() == wx.WXK_DOWN:
@@ -428,11 +429,11 @@ class LevelEditorUIBase(WxPandaShell):
 
     def onKeyUpEvent(self, evt):
         if evt.GetKeyCode() == wx.WXK_ALT:
-            base.direct.fAlt = 0
+            ShowBaseGlobal.direct.fAlt = 0
         elif evt.GetKeyCode() == wx.WXK_CONTROL:
-            base.direct.fControl = 0
+            ShowBaseGlobal.direct.fControl = 0
         elif evt.GetKeyCode() == wx.WXK_SHIFT:
-            base.direct.fShift = 0
+            ShowBaseGlobal.direct.fShift = 0
         elif evt.GetKeyCode() == wx.WXK_UP:
             messenger.send('arrow_up-up')
         elif evt.GetKeyCode() == wx.WXK_DOWN:
@@ -473,8 +474,8 @@ class LevelEditorUIBase(WxPandaShell):
                 input = 'control-%s'%chr(evt.GetKeyCode())
             elif evt.GetKeyCode() < 256:
                 input = chr(evt.GetKeyCode())
-        if input in base.direct.hotKeyMap.keys():
-            keyDesc = base.direct.hotKeyMap[input]
+        if input in ShowBaseGlobal.direct.hotKeyMap.keys():
+            keyDesc = ShowBaseGlobal.direct.hotKeyMap[input]
             messenger.send(keyDesc[1])
 
     def reset(self):
@@ -533,12 +534,12 @@ class LevelEditorUIBase(WxPandaShell):
 
     def toggleGridSnap(self, evt):
         if self.gridSnapMenuItem.IsChecked():
-            base.direct.manipulationControl.fGridSnap = 1
+            ShowBaseGlobal.direct.manipulationControl.fGridSnap = 1
             for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]:
                 grid.fXyzSnap = 1
 
         else:
-            base.direct.manipulationControl.fGridSnap = 0
+            ShowBaseGlobal.direct.manipulationControl.fGridSnap = 0
             for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]:
                 grid.fXyzSnap = 0
 
@@ -589,7 +590,7 @@ class LevelEditorUIBase(WxPandaShell):
         self.contextMenu.AppendSeparator()
 
     def replaceObject(self, evt, all=False):
-        currObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
+        currObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
         if currObj is None:
             print('No valid object is selected for replacement')
             return
@@ -636,13 +637,13 @@ class GridSizeUI(wx.Dialog):
         vbox.Add(okButton, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 5)
 
         self.SetSizer(vbox)
-        base.le.ui.bindKeyEvents(False)
+        ShowBaseGlobal.base.le.ui.bindKeyEvents(False)
 
     def onApply(self, evt):
         newSize = self.gridSizeSlider.GetValue()
         newSpacing = self.gridSpacingSlider.GetValue()
         self.parent.updateGrids(newSize, newSpacing)
-        base.le.ui.bindKeyEvents(True)
+        ShowBaseGlobal.base.le.ui.bindKeyEvents(True)
         self.Destroy()
 
 

+ 3 - 0
direct/src/leveleditor/LevelLoader.py

@@ -25,6 +25,9 @@ class LevelLoader(LevelLoaderBase):
 
     def initLoader(self):
         self.defaultPath = os.path.dirname(__file__)
+
+        from direct.showbase import ShowBaseGlobal
+        base = ShowBaseGlobal.base
         base.objectPalette = ObjectPalette()
         base.protoPalette = ProtoPalette()
         base.objectHandler = ObjectHandler(None)

+ 2 - 1
direct/src/leveleditor/testData.py

@@ -1,4 +1,5 @@
 from panda3d.core import Point3, VBase3
+from direct.showbase.ShowBaseGlobal import base
 
 if hasattr(base, 'le'):
     objectMgr = base.le.objectMgr
@@ -6,7 +7,7 @@ if hasattr(base, 'le'):
     ui.sceneGraphUI.reset()
 
 else:
-    objectMgr = base.objectMgr
+    objectMgr = base.objectMgr  # type: ignore[attr-defined]
 # temporary place holder for nodepath
 objects = {}
 

+ 3 - 1
direct/src/motiontrail/MotionTrail.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from panda3d.core import (
     BoundingVolume,
     ColorBlendAttrib,
@@ -92,7 +94,7 @@ class MotionTrail(NodePath, DirectObject):
     notify = directNotify.newCategory("MotionTrail")
 
     task_added = False
-    motion_trail_list = []
+    motion_trail_list: list[MotionTrail] = []
     motion_trail_task_name = "motion_trail_task"
 
     global_enable = True

+ 1 - 1
direct/src/particles/ParticleEffect.py

@@ -97,7 +97,7 @@ class ParticleEffect(NodePath):
     def addForceGroup(self, forceGroup):
         forceGroup.nodePath.reparentTo(self)
         forceGroup.particleEffect = self
-        self.forceGroupDict[forceGroup.getName()] = forceGroup
+        self.forceGroupDict[forceGroup.name] = forceGroup
 
         # Associate the force group with all particles
         for force in forceGroup:

+ 1 - 3
direct/src/showbase/BulletinBoard.py

@@ -55,8 +55,6 @@ class BulletinBoard:
     def __repr__(self):
         str  = 'Bulletin Board Contents\n'
         str += '======================='
-        keys = list(self._dict.keys())
-        keys.sort()
-        for postName in keys:
+        for postName in sorted(self._dict):
             str += '\n%s: %s' % (postName, self._dict[postName])
         return str

+ 4 - 3
direct/src/showbase/ContainerLeakDetector.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 import direct.showbase.DConfig as config
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
@@ -11,7 +13,6 @@ import types
 import weakref
 import random
 import builtins
-import sys
 
 
 deadEndTypes = frozenset((
@@ -566,7 +567,7 @@ class FindContainers(Job):
                 curObjRef = None
 
                 # types.CellType was added in Python 3.8
-                if sys.version_info >= (3, 8) and type(curObj) is types.CellType:
+                if type(curObj) is types.CellType:
                     child = curObj.cell_contents
                     hasLength = self._hasLength(child)
                     notDeadEnd = not self._isDeadEnd(child)
@@ -983,7 +984,7 @@ class ContainerLeakDetector(Job):
     """
     notify = directNotify.newCategory("ContainerLeakDetector")
     # set of containers that should not be examined
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
     def __init__(self, name, firstCheckDelay = None):
         Job.__init__(self, name)

+ 7 - 10
direct/src/showbase/ContainerReport.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import safeRepr
@@ -5,14 +7,13 @@ from direct.showbase.Job import Job
 from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 import types
-import sys
 import io
 
 
 class ContainerReport(Job):
     notify = directNotify.newCategory("ContainerReport")
     # set of containers that should not be included in the report
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
     def __init__(self, name, log=False, limit=None, threaded=False):
         Job.__init__(self, name)
@@ -122,7 +123,7 @@ class ContainerReport(Job):
                 continue
 
             # types.CellType was added in Python 3.8
-            if sys.version_info >= (3, 8) and type(parentObj) is types.CellType:
+            if type(parentObj) is types.CellType:
                 child = parentObj.cell_contents
                 if self._examine(child):
                     assert (self._queue.back() is child)
@@ -228,14 +229,11 @@ class ContainerReport(Job):
         if type not in self._type2id2len:
             return
         len2ids = invertDictLossless(self._type2id2len[type])
-        lengths = list(len2ids.keys())
-        lengths.sort()
-        lengths.reverse()
         print('=====')
         print('===== %s' % type)
         count = 0
         stop = False
-        for l in lengths:
+        for l in sorted(len2ids, reverse=True):
             #len2ids[l].sort()
             pathStrList = list()
             for id in len2ids[l]:
@@ -257,9 +255,8 @@ class ContainerReport(Job):
         for type in initialTypes:
             for i in self._outputType(type, **kArgs):
                 yield None
-        otherTypes = list(set(self._type2id2len.keys()).difference(set(initialTypes)))
-        otherTypes.sort(key=lambda obj: obj.__name__)
-        for type in otherTypes:
+        otherTypes = set(self._type2id2len).difference(initialTypes)
+        for type in sorted(otherTypes, key=lambda obj: obj.__name__):
             for i in self._outputType(type, **kArgs):
                 yield None
 

+ 0 - 144
direct/src/showbase/CountedResource.py

@@ -81,147 +81,3 @@ class CountedResource(object):
 
     def __del__(self):
         self.decrementCounter()
-
-
-if __debug__ and __name__ == '__main__':
-    class MouseResource(CountedResource):
-        """
-        A simple class to demonstrate the acquisition of a resource.
-        """
-        @classmethod
-        def acquire(cls):
-            # The call to the super-class's acquire() is
-            # not necessary at the moment, but may be in
-            # the future, so do it now for good measure.
-            super(MouseResource, cls).acquire()
-
-            # Now acquire the resource this class is
-            # managing.
-            print('-- Acquire Mouse')
-
-        @classmethod
-        def release(cls):
-            # First, release the resource this class is
-            # managing.
-            print('-- Release Mouse')
-
-            # The call to the super-class's release() is
-            # not necessary at the moment, but may be in
-            # the future, so do it now for good measure.
-            super(MouseResource, cls).release()
-
-
-        def __init__(self):
-            super(MouseResource, self).__init__()
-
-        def __del__(self):
-            super(MouseResource, self).__del__()
-
-    class CursorResource(CountedResource):
-        """
-        A class to demonstrate how to implement a dependent
-        resource.  Notice how this class also inherits from
-        CountedResource.  Instead of subclassing MouseCounter,
-        we will just acquire it in our __init__() and release
-        it in our __del__().
-        """
-        @classmethod
-        def acquire(cls):
-            super(CursorResource, cls).acquire()
-            print('-- Acquire Cursor')
-
-        @classmethod
-        def release(cls):
-            print('-- Release Cursor')
-
-            super(CursorResource, cls).release()
-
-        def __init__(self):
-            # The required resource references should
-            # be stored on 'self' since we want to
-            # release it when the object is deleted.
-            self.__mouseResource = MouseResource()
-
-            # Call the super-classes __init__()
-            # after all required resources are
-            # referenced.
-            super(CursorResource, self).__init__()
-
-        def __del__(self):
-            # Free up the most dependent resource
-            # first, the one this class is managing.
-            super(CursorResource, self).__del__()
-
-            # Now unlink any required resources.
-            del self.__mouseResource
-
-    class InvalidResource(MouseResource):
-        @classmethod
-        def acquire(cls):
-            super(InvalidResource, cls).acquire()
-            print('-- Acquire Invalid')
-
-        @classmethod
-        def release(cls):
-            print('-- Release Invalid')
-            super(InvalidResource, cls).release()
-
-    print('\nAllocate Mouse')
-    m = MouseResource()
-    print('Free up Mouse')
-    del m
-
-    print('\nAllocate Cursor')
-    c = CursorResource()
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Mouse then Cursor')
-    m = MouseResource()
-    c = CursorResource()
-    print('Free up Cursor')
-    del c
-    print('Free up Mouse')
-    del m
-
-    print('\nAllocate Mouse then Cursor')
-    m = MouseResource()
-    c = CursorResource()
-    print('Free up Mouse')
-    del m
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Cursor then Mouse')
-    c = CursorResource()
-    m = MouseResource()
-    print('Free up Mouse')
-    del m
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Cursor then Mouse')
-    c = CursorResource()
-    m = MouseResource()
-    print('Free up Cursor')
-    del c
-
-    # example of an invalid subclass
-    try:
-        print('\nAllocate Invalid')
-        i = InvalidResource()
-        print('Free up Invalid')
-    except AssertionError as e:
-        print(e)
-    print('')
-
-    print('Free up Mouse')
-    del m
-
-    def demoFunc():
-        print('\nAllocate Cursor within function')
-        c = CursorResource()
-
-        print('Cursor will be freed on function exit')
-
-    demoFunc()

+ 1 - 1
direct/src/showbase/DConfig.py

@@ -1,6 +1,6 @@
 "This module contains a deprecated shim emulating the old DConfig API."
 
-__all__ = []
+__all__ = ()
 
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
                           ConfigVariableDouble, ConfigVariableString)

+ 3 - 29
direct/src/showbase/DistancePhasedNode.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from panda3d.core import (
@@ -62,7 +64,7 @@ class DistancePhasedNode(PhasedObject, DirectObject, NodePath):
 
     notify = directNotify.newCategory("DistancePhasedObject")
     __InstanceSequence = 0
-    __InstanceDeque = []
+    __InstanceDeque: list[int] = []
 
     @staticmethod
     def __allocateId():
@@ -327,31 +329,3 @@ class BufferedDistancePhasedNode(DistancePhasedNode):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().markInternalBoundsStale()
-
-
-if __debug__ and 0:
-    cSphere = CollisionSphere(0, 0, 0, 0.1)
-    cNode = CollisionNode('camCol')
-    cNode.addSolid(cSphere)
-    cNodePath = NodePath(cNode)
-    cNodePath.reparentTo(base.cam)
-    # cNodePath.show()
-    # cNodePath.setPos(25,0,0)
-
-    base.cTrav = CollisionTraverser()
-
-    eventHandler = CollisionHandlerEvent()
-    eventHandler.addInPattern('enter%in')
-    eventHandler.addOutPattern('exit%in')
-
-    # messenger.toggleVerbose()
-    base.cTrav.addCollider(cNodePath, eventHandler)
-
-    p = BufferedDistancePhasedNode('p', {'At': (10, 20), 'Near': (100, 200), 'Far': (1000, 1020)},
-                                   autoCleanup=False,
-                                   fromCollideNode=cNodePath,
-                                   )
-
-    p.reparentTo(render)
-    p._DistancePhasedNode__oneTimeCollide()
-    base.eventMgr.doEvents()

+ 5 - 14
direct/src/showbase/ExceptionVarDump.py

@@ -69,7 +69,7 @@ oldExcepthook = None
 # from its main exception handler
 wantStackDumpLog = False
 wantStackDumpUpload = False
-variableDumpReasons = []
+variableDumpReasons: list = []
 dumpOnExceptionInit = False
 
 
@@ -116,15 +116,10 @@ def _excepthookDumpVars(eType, eValue, tb):
         for name, obj in frame.f_locals.items():
             if name in codeNames:
                 name2obj[name] = obj
-        # show them in alphabetical order
-        names = list(name2obj.keys())
-        names.sort()
-        # push them in reverse order so they'll be popped in the correct order
-        names.reverse()
 
         traversedIds = set()
-
-        for name in names:
+        # push them in reverse alphabetical order so they'll be popped in the correct order
+        for name in sorted(name2obj, reverse=True):
             stateStack.push([name, name2obj[name], traversedIds])
 
         while len(stateStack) > 0:
@@ -150,14 +145,10 @@ def _excepthookDumpVars(eType, eValue, tb):
                                 continue
                         attrName2obj[attrName] = attr
                 if len(attrName2obj) > 0:
-                    # show them in alphabetical order
-                    attrNames = list(attrName2obj.keys())
-                    attrNames.sort()
-                    # push them in reverse order so they'll be popped in the correct order
-                    attrNames.reverse()
                     ids = set(traversedIds)
                     ids.add(id(obj))
-                    for attrName in attrNames:
+                    # push them in reverse alphabetical order so they'll be popped in the correct order
+                    for attrName in sorted(attrName2obj, reverse=True):
                         obj = attrName2obj[attrName]
                         stateStack.push(['%s.%s' % (name, attrName), obj, ids])
 

+ 1 - 2
direct/src/showbase/GarbageReport.py

@@ -294,8 +294,7 @@ class GarbageReport(Job):
             if self._args.fullReport:
                 garbageIndices = range(self.numGarbage)
             else:
-                garbageIndices = list(self.cycleIds)
-                garbageIndices.sort()
+                garbageIndices = sorted(self.cycleIds)
             numGarbage = len(garbageIndices)
 
             # log each individual item with a number in front of it

+ 1 - 3
direct/src/showbase/JobManager.py

@@ -141,9 +141,7 @@ class JobManager:
 
     def _getSortedPriorities(self):
         # returns all job priorities in ascending order
-        priorities = list(self._pri2jobId2job.keys())
-        priorities.sort()
-        return priorities
+        return sorted(self._pri2jobId2job)
 
     def _process(self, task=None):
         if self._useOverflowTime is None:

+ 11 - 8
direct/src/showbase/Loader.py

@@ -5,7 +5,6 @@ sound, music, shaders and fonts from disk.
 __all__ = ['Loader']
 
 from panda3d.core import (
-    AudioLoadRequest,
     ConfigVariableBool,
     Filename,
     FontPool,
@@ -26,6 +25,7 @@ from panda3d.core import Loader as PandaLoader
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.DirectObject import DirectObject
 import warnings
+import sys
 
 # You can specify a phaseChecker callback to check
 # a modelPath to see if it is being loaded in the correct
@@ -152,16 +152,17 @@ class Loader(DirectObject):
         if not ConfigVariableBool('loader-support-entry-points', True):
             return
 
-        import importlib
-        try:
-            pkg_resources = importlib.import_module('pkg_resources')
-        except ImportError:
-            pkg_resources = None
+        from importlib.metadata import entry_points
+        eps = entry_points()
+        if sys.version_info < (3, 10):
+            loaders = eps.get('panda3d.loaders', ())
+        else:
+            loaders = eps.select(group='panda3d.loaders')
 
-        if pkg_resources:
+        if loaders:
             registry = LoaderFileTypeRegistry.getGlobalPtr()
 
-            for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'):
+            for entry_point in loaders:
                 registry.register_deferred_type(entry_point)
 
             cls._loadedPythonFileTypes = True
@@ -977,6 +978,8 @@ class Loader(DirectObject):
         just as in loadModel(); otherwise, the loading happens before
         loadSound() returns."""
 
+        from panda3d.core import AudioLoadRequest
+
         if not isinstance(soundPath, (tuple, list, set)):
             # We were given a single sound pathname or a MovieAudio instance.
             soundList = [soundPath]

+ 4 - 12
direct/src/showbase/Messenger.py

@@ -540,9 +540,7 @@ class Messenger:
         return a matching event (needle) if found (in haystack).
         This is primarily a debugging tool.
         """
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             if repr(event).find(needle) >= 0:
                 return {event: self.__callbacks[event]}
 
@@ -553,9 +551,7 @@ class Messenger:
         This is primarily a debugging tool.
         """
         matches = {}
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             if repr(event).find(needle) >= 0:
                 matches[event] = self.__callbacks[event]
                 # if the limit is not None, decrement and
@@ -596,9 +592,7 @@ class Messenger:
         Compact version of event, acceptor pairs
         """
         str = "The messenger is currently handling:\n" + "="*64 + "\n"
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             str += self.__eventRepr(event)
         # Print out the object: event dictionary too
         str += "="*64 + "\n"
@@ -617,9 +611,7 @@ class Messenger:
         """
         str = 'Messenger\n'
         str = str + '='*50 + '\n'
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             acceptorDict = self.__callbacks[event]
             str = str + 'Event: ' + event + '\n'
             for key in list(acceptorDict.keys()):

+ 3 - 11
direct/src/showbase/ObjectPool.py

@@ -90,10 +90,7 @@ class ObjectPool:
     def typeFreqStr(self):
         s  =   'Object Pool: Type Frequencies'
         s += '\n============================='
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types, reverse=True):
             types = makeList(self._count2types[count])
             for typ in types:
                 s += '\n%s\t%s' % (count, typ)
@@ -102,12 +99,10 @@ class ObjectPool:
     def printObjsByType(self):
         print('Object Pool: Objects By Type')
         print('\n============================')
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
         # print types with the smallest number of instances first, in case
         # there's a large group that waits a long time before printing
         #counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types):
             types = makeList(self._count2types[count])
             for typ in types:
                 print('TYPE: %s, %s objects' % (repr(typ), len(self._type2objs[typ])))
@@ -115,10 +110,7 @@ class ObjectPool:
 
     def printReferrers(self, numEach=3):
         """referrers of the first few of each type of object"""
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types, reverse=True):
             types = makeList(self._count2types[count])
             for typ in types:
                 print('\n\nTYPE: %s' % repr(typ))

+ 4 - 2
direct/src/showbase/ObjectReport.py

@@ -11,6 +11,8 @@
 >>> o.diff(o2)
 """
 
+from __future__ import annotations
+
 __all__ = ['ExclusiveObjectPool', 'ObjectReport']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -26,8 +28,8 @@ import builtins
 class ExclusiveObjectPool(DirectObject):
     # ObjectPool specialization that excludes particular objects
     # IDs of objects to globally exclude from reporting
-    _ExclObjs = []
-    _ExclObjIds = {}
+    _ExclObjs: list[object] = []
+    _ExclObjIds: dict[int, int] = {}
     _SyncMaster = Sync('ExclusiveObjectPool.ExcludedObjectList')
     _SerialNumGen = SerialNumGen()
 

+ 1 - 3
direct/src/showbase/OnScreenDebug.py

@@ -57,9 +57,7 @@ class OnScreenDebug:
         if not self.onScreenText:
             self.load()
         self.onScreenText.clearText()
-        entries = list(self.data.items())
-        entries.sort()
-        for k, v in entries:
+        for k, v in sorted(self.data.items()):
             if v[0] == self.frame:
                 # It was updated this frame (key equals value):
                 #isNew = " is"

+ 53 - 97
direct/src/showbase/PythonUtil.py

@@ -1,5 +1,7 @@
 """Contains miscellaneous utility functions and classes."""
 
+from __future__ import annotations
+
 __all__ = [
 
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
@@ -39,6 +41,7 @@ import time
 import builtins
 import importlib
 import functools
+from typing import Callable
 
 __report_indent = 3
 
@@ -699,9 +702,9 @@ if __debug__:
         return profileDecorator
 
     # intercept profile-related file operations to avoid disk access
-    movedOpenFuncs = []
-    movedDumpFuncs = []
-    movedLoadFuncs = []
+    movedOpenFuncs: list[Callable] = []
+    movedDumpFuncs: list[Callable] = []
+    movedLoadFuncs: list[Callable] = []
     profileFilenames = set()
     profileFilenameList = Stack()
     profileFilename2file = {}
@@ -1899,7 +1902,7 @@ class SubframeCall:
 
 
 class PStatScope:
-    collectors = {}
+    collectors: dict = {}
 
     def __init__(self, level = None):
         self.levels = []
@@ -2458,18 +2461,6 @@ def formatTimeCompact(seconds):
     return result
 
 
-if __debug__ and __name__ == '__main__':
-    ftc = formatTimeCompact
-    assert ftc(0) == '0s'
-    assert ftc(1) == '1s'
-    assert ftc(60) == '1m0s'
-    assert ftc(64) == '1m4s'
-    assert ftc(60*60) == '1h0m0s'
-    assert ftc(24*60*60) == '1d0h0m0s'
-    assert ftc(24*60*60 + 2*60*60 + 34*60 + 12) == '1d2h34m12s'
-    del ftc
-
-
 def formatTimeExact(seconds):
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # times that are e.g. 1 hour, 3 days etc.
@@ -2496,19 +2487,6 @@ def formatTimeExact(seconds):
     return result
 
 
-if __debug__ and __name__ == '__main__':
-    fte = formatTimeExact
-    assert fte(0) == '0s'
-    assert fte(1) == '1s'
-    assert fte(2) == '2s'
-    assert fte(61) == '1m1s'
-    assert fte(60) == '1m'
-    assert fte(60*60) == '1h'
-    assert fte(24*60*60) == '1d'
-    assert fte((24*60*60) + (2 * 60)) == '1d0h2m'
-    del fte
-
-
 class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
@@ -2540,28 +2518,6 @@ class AlphabetCounter:
     __next__ = next
 
 
-if __debug__ and __name__ == '__main__':
-    def testAlphabetCounter():
-        tempList = []
-        ac = AlphabetCounter()
-        for i in range(26*3):
-            tempList.append(ac.next())
-        assert tempList == [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
-                            'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
-                            'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ',]
-        ac = AlphabetCounter()
-        num  = 26 # A-Z
-        num += (26*26) # AA-ZZ
-        num += 26 # AAZ
-        num += 1 # ABA
-        num += 2 # ABC
-        for i in range(num):
-            x = ac.next()
-        assert x == 'ABC'
-    testAlphabetCounter()
-    del testAlphabetCounter
-
-
 class Default:
     # represents 'use the default value'
     # useful for keyword arguments to virtual methods
@@ -2665,52 +2621,52 @@ class PriorityCallbacks:
             callback()
 
 
-builtins.Functor = Functor
-builtins.Stack = Stack
-builtins.Queue = Queue
-builtins.SerialNumGen = SerialNumGen
-builtins.SerialMaskedGen = SerialMaskedGen
-builtins.ScratchPad = ScratchPad
-builtins.uniqueName = uniqueName
-builtins.serialNum = serialNum
+builtins.Functor = Functor  # type: ignore[attr-defined]
+builtins.Stack = Stack  # type: ignore[attr-defined]
+builtins.Queue = Queue  # type: ignore[attr-defined]
+builtins.SerialNumGen = SerialNumGen  # type: ignore[attr-defined]
+builtins.SerialMaskedGen = SerialMaskedGen  # type: ignore[attr-defined]
+builtins.ScratchPad = ScratchPad  # type: ignore[attr-defined]
+builtins.uniqueName = uniqueName  # type: ignore[attr-defined]
+builtins.serialNum = serialNum  # type: ignore[attr-defined]
 if __debug__:
-    builtins.profiled = profiled
-    builtins.exceptionLogged = exceptionLogged
-builtins.itype = itype
-builtins.appendStr = appendStr
-builtins.bound = bound
-builtins.clamp = clamp
-builtins.lerp = lerp
-builtins.makeList = makeList
-builtins.makeTuple = makeTuple
+    builtins.profiled = profiled  # type: ignore[attr-defined]
+    builtins.exceptionLogged = exceptionLogged  # type: ignore[attr-defined]
+builtins.itype = itype  # type: ignore[attr-defined]
+builtins.appendStr = appendStr  # type: ignore[attr-defined]
+builtins.bound = bound  # type: ignore[attr-defined]
+builtins.clamp = clamp  # type: ignore[attr-defined]
+builtins.lerp = lerp  # type: ignore[attr-defined]
+builtins.makeList = makeList  # type: ignore[attr-defined]
+builtins.makeTuple = makeTuple  # type: ignore[attr-defined]
 if __debug__:
-    builtins.printStack = printStack
-    builtins.printReverseStack = printReverseStack
-    builtins.printVerboseStack = printVerboseStack
-builtins.DelayedCall = DelayedCall
-builtins.DelayedFunctor = DelayedFunctor
-builtins.FrameDelayedCall = FrameDelayedCall
-builtins.SubframeCall = SubframeCall
-builtins.invertDict = invertDict
-builtins.invertDictLossless = invertDictLossless
-builtins.getBase = getBase
-builtins.getRepository = getRepository
-builtins.safeRepr = safeRepr
-builtins.fastRepr = fastRepr
-builtins.nullGen = nullGen
-builtins.flywheel = flywheel
-builtins.loopGen = loopGen
+    builtins.printStack = printStack  # type: ignore[attr-defined]
+    builtins.printReverseStack = printReverseStack  # type: ignore[attr-defined]
+    builtins.printVerboseStack = printVerboseStack  # type: ignore[attr-defined]
+builtins.DelayedCall = DelayedCall  # type: ignore[attr-defined]
+builtins.DelayedFunctor = DelayedFunctor  # type: ignore[attr-defined]
+builtins.FrameDelayedCall = FrameDelayedCall  # type: ignore[attr-defined]
+builtins.SubframeCall = SubframeCall  # type: ignore[attr-defined]
+builtins.invertDict = invertDict  # type: ignore[attr-defined]
+builtins.invertDictLossless = invertDictLossless  # type: ignore[attr-defined]
+builtins.getBase = getBase  # type: ignore[attr-defined]
+builtins.getRepository = getRepository  # type: ignore[attr-defined]
+builtins.safeRepr = safeRepr  # type: ignore[attr-defined]
+builtins.fastRepr = fastRepr  # type: ignore[attr-defined]
+builtins.nullGen = nullGen  # type: ignore[attr-defined]
+builtins.flywheel = flywheel  # type: ignore[attr-defined]
+builtins.loopGen = loopGen  # type: ignore[attr-defined]
 if __debug__:
-    builtins.StackTrace = StackTrace
-builtins.report = report
-builtins.pstatcollect = pstatcollect
-builtins.MiniLog = MiniLog
-builtins.MiniLogSentry = MiniLogSentry
-builtins.logBlock = logBlock
-builtins.HierarchyException = HierarchyException
-builtins.deeptype = deeptype
-builtins.Default = Default
-builtins.configIsToday = configIsToday
-builtins.typeName = typeName
-builtins.safeTypeName = safeTypeName
-builtins.histogramDict = histogramDict
+    builtins.StackTrace = StackTrace  # type: ignore[attr-defined]
+builtins.report = report  # type: ignore[attr-defined]
+builtins.pstatcollect = pstatcollect  # type: ignore[attr-defined]
+builtins.MiniLog = MiniLog  # type: ignore[attr-defined]
+builtins.MiniLogSentry = MiniLogSentry  # type: ignore[attr-defined]
+builtins.logBlock = logBlock  # type: ignore[attr-defined]
+builtins.HierarchyException = HierarchyException  # type: ignore[attr-defined]
+builtins.deeptype = deeptype  # type: ignore[attr-defined]
+builtins.Default = Default  # type: ignore[attr-defined]
+builtins.configIsToday = configIsToday  # type: ignore[attr-defined]
+builtins.typeName = typeName  # type: ignore[attr-defined]
+builtins.safeTypeName = safeTypeName  # type: ignore[attr-defined]
+builtins.histogramDict = histogramDict  # type: ignore[attr-defined]

+ 22 - 14
direct/src/showbase/ShowBase.py

@@ -120,9 +120,10 @@ from direct.extensions_native import NodePath_extensions # pylint: disable=unuse
 # This needs to be available early for DirectGUI imports
 import sys
 import builtins
-builtins.config = DConfig
+builtins.config = DConfig  # type: ignore[attr-defined]
 
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
+from direct.directnotify.Notifier import Notifier
 from .MessengerGlobal import messenger
 from .BulletinBoardGlobal import bulletinBoard
 from direct.task.TaskManagerGlobal import taskMgr
@@ -140,6 +141,7 @@ import importlib
 from direct.showbase import ExceptionVarDump
 from . import DirectObject
 from . import SfxPlayer
+from typing import ClassVar, Optional
 if __debug__:
     from direct.showbase import GarbageReport
     from direct.directutil import DeltaProfiler
@@ -160,8 +162,13 @@ def exitfunc():
 class ShowBase(DirectObject.DirectObject):
 
     #: The deprecated `.DConfig` interface for accessing config variables.
-    config = DConfig
-    notify = directNotify.newCategory("ShowBase")
+    config: ClassVar = DConfig
+    notify: ClassVar[Notifier] = directNotify.newCategory("ShowBase")
+    guiItems: ClassVar[dict]
+
+    render2d: NodePath
+    aspect2d: NodePath
+    pixel2d: NodePath
 
     def __init__(self, fStartDirect=True, windowType=None):
         """Opens a window, sets up a 3-D and several 2-D scene graphs, and
@@ -337,10 +344,10 @@ class ShowBase(DirectObject.DirectObject):
         self.tkRootCreated = False
 
         # This is used for syncing multiple PCs in a distributed cluster
-        try:
+        if hasattr(builtins, 'clusterSyncFlag'):
             # Has the cluster sync variable been set externally?
-            self.clusterSyncFlag = clusterSyncFlag
-        except NameError:
+            self.clusterSyncFlag = builtins.clusterSyncFlag
+        else:
             # Has the clusterSyncFlag been set via a config variable
             self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
 
@@ -424,6 +431,7 @@ class ShowBase(DirectObject.DirectObject):
         #: `.Loader.Loader` object.
         self.loader = Loader.Loader(self)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
+        ShowBaseGlobal.loader = self.loader
 
         #: The global event manager, as imported from `.EventManagerGlobal`.
         self.eventMgr = eventMgr
@@ -712,10 +720,9 @@ class ShowBase(DirectObject.DirectObject):
         except Exception:
             pass
 
-        if hasattr(self, 'win'):
-            del self.win
-            del self.winList
-            del self.pipe
+        self.win = None
+        self.winList.clear()
+        self.pipe = None
 
     def makeDefaultPipe(self, printPipeTypes = None):
         """
@@ -728,7 +735,7 @@ class ShowBase(DirectObject.DirectObject):
             # When the user didn't specify an explicit setting, take the value
             # from the config variable. We could just omit the parameter, however
             # this way we can keep backward compatibility.
-            printPipeTypes = ConfigVariableBool("print-pipe-types", True)
+            printPipeTypes = ConfigVariableBool("print-pipe-types", True).value
 
         selection = GraphicsPipeSelection.getGlobalPtr()
         if printPipeTypes:
@@ -3315,8 +3322,9 @@ class ShowBase(DirectObject.DirectObject):
         init_app_for_gui()
 
         # Disable the Windows message loop, since Tcl wants to handle this all
-        # on its own.
-        ConfigVariableBool('disable-message-loop', False).value = True
+        # on its own, except if the Panda window is on a separate thread.
+        if self.graphicsEngine.getThreadingModel().getDrawStage() == 0:
+            ConfigVariableBool('disable-message-loop', False).value = True
 
         if ConfigVariableBool('tk-main-loop', True):
             # Put Tkinter in charge of the main loop.  It really
@@ -3413,7 +3421,7 @@ class ShowBase(DirectObject.DirectObject):
         # Set fWantTk to 0 to avoid starting Tk with this call
         self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
 
-    def run(self): # pylint: disable=method-hidden
+    def run(self) -> None: # pylint: disable=method-hidden
         """This method runs the :class:`~direct.task.Task.TaskManager`
         when ``self.appRunner is None``, which is to say, when we are
         not running from within a p3d file.  When we *are* within a p3d

+ 8 - 3
direct/src/showbase/ShowBaseGlobal.py

@@ -11,7 +11,7 @@ Note that you cannot directly import :data:`~builtins.base` from this module
 since ShowBase may not have been created yet; instead, ShowBase dynamically
 adds itself to this module's scope when instantiated."""
 
-__all__ = []
+__all__ = ()
 
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify # pylint: disable=unused-import
@@ -19,9 +19,12 @@ from panda3d.core import VirtualFileSystem, Notify, ClockObject, PandaSystem
 from panda3d.core import ConfigPageManager, ConfigVariableManager, ConfigVariableBool
 from panda3d.core import NodePath, PGTop
 from . import DConfig as config # pylint: disable=unused-import
+from .Loader import Loader
 import warnings
 
-__dev__ = ConfigVariableBool('want-dev', __debug__).value
+__dev__: bool = ConfigVariableBool('want-dev', __debug__).value
+
+base: ShowBase
 
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
@@ -59,6 +62,8 @@ aspect2d = render2d.attachNewNode(PGTop("aspect2d"))
 #: A dummy scene graph that is not being rendered by anything.
 hidden = NodePath("hidden")
 
+loader: Loader
+
 # Set direct notify categories now that we have config
 directNotify.setDconfigLevels()
 
@@ -81,7 +86,7 @@ def inspect(anObject):
 
 
 import builtins
-builtins.inspect = inspect
+builtins.inspect = inspect  # type: ignore[attr-defined]
 
 # this also appears in AIBaseGlobal
 if (not __debug__) and __dev__:

+ 2 - 1
direct/src/showbase/TkGlobal.py

@@ -37,4 +37,5 @@ del bordercolors
 
 def spawnTkLoop():
     """Alias for :meth:`base.spawnTkLoop() <.ShowBase.spawnTkLoop>`."""
-    base.spawnTkLoop()
+    from direct.showbase import ShowBaseGlobal
+    ShowBaseGlobal.base.spawnTkLoop()

+ 1 - 1
direct/src/showbase/VerboseImport.py

@@ -3,7 +3,7 @@ This module hooks into Python's import mechanism to print out all imports to
 the standard output as they happen.
 """
 
-__all__ = []
+__all__ = ()
 
 
 import sys

+ 2 - 1
direct/src/showbase/WxGlobal.py

@@ -3,4 +3,5 @@
 
 def spawnWxLoop():
     """Alias for :meth:`base.spawnWxLoop() <.ShowBase.spawnWxLoop>`."""
-    base.spawnWxLoop()
+    from direct.showbase import ShowBaseGlobal
+    ShowBaseGlobal.base.spawnWxLoop()

+ 2 - 4
direct/src/showbase/extend_frozen.c

@@ -147,14 +147,12 @@ py_is_frozen_module(PyObject *self, PyObject *args) {
   i = 0;
   while (PyImport_FrozenModules[i].name != NULL) {
     if (strcmp(PyImport_FrozenModules[i].name, name) == 0) {
-      Py_INCREF(Py_True);
-      return Py_True;
+      Py_RETURN_TRUE;
     }
     ++i;
   }
 
-  Py_INCREF(Py_False);
-  return Py_False;
+  Py_RETURN_FALSE;
 }
 
 /*

+ 8 - 6
direct/src/showutil/TexMemWatcher.py

@@ -22,6 +22,7 @@ from panda3d.core import (
     WindowProperties,
 )
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase import ShowBaseGlobal
 from direct.task.TaskManagerGlobal import taskMgr
 import math
 import copy
@@ -109,7 +110,7 @@ class TexMemWatcher(DirectObject):
         # This is the maximum number of bitmask rows (within
         # self.limit) to allocate for packing.  This controls the
         # value assigned to self.quantize in repack().
-        self.maxHeight = base.config.GetInt('tex-mem-max-height', 300)
+        self.maxHeight = ConfigVariableInt('tex-mem-max-height', 300).value
 
         # The total number of texture bytes tracked, including overflow.
         self.totalSize = 0
@@ -122,6 +123,7 @@ class TexMemWatcher(DirectObject):
         self.placedQSize = 0
 
         # If no GSG is specified, use the main GSG.
+        base = ShowBaseGlobal.base
         if gsg is None:
             gsg = base.win.getGsg()
         elif isinstance(gsg, GraphicsOutput):
@@ -150,7 +152,7 @@ class TexMemWatcher(DirectObject):
         # Set this to tinydisplay if you're running on a machine with
         # limited texture memory.  That way you won't compete for
         # texture memory with the main scene.
-        moduleName = base.config.GetString('tex-mem-pipe', '')
+        moduleName = ConfigVariableString('tex-mem-pipe', '').value
         if moduleName:
             self.pipe = base.makeModulePipe(moduleName)
 
@@ -202,7 +204,7 @@ class TexMemWatcher(DirectObject):
 
         # How frequently should the texture memory window check for
         # state changes?
-        updateInterval = base.config.GetDouble("tex-mem-update-interval", 0.5)
+        updateInterval = ConfigVariableDouble("tex-mem-update-interval", 0.5).value
         self.task = taskMgr.doMethodLater(updateInterval, self.updateTextures, 'TexMemWatcher')
 
         self.setLimit(limit)
@@ -380,7 +382,7 @@ class TexMemWatcher(DirectObject):
             self.cleanedUp = True
 
             # Remove the window.
-            base.graphicsEngine.removeWindow(self.win)
+            self.win.engine.removeWindow(self.win)
             self.win = None
             self.gsg = None
             self.pipe = None
@@ -756,8 +758,8 @@ class TexMemWatcher(DirectObject):
 
         # Sort the regions from largest to smallest to maximize
         # packing effectiveness.
-        texRecords = list(self.texRecordsByTex.values())
-        texRecords.sort(key = lambda tr: (tr.tw, tr.th), reverse = True)
+        texRecords = sorted(self.texRecordsByTex.values(),
+                            key=lambda tr: (tr.tw, tr.th), reverse=True)
 
         for tr in texRecords:
             self.placeTexture(tr)

+ 1 - 1
direct/src/showutil/TexViewer.py

@@ -18,7 +18,7 @@ class TexViewer(DirectObject):
 
         # We'll put the full-resolution texture on the left.
         cm = CardMaker('left')
-        l, r, b, t = (-1, -0.1, 0, 0.9)
+        l, r, b, t = (-1.0, -0.1, 0.0, 0.9)
         cm.setFrame(l, r, b, t)
         left = cards.attachNewNode(cm.generate())
         left.setTexture(self.tex)

+ 2 - 2
direct/src/stdpy/pickle.py

@@ -45,7 +45,7 @@ BasePickler = pickle._Pickler
 BaseUnpickler = pickle._Unpickler
 
 
-class Pickler(BasePickler):
+class Pickler(BasePickler):  # type: ignore[misc, valid-type]
 
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
@@ -148,7 +148,7 @@ class Pickler(BasePickler):
         self.save_reduce(obj=obj, *rv)
 
 
-class Unpickler(BaseUnpickler):
+class Unpickler(BaseUnpickler):  # type: ignore[misc, valid-type]
 
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()

+ 3 - 111
direct/src/stdpy/threading.py

@@ -83,8 +83,8 @@ class ThreadBase:
 # Copy these static methods from Panda's Thread object.  These are
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # mode.
-ThreadBase.forceYield = core.Thread.forceYield
-ThreadBase.considerYield = core.Thread.considerYield
+ThreadBase.forceYield = core.Thread.forceYield  # type: ignore[attr-defined]
+ThreadBase.considerYield = core.Thread.considerYield  # type: ignore[attr-defined]
 
 
 class Thread(ThreadBase):
@@ -123,10 +123,7 @@ class Thread(ThreadBase):
         self.__dict__['ident'] = threadId
 
     def __del__(self):
-        # On interpreter shutdown, the _thread module might have
-        # already been cleaned up.
-        if _thread and _thread._remove_thread_id:
-            _thread._remove_thread_id(self.ident)
+        _thread._remove_thread_id(self.ident)
 
     def is_alive(self):
         thread = self.__thread
@@ -435,108 +432,3 @@ def setprofile(func):
 
 def stack_size(size = None):
     raise ThreadError
-
-
-if __debug__:
-    def _test():
-        from collections import deque
-
-        _sleep = core.Thread.sleep
-
-        _VERBOSE = False
-
-        class _Verbose(object):
-
-            def __init__(self, verbose=None):
-                if verbose is None:
-                    verbose = _VERBOSE
-                self.__verbose = verbose
-
-            def _note(self, format, *args):
-                if self.__verbose:
-                    format = format % args
-                    format = "%s: %s\n" % (
-                        currentThread().getName(), format)
-                    _sys.stderr.write(format)
-
-        class BoundedQueue(_Verbose):
-
-            def __init__(self, limit):
-                _Verbose.__init__(self)
-                self.mon = Lock(name = "BoundedQueue.mon")
-                self.rc = Condition(self.mon)
-                self.wc = Condition(self.mon)
-                self.limit = limit
-                self.queue = deque()
-
-            def put(self, item):
-                self.mon.acquire()
-                while len(self.queue) >= self.limit:
-                    self._note("put(%s): queue full", item)
-                    self.wc.wait()
-                self.queue.append(item)
-                self._note("put(%s): appended, length now %d",
-                           item, len(self.queue))
-                self.rc.notify()
-                self.mon.release()
-
-            def get(self):
-                self.mon.acquire()
-                while not self.queue:
-                    self._note("get(): queue empty")
-                    self.rc.wait()
-                item = self.queue.popleft()
-                self._note("get(): got %s, %d left", item, len(self.queue))
-                self.wc.notify()
-                self.mon.release()
-                return item
-
-        class ProducerThread(Thread):
-
-            def __init__(self, queue, quota):
-                Thread.__init__(self, name="Producer")
-                self.queue = queue
-                self.quota = quota
-
-            def run(self):
-                from random import random
-                counter = 0
-                while counter < self.quota:
-                    counter = counter + 1
-                    self.queue.put("%s.%d" % (self.getName(), counter))
-                    _sleep(random() * 0.00001)
-
-        class ConsumerThread(Thread):
-
-            def __init__(self, queue, count):
-                Thread.__init__(self, name="Consumer")
-                self.queue = queue
-                self.count = count
-
-            def run(self):
-                while self.count > 0:
-                    item = self.queue.get()
-                    print(item)
-                    self.count = self.count - 1
-
-        NP = 3
-        QL = 4
-        NI = 5
-
-        Q = BoundedQueue(QL)
-        P = []
-        for i in range(NP):
-            t = ProducerThread(Q, NI)
-            t.setName("Producer-%d" % (i+1))
-            P.append(t)
-        C = ConsumerThread(Q, NI*NP)
-        for t in P:
-            t.start()
-            _sleep(0.000001)
-        C.start()
-        for t in P:
-            t.join()
-        C.join()
-
-    if __name__ == '__main__':
-        _test()

+ 1 - 90
direct/src/stdpy/threading2.py

@@ -65,7 +65,7 @@ if __debug__:
 
 else:
     # Disable this when using "python -O"
-    class _Verbose(object):
+    class _Verbose(object):  # type: ignore[no-redef]
         def __init__(self, verbose=None):
             pass
         def _note(self, *args):
@@ -762,92 +762,3 @@ def main_thread():
 ##     from thread import _local as local
 ## except ImportError:
 ##     from _threading_local import local
-
-
-# Self-test code
-if __debug__:
-    def _test():
-        from collections import deque
-
-        class BoundedQueue(_Verbose):
-
-            def __init__(self, limit):
-                _Verbose.__init__(self)
-                self.mon = RLock()
-                self.rc = Condition(self.mon)
-                self.wc = Condition(self.mon)
-                self.limit = limit
-                self.queue = deque()
-
-            def put(self, item):
-                self.mon.acquire()
-                while len(self.queue) >= self.limit:
-                    self._note("put(%s): queue full", item)
-                    self.wc.wait()
-                self.queue.append(item)
-                self._note("put(%s): appended, length now %d",
-                           item, len(self.queue))
-                self.rc.notify()
-                self.mon.release()
-
-            def get(self):
-                self.mon.acquire()
-                while not self.queue:
-                    self._note("get(): queue empty")
-                    self.rc.wait()
-                item = self.queue.popleft()
-                self._note("get(): got %s, %d left", item, len(self.queue))
-                self.wc.notify()
-                self.mon.release()
-                return item
-
-        class ProducerThread(Thread):
-
-            def __init__(self, queue, quota):
-                Thread.__init__(self, name="Producer")
-                self.queue = queue
-                self.quota = quota
-
-            def run(self):
-                from random import random
-                counter = 0
-                while counter < self.quota:
-                    counter = counter + 1
-                    self.queue.put("%s.%d" % (self.getName(), counter))
-                    _sleep(random() * 0.00001)
-
-
-        class ConsumerThread(Thread):
-
-            def __init__(self, queue, count):
-                Thread.__init__(self, name="Consumer")
-                self.queue = queue
-                self.count = count
-
-            def run(self):
-                while self.count > 0:
-                    item = self.queue.get()
-                    print(item)
-                    self.count = self.count - 1
-
-        NP = 3
-        QL = 4
-        NI = 5
-
-        Q = BoundedQueue(QL)
-        P = []
-        for i in range(NP):
-            t = ProducerThread(Q, NI)
-            t.setName("Producer-%d" % (i+1))
-            P.append(t)
-        C = ConsumerThread(Q, NI*NP)
-        for t in P:
-            t.start()
-            _sleep(0.000001)
-        C.start()
-        for t in P:
-            t.join()
-        C.join()
-
-    if __name__ == '__main__':
-        _test()

+ 2 - 0
direct/src/task/MiniTask.py

@@ -12,6 +12,8 @@ class MiniTask:
     done = 0
     cont = 1
 
+    name: str
+
     def __init__(self, callback):
         self.__call__ = callback
 

+ 105 - 574
direct/src/task/Task.py

@@ -6,6 +6,8 @@ For more information about the task system, consult the
 :ref:`tasks-and-event-handling` page in the programming manual.
 """
 
+from __future__ import annotations
+
 __all__ = ['Task', 'TaskManager',
            'cont', 'done', 'again', 'pickup', 'exit',
            'sequence', 'loop', 'pause']
@@ -13,6 +15,7 @@ __all__ = ['Task', 'TaskManager',
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Functor, ScratchPad
 from direct.showbase.MessengerGlobal import messenger
+from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
 import types
 import random
 import importlib
@@ -20,11 +23,12 @@ import sys
 
 # On Android, there's no use handling SIGINT, and in fact we can't, since we
 # run the application in a separate thread from the main thread.
+signal: types.ModuleType | None
 if hasattr(sys, 'getandroidapilevel'):
     signal = None
 else:
     try:
-        import _signal as signal
+        import _signal as signal  # type: ignore[import, no-redef]
     except ImportError:
         signal = None
 
@@ -41,8 +45,15 @@ from panda3d.core import (
 )
 from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
 
+# The following variables are typing constructs used in annotations
+# to succinctly express all the types that can be converted into tasks.
+_T = TypeVar('_T', covariant=True)
+_TaskCoroutine = Union[Coroutine[Any, None, _T], Generator[Any, None, _T]]
+_TaskFunction = Callable[..., Union[int, _TaskCoroutine[Union[int, None]], None]]
+_FuncOrTask = Union[_TaskFunction, _TaskCoroutine[Any], AsyncTask]
+
 
-def print_exc_plus():
+def print_exc_plus() -> None:
     """
     Print the usual traceback information, followed by a listing of all the
     local variables in each frame.
@@ -50,12 +61,13 @@ def print_exc_plus():
     import traceback
 
     tb = sys.exc_info()[2]
+    assert tb is not None
     while 1:
         if not tb.tb_next:
             break
         tb = tb.tb_next
     stack = []
-    f = tb.tb_frame
+    f: types.FrameType | None = tb.tb_frame
     while f:
         stack.append(f)
         f = f.f_back
@@ -82,11 +94,11 @@ def print_exc_plus():
 # these Python names, and define them both at the module level, here,
 # and at the class level (below).  The preferred access is via the
 # class level.
-done = AsyncTask.DSDone
-cont = AsyncTask.DSCont
-again = AsyncTask.DSAgain
-pickup = AsyncTask.DSPickup
-exit = AsyncTask.DSExit
+done: Final = AsyncTask.DSDone
+cont: Final = AsyncTask.DSCont
+again: Final = AsyncTask.DSAgain
+pickup: Final = AsyncTask.DSPickup
+exit: Final = AsyncTask.DSExit
 
 #: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
 Task = PythonTask
@@ -110,7 +122,7 @@ gather = Task.gather
 shield = Task.shield
 
 
-def sequence(*taskList):
+def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('sequence')
     for task in taskList:
         seq.addTask(task)
@@ -120,7 +132,7 @@ def sequence(*taskList):
 Task.DtoolClassDict['sequence'] = staticmethod(sequence)
 
 
-def loop(*taskList):
+def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('loop')
     for task in taskList:
         seq.addTask(task)
@@ -140,10 +152,12 @@ class TaskManager:
 
     MaxEpochSpeed = 1.0/30.0
 
-    def __init__(self):
+    __prevHandler: Any
+
+    def __init__(self) -> None:
         self.mgr = AsyncTaskManager.getGlobalPtr()
 
-        self.resumeFunc = None
+        self.resumeFunc: Callable[[], object] | None = None
         self.globalClock = self.mgr.getClock()
         self.stepping = False
         self.running = False
@@ -153,12 +167,12 @@ class TaskManager:
         if signal:
             self.__prevHandler = signal.default_int_handler
 
-        self._frameProfileQueue = []
+        self._frameProfileQueue: list[tuple[int, Any, Callable[[], object] | None]] = []
 
         # this will be set when it's safe to import StateVar
-        self._profileFrames = None
+        self._profileFrames: Any = None
         self._frameProfiler = None
-        self._profileTasks = None
+        self._profileTasks: Any = None
         self._taskProfiler = None
         self._taskProfileInfo = ScratchPad(
             taskId = None,
@@ -166,7 +180,7 @@ class TaskManager:
             session = None,
         )
 
-    def finalInit(self):
+    def finalInit(self) -> None:
         # This function should be called once during startup, after
         # most things are imported.
         from direct.fsm.StatePush import StateVar
@@ -175,7 +189,7 @@ class TaskManager:
         self._profileFrames = StateVar(False)
         self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
 
-    def destroy(self):
+    def destroy(self) -> None:
         # This should be safe to call multiple times.
         self.running = False
         self.notify.info("TaskManager.destroy()")
@@ -183,11 +197,14 @@ class TaskManager:
         self._frameProfileQueue.clear()
         self.mgr.cleanup()
 
-    def setClock(self, clockObject):
+    def __getClock(self) -> ClockObject:
+        return self.mgr.getClock()
+
+    def setClock(self, clockObject: ClockObject) -> None:
         self.mgr.setClock(clockObject)
         self.globalClock = clockObject
 
-    clock = property(lambda self: self.mgr.getClock(), setClock)
+    clock = property(__getClock, setClock)
 
     def invokeDefaultHandler(self, signalNumber, stackFrame):
         print('*** allowing mid-frame keyboard interrupt.')
@@ -208,13 +225,13 @@ class TaskManager:
             # Next time around invoke the default handler
             signal.signal(signal.SIGINT, self.invokeDefaultHandler)
 
-    def getCurrentTask(self):
+    def getCurrentTask(self) -> AsyncTask | None:
         """ Returns the task currently executing on this thread, or
         None if this is being called outside of the task manager. """
 
         return Thread.getCurrentThread().getCurrentTask()
 
-    def hasTaskChain(self, chainName):
+    def hasTaskChain(self, chainName: str) -> bool:
         """ Returns true if a task chain with the indicated name has
         already been defined, or false otherwise.  Note that
         setupTaskChain() will implicitly define a task chain if it has
@@ -224,9 +241,16 @@ class TaskManager:
 
         return self.mgr.findTaskChain(chainName) is not None
 
-    def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
-                       threadPriority = None, frameBudget = None,
-                       frameSync = None, timeslicePriority = None):
+    def setupTaskChain(
+        self,
+        chainName: str,
+        numThreads: int | None = None,
+        tickClock: bool | None = None,
+        threadPriority: int | None = None,
+        frameBudget: float | None = None,
+        frameSync: bool | None = None,
+        timeslicePriority: bool | None = None,
+    ) -> None:
         """Defines a new task chain.  Each task chain executes tasks
         potentially in parallel with all of the other task chains (if
         numThreads is more than zero).  When a new task is created, it
@@ -290,40 +314,50 @@ class TaskManager:
         if timeslicePriority is not None:
             chain.setTimeslicePriority(timeslicePriority)
 
-    def hasTaskNamed(self, taskName):
+    def hasTaskNamed(self, taskName: str) -> bool:
         """Returns true if there is at least one task, active or
         sleeping, with the indicated name. """
 
         return bool(self.mgr.findTask(taskName))
 
-    def getTasksNamed(self, taskName):
+    def getTasksNamed(self, taskName: str) -> list[AsyncTask]:
         """Returns a list of all tasks, active or sleeping, with the
         indicated name. """
         return list(self.mgr.findTasks(taskName))
 
-    def getTasksMatching(self, taskPattern):
+    def getTasksMatching(self, taskPattern: GlobPattern | str) -> list[AsyncTask]:
         """Returns a list of all tasks, active or sleeping, with a
         name that matches the pattern, which can include standard
         shell globbing characters like \\*, ?, and []. """
 
         return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
 
-    def getAllTasks(self):
+    def getAllTasks(self) -> list[AsyncTask]:
         """Returns list of all tasks, active and sleeping, in
         arbitrary order. """
         return list(self.mgr.getTasks())
 
-    def getTasks(self):
+    def getTasks(self) -> list[AsyncTask]:
         """Returns list of all active tasks in arbitrary order. """
         return list(self.mgr.getActiveTasks())
 
-    def getDoLaters(self):
+    def getDoLaters(self) -> list[AsyncTask]:
         """Returns list of all sleeping tasks in arbitrary order. """
         return list(self.mgr.getSleepingTasks())
 
-    def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
-                      sort = None, priority = None, taskChain = None,
-                      uponDeath = None, appendTask = False, owner = None):
+    def doMethodLater(
+        self,
+        delayTime: float,
+        funcOrTask: _FuncOrTask,
+        name: str | None,
+        extraArgs: Sequence | None = None,
+        sort: int | None = None,
+        priority: int | None = None,
+        taskChain: str | None = None,
+        uponDeath: Callable[[], object] | None = None,
+        appendTask: bool = False,
+        owner = None,
+    ) -> AsyncTask:
         """Adds a task to be performed at some time in the future.
         This is identical to `add()`, except that the specified
         delayTime is applied to the Task object first, which means
@@ -346,9 +380,19 @@ class TaskManager:
 
     do_method_later = doMethodLater
 
-    def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
-            priority = None, uponDeath = None, appendTask = False,
-            taskChain = None, owner = None, delay = None):
+    def add(
+        self,
+        funcOrTask: _FuncOrTask,
+        name: str | None = None,
+        sort: int | None = None,
+        extraArgs: Sequence | None = None,
+        priority: int | None = None,
+        uponDeath: Callable[[], object] | None = None,
+        appendTask: bool = False,
+        taskChain: str | None = None,
+        owner = None,
+        delay: float | None = None,
+    ) -> AsyncTask:
         """
         Add a new task to the taskMgr.  The task will begin executing
         immediately, or next frame if its sort value has already
@@ -415,7 +459,18 @@ class TaskManager:
         self.mgr.add(task)
         return task
 
-    def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
+    def __setupTask(
+        self,
+        funcOrTask: _FuncOrTask,
+        name: str | None,
+        priority: int | None,
+        sort: int | None,
+        extraArgs: Sequence | None,
+        taskChain: str | None,
+        appendTask: bool,
+        owner,
+        uponDeath: Callable[[], object] | None,
+    ) -> AsyncTask:
         wasTask = False
         if isinstance(funcOrTask, AsyncTask):
             task = funcOrTask
@@ -473,7 +528,7 @@ class TaskManager:
 
         return task
 
-    def remove(self, taskOrName):
+    def remove(self, taskOrName: AsyncTask | str | list[AsyncTask | str]) -> int:
         """Removes a task from the task manager.  The task is stopped,
         almost as if it had returned task.done.  (But if the task is
         currently executing, it will finish out its current frame
@@ -485,13 +540,15 @@ class TaskManager:
         if isinstance(taskOrName, AsyncTask):
             return self.mgr.remove(taskOrName)
         elif isinstance(taskOrName, list):
+            count = 0
             for task in taskOrName:
-                self.remove(task)
+                count += self.remove(task)
+            return count
         else:
             tasks = self.mgr.findTasks(taskOrName)
             return self.mgr.remove(tasks)
 
-    def removeTasksMatching(self, taskPattern):
+    def removeTasksMatching(self, taskPattern: GlobPattern | str) -> int:
         """Removes all tasks whose names match the pattern, which can
         include standard shell globbing characters like \\*, ?, and [].
         See also :meth:`remove()`.
@@ -501,7 +558,7 @@ class TaskManager:
         tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
         return self.mgr.remove(tasks)
 
-    def step(self):
+    def step(self) -> None:
         """Invokes the task manager for one frame, and then returns.
         Normally, this executes each task exactly once, though task
         chains that are in sub-threads or that have frame budgets
@@ -512,7 +569,7 @@ class TaskManager:
         # Replace keyboard interrupt handler during task list processing
         # so we catch the keyboard interrupt but don't handle it until
         # after task list processing is complete.
-        self.fKeyboardInterrupt = 0
+        self.fKeyboardInterrupt = False
         self.interruptCount = 0
 
         if signal:
@@ -534,7 +591,7 @@ class TaskManager:
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
 
-    def run(self):
+    def run(self) -> None:
         """Starts the task manager running.  Does not return until an
         exception is encountered (including KeyboardInterrupt). """
 
@@ -560,11 +617,11 @@ class TaskManager:
                     if len(self._frameProfileQueue) > 0:
                         numFrames, session, callback = self._frameProfileQueue.pop(0)
 
-                        def _profileFunc(numFrames=numFrames):
+                        def _profileFunc(numFrames: int = numFrames) -> None:
                             self._doProfiledFrames(numFrames)
                         session.setFunc(_profileFunc)
                         session.run()
-                        _profileFunc = None
+                        del _profileFunc
                         if callback:
                             callback()
                         session.release()
@@ -617,7 +674,7 @@ class TaskManager:
             message = ioError
         return code, message
 
-    def stop(self):
+    def stop(self) -> None:
         # Set a flag so we will stop before beginning next frame
         self.running = False
 
@@ -782,12 +839,12 @@ class TaskManager:
             task = tasks.getTask(i)
         return task
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return str(self.mgr)
 
     # In the event we want to do frame time managment, this is the
     # function to replace or overload.
-    def doYield(self, frameStartTime, nextScheduledTaskTime):
+    def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
         pass
 
     #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
@@ -801,532 +858,6 @@ class TaskManager:
     #        time.sleep(delta)
     #        delta = minFinTime - self.globalClock.getRealTime()
 
-    if __debug__:
-        # to catch memory leaks during the tests at the bottom of the file
-        def _startTrackingMemLeaks(self):
-            pass
-
-        def _stopTrackingMemLeaks(self):
-            pass
-
-        def _checkMemLeaks(self):
-            pass
-
-    def _runTests(self):
-        if __debug__:
-            tm = TaskManager()
-            tm.setClock(ClockObject())
-            tm.setupTaskChain("default", tickClock = True)
-
-            # check for memory leaks after every test
-            tm._startTrackingMemLeaks()
-            tm._checkMemLeaks()
-
-            # run-once task
-            l = []
-
-            def _testDone(task, l=l):
-                l.append(None)
-                return task.done
-            tm.add(_testDone, 'testDone')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 1
-            _testDone = None
-            tm._checkMemLeaks()
-
-            # remove by name
-            def _testRemoveByName(task):
-                return task.done
-            tm.add(_testRemoveByName, 'testRemoveByName')
-            assert tm.remove('testRemoveByName') == 1
-            assert tm.remove('testRemoveByName') == 0
-            _testRemoveByName = None
-            tm._checkMemLeaks()
-
-            # duplicate named tasks
-            def _testDupNamedTasks(task):
-                return task.done
-            tm.add(_testDupNamedTasks, 'testDupNamedTasks')
-            tm.add(_testDupNamedTasks, 'testDupNamedTasks')
-            assert tm.remove('testRemoveByName') == 0
-            _testDupNamedTasks = None
-            tm._checkMemLeaks()
-
-            # continued task
-            l = []
-
-            def _testCont(task, l = l):
-                l.append(None)
-                return task.cont
-            tm.add(_testCont, 'testCont')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.remove('testCont')
-            _testCont = None
-            tm._checkMemLeaks()
-
-            # continue until done task
-            l = []
-
-            def _testContDone(task, l = l):
-                l.append(None)
-                if len(l) >= 2:
-                    return task.done
-                else:
-                    return task.cont
-            tm.add(_testContDone, 'testContDone')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.step()
-            assert len(l) == 2
-            assert not tm.hasTaskNamed('testContDone')
-            _testContDone = None
-            tm._checkMemLeaks()
-
-            # hasTaskNamed
-            def _testHasTaskNamed(task):
-                return task.done
-            tm.add(_testHasTaskNamed, 'testHasTaskNamed')
-            assert tm.hasTaskNamed('testHasTaskNamed')
-            tm.step()
-            assert not tm.hasTaskNamed('testHasTaskNamed')
-            _testHasTaskNamed = None
-            tm._checkMemLeaks()
-
-            # task sort
-            l = []
-
-            def _testPri1(task, l = l):
-                l.append(1)
-                return task.cont
-
-            def _testPri2(task, l = l):
-                l.append(2)
-                return task.cont
-            tm.add(_testPri1, 'testPri1', sort = 1)
-            tm.add(_testPri2, 'testPri2', sort = 2)
-            tm.step()
-            assert len(l) == 2
-            assert l == [1, 2,]
-            tm.step()
-            assert len(l) == 4
-            assert l == [1, 2, 1, 2,]
-            tm.remove('testPri1')
-            tm.remove('testPri2')
-            _testPri1 = None
-            _testPri2 = None
-            tm._checkMemLeaks()
-
-            # task extraArgs
-            l = []
-
-            def _testExtraArgs(arg1, arg2, l=l):
-                l.extend([arg1, arg2,])
-                return done
-            tm.add(_testExtraArgs, 'testExtraArgs', extraArgs=[4,5])
-            tm.step()
-            assert len(l) == 2
-            assert l == [4, 5,]
-            _testExtraArgs = None
-            tm._checkMemLeaks()
-
-            # task appendTask
-            l = []
-
-            def _testAppendTask(arg1, arg2, task, l=l):
-                l.extend([arg1, arg2,])
-                return task.done
-            tm.add(_testAppendTask, '_testAppendTask', extraArgs=[4,5], appendTask=True)
-            tm.step()
-            assert len(l) == 2
-            assert l == [4, 5,]
-            _testAppendTask = None
-            tm._checkMemLeaks()
-
-            # task uponDeath
-            l = []
-
-            def _uponDeathFunc(task, l=l):
-                l.append(task.name)
-
-            def _testUponDeath(task):
-                return done
-            tm.add(_testUponDeath, 'testUponDeath', uponDeath=_uponDeathFunc)
-            tm.step()
-            assert len(l) == 1
-            assert l == ['testUponDeath']
-            _testUponDeath = None
-            _uponDeathFunc = None
-            tm._checkMemLeaks()
-
-            # task owner
-            class _TaskOwner:
-                def _addTask(self, task):
-                    self.addedTaskName = task.name
-
-                def _clearTask(self, task):
-                    self.clearedTaskName = task.name
-            to = _TaskOwner()
-            l = []
-
-            def _testOwner(task):
-                return done
-            tm.add(_testOwner, 'testOwner', owner=to)
-            tm.step()
-            assert getattr(to, 'addedTaskName', None) == 'testOwner'
-            assert getattr(to, 'clearedTaskName', None) == 'testOwner'
-            _testOwner = None
-            del to
-            _TaskOwner = None
-            tm._checkMemLeaks()
-
-            doLaterTests = [0,]
-
-            # doLater
-            l = []
-
-            def _testDoLater1(task, l=l):
-                l.append(1)
-
-            def _testDoLater2(task, l=l):
-                l.append(2)
-
-            def _monitorDoLater(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .03:
-                    assert l == [1, 2,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLater1, 'testDoLater1')
-            tm.doMethodLater(.02, _testDoLater2, 'testDoLater2')
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLater, 'monitorDoLater', sort=10)
-            _testDoLater1 = None
-            _testDoLater2 = None
-            _monitorDoLater = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater sort
-            l = []
-
-            def _testDoLaterPri1(task, l=l):
-                l.append(1)
-
-            def _testDoLaterPri2(task, l=l):
-                l.append(2)
-
-            def _monitorDoLaterPri(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [1, 2,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterPri1, 'testDoLaterPri1', sort=1)
-            tm.doMethodLater(.01, _testDoLaterPri2, 'testDoLaterPri2', sort=2)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterPri, 'monitorDoLaterPri', sort=10)
-            _testDoLaterPri1 = None
-            _testDoLaterPri2 = None
-            _monitorDoLaterPri = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater extraArgs
-            l = []
-
-            def _testDoLaterExtraArgs(arg1, l=l):
-                l.append(arg1)
-
-            def _monitorDoLaterExtraArgs(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [3,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterExtraArgs, 'testDoLaterExtraArgs', extraArgs=[3,])
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterExtraArgs, 'monitorDoLaterExtraArgs', sort=10)
-            _testDoLaterExtraArgs = None
-            _monitorDoLaterExtraArgs = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater appendTask
-            l = []
-
-            def _testDoLaterAppendTask(arg1, task, l=l):
-                assert task.name == 'testDoLaterAppendTask'
-                l.append(arg1)
-
-            def _monitorDoLaterAppendTask(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [4,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterAppendTask, 'testDoLaterAppendTask',
-                             extraArgs=[4,], appendTask=True)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterAppendTask, 'monitorDoLaterAppendTask', sort=10)
-            _testDoLaterAppendTask = None
-            _monitorDoLaterAppendTask = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater uponDeath
-            l = []
-
-            def _testUponDeathFunc(task, l=l):
-                assert task.name == 'testDoLaterUponDeath'
-                l.append(10)
-
-            def _testDoLaterUponDeath(arg1, l=l):
-                return done
-
-            def _monitorDoLaterUponDeath(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [10,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterUponDeath, 'testDoLaterUponDeath',
-                             uponDeath=_testUponDeathFunc)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterUponDeath, 'monitorDoLaterUponDeath', sort=10)
-            _testUponDeathFunc = None
-            _testDoLaterUponDeath = None
-            _monitorDoLaterUponDeath = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater owner
-            class _DoLaterOwner:
-                def _addTask(self, task):
-                    self.addedTaskName = task.name
-
-                def _clearTask(self, task):
-                    self.clearedTaskName = task.name
-            doLaterOwner = _DoLaterOwner()
-            l = []
-
-            def _testDoLaterOwner(l=l):
-                pass
-
-            def _monitorDoLaterOwner(task, tm=tm, l=l, doLaterOwner=doLaterOwner,
-                                     doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert getattr(doLaterOwner, 'addedTaskName', None) == 'testDoLaterOwner'
-                    assert getattr(doLaterOwner, 'clearedTaskName', None) == 'testDoLaterOwner'
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterOwner, 'testDoLaterOwner',
-                             owner=doLaterOwner)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterOwner, 'monitorDoLaterOwner', sort=10)
-            _testDoLaterOwner = None
-            _monitorDoLaterOwner = None
-            del doLaterOwner
-            _DoLaterOwner = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # run the doLater tests
-            while doLaterTests[0] > 0:
-                tm.step()
-            del doLaterTests
-            tm._checkMemLeaks()
-
-            # getTasks
-            def _testGetTasks(task):
-                return task.cont
-            # No doLaterProcessor in the new world.
-            assert len(tm.getTasks()) == 0
-            tm.add(_testGetTasks, 'testGetTasks1')
-            assert len(tm.getTasks()) == 1
-            assert (tm.getTasks()[0].name == 'testGetTasks1' or
-                    tm.getTasks()[1].name == 'testGetTasks1')
-            tm.add(_testGetTasks, 'testGetTasks2')
-            tm.add(_testGetTasks, 'testGetTasks3')
-            assert len(tm.getTasks()) == 3
-            tm.remove('testGetTasks2')
-            assert len(tm.getTasks()) == 2
-            tm.remove('testGetTasks1')
-            tm.remove('testGetTasks3')
-            assert len(tm.getTasks()) == 0
-            _testGetTasks = None
-            tm._checkMemLeaks()
-
-            # getDoLaters
-            def _testGetDoLaters():
-                pass
-            assert len(tm.getDoLaters()) == 0
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater1')
-            assert len(tm.getDoLaters()) == 1
-            assert tm.getDoLaters()[0].name == 'testDoLater1'
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater2')
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater3')
-            assert len(tm.getDoLaters()) == 3
-            tm.remove('testDoLater2')
-            assert len(tm.getDoLaters()) == 2
-            tm.remove('testDoLater1')
-            tm.remove('testDoLater3')
-            assert len(tm.getDoLaters()) == 0
-            _testGetDoLaters = None
-            tm._checkMemLeaks()
-
-            # duplicate named doLaters removed via taskMgr.remove
-            def _testDupNameDoLaters():
-                pass
-            # the doLaterProcessor is always running
-            tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater')
-            tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater')
-            assert len(tm.getDoLaters()) == 2
-            tm.remove('testDupNameDoLater')
-            assert len(tm.getDoLaters()) == 0
-            _testDupNameDoLaters = None
-            tm._checkMemLeaks()
-
-            # duplicate named doLaters removed via remove()
-            def _testDupNameDoLatersRemove():
-                pass
-            # the doLaterProcessor is always running
-            dl1 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove')
-            dl2 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove')
-            assert len(tm.getDoLaters()) == 2
-            dl2.remove()
-            assert len(tm.getDoLaters()) == 1
-            dl1.remove()
-            assert len(tm.getDoLaters()) == 0
-            _testDupNameDoLatersRemove = None
-            # nameDict etc. isn't cleared out right away with task.remove()
-            tm._checkMemLeaks()
-
-            # getTasksNamed
-            def _testGetTasksNamed(task):
-                return task.cont
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 0
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 1
-            assert tm.getTasksNamed('testGetTasksNamed')[0].name == 'testGetTasksNamed'
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 3
-            tm.remove('testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 0
-            _testGetTasksNamed = None
-            tm._checkMemLeaks()
-
-            # removeTasksMatching
-            def _testRemoveTasksMatching(task):
-                return task.cont
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 0
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1')
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 1
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching*')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 0
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 0
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1a')
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2a')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 1
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching?a')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 0
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 0
-            _testRemoveTasksMatching = None
-            tm._checkMemLeaks()
-
-            # create Task object and add to mgr
-            l = []
-
-            def _testTaskObj(task, l=l):
-                l.append(None)
-                return task.cont
-            t = Task(_testTaskObj)
-            tm.add(t, 'testTaskObj')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.remove('testTaskObj')
-            tm.step()
-            assert len(l) == 2
-            _testTaskObj = None
-            tm._checkMemLeaks()
-
-            # remove Task via task.remove()
-            l = []
-
-            def _testTaskObjRemove(task, l=l):
-                l.append(None)
-                return task.cont
-            t = Task(_testTaskObjRemove)
-            tm.add(t, 'testTaskObjRemove')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            t.remove()
-            tm.step()
-            assert len(l) == 2
-            del t
-            _testTaskObjRemove = None
-            tm._checkMemLeaks()
-
-            # this test fails, and it's not clear what the correct behavior should be.
-            # sort passed to Task.__init__ is always overridden by taskMgr.add()
-            # even if no sort is specified, and calling Task.setSort() has no
-            # effect on the taskMgr's behavior.
-            # set/get Task sort
-            #l = []
-            #def _testTaskObjSort(arg, task, l=l):
-            #    l.append(arg)
-            #    return task.cont
-            #t1 = Task(_testTaskObjSort, sort=1)
-            #t2 = Task(_testTaskObjSort, sort=2)
-            #tm.add(t1, 'testTaskObjSort1', extraArgs=['a',], appendTask=True)
-            #tm.add(t2, 'testTaskObjSort2', extraArgs=['b',], appendTask=True)
-            #tm.step()
-            #assert len(l) == 2
-            #assert l == ['a', 'b']
-            #assert t1.getSort() == 1
-            #assert t2.getSort() == 2
-            #t1.setSort(3)
-            #assert t1.getSort() == 3
-            #tm.step()
-            #assert len(l) == 4
-            #assert l == ['a', 'b', 'b', 'a',]
-            #t1.remove()
-            #t2.remove()
-            #tm.step()
-            #assert len(l) == 4
-            #del t1
-            #del t2
-            #_testTaskObjSort = None
-            #tm._checkMemLeaks()
-
-            del l
-            tm.destroy()
-            del tm
-
 
 if __debug__:
     def checkLeak():

+ 90 - 89
direct/src/tkpanels/DirectSessionPanel.py

@@ -20,6 +20,7 @@ from direct.tkwidgets import VectorWidgets
 from direct.tkwidgets import SceneGraphExplorer
 from direct.tkwidgets import MemoryExplorer
 from direct.task.TaskManagerGlobal import taskMgr
+from direct.showbase import ShowBaseGlobal
 from .TaskManagerPanel import TaskManagerWidget
 import Pmw
 import tkinter as tk
@@ -44,8 +45,8 @@ class DirectSessionPanel(AppShell):
         AppShell.__init__(self, parent)
 
         # Active light
-        if len(base.direct.lights) > 0:
-            name = base.direct.lights.getNameList()[0]
+        if len(ShowBaseGlobal.direct.lights) > 0:
+            name = ShowBaseGlobal.direct.lights.getNameList()[0]
             self.lightMenu.selectitem(name)
             self.selectLightNamed(name)
         else:
@@ -62,14 +63,14 @@ class DirectSessionPanel(AppShell):
         # Initialize state
         # Dictionary keeping track of all node paths selected so far
         self.nodePathDict = {}
-        self.nodePathDict['widget'] = base.direct.widget
+        self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
         self.nodePathNames = ['widget']
 
         # Dictionary keeping track of all jb node paths selected so far
         self.jbNodePathDict = {}
         self.jbNodePathDict['none'] = 'No Node Path'
-        self.jbNodePathDict['widget'] = base.direct.widget
-        self.jbNodePathDict['camera'] = base.direct.camera
+        self.jbNodePathDict['widget'] = ShowBaseGlobal.direct.widget
+        self.jbNodePathDict['camera'] = ShowBaseGlobal.direct.camera
         self.jbNodePathNames = ['camera', 'selected', 'none']
 
         # Set up event hooks
@@ -93,7 +94,7 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenu('DIRECT', 'Direct Session Panel Operations')
 
         self.directEnabled = tk.BooleanVar()
-        self.directEnabled.set(1)
+        self.directEnabled.set(True)
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Enabled',
                                  label = 'Enable',
@@ -101,7 +102,7 @@ class DirectSessionPanel(AppShell):
                                  command = self.toggleDirect)
 
         self.directGridEnabled = tk.BooleanVar()
-        self.directGridEnabled.set(base.direct.grid.isEnabled())
+        self.directGridEnabled.set(ShowBaseGlobal.direct.grid.isEnabled())
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Grid Enabled',
                                  label = 'Enable Grid',
@@ -111,16 +112,16 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenuitem('DIRECT', 'command',
                                  'Toggle Object Handles Visability',
                                  label = 'Toggle Widget Viz',
-                                 command = base.direct.toggleWidgetVis)
+                                 command = ShowBaseGlobal.direct.toggleWidgetVis)
 
         self.menuBar.addmenuitem(
             'DIRECT', 'command',
             'Toggle Widget Move/COA Mode',
             label = 'Toggle Widget Mode',
-            command = base.direct.manipulationControl.toggleObjectHandlesMode)
+            command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)
 
         self.directWidgetOnTop = tk.BooleanVar()
-        self.directWidgetOnTop.set(0)
+        self.directWidgetOnTop.set(False)
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Widget On Top',
                                  label = 'Widget On Top',
@@ -130,7 +131,7 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenuitem('DIRECT', 'command',
                                  'Deselect All',
                                  label = 'Deselect All',
-                                 command = base.direct.deselectAll)
+                                 command = ShowBaseGlobal.direct.deselectAll)
 
         # Get a handle to the menu frame
         menuFrame = self.menuFrame
@@ -150,8 +151,8 @@ class DirectSessionPanel(AppShell):
         self.bind(self.nodePathMenu, 'Select node path to manipulate')
 
         self.undoButton = tk.Button(menuFrame, text = 'Undo',
-                                    command = base.direct.undo)
-        if base.direct.undoList:
+                                    command = ShowBaseGlobal.direct.undo)
+        if ShowBaseGlobal.direct.undoList:
             self.undoButton['state'] = 'normal'
         else:
             self.undoButton['state'] = 'disabled'
@@ -159,8 +160,8 @@ class DirectSessionPanel(AppShell):
         self.bind(self.undoButton, 'Undo last operation')
 
         self.redoButton = tk.Button(menuFrame, text = 'Redo',
-                                    command = base.direct.redo)
-        if base.direct.redoList:
+                                    command = ShowBaseGlobal.direct.redo)
+        if ShowBaseGlobal.direct.redoList:
             self.redoButton['state'] = 'normal'
         else:
             self.redoButton['state'] = 'disabled'
@@ -177,7 +178,7 @@ class DirectSessionPanel(AppShell):
 
         # Scene Graph Explorer
         self.SGE = SceneGraphExplorer.SceneGraphExplorer(
-            sgeFrame, nodePath = render,
+            sgeFrame, nodePath = ShowBaseGlobal.base.render,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_height = 300)
         self.SGE.pack(fill = tk.BOTH, expand = 1)
@@ -218,7 +219,7 @@ class DirectSessionPanel(AppShell):
         tk.Label(drFrame, text = 'Display Region',
                  font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
 
-        nameList = ['Display Region ' + repr(x) for x in range(len(base.direct.drList))]
+        nameList = ['Display Region ' + repr(x) for x in range(len(ShowBaseGlobal.direct.drList))]
         self.drMenu = Pmw.ComboBox(
             drFrame, labelpos = tk.W, label_text = 'Display Region:',
             entry_width = 20,
@@ -264,7 +265,7 @@ class DirectSessionPanel(AppShell):
 
         frame = tk.Frame(fovFrame)
         self.lockedFov = tk.BooleanVar()
-        self.lockedFov.set(1)
+        self.lockedFov.set(True)
         self.lockedFovButton = tk.Checkbutton(
             frame,
             text = 'Locked',
@@ -289,25 +290,25 @@ class DirectSessionPanel(AppShell):
         self.toggleBackfaceButton = tk.Button(
             toggleFrame,
             text = 'Backface',
-            command = base.toggleBackface)
+            command = ShowBaseGlobal.base.toggleBackface)
         self.toggleBackfaceButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
         self.toggleLightsButton = tk.Button(
             toggleFrame,
             text = 'Lights',
-            command = base.direct.lights.toggle)
+            command = ShowBaseGlobal.direct.lights.toggle)
         self.toggleLightsButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
         self.toggleTextureButton = tk.Button(
             toggleFrame,
             text = 'Texture',
-            command = base.toggleTexture)
+            command = ShowBaseGlobal.base.toggleTexture)
         self.toggleTextureButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
         self.toggleWireframeButton = tk.Button(
             toggleFrame,
             text = 'Wireframe',
-            command = base.toggleWireframe)
+            command = ShowBaseGlobal.base.toggleWireframe)
         self.toggleWireframeButton.pack(fill = tk.X, expand = 1)
         toggleFrame.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
@@ -354,7 +355,7 @@ class DirectSessionPanel(AppShell):
         mainSwitchFrame.pack(fill = tk.X, expand = 0)
 
         # Widget to select a light to configure
-        nameList = base.direct.lights.getNameList()
+        nameList = ShowBaseGlobal.direct.lights.getNameList()
         lightMenuFrame = tk.Frame(lightFrame)
 
         self.lightMenu = Pmw.ComboBox(
@@ -510,36 +511,36 @@ class DirectSessionPanel(AppShell):
             gridPage,
             text = 'Grid Spacing',
             min = 0.1,
-            value = base.direct.grid.getGridSpacing())
-        self.gridSpacing['command'] = base.direct.grid.setGridSpacing
+            value = ShowBaseGlobal.direct.grid.getGridSpacing())
+        self.gridSpacing['command'] = ShowBaseGlobal.direct.grid.setGridSpacing
         self.gridSpacing.pack(fill = tk.X, expand = 0)
 
         self.gridSize = Floater.Floater(
             gridPage,
             text = 'Grid Size',
             min = 1.0,
-            value = base.direct.grid.getGridSize())
-        self.gridSize['command'] = base.direct.grid.setGridSize
+            value = ShowBaseGlobal.direct.grid.getGridSize())
+        self.gridSize['command'] = ShowBaseGlobal.direct.grid.setGridSize
         self.gridSize.pack(fill = tk.X, expand = 0)
 
         self.gridSnapAngle = Dial.AngleDial(
             gridPage,
             text = 'Snap Angle',
             style = 'mini',
-            value = base.direct.grid.getSnapAngle())
-        self.gridSnapAngle['command'] = base.direct.grid.setSnapAngle
+            value = ShowBaseGlobal.direct.grid.getSnapAngle())
+        self.gridSnapAngle['command'] = ShowBaseGlobal.direct.grid.setSnapAngle
         self.gridSnapAngle.pack(fill = tk.X, expand = 0)
 
     def createDevicePage(self, devicePage):
         tk.Label(devicePage, text = 'DEVICES',
               font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
 
-        if base.direct.joybox is not None:
+        if ShowBaseGlobal.direct.joybox is not None:
             joyboxFrame = tk.Frame(devicePage, borderwidth = 2, relief = 'sunken')
             tk.Label(joyboxFrame, text = 'Joybox',
                      font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
             self.enableJoybox = tk.BooleanVar()
-            self.enableJoybox.set(1)
+            self.enableJoybox.set(True)
             self.enableJoyboxButton = tk.Checkbutton(
                 joyboxFrame,
                 text = 'Enabled/Disabled',
@@ -581,7 +582,7 @@ class DirectSessionPanel(AppShell):
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 min = 1.0, max = 100.0)
             self.jbXyzSF['command'] = (
-                lambda v: base.direct.joybox.setXyzMultiplier(v))
+                lambda v: ShowBaseGlobal.direct.joybox.setXyzMultiplier(v))
             self.jbXyzSF.pack(fill = tk.X, expand = 0)
             self.bind(self.jbXyzSF, 'Set joybox XYZ speed multiplier')
 
@@ -592,7 +593,7 @@ class DirectSessionPanel(AppShell):
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 min = 1.0, max = 100.0)
             self.jbHprSF['command'] = (
-                lambda v: base.direct.joybox.setHprMultiplier(v))
+                lambda v: ShowBaseGlobal.direct.joybox.setHprMultiplier(v))
             self.jbHprSF.pack(fill = tk.X, expand = 0)
             self.bind(self.jbHprSF, 'Set joybox HPR speed multiplier')
 
@@ -604,30 +605,30 @@ class DirectSessionPanel(AppShell):
 
     def createMemPage(self, memPage):
         self.MemExp = MemoryExplorer.MemoryExplorer(
-            memPage, nodePath = render,
+            memPage, nodePath = ShowBaseGlobal.base.render,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_height = 250)
         self.MemExp.pack(fill = tk.BOTH, expand = 1)
 
     def toggleDirect(self):
         if self.directEnabled.get():
-            base.direct.enable()
+            ShowBaseGlobal.direct.enable()
         else:
-            base.direct.disable()
+            ShowBaseGlobal.direct.disable()
 
     def toggleDirectGrid(self):
         if self.directGridEnabled.get():
-            base.direct.grid.enable()
+            ShowBaseGlobal.direct.grid.enable()
         else:
-            base.direct.grid.disable()
+            ShowBaseGlobal.direct.grid.disable()
 
     def toggleWidgetOnTop(self):
         if self.directWidgetOnTop.get():
-            base.direct.widget.setBin('gui-popup', 0)
-            base.direct.widget.setDepthTest(0)
+            ShowBaseGlobal.direct.widget.setBin('gui-popup', 0)
+            ShowBaseGlobal.direct.widget.setDepthTest(0)
         else:
-            base.direct.widget.clearBin()
-            base.direct.widget.setDepthTest(1)
+            ShowBaseGlobal.direct.widget.clearBin()
+            ShowBaseGlobal.direct.widget.setDepthTest(1)
 
     def selectedNodePathHook(self, nodePath):
         # Make sure node path is in nodePathDict
@@ -657,7 +658,7 @@ class DirectSessionPanel(AppShell):
         # Did we finally get something?
         if nodePath is not None:
             # Yes, select it!
-            base.direct.select(nodePath)
+            ShowBaseGlobal.direct.select(nodePath)
 
     def addNodePath(self, nodePath):
         self.addNodePathToDict(nodePath, self.nodePathNames,
@@ -665,25 +666,25 @@ class DirectSessionPanel(AppShell):
 
     def selectJBModeNamed(self, name):
         if name == 'Joe Mode':
-            base.direct.joybox.joeMode()
+            ShowBaseGlobal.direct.joybox.joeMode()
         elif name == 'Drive Mode':
-            base.direct.joybox.driveMode()
+            ShowBaseGlobal.direct.joybox.driveMode()
         elif name == 'Orbit Mode':
-            base.direct.joybox.orbitMode()
+            ShowBaseGlobal.direct.joybox.orbitMode()
         elif name == 'Look At Mode':
-            base.direct.joybox.lookAtMode()
+            ShowBaseGlobal.direct.joybox.lookAtMode()
         elif name == 'Look Around Mode':
-            base.direct.joybox.lookAroundMode()
+            ShowBaseGlobal.direct.joybox.lookAroundMode()
         elif name == 'Walkthru Mode':
-            base.direct.joybox.walkthruMode()
+            ShowBaseGlobal.direct.joybox.walkthruMode()
         elif name == 'Demo Mode':
-            base.direct.joybox.demoMode()
+            ShowBaseGlobal.direct.joybox.demoMode()
         elif name == 'HPRXYZ Mode':
-            base.direct.joybox.hprXyzMode()
+            ShowBaseGlobal.direct.joybox.hprXyzMode()
 
     def selectJBNodePathNamed(self, name):
         if name == 'selected':
-            nodePath = base.direct.selected.last
+            nodePath = ShowBaseGlobal.direct.selected.last
             # Add Combo box entry for this selected object
             self.addJBNodePath(nodePath)
         else:
@@ -708,9 +709,9 @@ class DirectSessionPanel(AppShell):
         if nodePath is not None:
             # Yes, select it!
             if nodePath == 'No Node Path':
-                base.direct.joybox.setNodePath(None)
+                ShowBaseGlobal.direct.joybox.setNodePath(None)
             else:
-                base.direct.joybox.setNodePath(nodePath)
+                ShowBaseGlobal.direct.joybox.setNodePath(nodePath)
 
     def addJBNodePath(self, nodePath):
         self.addNodePathToDict(nodePath, self.jbNodePathNames,
@@ -741,14 +742,13 @@ class DirectSessionPanel(AppShell):
         self.setBackgroundColorVec((r, g, b))
 
     def setBackgroundColorVec(self, color):
-        base.setBackgroundColor(color[0]/255.0,
-                                color[1]/255.0,
-                                color[2]/255.0)
+        ShowBaseGlobal.base.setBackgroundColor(
+            color[0] / 255.0, color[1] / 255.0, color[2] / 255.0)
 
     def selectDisplayRegionNamed(self, name):
         if name.find('Display Region ') >= 0:
             drIndex = int(name[-1:])
-            self.activeDisplayRegion = base.direct.drList[drIndex]
+            self.activeDisplayRegion = ShowBaseGlobal.direct.drList[drIndex]
         else:
             self.activeDisplayRegion = None
         # Make sure info is current
@@ -758,13 +758,13 @@ class DirectSessionPanel(AppShell):
         dr = self.activeDisplayRegion
         if dr:
             dr.camLens.setNear(near)
-            cluster('base.camLens.setNear(%f)' % near, 0)
+            ShowBaseGlobal.direct.cluster('base.camLens.setNear(%f)' % near, 0)
 
     def setFar(self, far):
         dr = self.activeDisplayRegion
         if dr:
             dr.camLens.setFar(far)
-            cluster('base.camLens.setFar(%f)' % far, 0)
+            ShowBaseGlobal.direct.cluster('base.camLens.setFar(%f)' % far, 0)
 
     def setHFov(self, hFov):
         dr = self.activeDisplayRegion
@@ -802,10 +802,10 @@ class DirectSessionPanel(AppShell):
     # Lights #
     def selectLightNamed(self, name):
         # See if light exists
-        self.activeLight = base.direct.lights[name]
+        self.activeLight = ShowBaseGlobal.direct.lights[name]
         # If not...create new one
         if self.activeLight is None:
-            self.activeLight = base.direct.lights.create(name)
+            self.activeLight = ShowBaseGlobal.direct.lights.create(name)
         # Do we have a valid light at this point?
         if self.activeLight:
             light = self.activeLight.getLight()
@@ -820,28 +820,28 @@ class DirectSessionPanel(AppShell):
         else:
             # Restore valid data
             listbox = self.lightMenu.component('scrolledlist')
-            listbox.setlist(base.direct.lights.getNameList())
-            if len(base.direct.lights) > 0:
-                self.lightMenu.selectitem(base.direct.lights.getNameList()[0])
+            listbox.setlist(ShowBaseGlobal.direct.lights.getNameList())
+            if len(ShowBaseGlobal.direct.lights) > 0:
+                self.lightMenu.selectitem(ShowBaseGlobal.direct.lights.getNameList()[0])
         # Make sure info is current
         self.updateLightInfo()
 
     def addAmbient(self):
-        return base.direct.lights.create('ambient')
+        return ShowBaseGlobal.direct.lights.create('ambient')
 
     def addDirectional(self):
-        return base.direct.lights.create('directional')
+        return ShowBaseGlobal.direct.lights.create('directional')
 
     def addPoint(self):
-        return base.direct.lights.create('point')
+        return ShowBaseGlobal.direct.lights.create('point')
 
     def addSpot(self):
-        return base.direct.lights.create('spot')
+        return ShowBaseGlobal.direct.lights.create('spot')
 
     def addLight(self, light):
         # Make list reflect current list of lights
         listbox = self.lightMenu.component('scrolledlist')
-        listbox.setlist(base.direct.lights.getNameList())
+        listbox.setlist(ShowBaseGlobal.direct.lights.getNameList())
         # Select the newly added light
         self.lightMenu.selectitem(light.getName())
         # And show corresponding page
@@ -849,16 +849,16 @@ class DirectSessionPanel(AppShell):
 
     def toggleLights(self):
         if self.enableLights.get():
-            base.direct.lights.allOn()
+            ShowBaseGlobal.direct.lights.allOn()
         else:
-            base.direct.lights.allOff()
+            ShowBaseGlobal.direct.lights.allOff()
 
     def toggleActiveLight(self):
         if self.activeLight:
             if self.lightActive.get():
-                base.direct.lights.setOn(self.activeLight)
+                ShowBaseGlobal.direct.lights.setOn(self.activeLight)
             else:
-                base.direct.lights.setOff(self.activeLight)
+                ShowBaseGlobal.direct.lights.setOff(self.activeLight)
 
     def setLightColor(self, color):
         if self.activeLight:
@@ -893,22 +893,22 @@ class DirectSessionPanel(AppShell):
     ## GRID CONTROLS ##
     def toggleGrid(self):
         if self.enableGrid.get():
-            base.direct.grid.enable()
+            ShowBaseGlobal.direct.grid.enable()
         else:
-            base.direct.grid.disable()
+            ShowBaseGlobal.direct.grid.disable()
 
     def toggleXyzSnap(self):
-        base.direct.grid.setXyzSnap(self.xyzSnap.get())
+        ShowBaseGlobal.direct.grid.setXyzSnap(self.xyzSnap.get())
 
     def toggleHprSnap(self):
-        base.direct.grid.setHprSnap(self.hprSnap.get())
+        ShowBaseGlobal.direct.grid.setHprSnap(self.hprSnap.get())
 
     ## DEVICE CONTROLS
     def toggleJoybox(self):
         if self.enableJoybox.get():
-            base.direct.joybox.enable()
+            ShowBaseGlobal.direct.joybox.enable()
         else:
-            base.direct.joybox.disable()
+            ShowBaseGlobal.direct.joybox.disable()
 
     ## UPDATE INFO ##
     def updateInfo(self, page = 'Environment'):
@@ -920,7 +920,7 @@ class DirectSessionPanel(AppShell):
             self.updateGridInfo()
 
     def updateEnvironmentInfo(self):
-        bkgrdColor = base.getBackgroundColor() * 255.0
+        bkgrdColor = ShowBaseGlobal.base.getBackgroundColor() * 255.0
         self.backgroundColor.set([bkgrdColor[0],
                                   bkgrdColor[1],
                                   bkgrdColor[2],
@@ -936,6 +936,7 @@ class DirectSessionPanel(AppShell):
 
     def updateLightInfo(self, page = None):
         # Set main lighting button
+        render = ShowBaseGlobal.base.render
         self.enableLights.set(
             render.node().hasAttrib(LightAttrib.getClassType()))
 
@@ -974,16 +975,16 @@ class DirectSessionPanel(AppShell):
                 self.pQuadraticAttenuation.set(att[2], 0)
 
     def updateGridInfo(self):
-        self.enableGrid.set(base.direct.grid.isEnabled())
-        self.xyzSnap.set(base.direct.grid.getXyzSnap())
-        self.hprSnap.set(base.direct.grid.getHprSnap())
-        self.gridSpacing.set(base.direct.grid.getGridSpacing(), 0)
-        self.gridSize.set(base.direct.grid.getGridSize(), 0)
-        self.gridSnapAngle.set(base.direct.grid.getSnapAngle(), 0)
+        self.enableGrid.set(ShowBaseGlobal.direct.grid.isEnabled())
+        self.xyzSnap.set(ShowBaseGlobal.direct.grid.getXyzSnap())
+        self.hprSnap.set(ShowBaseGlobal.direct.grid.getHprSnap())
+        self.gridSpacing.set(ShowBaseGlobal.direct.grid.getGridSpacing(), 0)
+        self.gridSize.set(ShowBaseGlobal.direct.grid.getGridSize(), 0)
+        self.gridSnapAngle.set(ShowBaseGlobal.direct.grid.getSnapAngle(), 0)
 
     # UNDO/REDO
     def pushUndo(self, fResetRedo = 1):
-        base.direct.pushUndo([self['nodePath']])
+        ShowBaseGlobal.direct.pushUndo([self['nodePath']])
 
     def undoHook(self, nodePathList = []):
         pass
@@ -997,7 +998,7 @@ class DirectSessionPanel(AppShell):
         self.undoButton.configure(state = 'disabled')
 
     def pushRedo(self):
-        base.direct.pushRedo([self['nodePath']])
+        ShowBaseGlobal.direct.pushRedo([self['nodePath']])
 
     def redoHook(self, nodePathList = []):
         pass

+ 1 - 2
direct/src/tkpanels/FSMInspector.py

@@ -358,8 +358,7 @@ class FSMInspector(AppShell):
 
     def printLayout(self):
         dict = self.stateInspectorDict
-        keys = list(dict.keys())
-        keys.sort()
+        keys = sorted(dict)
         print("ClassicFSM.ClassicFSM('%s', [" % self.name)
         for key in keys[:-1]:
             si = dict[key]

+ 7 - 7
direct/src/tkpanels/Inspector.py

@@ -11,6 +11,7 @@ so that I can just type: ``inspect(anObject)`` any time.
 See :ref:`inspection-utilities` for more information.
 """
 
+from __future__ import annotations
 
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
 
@@ -31,6 +32,8 @@ def inspect(anObject):
 
 ### private
 
+_InspectorMap: dict[str, str]
+
 
 def inspectorFor(anObject):
     typeName = type(anObject).__name__.capitalize() + 'Type'
@@ -95,9 +98,7 @@ class Inspector:
 
     def initializePartsList(self):
         self._partsList = []
-        keys = self.namedParts()
-        keys.sort()
-        for each in keys:
+        for each in sorted(self.namedParts()):
             self._partsList.append(each)
             #if not callable(getattr(self.object, each)):
             #    self._partsList.append(each)
@@ -202,9 +203,7 @@ class DictionaryInspector(Inspector):
 
     def initializePartsList(self):
         Inspector.initializePartsList(self)
-        keys = list(self.object.keys())
-        keys.sort()
-        for each in keys:
+        for each in sorted(self.object):
             self._partsList.append(each)
 
     def partNumber(self, partNumber):
@@ -400,7 +399,8 @@ class InspectorWindow:
         self.listWidget.component('listbox').focus_set()
 
     def showHelp(self):
-        help = tk.Toplevel(base.tkRoot)
+        from direct.showbase import ShowBaseGlobal
+        help = tk.Toplevel(ShowBaseGlobal.base.tkRoot)
         help.title("Inspector Help")
         frame = tk.Frame(help)
         frame.pack()

+ 2 - 24
direct/src/tkpanels/ParticlePanel.py

@@ -1174,9 +1174,7 @@ class ParticlePanel(AppShell):
         self.particlesLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         particles = self.particleEffect.getParticlesList()
-        names = [x.getName() for x in particles]
-        names.sort()
-        for name in names:
+        for name in sorted(x.getName() for x in particles):
             particle = self.particleEffect.getParticlesNamed(name)
             self.particlesLabelMenu.add_command(
                 label = name,
@@ -1199,9 +1197,7 @@ class ParticlePanel(AppShell):
         self.forceGroupLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         forceGroupList = self.particleEffect.getForceGroupList()
-        names = [x.getName() for x in forceGroupList]
-        names.sort()
-        for name in names:
+        for name in sorted(x.getName() for x in forceGroupList):
             force = self.particleEffect.getForceGroupNamed(name)
             self.forceGroupLabelMenu.add_command(
                 label = name,
@@ -2913,21 +2909,3 @@ class ParticlePanel(AppShell):
                            min = 0.01,
                            value = force.getRadius())
         self.createForceActiveWidget(frame, pageName, forceName, force)
-
-######################################################################
-
-
-# Create demo in root window for testing.
-if __name__ == '__main__':
-    try:
-        base
-    except NameError:
-        from direct.showbase.ShowBase import ShowBase
-        base = ShowBase()
-
-    root = Pmw.initialise()
-    pp = ParticlePanel()
-    base.pp=pp
-    #ve = VectorEntry(Toplevel(), relief = GROOVE)
-    #ve.pack()
-    base.run()

+ 26 - 33
direct/src/tkpanels/Placer.py

@@ -9,6 +9,7 @@ from direct.tkwidgets import Dial
 from direct.tkwidgets import Floater
 from direct.directtools.DirectGlobals import ZERO_VEC, UNIT_VEC
 from direct.showbase.MessengerGlobal import messenger
+from direct.showbase import ShowBaseGlobal
 from direct.task.TaskManagerGlobal import taskMgr
 import Pmw
 import tkinter as tk
@@ -28,7 +29,7 @@ class Placer(AppShell):
         INITOPT = Pmw.INITOPT
         optiondefs = (
             ('title',       self.appname,       None),
-            ('nodePath',    base.direct.camera,      None),
+            ('nodePath',    ShowBaseGlobal.direct.camera,      None),
         )
         self.defineoptions(kw, optiondefs)
 
@@ -39,23 +40,23 @@ class Placer(AppShell):
 
     def appInit(self):
         # Initialize state
-        self.tempCS = base.direct.group.attachNewNode('placerTempCS')
-        self.orbitFromCS = base.direct.group.attachNewNode(
+        self.tempCS = ShowBaseGlobal.direct.group.attachNewNode('placerTempCS')
+        self.orbitFromCS = ShowBaseGlobal.direct.group.attachNewNode(
             'placerOrbitFromCS')
-        self.orbitToCS = base.direct.group.attachNewNode('placerOrbitToCS')
+        self.orbitToCS = ShowBaseGlobal.direct.group.attachNewNode('placerOrbitToCS')
         self.refCS = self.tempCS
 
         # Dictionary keeping track of all node paths manipulated so far
         self.nodePathDict = {}
-        self.nodePathDict['camera'] = base.direct.camera
-        self.nodePathDict['widget'] = base.direct.widget
+        self.nodePathDict['camera'] = ShowBaseGlobal.direct.camera
+        self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
         self.nodePathNames = ['camera', 'widget', 'selected']
 
         self.refNodePathDict = {}
         self.refNodePathDict['parent'] = self['nodePath'].getParent()
         self.refNodePathDict['render'] = render
-        self.refNodePathDict['camera'] = base.direct.camera
-        self.refNodePathDict['widget'] = base.direct.widget
+        self.refNodePathDict['camera'] = ShowBaseGlobal.direct.camera
+        self.refNodePathDict['widget'] = ShowBaseGlobal.direct.widget
         self.refNodePathNames = ['parent', 'self', 'render',
                                  'camera', 'widget', 'selected']
 
@@ -103,12 +104,12 @@ class Placer(AppShell):
             'Placer', 'command',
             'Toggle widget visability',
             label = 'Toggle Widget Vis',
-            command = base.direct.toggleWidgetVis)
+            command = ShowBaseGlobal.direct.toggleWidgetVis)
         self.menuBar.addmenuitem(
             'Placer', 'command',
             'Toggle widget manipulation mode',
             label = 'Toggle Widget Mode',
-            command = base.direct.manipulationControl.toggleObjectHandlesMode)
+            command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)
 
         # Get a handle to the menu frame
         menuFrame = self.menuFrame
@@ -145,8 +146,8 @@ class Placer(AppShell):
         self.bind(self.refNodePathMenu, 'Select relative node path')
 
         self.undoButton = tk.Button(menuFrame, text = 'Undo',
-                                    command = base.direct.undo)
-        if base.direct.undoList:
+                                    command = ShowBaseGlobal.direct.undo)
+        if ShowBaseGlobal.direct.undoList:
             self.undoButton['state'] = 'normal'
         else:
             self.undoButton['state'] = 'disabled'
@@ -154,8 +155,8 @@ class Placer(AppShell):
         self.bind(self.undoButton, 'Undo last operation')
 
         self.redoButton = tk.Button(menuFrame, text = 'Redo',
-                                    command = base.direct.redo)
-        if base.direct.redoList:
+                                    command = ShowBaseGlobal.direct.redo)
+        if ShowBaseGlobal.direct.redoList:
             self.redoButton['state'] = 'normal'
         else:
             self.redoButton['state'] = 'disabled'
@@ -394,7 +395,7 @@ class Placer(AppShell):
             # Add Combo box entry for the initial node path
             self.addNodePath(nodePath)
         elif name == 'selected':
-            nodePath = base.direct.selected.last
+            nodePath = ShowBaseGlobal.direct.selected.last
             # Add Combo box entry for this selected object
             self.addNodePath(nodePath)
         else:
@@ -417,7 +418,7 @@ class Placer(AppShell):
             else:
                 if name == 'widget':
                     # Record relationship between selected nodes and widget
-                    base.direct.selected.getWrtAll()
+                    ShowBaseGlobal.direct.selected.getWrtAll()
         # Update active node path
         self.setActiveNodePath(nodePath)
 
@@ -449,7 +450,7 @@ class Placer(AppShell):
         if name == 'self':
             nodePath = self.tempCS
         elif name == 'selected':
-            nodePath = base.direct.selected.last
+            nodePath = ShowBaseGlobal.direct.selected.last
             # Add Combo box entry for this selected object
             self.addRefNodePath(nodePath)
         elif name == 'parent':
@@ -560,13 +561,13 @@ class Placer(AppShell):
         elif self.movementMode == 'Orbit:':
             self.xformOrbit(value, axis)
         if self.nodePathMenu.get() == 'widget':
-            if base.direct.manipulationControl.fSetCoa:
+            if ShowBaseGlobal.direct.manipulationControl.fSetCoa:
                 # Update coa based on current widget position
-                base.direct.selected.last.mCoa2Dnp.assign(
-                    base.direct.widget.getMat(base.direct.selected.last))
+                ShowBaseGlobal.direct.selected.last.mCoa2Dnp.assign(
+                    ShowBaseGlobal.direct.widget.getMat(ShowBaseGlobal.direct.selected.last))
             else:
                 # Move the objects with the widget
-                base.direct.selected.moveWrtWidgetAll()
+                ShowBaseGlobal.direct.selected.moveWrtWidgetAll()
 
     def xformStart(self, data):
         # Record undo point
@@ -575,7 +576,7 @@ class Placer(AppShell):
         if self.nodePathMenu.get() == 'widget':
             taskMgr.remove('followSelectedNodePath')
             # Record relationship between selected nodes and widget
-            base.direct.selected.getWrtAll()
+            ShowBaseGlobal.direct.selected.getWrtAll()
         # Record initial state
         self.deltaHpr = self['nodePath'].getHpr(self.refCS)
         # Update placer to reflect new state
@@ -590,7 +591,7 @@ class Placer(AppShell):
         # If moving widget restart follow task
         if self.nodePathMenu.get() == 'widget':
             # Restart followSelectedNodePath task
-            base.direct.manipulationControl.spawnFollowSelectedNodePathTask()
+            ShowBaseGlobal.direct.manipulationControl.spawnFollowSelectedNodePathTask()
 
     def xformRelative(self, value, axis):
         nodePath = self['nodePath']
@@ -729,7 +730,7 @@ class Placer(AppShell):
             self.xformStop(None)
 
     def pushUndo(self, fResetRedo = 1):
-        base.direct.pushUndo([self['nodePath']])
+        ShowBaseGlobal.direct.pushUndo([self['nodePath']])
 
     def undoHook(self, nodePathList = []):
         # Reflect new changes
@@ -744,7 +745,7 @@ class Placer(AppShell):
         self.undoButton.configure(state = 'disabled')
 
     def pushRedo(self):
-        base.direct.pushRedo([self['nodePath']])
+        ShowBaseGlobal.direct.pushRedo([self['nodePath']])
 
     def redoHook(self, nodePathList = []):
         # Reflect new changes
@@ -786,11 +787,3 @@ class Placer(AppShell):
 
 def place(nodePath):
     return Placer(nodePath = nodePath)
-
-######################################################################
-
-
-# Create demo in root window for testing.
-if __name__ == '__main__':
-    root = Pmw.initialise()
-    widget = Placer()

Some files were not shown because too many files changed in this diff