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
         libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
-        python3-setuptools
+        python3-setuptools python3-tk
 
 
     - name: Cache dependencies (Windows)
     - name: Cache dependencies (Windows)
       if: runner.os == 'Windows'
       if: runner.os == 'Windows'
       uses: actions/cache@v1
       uses: actions/cache@v1
       with:
       with:
         path: thirdparty
         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)
     - name: Install dependencies (Windows)
       if: runner.os == 'Windows'
       if: runner.os == 'Windows'
       shell: powershell
       shell: powershell
       run: |
       run: |
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
           $wc = New-Object System.Net.WebClient
           $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
           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)
     - name: ccache (non-Windows)
@@ -180,25 +180,25 @@ jobs:
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
 
 
-    - name: Setup Python (Python 3.7)
+    - name: Setup Python (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
-        python-version: '3.7'
-    - name: Configure (Python 3.7)
+        python-version: '3.8'
+    - name: Configure (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       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" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.7)
+    - name: Build (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.7)
+    - name: Test (Python 3.8)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -207,30 +207,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.8)
+    - name: Setup Python (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
-        python-version: '3.8'
-    - name: Configure (Python 3.8)
+        python-version: '3.9'
+    - name: Configure (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       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" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.8)
+    - name: Build (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.8)
+    - name: Test (Python 3.9)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -239,30 +239,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.9)
+    - name: Setup Python (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
-        python-version: '3.9'
-    - name: Configure (Python 3.9)
+        python-version: '3.10'
+    - name: Configure (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       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" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.9)
+    - name: Build (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.9)
+    - name: Test (Python 3.10)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -271,30 +271,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.10)
+    - name: Setup Python (Python 3.11)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
-        python-version: '3.10'
-    - name: Configure (Python 3.10)
+        python-version: '3.11'
+    - name: Configure (Python 3.11)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       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" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.10)
+    - name: Build (Python 3.11)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.10)
+    - name: Test (Python 3.11)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -303,30 +303,30 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.11)
+    - name: Setup Python (Python 3.12)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
-        python-version: '3.11'
-    - name: Configure (Python 3.11)
+        python-version: '3.12'
+    - name: Configure (Python 3.12)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       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" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.11)
+    - name: Build (Python 3.12)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.11)
+    - name: Test (Python 3.12)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -335,7 +335,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
@@ -375,9 +375,9 @@ jobs:
       shell: powershell
       shell: powershell
       run: |
       run: |
         $wc = New-Object System.Net.WebClient
         $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
         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)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
@@ -387,6 +387,20 @@ jobs:
         rmdir panda3d-1.10.14
         rmdir panda3d-1.10.14
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
         (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
     - name: Set up Python 3.11
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
       with:
       with:
@@ -398,8 +412,8 @@ jobs:
     - name: Test Python 3.11
     - name: Test Python 3.11
       shell: bash
       shell: bash
       run: |
       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
     - name: Set up Python 3.10
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
@@ -412,8 +426,8 @@ jobs:
     - name: Test Python 3.10
     - name: Test Python 3.10
       shell: bash
       shell: bash
       run: |
       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
     - name: Set up Python 3.9
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
@@ -426,8 +440,8 @@ jobs:
     - name: Test Python 3.9
     - name: Test Python 3.9
       shell: bash
       shell: bash
       run: |
       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
     - name: Set up Python 3.8
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
@@ -440,23 +454,39 @@ jobs:
     - name: Test Python 3.8
     - name: Test Python 3.8
       shell: bash
       shell: bash
       run: |
       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: |
       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
       shell: bash
       run: |
       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: |
       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
 *.save.1
 *.sublime-workspace
 *.sublime-workspace
 .vscode/
 .vscode/
+.idea/
 
 
 # Temporary build files
 # Temporary build files
 /_vfsimporter.*
 /_vfsimporter.*
@@ -51,9 +52,10 @@ cmake_install.cmake
 install_manifest.txt
 install_manifest.txt
 CTestTestfile.cmake
 CTestTestfile.cmake
 
 
-# Windows
+# Operating system
 Thumbs.db
 Thumbs.db
 ehthumbs.db
 ehthumbs.db
+.DS_Store
 
 
 # macOS
 # macOS
 .DS_Store
 .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
 * Max Voss
 * Hawkheart
 * Hawkheart
 * Veronica
 * Veronica
+* Cody Sevier
+* Marek Alexa
 
 
 ## Enthusiasts
 ## Enthusiasts
 
 
@@ -28,6 +30,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * SureBet
 * SureBet
 * Gyedo Jeon
 * Gyedo Jeon
 * GameDev JONI
 * GameDev JONI
+* Max Rodriguez
+* Jethro Schoppenhorst
 
 
 ## Backers
 ## Backers
 
 

+ 3 - 3
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 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
 If you are familiar with installing Python packages, you can use
 the following command:
 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
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 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
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
 prompt using the following command.  Change the `--msvc-version` option based

+ 6 - 1
cmake/install/Panda3DConfig.cmake

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

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

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

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

@@ -281,10 +281,7 @@ class DirectCameraControl(DirectObject):
         angle = getCrankAngle(state.coaCenter)
         angle = getCrankAngle(state.coaCenter)
         deltaAngle = angle - state.lastAngle
         deltaAngle = angle - state.lastAngle
         state.lastAngle = angle
         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)
         SEditor.camera.setTransform(self.camManipRef, wrt)
         return Task.cont
         return Task.cont
 
 

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

@@ -324,10 +324,7 @@ class DirectManipulationControl(DirectObject):
         if self.rotateAxis == 'x':
         if self.rotateAxis == 'x':
             SEditor.widget.setP(SEditor.widget, deltaAngle)
             SEditor.widget.setP(SEditor.widget, deltaAngle)
         elif self.rotateAxis == 'y':
         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':
         elif self.rotateAxis == 'z':
             SEditor.widget.setH(SEditor.widget, deltaAngle)
             SEditor.widget.setH(SEditor.widget, deltaAngle)
         # Record crank angle for next time around
         # Record crank angle for next time around
@@ -456,10 +453,7 @@ class DirectManipulationControl(DirectObject):
         deltaAngle = angle - state.lastAngle
         deltaAngle = angle - state.lastAngle
         state.lastAngle = angle
         state.lastAngle = angle
         # Mouse motion edge to edge of display region results in one full turn
         # 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):
     def scale3D(self, state):
         # Scale the selected node based upon up down mouse motion
         # 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)
                         self.setLODNode(node = lodNode)
                         # preserve numerical order for lod's
                         # preserve numerical order for lod's
                         # this will make it easier to set ranges
                         # 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
                             # make a node under the LOD switch
                             # for each lod (just because!)
                             # for each lod (just because!)
                             self.addLOD(str(lodName))
                             self.addLOD(str(lodName))
@@ -302,9 +300,7 @@ class Actor(DirectObject, NodePath):
                         # it is a single part actor w/LOD
                         # it is a single part actor w/LOD
                         self.setLODNode(node = lodNode)
                         self.setLODNode(node = lodNode)
                         # preserve order of LOD's
                         # preserve order of LOD's
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             self.addLOD(str(lodName))
                             self.addLOD(str(lodName))
                             # pass in dictionary of parts
                             # pass in dictionary of parts
                             self.loadModel(models[lodName], lodName=lodName,
                             self.loadModel(models[lodName], lodName=lodName,
@@ -323,9 +319,7 @@ class Actor(DirectObject, NodePath):
                         if isinstance(models, dict):
                         if isinstance(models, dict):
                             if isinstance(models[next(iter(models))], dict):
                             if isinstance(models[next(iter(models))], dict):
                                 # then we have a multi-part w/ LOD
                                 # 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
                                     # iterate over both dicts
                                     for partName in anims:
                                     for partName in anims:
                                         self.loadAnims(
                                         self.loadAnims(
@@ -336,9 +330,7 @@ class Actor(DirectObject, NodePath):
                                     self.loadAnims(anims[partName], partName)
                                     self.loadAnims(anims[partName], partName)
                     elif isinstance(models, dict):
                     elif isinstance(models, dict):
                         # then we have single-part w/ LOD
                         # 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)
                             self.loadAnims(anims, lodName=lodName)
                     else:
                     else:
                         # else it is single-part w/o LOD
                         # else it is single-part w/o LOD
@@ -603,9 +595,6 @@ class Actor(DirectObject, NodePath):
         return bundles
         return bundles
 
 
     def __updateSortedLODNames(self):
     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
         # Reverse sort the doing a string->int
         def sortKey(x):
         def sortKey(x):
             if not str(x).isdigit():
             if not str(x).isdigit():
@@ -622,7 +611,9 @@ class Actor(DirectObject, NodePath):
             else:
             else:
                 return int(x)
                 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):
     def getLODNames(self):
         """
         """

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

@@ -169,12 +169,9 @@ class ClusterClient(DirectObject.DirectObject):
             self.serverList[server].sendNamedMovementDone()
             self.serverList[server].sendNamedMovementDone()
 
 
     def redoSortedPriorities(self):
     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):
     def moveObject(self, nodePath, object, serverList, offset, hasColor = True):
         self.notify.debug('moving object '+object)
         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)
             self.objectMappings.pop(name)
 
 
     def redoSortedPriorities(self):
     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,
     def addControlMapping(self, objectName, controlledName, offset = None,
                           priority = 0):
                           priority = 0):

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

@@ -41,9 +41,7 @@ void Extension<DCClass>::
 set_class_def(PyObject *class_def) {
 set_class_def(PyObject *class_def) {
   PythonClassDefsImpl *defs = do_get_defs();
   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>::
 PyObject *Extension<DCClass>::
 get_class_def() const {
 get_class_def() const {
   if (!has_class_def()) {
   if (!has_class_def()) {
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
   }
 
 
   PythonClassDefsImpl *defs = do_get_defs();
   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) {
 set_owner_class_def(PyObject *owner_class_def) {
   PythonClassDefsImpl *defs = do_get_defs();
   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>::
 PyObject *Extension<DCClass>::
 get_owner_class_def() const {
 get_owner_class_def() const {
   if (!has_owner_class_def()) {
   if (!has_owner_class_def()) {
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
   }
 
 
   PythonClassDefsImpl *defs = do_get_defs();
   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) {
   switch (pack_type) {
   case PT_invalid:
   case PT_invalid:
-    object = Py_None;
-    Py_INCREF(object);
+    object = Py_NewRef(Py_None);
     _this->unpack_skip();
     _this->unpack_skip();
     break;
     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
 /* 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
    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
    it under the terms of the GNU General Public License as published by
@@ -15,7 +16,7 @@
    GNU General Public License for more details.
    GNU General Public License for more details.
 
 
    You should have received a copy of the GNU General Public License
    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
 /* As a special exception, you may create a larger work that contains
    part or all of the Bison parser skeleton and distribute that work
    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
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
    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
 #ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 /* Debug traces.  */
 /* Debug traces.  */
@@ -40,54 +45,63 @@
 extern int dcyydebug;
 extern int dcyydebug;
 #endif
 #endif
 
 
-/* Token type.  */
+/* Token kinds.  */
 #ifndef YYTOKENTYPE
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
 # define YYTOKENTYPE
   enum 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
 #endif
-/* Tokens.  */
+/* Token kinds.  */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
 #define UNSIGNED_INTEGER 258
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define SIGNED_INTEGER 259
 #define REAL 260
 #define REAL 260
@@ -134,6 +148,8 @@ extern int dcyydebug;
 
 
 extern YYSTYPE dcyylval;
 extern YYSTYPE dcyylval;
 
 
+
 int dcyyparse (void);
 int dcyyparse (void);
 
 
+
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

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

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

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

@@ -6,7 +6,7 @@ from direct.showbase import ShowBase
 base = ShowBase.ShowBase()
 base = ShowBase.ShowBase()
 
 
 # Put an axis in the world:
 # 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.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.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()
 base = ThreeUpShow.ThreeUpShow()
 
 
 # Put an axis in the world:
 # 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.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)
 base.camLens.setFov(52.0)

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

@@ -41,8 +41,12 @@ class DirectJoybox(DirectObject):
     xyzMultiplier = 1.0
     xyzMultiplier = 1.0
     hprMultiplier = 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
         # See if device manager has been initialized
         if base.direct.deviceManager is None:
         if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()
             base.direct.deviceManager = DirectDeviceManager()

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

@@ -2,6 +2,10 @@
 DirectNotify module: this module contains the DirectNotify class
 DirectNotify module: this module contains the DirectNotify class
 """
 """
 
 
+from __future__ import annotations
+
+from panda3d.core import StreamWriter
+
 from . import Notifier
 from . import Notifier
 from . import Logger
 from . import Logger
 
 
@@ -12,39 +16,39 @@ class DirectNotify:
     mulitple notify categories via a dictionary of Notifiers.
     mulitple notify categories via a dictionary of Notifiers.
     """
     """
 
 
-    def __init__(self):
+    def __init__(self) -> None:
         """
         """
         DirectNotify class keeps a dictionary of Notfiers
         DirectNotify class keeps a dictionary of Notfiers
         """
         """
-        self.__categories = {}
+        self.__categories: dict[str, Notifier.Notifier] = {}
         # create a default log file
         # create a default log file
         self.logger = Logger.Logger()
         self.logger = Logger.Logger()
 
 
         # This will get filled in later by ShowBase.py with a
         # This will get filled in later by ShowBase.py with a
         # C++-level StreamWriter object for writing to standard
         # C++-level StreamWriter object for writing to standard
         # output.
         # output.
-        self.streamWriter = None
+        self.streamWriter: StreamWriter | None = None
 
 
-    def __str__(self):
+    def __str__(self) -> str:
         """
         """
         Print handling routine
         Print handling routine
         """
         """
         return "DirectNotify categories: %s" % (self.__categories)
         return "DirectNotify categories: %s" % (self.__categories)
 
 
     #getters and setters
     #getters and setters
-    def getCategories(self):
+    def getCategories(self) -> list[str]:
         """
         """
         Return list of category dictionary keys
         Return list of category dictionary keys
         """
         """
         return list(self.__categories.keys())
         return list(self.__categories.keys())
 
 
-    def getCategory(self, categoryName):
+    def getCategory(self, categoryName: str) -> Notifier.Notifier | None:
         """getCategory(self, string)
         """getCategory(self, string)
         Return the category with given name if present, None otherwise
         Return the category with given name if present, None otherwise
         """
         """
         return self.__categories.get(categoryName, None)
         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)
         """newCategory(self, string)
         Make a new notify category named categoryName. Return new category
         Make a new notify category named categoryName. Return new category
         if no such category exists, else return existing category
         if no such category exists, else return existing category
@@ -52,9 +56,11 @@ class DirectNotify:
         if categoryName not in self.__categories:
         if categoryName not in self.__categories:
             self.__categories[categoryName] = Notifier.Notifier(categoryName, logger)
             self.__categories[categoryName] = Notifier.Notifier(categoryName, logger)
             self.setDconfigLevel(categoryName)
             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
         Check to see if this category has a dconfig variable
         to set the notify severity and then set that level. You cannot
         to set the notify severity and then set that level. You cannot
@@ -77,40 +83,42 @@ class DirectNotify:
             level = 'error'
             level = 'error'
 
 
         category = self.getCategory(categoryName)
         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
         # Note - this print statement is making it difficult to
         # achieve "no output unless there's an error" operation - Josh
         # achieve "no output unless there's an error" operation - Josh
         # print ("Setting DirectNotify category: " + categoryName +
         # print ("Setting DirectNotify category: " + categoryName +
         #        " to severity: " + level)
         #        " to severity: " + level)
         if level == "error":
         if level == "error":
-            category.setWarning(0)
-            category.setInfo(0)
-            category.setDebug(0)
+            category.setWarning(False)
+            category.setInfo(False)
+            category.setDebug(False)
         elif level == "warning":
         elif level == "warning":
-            category.setWarning(1)
-            category.setInfo(0)
-            category.setDebug(0)
+            category.setWarning(True)
+            category.setInfo(False)
+            category.setDebug(False)
         elif level == "info":
         elif level == "info":
-            category.setWarning(1)
-            category.setInfo(1)
-            category.setDebug(0)
+            category.setWarning(True)
+            category.setInfo(True)
+            category.setDebug(False)
         elif level == "debug":
         elif level == "debug":
-            category.setWarning(1)
-            category.setInfo(1)
-            category.setDebug(1)
+            category.setWarning(True)
+            category.setInfo(True)
+            category.setDebug(True)
         else:
         else:
             print("DirectNotify: unknown notify level: " + str(level)
             print("DirectNotify: unknown notify level: " + str(level)
                    + " for category: " + str(categoryName))
                    + " for category: " + str(categoryName))
 
 
-    def setDconfigLevels(self):
+    def setDconfigLevels(self) -> None:
         for categoryName in self.getCategories():
         for categoryName in self.getCategories():
             self.setDconfigLevel(categoryName)
             self.setDconfigLevel(categoryName)
 
 
-    def setVerbose(self):
+    def setVerbose(self) -> None:
         for categoryName in self.getCategories():
         for categoryName in self.getCategories():
             category = self.getCategory(categoryName)
             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):
     def popupControls(self, tl = None):
         # Don't use a regular import, to prevent ModuleFinder from picking
         # 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 = importlib.import_module('direct.tkpanels.NotifyPanel')
         NotifyPanel.NotifyPanel(self, tl)
         NotifyPanel.NotifyPanel(self, tl)
 
 
-    def giveNotify(self,cls):
+    def giveNotify(self, cls) -> None:
         cls.notify = self.newCategory(cls.__name__)
         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
 """Logger module: contains the logger class which creates and writes
    data to log files on disk"""
    data to log files on disk"""
 
 
+from __future__ import annotations
+
+import io
 import time
 import time
 import math
 import math
 
 
 
 
 class Logger:
 class Logger:
-    def __init__(self, fileName="log"):
+    def __init__(self, fileName: str = "log") -> None:
         """
         """
         Logger constructor
         Logger constructor
         """
         """
-        self.__timeStamp = 1
+        self.__timeStamp = True
         self.__startTime = 0.0
         self.__startTime = 0.0
-        self.__logFile = None
+        self.__logFile: io.TextIOWrapper | None = None
         self.__logFileName = fileName
         self.__logFileName = fileName
 
 
-    def setTimeStamp(self, enable):
+    def setTimeStamp(self, enable: bool) -> None:
         """
         """
         Toggle time stamp printing with log entries on and off
         Toggle time stamp printing with log entries on and off
         """
         """
         self.__timeStamp = enable
         self.__timeStamp = enable
 
 
-    def getTimeStamp(self):
+    def getTimeStamp(self) -> bool:
         """
         """
         Return whether or not we are printing time stamps with log entries
         Return whether or not we are printing time stamps with log entries
         """
         """
@@ -29,24 +32,25 @@ class Logger:
 
 
     # logging control
     # logging control
 
 
-    def resetStartTime(self):
+    def resetStartTime(self) -> None:
         """
         """
         Reset the start time of the log file for time stamps
         Reset the start time of the log file for time stamps
         """
         """
         self.__startTime = time.time()
         self.__startTime = time.time()
 
 
-    def log(self, entryString):
+    def log(self, entryString: str) -> None:
         """log(self, string)
         """log(self, string)
         Print the given string to the log file"""
         Print the given string to the log file"""
         if self.__logFile is None:
         if self.__logFile is None:
             self.__openLogFile()
             self.__openLogFile()
+        assert self.__logFile is not None
         if self.__timeStamp:
         if self.__timeStamp:
             self.__logFile.write(self.__getTimeStamp())
             self.__logFile.write(self.__getTimeStamp())
         self.__logFile.write(entryString + '\n')
         self.__logFile.write(entryString + '\n')
 
 
     # logging functions
     # logging functions
 
 
-    def __openLogFile(self):
+    def __openLogFile(self) -> None:
         """
         """
         Open a file for logging error/warning messages
         Open a file for logging error/warning messages
         """
         """
@@ -56,14 +60,14 @@ class Logger:
         logFileName = self.__logFileName + "." + st
         logFileName = self.__logFileName + "." + st
         self.__logFile = open(logFileName, "w")
         self.__logFile = open(logFileName, "w")
 
 
-    def __closeLogFile(self):
+    def __closeLogFile(self) -> None:
         """
         """
         Close the error/warning output file
         Close the error/warning output file
         """
         """
         if self.__logFile is not None:
         if self.__logFile is not None:
             self.__logFile.close()
             self.__logFile.close()
 
 
-    def __getTimeStamp(self):
+    def __getTimeStamp(self) -> str:
         """
         """
         Return the offset between current time and log file startTime
         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
 Notifier module: contains methods for handling information output
 for the programmer/user
 for the programmer/user
 """
 """
+
+from __future__ import annotations
+
+from .Logger import Logger
 from .LoggerGlobal import defaultLogger
 from .LoggerGlobal import defaultLogger
 from direct.showbase import PythonUtil
 from direct.showbase import PythonUtil
 from panda3d.core import ConfigVariableBool, NotifyCategory, StreamWriter, Notify
 from panda3d.core import ConfigVariableBool, NotifyCategory, StreamWriter, Notify
 import time
 import time
 import sys
 import sys
+from typing import NoReturn
 
 
 
 
 class NotifierException(Exception):
 class NotifierException(Exception):
@@ -20,13 +25,13 @@ class Notifier:
     # messages instead of writing them to the console.  This is
     # messages instead of writing them to the console.  This is
     # particularly useful for integrating the Python notify system
     # particularly useful for integrating the Python notify system
     # with the C++ notify system.
     # with the C++ notify system.
-    streamWriter = None
+    streamWriter: StreamWriter | None = None
     if ConfigVariableBool('notify-integrate', True):
     if ConfigVariableBool('notify-integrate', True):
         streamWriter = StreamWriter(Notify.out(), False)
         streamWriter = StreamWriter(Notify.out(), False)
 
 
     showTime = ConfigVariableBool('notify-timestamp', False)
     showTime = ConfigVariableBool('notify-timestamp', False)
 
 
-    def __init__(self, name, logger=None):
+    def __init__(self, name: str, logger: Logger | None = None) -> None:
         """
         """
         Parameters:
         Parameters:
             name (str): a string name given to this Notifier instance.
             name (str): a string name given to this Notifier instance.
@@ -42,12 +47,12 @@ class Notifier:
             self.__logger = logger
             self.__logger = logger
 
 
         # Global default levels are initialized here
         # 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
         Call this method on any Notify object to globally change the
         timestamp printed for each line of all Notify objects.
         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))
         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
         Return the time as a string suitable for printing at the
         head of any notify message
         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.
         # 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))
         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.
         Return the time as a string.
         The Only in the name is referring to not showing the date.
         The Only in the name is referring to not showing the date.
         """
         """
         return time.strftime("%H:%M:%S", time.localtime(time.time() + self.serverDelta))
         return time.strftime("%H:%M:%S", time.localtime(time.time() + self.serverDelta))
 
 
-    def __str__(self):
+    def __str__(self) -> str:
         """
         """
         Print handling routine
         Print handling routine
         """
         """
@@ -89,26 +94,26 @@ class Notifier:
                (self.__name, self.__info, self.__warning, self.__debug, self.__logging)
                (self.__name, self.__info, self.__warning, self.__debug, self.__logging)
 
 
     # Severity funcs
     # Severity funcs
-    def setSeverity(self, severity):
+    def setSeverity(self, severity: int) -> None:
         from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
         from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
         if severity >= 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:
         elif severity == NSWarning:
-            self.setWarning(1)
-            self.setInfo(0)
-            self.setDebug(0)
+            self.setWarning(True)
+            self.setInfo(False)
+            self.setDebug(False)
         elif severity == NSInfo:
         elif severity == NSInfo:
-            self.setWarning(1)
-            self.setInfo(1)
-            self.setDebug(0)
+            self.setWarning(True)
+            self.setInfo(True)
+            self.setDebug(False)
         elif severity <= NSDebug:
         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
         from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
         if self.getDebug():
         if self.getDebug():
             return NSDebug
             return NSDebug
@@ -120,7 +125,7 @@ class Notifier:
             return NSError
             return NSError
 
 
     # error funcs
     # 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:
         Raise an exception with given string and optional type:
         Exception: error
         Exception: error
@@ -134,7 +139,7 @@ class Notifier:
         raise exception(errorString)
         raise exception(errorString)
 
 
     # warning funcs
     # warning funcs
-    def warning(self, warningString):
+    def warning(self, warningString: object) -> int:
         """
         """
         Issue the warning message if warn flag is on
         Issue the warning message if warn flag is on
         """
         """
@@ -148,20 +153,20 @@ class Notifier:
             self.__print(string)
             self.__print(string)
         return 1 # to allow assert myNotify.warning("blah")
         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
         Enable/Disable the printing of warning messages
         """
         """
         self.__warning = enable
         self.__warning = enable
 
 
-    def getWarning(self):
+    def getWarning(self) -> bool:
         """
         """
         Return whether the printing of warning messages is on or off
         Return whether the printing of warning messages is on or off
         """
         """
         return self.__warning
         return self.__warning
 
 
     # debug funcs
     # debug funcs
-    def debug(self, debugString):
+    def debug(self, debugString: object) -> int:
         """
         """
         Issue the debug message if debug flag is on
         Issue the debug message if debug flag is on
         """
         """
@@ -175,20 +180,20 @@ class Notifier:
             self.__print(string)
             self.__print(string)
         return 1 # to allow assert myNotify.debug("blah")
         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
         Enable/Disable the printing of debug messages
         """
         """
         self.__debug = enable
         self.__debug = enable
 
 
-    def getDebug(self):
+    def getDebug(self) -> bool:
         """
         """
         Return whether the printing of debug messages is on or off
         Return whether the printing of debug messages is on or off
         """
         """
         return self.__debug
         return self.__debug
 
 
     # info funcs
     # info funcs
-    def info(self, infoString):
+    def info(self, infoString: object) -> int:
         """
         """
         Print the given informational string, if info flag is on
         Print the given informational string, if info flag is on
         """
         """
@@ -202,39 +207,39 @@ class Notifier:
             self.__print(string)
             self.__print(string)
         return 1 # to allow assert myNotify.info("blah")
         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 whether the printing of info messages is on or off
         """
         """
         return self.__info
         return self.__info
 
 
-    def setInfo(self, enable):
+    def setInfo(self, enable: bool) -> None:
         """
         """
         Enable/Disable informational message  printing
         Enable/Disable informational message  printing
         """
         """
         self.__info = enable
         self.__info = enable
 
 
     # log funcs
     # log funcs
-    def __log(self, logEntry):
+    def __log(self, logEntry: str) -> None:
         """
         """
         Determine whether to send informational message to the logger
         Determine whether to send informational message to the logger
         """
         """
         if self.__logging:
         if self.__logging:
             self.__logger.log(logEntry)
             self.__logger.log(logEntry)
 
 
-    def getLogging(self):
+    def getLogging(self) -> bool:
         """
         """
         Return 1 if logging enabled, 0 otherwise
         Return 1 if logging enabled, 0 otherwise
         """
         """
         return self.__logging
         return self.__logging
 
 
-    def setLogging(self, enable):
+    def setLogging(self, enable: bool) -> None:
         """
         """
         Set the logging flag to int (1=on, 0=off)
         Set the logging flag to int (1=on, 0=off)
         """
         """
         self.__logging = enable
         self.__logging = enable
 
 
-    def __print(self, string):
+    def __print(self, string: str) -> None:
         """
         """
         Prints the string to output followed by a newline.
         Prints the string to output followed by a newline.
         """
         """
@@ -251,7 +256,7 @@ class Notifier:
         the function call (with parameters).
         the function call (with parameters).
         """
         """
         #f.f_locals['self'].__init__.im_class.__name__
         #f.f_locals['self'].__init__.im_class.__name__
-        if self.__debug:
+        if __debug__ and self.__debug:
             state = ''
             state = ''
             doId = ''
             doId = ''
             if obj is not None:
             if obj is not None:
@@ -285,13 +290,13 @@ class Notifier:
             self.__print(string)
             self.__print(string)
         return 1 # to allow assert self.notify.debugStateCall(self)
         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
         If this notify is in debug mode, print the time of the
         call followed by the notifier category and
         call followed by the notifier category and
         the function call (with parameters).
         the function call (with parameters).
         """
         """
-        if self.__debug:
+        if __debug__ and self.__debug:
             message = str(debugString)
             message = str(debugString)
             string = ":%s:%s \"%s\" %s"%(
             string = ":%s:%s \"%s\" %s"%(
                 self.getOnlyTime(),
                 self.getOnlyTime(),

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

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

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

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

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

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

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

@@ -66,9 +66,7 @@ class DirectLights(NodePath):
 
 
     def getNameList(self):
     def getNameList(self):
         # Return a sorted list of all lights in the light dict
         # 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):
     def create(self, ltype):
         ltype = ltype.lower()
         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,
     ConfigVariableBool,
     ConfigVariableString,
     ConfigVariableString,
     CSDefault,
     CSDefault,
+    GraphicsWindow,
     NodePath,
     NodePath,
     Point3,
     Point3,
     TextNode,
     TextNode,
@@ -35,19 +36,33 @@ from direct.gui import OnscreenText
 from direct.interval.IntervalGlobal import Func, Sequence
 from direct.interval.IntervalGlobal import Func, Sequence
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.MessengerGlobal import messenger
 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):
 class DirectSession(DirectObject):
 
 
     # post this to the bboard to make sure DIRECT doesn't turn on
     # post this to the bboard to make sure DIRECT doesn't turn on
     DIRECTdisablePost = 'disableDIRECT'
     DIRECTdisablePost = 'disableDIRECT'
 
 
+    cam: NodePath
+    camera: NodePath
+    oobeCamera: NodePath
+
     def __init__(self):
     def __init__(self):
         # Establish a global pointer to the direct object early on
         # Establish a global pointer to the direct object early on
         # so dependant classes can access it in their code
         # 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
         # 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.font = TextNode.getDefaultFont()
         self.fEnabled = 0
         self.fEnabled = 0
         self.fEnabledLight = 0
         self.fEnabledLight = 0
@@ -57,7 +72,7 @@ class DirectSession(DirectObject):
         self.drList = DisplayRegionList()
         self.drList = DisplayRegionList()
         self.iRayList = [x.iRay for x in self.drList]
         self.iRayList = [x.iRay for x in self.drList]
         self.dr = self.drList[0]
         self.dr = self.drList[0]
-        self.win = base.win
+        self.win: GraphicsWindow = base.win
         self.camera = base.camera
         self.camera = base.camera
         self.cam = base.cam
         self.cam = base.cam
         self.camNode = base.camNode
         self.camNode = base.camNode
@@ -70,7 +85,7 @@ class DirectSession(DirectObject):
         self.useObjectHandles()
         self.useObjectHandles()
         self.grid = DirectGrid()
         self.grid = DirectGrid()
         self.grid.disable()
         self.grid.disable()
-        self.lights = DirectLights(base.direct.group)
+        self.lights = DirectLights(self.group)
         # Create some default lights
         # Create some default lights
         self.lights.createDefaultLights()
         self.lights.createDefaultLights()
         # But turn them off
         # But turn them off
@@ -308,13 +323,16 @@ class DirectSession(DirectObject):
         if base.wantTk:
         if base.wantTk:
             from direct.tkpanels import DirectSessionPanel
             from direct.tkpanels import DirectSessionPanel
             self.panel = DirectSessionPanel.DirectSessionPanel(parent = base.tkRoot)
             self.panel = DirectSessionPanel.DirectSessionPanel(parent = base.tkRoot)
-        try:
+
+        clusterMode: str
+        if hasattr(builtins, 'clusterMode'):
             # Has the clusterMode been set externally (i.e. via the
             # Has the clusterMode been set externally (i.e. via the
             # bootstrap application?
             # bootstrap application?
-            self.clusterMode = clusterMode
-        except NameError:
+            clusterMode = builtins.clusterMode
+        else:
             # Has the clusterMode been set via a config variable?
             # 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':
         if self.clusterMode == 'client':
             from direct.cluster.ClusterClient import createClusterClient
             from direct.cluster.ClusterClient import createClusterClient
@@ -325,7 +343,7 @@ class DirectSession(DirectObject):
         else:
         else:
             from direct.cluster.ClusterClient import DummyClusterClient
             from direct.cluster.ClusterClient import DummyClusterClient
             self.cluster = DummyClusterClient()
             self.cluster = DummyClusterClient()
-        __builtins__['cluster'] = self.cluster
+        setattr(builtins, 'cluster', self.cluster)
 
 
     def addPassThroughKey(self,key):
     def addPassThroughKey(self,key):
 
 
@@ -412,10 +430,10 @@ class DirectSession(DirectObject):
 
 
         if self.oobeMode:
         if self.oobeMode:
             # Position a target point to lerp the oobe camera to
             # 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(
             ival = self.oobeCamera.posHprInterval(
                 2.0, pos = Point3(0), hpr = Vec3(0),
                 2.0, pos = Point3(0), hpr = Vec3(0),
-                other = base.direct.cameraControl.camManipRef,
+                other = self.cameraControl.camManipRef,
                 blendType = 'easeInOut')
                 blendType = 'easeInOut')
             ival = Sequence(ival, Func(self.endOOBE), name = 'oobeTransition')
             ival = Sequence(ival, Func(self.endOOBE), name = 'oobeTransition')
             ival.start()
             ival.start()
@@ -432,20 +450,20 @@ class DirectSession(DirectObject):
             # Put camera under new oobe camera
             # Put camera under new oobe camera
             self.cam.reparentTo(self.oobeCamera)
             self.cam.reparentTo(self.oobeCamera)
             # Position a target point to lerp the oobe camera to
             # 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))
                 self.trueCamera, Vec3(-2, -20, 5))
-            base.direct.cameraControl.camManipRef.lookAt(self.trueCamera)
+            self.cameraControl.camManipRef.lookAt(self.trueCamera)
             ival = self.oobeCamera.posHprInterval(
             ival = self.oobeCamera.posHprInterval(
                 2.0, pos = Point3(0), hpr = Vec3(0),
                 2.0, pos = Point3(0), hpr = Vec3(0),
-                other = base.direct.cameraControl.camManipRef,
+                other = self.cameraControl.camManipRef,
                 blendType = 'easeInOut')
                 blendType = 'easeInOut')
             ival = Sequence(ival, Func(self.beginOOBE), name = 'oobeTransition')
             ival = Sequence(ival, Func(self.beginOOBE), name = 'oobeTransition')
             ival.start()
             ival.start()
 
 
     def beginOOBE(self):
     def beginOOBE(self):
         # Make sure we've reached our final destination
         # 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
         self.oobeMode = 1
 
 
     def endOOBE(self):
     def endOOBE(self):
@@ -453,7 +471,7 @@ class DirectSession(DirectObject):
         self.oobeCamera.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
         self.oobeCamera.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
         # Disable OOBE mode.
         # Disable OOBE mode.
         self.cam.reparentTo(self.trueCamera)
         self.cam.reparentTo(self.trueCamera)
-        base.direct.camera = self.trueCamera
+        self.camera = self.trueCamera
         # Get rid of ancillary node paths
         # Get rid of ancillary node paths
         self.oobeVis.reparentTo(hidden)
         self.oobeVis.reparentTo(hidden)
         self.oobeCamera.reparentTo(hidden)
         self.oobeCamera.reparentTo(hidden)
@@ -501,7 +519,7 @@ class DirectSession(DirectObject):
     def inputHandler(self, input):
     def inputHandler(self, input):
         if not hasattr(self, 'oobeMode') or self.oobeMode == 0:
         if not hasattr(self, 'oobeMode') or self.oobeMode == 0:
             # [gjeon] change current camera dr, iRay, mouseWatcher accordingly to support multiple windows
             # [gjeon] change current camera dr, iRay, mouseWatcher accordingly to support multiple windows
-            if base.direct.manipulationControl.fMultiView:
+            if self.manipulationControl.fMultiView:
                 # handling orphan events
                 # handling orphan events
                 if self.fMouse1 and 'mouse1' not in input or\
                 if self.fMouse1 and 'mouse1' not in input or\
                    self.fMouse2 and 'mouse2' not in input or\
                    self.fMouse2 and 'mouse2' not in input or\
@@ -518,7 +536,7 @@ class DirectSession(DirectObject):
                     return
                     return
 
 
                 if (self.fMouse1 or self.fMouse2 or self.fMouse3) and\
                 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'):
                    input.endswith('-up'):
                     # to handle orphan events
                     # to handle orphan events
                     return
                     return
@@ -551,14 +569,14 @@ class DirectSession(DirectObject):
                     self.cam = NodePath(winCtrl.camNode)
                     self.cam = NodePath(winCtrl.camNode)
                     self.camNode = winCtrl.camNode
                     self.camNode = winCtrl.camNode
                     if hasattr(winCtrl, 'grid'):
                     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.mouseWatcher = winCtrl.mouseWatcher
                     base.mouseWatcherNode = winCtrl.mouseWatcher.node()
                     base.mouseWatcherNode = winCtrl.mouseWatcher.node()
-                    base.direct.dr.mouseUpdate()
+                    self.dr.mouseUpdate()
                     DG.LE_showInOneCam(self.selectedNPReadout, self.camera.getName())
                     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
                 input = input[8:] # get rid of camera prefix
                 if self.fAlt and 'alt' not in input and not input.endswith('-up'):
                 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'):
         if not taskMgr.hasTaskNamed('resizeObjectHandles'):
             dnp = self.selected.last
             dnp = self.selected.last
             if dnp:
             if dnp:
-                direct = base.direct
-
                 if self.manipulationControl.fMultiView:
                 if self.manipulationControl.fMultiView:
                     for i in range(3):
                     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)
                         self.manipulationControl.widgetList[i].setDirectScalingFactor(sf)
 
 
                     nodeCamDist = Vec3(dnp.getPos(base.camList[3])).length()
                     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)
                     self.manipulationControl.widgetList[3].setDirectScalingFactor(sf)
 
 
                 else:
                 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)
                     self.widget.setDirectScalingFactor(sf)
         return Task.cont
         return Task.cont
 
 
@@ -755,7 +771,7 @@ class DirectSession(DirectObject):
             messenger.send('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', [dnp, fMultiSelect, fSelectTag, fLEPane])
             messenger.send('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', [dnp, fMultiSelect, fSelectTag, fLEPane])
 
 
     def followSelectedNodePathTask(self, state):
     def followSelectedNodePathTask(self, state):
-        mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
+        mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(base.render)
         decomposeMatrix(mCoa2Render,
         decomposeMatrix(mCoa2Render,
                         self.scale, self.hpr, self.pos,
                         self.scale, self.hpr, self.pos,
                         CSDefault)
                         CSDefault)
@@ -874,7 +890,7 @@ class DirectSession(DirectObject):
         if nodePath == 'None Given':
         if nodePath == 'None Given':
             # If nothing specified, try selected node path
             # If nothing specified, try selected node path
             nodePath = self.selected.last
             nodePath = self.selected.last
-        base.direct.select(nodePath)
+        self.select(nodePath)
 
 
         def fitTask(state, self = self):
         def fitTask(state, self = self):
             self.cameraControl.fitOnWidget()
             self.cameraControl.fitOnWidget()
@@ -1061,7 +1077,7 @@ class DirectSession(DirectObject):
 
 
     def useObjectHandles(self):
     def useObjectHandles(self):
         self.widget = self.manipulationControl.objectHandles
         self.widget = self.manipulationControl.objectHandles
-        self.widget.reparentTo(base.direct.group)
+        self.widget.reparentTo(self.group)
 
 
     def hideSelectedNPReadout(self):
     def hideSelectedNPReadout(self):
         self.selectedNPReadout.reparentTo(hidden)
         self.selectedNPReadout.reparentTo(hidden)
@@ -1167,14 +1183,14 @@ class DisplayRegionContext(DirectObject):
             self.camLens.setFov(hfov, vfov)
             self.camLens.setFov(hfov, vfov)
 
 
     def getWidth(self):
     def getWidth(self):
-        prop = base.direct.win.getProperties()
+        prop = ShowBaseGlobal.direct.win.getProperties()
         if prop.hasSize():
         if prop.hasSize():
             return prop.getXSize()
             return prop.getXSize()
         else:
         else:
             return 640
             return 640
 
 
     def getHeight(self):
     def getHeight(self):
-        prop = base.direct.win.getProperties()
+        prop = ShowBaseGlobal.direct.win.getProperties()
         if prop.hasSize():
         if prop.hasSize():
             return prop.getYSize()
             return prop.getYSize()
         else:
         else:
@@ -1208,9 +1224,10 @@ class DisplayRegionContext(DirectObject):
 
 
         # Values for this frame
         # Values for this frame
         # This ranges from -1 to 1
         # 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.mouseX = (self.mouseX-self.originX)*self.scaleX
             self.mouseY = (self.mouseY-self.originY)*self.scaleY
             self.mouseY = (self.mouseY-self.originY)*self.scaleY
         # Delta percent of window the mouse moved
         # Delta percent of window the mouse moved
@@ -1262,6 +1279,9 @@ class DisplayRegionList(DirectObject):
     def __len__(self):
     def __len__(self):
         return len(self.displayRegionList)
         return len(self.displayRegionList)
 
 
+    def __iter__(self):
+        return iter(self.displayRegionList)
+
     def updateContext(self):
     def updateContext(self):
         self.contextTask(None)
         self.contextTask(None)
 
 
@@ -1296,7 +1316,7 @@ class DisplayRegionList(DirectObject):
 
 
     def getCurrentDr(self):
     def getCurrentDr(self):
         if not self.tryToGetCurrentDr:
         if not self.tryToGetCurrentDr:
-            return base.direct.dr
+            return ShowBaseGlobal.direct.dr
         for dr in self.displayRegionList:
         for dr in self.displayRegionList:
             if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
             if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
                 dr.mouseY >= -1.0 and dr.mouseY <= 1.0):
                 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 sys
 import os
 import os
 import marshal
 import marshal
-import imp
 import platform
 import platform
 import struct
 import struct
 import io
 import io
@@ -13,6 +12,7 @@ import sysconfig
 import zipfile
 import zipfile
 import importlib
 import importlib
 import warnings
 import warnings
+from importlib import machinery
 
 
 from . import pefile
 from . import pefile
 
 
@@ -24,6 +24,16 @@ except ImportError:
 
 
 from panda3d.core import Filename, Multifile, PandaSystem, StringStream
 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
 # 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.
 # debug build, and we have to build the module with debug options.
 # This is only relevant on Windows.
 # 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
 # NB. if encodings are removed, be sure to remove them from the shortcut in
 # deploy-stub.c.
 # deploy-stub.c.
 startupModules = [
 startupModules = [
-    'imp', 'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
+    'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
     'importlib.util',
     'importlib.util',
 ]
 ]
 
 
@@ -262,10 +272,15 @@ class CompilationEnvironment:
                 self.arch = '-arch x86_64'
                 self.arch = '-arch x86_64'
             elif proc in ('arm64', 'aarch64'):
             elif proc in ('arm64', 'aarch64'):
                 self.arch = '-arch arm64'
                 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:
         else:
             # Unix
             # Unix
@@ -897,12 +912,11 @@ class Freezer:
 
 
         # Suffix/extension for Python C extension modules
         # Suffix/extension for Python C extension modules
         if self.platform == PandaSystem.getPlatform():
         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:
         else:
             suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
             suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
 
 
@@ -1146,15 +1160,12 @@ class Freezer:
 
 
         # Walk through the list in sorted order, so we reach parents
         # Walk through the list in sorted order, so we reach parents
         # before children.
         # before children.
-        names = list(self.modules.items())
-        names.sort()
-
         excludeDict = {}
         excludeDict = {}
         implicitParentDict = {}
         implicitParentDict = {}
         includes = []
         includes = []
         autoIncludes = []
         autoIncludes = []
         origToNewName = {}
         origToNewName = {}
-        for newName, mdef in names:
+        for newName, mdef in sorted(self.modules.items()):
             moduleName = mdef.moduleName
             moduleName = mdef.moduleName
             origToNewName[moduleName] = newName
             origToNewName[moduleName] = newName
             if mdef.implicit and '.' in newName:
             if mdef.implicit and '.' in newName:
@@ -1319,11 +1330,11 @@ class Freezer:
             ext = mdef.filename.getExtension()
             ext = mdef.filename.getExtension()
             if ext == 'pyc' or ext == 'pyo':
             if ext == 'pyc' or ext == 'pyo':
                 fp = open(pathname, 'rb')
                 fp = open(pathname, 'rb')
-                stuff = ("", "rb", imp.PY_COMPILED)
+                stuff = ("", "rb", _PY_COMPILED)
                 self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
                 self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
             else:
             else:
-                stuff = ("", "rb", imp.PY_SOURCE)
-                if mdef.text:
+                stuff = ("", "rb", _PY_SOURCE)
+                if mdef.text is not None:
                     fp = io.StringIO(mdef.text)
                     fp = io.StringIO(mdef.text)
                 else:
                 else:
                     fp = open(pathname, 'rb')
                     fp = open(pathname, 'rb')
@@ -1418,7 +1429,7 @@ class Freezer:
 
 
     def __addPyc(self, multifile, filename, code, compressionLevel):
     def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
         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)
             data += marshal.dumps(code)
 
 
             stream = StringStream(data)
             stream = StringStream(data)
@@ -1608,7 +1619,7 @@ class Freezer:
             # trouble importing it as a builtin module.  Synthesize a frozen
             # trouble importing it as a builtin module.  Synthesize a frozen
             # module that loads it as builtin.
             # module that loads it as builtin.
             if '.' in moduleName and self.linkExtensionModules:
             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)
                 code = marshal.dumps(code)
                 mangledName = self.mangleName(moduleName)
                 mangledName = self.mangleName(moduleName)
                 moduleDefs.append(self.makeModuleDef(mangledName, code))
                 moduleDefs.append(self.makeModuleDef(mangledName, code))
@@ -1890,9 +1901,19 @@ class Freezer:
             if '.' in moduleName and not self.platform.startswith('android'):
             if '.' in moduleName and not self.platform.startswith('android'):
                 if self.platform.startswith("macosx") and not use_console:
                 if self.platform.startswith("macosx") and not use_console:
                     # We write the Frameworks directory to sys.path[0].
                     # 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:
                 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 = compile(code, moduleName, 'exec', optimize=self.optimize)
                 code = marshal.dumps(code)
                 code = marshal.dumps(code)
                 moduleList.append((moduleName, len(pool), len(code)))
                 moduleList.append((moduleName, len(pool), len(code)))
@@ -1927,6 +1948,9 @@ class Freezer:
         if self.platform.startswith('win'):
         if self.platform.startswith('win'):
             # We don't use mmap on Windows.  Align just for good measure.
             # We don't use mmap on Windows.  Align just for good measure.
             blob_align = 32
             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:
         else:
             # Align to page size, so that it can be mmapped.
             # Align to page size, so that it can be mmapped.
             blob_align = 4096
             blob_align = 4096
@@ -2403,9 +2427,6 @@ class Freezer:
         return True
         return True
 
 
 
 
-_PKG_NAMESPACE_DIRECTORY = object()
-
-
 class PandaModuleFinder(modulefinder.ModuleFinder):
 class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
     def __init__(self, *args, **kw):
     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.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)
         self.optimize = kw.pop('optimize', -1)
 
 
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
@@ -2566,7 +2592,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
         suffix, mode, type = file_info
         suffix, mode, type = file_info
         self.msgin(2, "load_module", fqname, fp and "fp", pathname)
         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)
             m = self.load_package(fqname, pathname)
             self.msgout(2, "load_module ->", m)
             self.msgout(2, "load_module ->", m)
             return m
             return m
@@ -2577,7 +2603,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             m.__path__ = pathname
             m.__path__ = pathname
             return m
             return m
 
 
-        if type == imp.PY_SOURCE:
+        if type == _PY_SOURCE:
             if fqname in overrideModules:
             if fqname in overrideModules:
                 # This module has a custom override.
                 # This module has a custom override.
                 code = overrideModules[fqname]
                 code = overrideModules[fqname]
@@ -2601,7 +2627,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
             code += b'\n' if isinstance(code, bytes) else '\n'
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec', optimize=self.optimize)
             co = compile(code, pathname, 'exec', optimize=self.optimize)
-        elif type == imp.PY_COMPILED:
+        elif type == _PY_COMPILED:
             if sys.version_info >= (3, 7):
             if sys.version_info >= (3, 7):
                 try:
                 try:
                     data = fp.read()
                     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 we have a custom override for this module, we know we have it.
         if fullname in overrideModules:
         if fullname in overrideModules:
-            return (None, '', ('.py', 'r', imp.PY_SOURCE))
+            return (None, '', ('.py', 'r', _PY_SOURCE))
 
 
         # It's built into the interpreter.
         # It's built into the interpreter.
         if fullname in self.builtin_module_names:
         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 no search path is given, look for a built-in module.
         if path is None:
         if path is None:
@@ -2809,7 +2835,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             for suffix, mode, _ in self.suffixes:
             for suffix, mode, _ in self.suffixes:
                 init = os.path.join(basename, '__init__' + suffix)
                 init = os.path.join(basename, '__init__' + suffix)
                 if self._open_file(init, mode):
                 if self._open_file(init, mode):
-                    return (None, basename, ('', '', imp.PKG_DIRECTORY))
+                    return (None, basename, ('', '', _PKG_DIRECTORY))
 
 
             # This may be a namespace package.
             # This may be a namespace package.
             if self._dir_exists(basename):
             if self._dir_exists(basename):
@@ -2821,7 +2847,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             # Only if we're not looking on a particular path, though.
             # Only if we're not looking on a particular path, though.
             if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
             if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
                 # It's a frozen module.
                 # 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
         # If we found folders on the path with this module name without an
         # __init__.py file, we should consider this a namespace package.
         # __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 shutil
 import stat
 import stat
 import struct
 import struct
-import imp
 import string
 import string
 import tempfile
 import tempfile
 
 
@@ -55,10 +54,16 @@ def _register_python_loaders():
 
 
     _register_python_loaders.done = True
     _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)
         registry.register_deferred_type(entry_point)
 
 
 
 
@@ -466,8 +471,10 @@ class build_apps(setuptools.Command):
         if self.bam_model_extensions:
         if self.bam_model_extensions:
             for ext in self.bam_model_extensions:
             for ext in self.bam_model_extensions:
                 ext = '.' + ext.lstrip('.')
                 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
                 self.file_handlers[ext] = _model_to_bam
 
 
         tmp = self.default_file_handlers.copy()
         tmp = self.default_file_handlers.copy()
@@ -717,6 +724,7 @@ class build_apps(setuptools.Command):
             'CFBundlePackageType': 'APPL',
             'CFBundlePackageType': 'APPL',
             'CFBundleSignature': '', #TODO
             'CFBundleSignature': '', #TODO
             'CFBundleExecutable': self.macos_main_app,
             'CFBundleExecutable': self.macos_main_app,
+            'NSHighResolutionCapable': 'True',
         }
         }
 
 
         icon = self.icon_objects.get(
         icon = self.icon_objects.get(
@@ -859,16 +867,12 @@ class build_apps(setuptools.Command):
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             etcdir = os.path.join(libdir, '..', 'etc')
             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'):
                 if fn.lower().endswith('.prc'):
                     with open(os.path.join(etcdir, fn)) as f:
                     with open(os.path.join(etcdir, fn)) as f:
                         prcstring += f.read()
                         prcstring += f.read()
         else:
         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:
                 with p3dwhl.open(fn) as f:
                     prcstring += f.read().decode('utf8')
                     prcstring += f.read().decode('utf8')
 
 
@@ -1071,7 +1075,7 @@ class build_apps(setuptools.Command):
             freezer_extras.update(freezer.extras)
             freezer_extras.update(freezer.extras)
             freezer_modules.update(freezer.getAllModuleNames())
             freezer_modules.update(freezer.getAllModuleNames())
             for suffix in freezer.mf.suffixes:
             for suffix in freezer.mf.suffixes:
-                if suffix[2] == imp.C_EXTENSION:
+                if suffix[2] == 3: # imp.C_EXTENSION:
                     ext_suffixes.add(suffix[0])
                     ext_suffixes.add(suffix[0])
 
 
         for appname, scriptname in self.gui_apps.items():
         for appname, scriptname in self.gui_apps.items():
@@ -1702,7 +1706,7 @@ class bdist_apps(setuptools.Command):
             setattr(self, opt, None)
             setattr(self, opt, None)
 
 
     def finalize_options(self):
     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
         # We need to massage the inputs a bit in case they came from a
         # setup.cfg file.
         # setup.cfg file.
@@ -1716,11 +1720,17 @@ class bdist_apps(setuptools.Command):
             self.signing_certificate = os.path.abspath(self.signing_certificate)
             self.signing_certificate = os.path.abspath(self.signing_certificate)
             self.signing_private_key = os.path.abspath(self.signing_private_key)
             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 = self.DEFAULT_INSTALLER_FUNCS.copy()
         tmp.update(self.installer_functions)
         tmp.update(self.installer_functions)
         tmp.update({
         tmp.update({
             entrypoint.name: entrypoint.load()
             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
         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 otp.ai.AIBaseGlobal import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.DirectObject import DirectObject
 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
     will be called again when the new self.neededObjects is complete.  You
     may repeat this as necessary.
     may repeat this as necessary.
     """
     """
-    _asyncRequests = {}
+    _asyncRequests: dict[int, AsyncRequest] = {}
 
 
     notify = DirectNotifyGlobal.directNotify.newCategory('AsyncRequest')
     notify = DirectNotifyGlobal.directNotify.newCategory('AsyncRequest')
 
 

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

@@ -76,41 +76,3 @@ if __debug__:
         def flush(self):
         def flush(self):
             CachedDOData.flush(self)
             CachedDOData.flush(self)
             self._flushed = True
             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
         # override and destroy the cached data
         # cached data is typically created by the DistributedObject and destroyed here
         # cached data is typically created by the DistributedObject and destroyed here
         pass
         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")
                     f"Asked to update non-existent DistObj {doId} and failed to find it")
 
 
     def __doUpdateOwner(self, doId, di):
     def __doUpdateOwner(self, doId, di):
+        if not self.hasOwnerView():
+            return False
+
         ovObj = self.doId2ownerView.get(doId)
         ovObj = self.doId2ownerView.get(doId)
         if ovObj:
         if ovObj:
             odg = Datagram(di.getDatagram())
             odg = Datagram(di.getDatagram())

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

@@ -23,7 +23,7 @@ GRID_Z_OFFSET = 0.0
 
 
 class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
 class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
     notify = directNotify.newCategory("DistributedCartesianGrid")
     notify = directNotify.newCategory("DistributedCartesianGrid")
-    notify.setDebug(0)
+    notify.setDebug(False)
 
 
     VisualizeGrid = ConfigVariableBool("visualize-cartesian-grid", 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
         # setLocation destroys self._zoneData if we move away to
         # a different zone
         # a different zone
         if self._zoneData is None:
         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)
             self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId)
         return self._zoneData
         return self._zoneData
 
 
@@ -489,7 +489,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # simultaneously on different lists of avatars, although they
         # should have different names.
         # should have different names.
 
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
         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
         # simultaneously on different lists of avatars, although they
         # should have different names.
         # should have different names.
 
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

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

@@ -149,10 +149,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count.setdefault(className, 0)
             class2count[className] += 1
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
         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()
             count2classes[count].sort()
             for name in count2classes[count]:
             for name in count2classes[count]:
                 print('%s %s' % (count, name))
                 print('%s %s' % (count, name))
@@ -166,10 +163,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count.setdefault(className, 0)
             class2count[className] += 1
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
         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()
             count2classes[count].sort()
             for name in count2classes[count]:
             for name in count2classes[count]:
                 # print '%s %s' % (count, name)
                 # 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.
 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 panda3d.core import ConfigVariableBool
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
@@ -98,9 +100,9 @@ class DoInterestManager(DirectObject.DirectObject):
     _ContextIdSerialNum = 100
     _ContextIdSerialNum = 100
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
 
 
-    _interests = {}
+    _interests: dict[int, InterestState] = {}
     if __debug__:
     if __debug__:
-        _debug_interestHistory = []
+        _debug_interestHistory: list[tuple] = []
         _debug_maxDescriptionLen = 40
         _debug_maxDescriptionLen = 40
 
 
     _SerialGen = SerialNumGen()
     _SerialGen = SerialNumGen()
@@ -512,8 +514,7 @@ class DoInterestManager(DirectObject.DirectObject):
         datagram = PyDatagram()
         datagram = PyDatagram()
         # Add message type
         # Add message type
         if isinstance(zoneIdList, list):
         if isinstance(zoneIdList, list):
-            vzl = list(zoneIdList)
-            vzl.sort()
+            vzl = sorted(zoneIdList)
             uniqueElements(vzl)
             uniqueElements(vzl)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint32(contextId)
             datagram.addUint32(contextId)

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

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

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

@@ -1,143 +1,148 @@
 """MsgTypes module: contains distributed object message types"""
 """MsgTypes module: contains distributed object message types"""
 
 
+from __future__ import annotations
+
 from direct.showbase.PythonUtil import invertDictLossless
 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
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
 MsgId2Names = invertDictLossless(MsgName2Id)
@@ -146,7 +151,7 @@ MsgId2Names = invertDictLossless(MsgName2Id)
 globals().update(MsgName2Id)
 globals().update(MsgName2Id)
 
 
 # These messages are ignored when the client is headed to the quiet zone
 # 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
     # We mustn't ignore updates, because some updates for localToon
     # are always important.
     # are always important.

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

@@ -5,25 +5,22 @@ implementation. """
 
 
 from direct.showbase.PythonUtil import invertDictLossless
 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
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
 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
       // while we call the update method--otherwise, the update method might
       // get into trouble if it tried to delete the object from the doId2do
       // get into trouble if it tried to delete the object from the doId2do
       // map.
       // 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 (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #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
         // raised while we call the update method--otherwise, the update
         // method might get into trouble if it tried to delete the object from
         // method might get into trouble if it tried to delete the object from
         // the doId2do map.
         // 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
         // make a copy of the datagram iterator so that we can use the main
         // iterator for the non-owner update
         // iterator for the non-owner update
         DatagramIterator _odi(_di);
         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 (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #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
         // while we call the update method--otherwise, the update method might
         // get into trouble if it tried to delete the object from the doId2do
         // get into trouble if it tried to delete the object from the doId2do
         // map.
         // 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 (PyErr_Occurred()) {
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #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
 del iPosHprScale
 #####################################################################
 #####################################################################
 def place(self):
 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
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     # it up as a dependency when building a .p3d package.
     import importlib
     import importlib
@@ -446,7 +447,8 @@ Dtool_funcToMethod(place, NodePath)
 del place
 del place
 #####################################################################
 #####################################################################
 def explore(self):
 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
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     # it up as a dependency when building a .p3d package.
     import importlib
     import importlib
@@ -457,7 +459,8 @@ Dtool_funcToMethod(explore, NodePath)
 del explore
 del explore
 #####################################################################
 #####################################################################
 def rgbPanel(self, cb = None):
 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
     # Don't use a regular import, to prevent ModuleFinder from picking
     # it up as a dependency when building a .p3d package.
     # it up as a dependency when building a .p3d package.
     import importlib
     import importlib
@@ -468,6 +471,8 @@ Dtool_funcToMethod(rgbPanel, NodePath)
 del rgbPanel
 del rgbPanel
 #####################################################################
 #####################################################################
 def select(self):
 def select(self):
+    from direct.showbase import ShowBaseGlobal
+    base = ShowBaseGlobal.base
     base.startDirect(fWantTk = 0)
     base.startDirect(fWantTk = 0)
     base.direct.select(self)
     base.direct.select(self)
 
 
@@ -475,6 +480,8 @@ Dtool_funcToMethod(select, NodePath)
 del select
 del select
 #####################################################################
 #####################################################################
 def deselect(self):
 def deselect(self):
+    from direct.showbase import ShowBaseGlobal
+    base = ShowBaseGlobal.base
     base.startDirect(fWantTk = 0)
     base.startDirect(fWantTk = 0)
     base.direct.deselect(self)
     base.direct.deselect(self)
 
 
@@ -676,7 +683,8 @@ def flattenMultitex(self, stateFrom = None, target = None,
     mr.setAllowTexMat(allowTexMat)
     mr.setAllowTexMat(allowTexMat)
 
 
     if win is None:
     if win is None:
-        win = base.win
+        from direct.showbase import ShowBaseGlobal
+        win = ShowBaseGlobal.base.win
 
 
     if stateFrom is None:
     if stateFrom is None:
         mr.scan(self)
         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.
     existing code.  New code should use the :mod:`.FSM` module instead.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['ClassicFSM']
 __all__ = ['ClassicFSM']
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -13,7 +15,7 @@ from direct.showbase.MessengerGlobal import messenger
 import weakref
 import weakref
 
 
 if __debug__:
 if __debug__:
-    _debugFsms = {}
+    _debugFsms: dict[str, weakref.ref] = {}
 
 
     def printDebugFsmList():
     def printDebugFsmList():
         for k in sorted(_debugFsms.keys()):
         for k in sorted(_debugFsms.keys()):

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

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

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

@@ -1,6 +1,8 @@
 # classes for event-driven programming
 # classes for event-driven programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 
 
+from __future__ import annotations
+
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
            'EventArgument', ]
            'EventArgument', ]
 
 
@@ -46,11 +48,6 @@ class PushesStateChanges:
         for subscriber in self._subscribers:
         for subscriber in self._subscribers:
             subscriber._recvStatePush(self)
             subscriber._recvStatePush(self)
 
 
-if __debug__:
-    psc = PushesStateChanges(0)
-    assert psc.getState() == 0
-    psc.destroy()
-    del psc
 
 
 class ReceivesStateChanges:
 class ReceivesStateChanges:
     # base class for objects that subscribe to state changes from PushesStateChanges objects
     # base class for objects that subscribe to state changes from PushesStateChanges objects
@@ -83,10 +80,6 @@ class ReceivesStateChanges:
     def _recvStatePush(self, source):
     def _recvStatePush(self, source):
         pass
         pass
 
 
-if __debug__:
-    rsc = ReceivesStateChanges(None)
-    rsc.destroy()
-    del rsc
 
 
 class StateVar(PushesStateChanges):
 class StateVar(PushesStateChanges):
     # coder-friendly object that allows values to be set on it and pushes those values
     # 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):
     def get(self):
         return PushesStateChanges.getState(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):
 class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
     # base class that can be used to create a state-change notification chain
     # 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
         # got a state push, apply new state to self
         self._handlePotentialStateChange(source._value)
         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:
 class ReceivesMultipleStateChanges:
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
@@ -179,15 +140,6 @@ class ReceivesMultipleStateChanges:
     def _recvMultiStatePush(self, key, source):
     def _recvMultiStatePush(self, key, source):
         pass
         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):
 class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
     # calls func with provided args whenever arguments' state changes
     # calls func with provided args whenever arguments' state changes
@@ -249,47 +201,6 @@ class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
             self._func(*self._bakedArgs, **self._bakedKargs)
             self._func(*self._bakedArgs, **self._bakedKargs)
             PushesStateChanges._handleStateChange(self)
             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):
 class EnterExit(StateChangeNode):
     # call enterFunc when our state becomes true, exitFunc when it becomes false
     # call enterFunc when our state becomes true, exitFunc when it becomes false
@@ -314,33 +225,6 @@ class EnterExit(StateChangeNode):
             self._exitFunc()
             self._exitFunc()
         StateChangeNode._handleStateChange(self)
         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):
 class Pulse(PushesStateChanges):
     # changes state to True then immediately to False whenever sendPulse is called
     # changes state to True then immediately to False whenever sendPulse is called
@@ -351,25 +235,6 @@ class Pulse(PushesStateChanges):
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(False)
         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):
 class EventPulse(Pulse, DirectObject):
     # sends a True-False "pulse" whenever a specific messenger message is sent
     # sends a True-False "pulse" whenever a specific messenger message is sent
@@ -381,6 +246,7 @@ class EventPulse(Pulse, DirectObject):
         self.ignoreAll()
         self.ignoreAll()
         Pulse.destroy(self)
         Pulse.destroy(self)
 
 
+
 class EventArgument(PushesStateChanges, DirectObject):
 class EventArgument(PushesStateChanges, DirectObject):
     # tracks a particular argument to a particular messenger event
     # tracks a particular argument to a particular messenger event
     def __init__(self, event, index=0):
     def __init__(self, event, index=0):
@@ -396,6 +262,7 @@ class EventArgument(PushesStateChanges, DirectObject):
     def _handleEvent(self, *args):
     def _handleEvent(self, *args):
         self._handlePotentialStateChange(args[self._index])
         self._handlePotentialStateChange(args[self._index])
 
 
+
 class AttrSetter(StateChangeNode):
 class AttrSetter(StateChangeNode):
     def __init__(self, source, object, attrName):
     def __init__(self, source, object, attrName):
         self._object = object
         self._object = object
@@ -406,19 +273,3 @@ class AttrSetter(StateChangeNode):
     def _handleStateChange(self):
     def _handleStateChange(self):
         setattr(self._object, self._attrName, self._value)
         setattr(self._object, self._attrName, self._value)
         StateChangeNode._handleStateChange(self)
         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.
 in-depth explanation and an example of how to use this class.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = [
 __all__ = [
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
@@ -45,7 +47,7 @@ def cleanupDialog(uniqueName):
 
 
 class DirectDialog(DirectFrame):
 class DirectDialog(DirectFrame):
 
 
-    AllDialogs = {}
+    AllDialogs: dict[str, DirectDialog] = {}
     PanelIndex = 0
     PanelIndex = 0
 
 
     def __init__(self, parent=None, **kw):
     def __init__(self, parent=None, **kw):

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

@@ -28,7 +28,7 @@ class DirectEntry(DirectFrame):
     to keyboard buttons
     to keyboard buttons
     """
     """
 
 
-    directWtext = ConfigVariableBool('direct-wtext', 1)
+    directWtext = ConfigVariableBool('direct-wtext', True)
 
 
     AllowCapNamePrefixes = ("Al", "Ap", "Ben", "De", "Del", "Della", "Delle", "Der", "Di", "Du",
     AllowCapNamePrefixes = ("Al", "Ap", "Ben", "De", "Del", "Della", "Delle", "Der", "Di", "Du",
                             "El", "Fitz", "La", "Las", "Le", "Les", "Lo", "Los",
                             "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.
     see if any keywords are left unused.  If so, an error is raised.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
 
 
@@ -227,7 +229,12 @@ class DirectGuiBase(DirectObject.DirectObject):
                         del keywords[name]
                         del keywords[name]
                     else:
                     else:
                         # Use optionDefs value
                         # 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:
                 elif optionInfo[name][FUNCTION] is None:
                     # Only override function if not defined by derived class
                     # Only override function if not defined by derived class
                     optionInfo[name][FUNCTION] = function
                     optionInfo[name][FUNCTION] = function
@@ -616,9 +623,7 @@ class DirectGuiBase(DirectObject.DirectObject):
 
 
     def components(self):
     def components(self):
         # Return a list of all components.
         # Return a list of all components.
-        names = list(self.__componentInfo.keys())
-        names.sort()
-        return names
+        return sorted(self.__componentInfo)
 
 
     def hascomponent(self, component):
     def hascomponent(self, component):
         return component in self.__componentInfo
         return component in self.__componentInfo
@@ -682,7 +687,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     else:
     else:
         inactiveInitState = DGG.DISABLED
         inactiveInitState = DGG.DISABLED
 
 
-    guiDict = {}
+    guiDict: dict[str, DirectGuiWidget] = {}
 
 
     def __init__(self, parent = None, **kw):
     def __init__(self, parent = None, **kw):
         # Direct gui widgets are node paths
         # 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
 that can be used during widget construction
 """
 """
 
 
-__all__ = []
+__all__ = ()
 
 
 from panda3d.core import (
 from panda3d.core import (
     KeyboardButton,
     KeyboardButton,

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

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

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

@@ -1,5 +1,7 @@
 """FunctionInterval module: contains the FunctionInterval class"""
 """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']
 __all__ = ['FunctionInterval', 'EventInterval', 'AcceptInterval', 'IgnoreInterval', 'ParentInterval', 'WrtParentInterval', 'PosInterval', 'HprInterval', 'ScaleInterval', 'PosHprInterval', 'HprScaleInterval', 'PosHprScaleInterval', 'Func', 'Wait']
 
 
 from panda3d.direct import WaitInterval
 from panda3d.direct import WaitInterval
@@ -8,12 +10,6 @@ from direct.directnotify.DirectNotifyGlobal import directNotify
 from . import Interval
 from . import Interval
 
 
 
 
-#############################################################
-###                                                       ###
-### See examples of function intervals in IntervalTest.py ###
-###                                                       ###
-#############################################################
-
 class FunctionInterval(Interval.Interval):
 class FunctionInterval(Interval.Interval):
     # Name counter
     # Name counter
     functionIntervalNum = 1
     functionIntervalNum = 1
@@ -23,7 +19,7 @@ class FunctionInterval(Interval.Interval):
     # should not cause any leaks.
     # should not cause any leaks.
     if __debug__:
     if __debug__:
         import weakref
         import weakref
-        FunctionIntervals = weakref.WeakKeyDictionary()
+        FunctionIntervals: weakref.WeakKeyDictionary[FunctionInterval, int] = weakref.WeakKeyDictionary()
 
 
         @classmethod
         @classmethod
         def replaceMethod(cls, oldFunction, newFunction):
         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.
  * Yields continuously until the interval is done.
  */
  */
-static PyObject *gen_next(PyObject *self) {
+static PyObject *gen_next_c_interval(PyObject *self) {
   const CInterval *ival;
   const CInterval *ival;
   if (!Dtool_Call_ExtractThisPointer(self, Dtool_CInterval, (void **)&ival)) {
   if (!Dtool_Call_ExtractThisPointer(self, Dtool_CInterval, (void **)&ival)) {
     return nullptr;
     return nullptr;
@@ -32,8 +32,7 @@ static PyObject *gen_next(PyObject *self) {
 
 
   if (ival->get_state() != CInterval::S_final) {
   if (ival->get_state() != CInterval::S_final) {
     // Try again next frame.
     // Try again next frame.
-    Py_INCREF(Py_None);
-    return Py_None;
+    return Py_NewRef(Py_None);
   }
   }
   else {
   else {
     PyErr_SetNone(PyExc_StopIteration);
     PyErr_SetNone(PyExc_StopIteration);
@@ -55,7 +54,7 @@ __await__(PyObject *self) {
   // we call this via Python.
   // we call this via Python.
   PyObject *result = PyObject_CallMethod(self, "start", nullptr);
   PyObject *result = PyObject_CallMethod(self, "start", nullptr);
   Py_XDECREF(result);
   Py_XDECREF(result);
-  return Dtool_NewGenerator(self, &gen_next);
+  return Dtool_NewGenerator(self, &gen_next_c_interval);
 }
 }
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

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

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

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

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

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

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

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

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

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

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

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

@@ -55,8 +55,6 @@ class BulletinBoard:
     def __repr__(self):
     def __repr__(self):
         str  = 'Bulletin Board Contents\n'
         str  = 'Bulletin Board Contents\n'
         str += '======================='
         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])
             str += '\n%s: %s' % (postName, self._dict[postName])
         return str
         return str

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

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 import direct.showbase.DConfig as config
 import direct.showbase.DConfig as config
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
@@ -11,7 +13,6 @@ import types
 import weakref
 import weakref
 import random
 import random
 import builtins
 import builtins
-import sys
 
 
 
 
 deadEndTypes = frozenset((
 deadEndTypes = frozenset((
@@ -566,7 +567,7 @@ class FindContainers(Job):
                 curObjRef = None
                 curObjRef = None
 
 
                 # types.CellType was added in Python 3.8
                 # 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
                     child = curObj.cell_contents
                     hasLength = self._hasLength(child)
                     hasLength = self._hasLength(child)
                     notDeadEnd = not self._isDeadEnd(child)
                     notDeadEnd = not self._isDeadEnd(child)
@@ -983,7 +984,7 @@ class ContainerLeakDetector(Job):
     """
     """
     notify = directNotify.newCategory("ContainerLeakDetector")
     notify = directNotify.newCategory("ContainerLeakDetector")
     # set of containers that should not be examined
     # set of containers that should not be examined
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
 
     def __init__(self, name, firstCheckDelay = None):
     def __init__(self, name, firstCheckDelay = None):
         Job.__init__(self, name)
         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.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import safeRepr
 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.JobManagerGlobal import jobMgr
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 import types
 import types
-import sys
 import io
 import io
 
 
 
 
 class ContainerReport(Job):
 class ContainerReport(Job):
     notify = directNotify.newCategory("ContainerReport")
     notify = directNotify.newCategory("ContainerReport")
     # set of containers that should not be included in the report
     # 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):
     def __init__(self, name, log=False, limit=None, threaded=False):
         Job.__init__(self, name)
         Job.__init__(self, name)
@@ -122,7 +123,7 @@ class ContainerReport(Job):
                 continue
                 continue
 
 
             # types.CellType was added in Python 3.8
             # 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
                 child = parentObj.cell_contents
                 if self._examine(child):
                 if self._examine(child):
                     assert (self._queue.back() is child)
                     assert (self._queue.back() is child)
@@ -228,14 +229,11 @@ class ContainerReport(Job):
         if type not in self._type2id2len:
         if type not in self._type2id2len:
             return
             return
         len2ids = invertDictLossless(self._type2id2len[type])
         len2ids = invertDictLossless(self._type2id2len[type])
-        lengths = list(len2ids.keys())
-        lengths.sort()
-        lengths.reverse()
         print('=====')
         print('=====')
         print('===== %s' % type)
         print('===== %s' % type)
         count = 0
         count = 0
         stop = False
         stop = False
-        for l in lengths:
+        for l in sorted(len2ids, reverse=True):
             #len2ids[l].sort()
             #len2ids[l].sort()
             pathStrList = list()
             pathStrList = list()
             for id in len2ids[l]:
             for id in len2ids[l]:
@@ -257,9 +255,8 @@ class ContainerReport(Job):
         for type in initialTypes:
         for type in initialTypes:
             for i in self._outputType(type, **kArgs):
             for i in self._outputType(type, **kArgs):
                 yield None
                 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):
             for i in self._outputType(type, **kArgs):
                 yield None
                 yield None
 
 

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

@@ -81,147 +81,3 @@ class CountedResource(object):
 
 
     def __del__(self):
     def __del__(self):
         self.decrementCounter()
         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."
 "This module contains a deprecated shim emulating the old DConfig API."
 
 
-__all__ = []
+__all__ = ()
 
 
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
                           ConfigVariableDouble, ConfigVariableString)
                           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.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from panda3d.core import (
 from panda3d.core import (
@@ -62,7 +64,7 @@ class DistancePhasedNode(PhasedObject, DirectObject, NodePath):
 
 
     notify = directNotify.newCategory("DistancePhasedObject")
     notify = directNotify.newCategory("DistancePhasedObject")
     __InstanceSequence = 0
     __InstanceSequence = 0
-    __InstanceDeque = []
+    __InstanceDeque: list[int] = []
 
 
     @staticmethod
     @staticmethod
     def __allocateId():
     def __allocateId():
@@ -327,31 +329,3 @@ class BufferedDistancePhasedNode(DistancePhasedNode):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().markInternalBoundsStale()
             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
 # from its main exception handler
 wantStackDumpLog = False
 wantStackDumpLog = False
 wantStackDumpUpload = False
 wantStackDumpUpload = False
-variableDumpReasons = []
+variableDumpReasons: list = []
 dumpOnExceptionInit = False
 dumpOnExceptionInit = False
 
 
 
 
@@ -116,15 +116,10 @@ def _excepthookDumpVars(eType, eValue, tb):
         for name, obj in frame.f_locals.items():
         for name, obj in frame.f_locals.items():
             if name in codeNames:
             if name in codeNames:
                 name2obj[name] = obj
                 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()
         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])
             stateStack.push([name, name2obj[name], traversedIds])
 
 
         while len(stateStack) > 0:
         while len(stateStack) > 0:
@@ -150,14 +145,10 @@ def _excepthookDumpVars(eType, eValue, tb):
                                 continue
                                 continue
                         attrName2obj[attrName] = attr
                         attrName2obj[attrName] = attr
                 if len(attrName2obj) > 0:
                 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 = set(traversedIds)
                     ids.add(id(obj))
                     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]
                         obj = attrName2obj[attrName]
                         stateStack.push(['%s.%s' % (name, attrName), obj, ids])
                         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:
             if self._args.fullReport:
                 garbageIndices = range(self.numGarbage)
                 garbageIndices = range(self.numGarbage)
             else:
             else:
-                garbageIndices = list(self.cycleIds)
-                garbageIndices.sort()
+                garbageIndices = sorted(self.cycleIds)
             numGarbage = len(garbageIndices)
             numGarbage = len(garbageIndices)
 
 
             # log each individual item with a number in front of it
             # 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):
     def _getSortedPriorities(self):
         # returns all job priorities in ascending order
         # 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):
     def _process(self, task=None):
         if self._useOverflowTime is 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']
 __all__ = ['Loader']
 
 
 from panda3d.core import (
 from panda3d.core import (
-    AudioLoadRequest,
     ConfigVariableBool,
     ConfigVariableBool,
     Filename,
     Filename,
     FontPool,
     FontPool,
@@ -26,6 +25,7 @@ from panda3d.core import Loader as PandaLoader
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 import warnings
 import warnings
+import sys
 
 
 # You can specify a phaseChecker callback to check
 # You can specify a phaseChecker callback to check
 # a modelPath to see if it is being loaded in the correct
 # 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):
         if not ConfigVariableBool('loader-support-entry-points', True):
             return
             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()
             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)
                 registry.register_deferred_type(entry_point)
 
 
             cls._loadedPythonFileTypes = True
             cls._loadedPythonFileTypes = True
@@ -977,6 +978,8 @@ class Loader(DirectObject):
         just as in loadModel(); otherwise, the loading happens before
         just as in loadModel(); otherwise, the loading happens before
         loadSound() returns."""
         loadSound() returns."""
 
 
+        from panda3d.core import AudioLoadRequest
+
         if not isinstance(soundPath, (tuple, list, set)):
         if not isinstance(soundPath, (tuple, list, set)):
             # We were given a single sound pathname or a MovieAudio instance.
             # We were given a single sound pathname or a MovieAudio instance.
             soundList = [soundPath]
             soundList = [soundPath]

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

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

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

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

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

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

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

@@ -57,9 +57,7 @@ class OnScreenDebug:
         if not self.onScreenText:
         if not self.onScreenText:
             self.load()
             self.load()
         self.onScreenText.clearText()
         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:
             if v[0] == self.frame:
                 # It was updated this frame (key equals value):
                 # It was updated this frame (key equals value):
                 #isNew = " is"
                 #isNew = " is"

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

@@ -1,5 +1,7 @@
 """Contains miscellaneous utility functions and classes."""
 """Contains miscellaneous utility functions and classes."""
 
 
+from __future__ import annotations
+
 __all__ = [
 __all__ = [
 
 
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
@@ -39,6 +41,7 @@ import time
 import builtins
 import builtins
 import importlib
 import importlib
 import functools
 import functools
+from typing import Callable
 
 
 __report_indent = 3
 __report_indent = 3
 
 
@@ -699,9 +702,9 @@ if __debug__:
         return profileDecorator
         return profileDecorator
 
 
     # intercept profile-related file operations to avoid disk access
     # intercept profile-related file operations to avoid disk access
-    movedOpenFuncs = []
-    movedDumpFuncs = []
-    movedLoadFuncs = []
+    movedOpenFuncs: list[Callable] = []
+    movedDumpFuncs: list[Callable] = []
+    movedLoadFuncs: list[Callable] = []
     profileFilenames = set()
     profileFilenames = set()
     profileFilenameList = Stack()
     profileFilenameList = Stack()
     profileFilename2file = {}
     profileFilename2file = {}
@@ -1899,7 +1902,7 @@ class SubframeCall:
 
 
 
 
 class PStatScope:
 class PStatScope:
-    collectors = {}
+    collectors: dict = {}
 
 
     def __init__(self, level = None):
     def __init__(self, level = None):
         self.levels = []
         self.levels = []
@@ -2458,18 +2461,6 @@ def formatTimeCompact(seconds):
     return result
     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):
 def formatTimeExact(seconds):
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # times that are e.g. 1 hour, 3 days etc.
     # times that are e.g. 1 hour, 3 days etc.
@@ -2496,19 +2487,6 @@ def formatTimeExact(seconds):
     return result
     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:
 class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
     def __init__(self):
@@ -2540,28 +2518,6 @@ class AlphabetCounter:
     __next__ = next
     __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:
 class Default:
     # represents 'use the default value'
     # represents 'use the default value'
     # useful for keyword arguments to virtual methods
     # useful for keyword arguments to virtual methods
@@ -2665,52 +2621,52 @@ class PriorityCallbacks:
             callback()
             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__:
 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__:
 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__:
 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
 # This needs to be available early for DirectGUI imports
 import sys
 import sys
 import builtins
 import builtins
-builtins.config = DConfig
+builtins.config = DConfig  # type: ignore[attr-defined]
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
+from direct.directnotify.Notifier import Notifier
 from .MessengerGlobal import messenger
 from .MessengerGlobal import messenger
 from .BulletinBoardGlobal import bulletinBoard
 from .BulletinBoardGlobal import bulletinBoard
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
@@ -140,6 +141,7 @@ import importlib
 from direct.showbase import ExceptionVarDump
 from direct.showbase import ExceptionVarDump
 from . import DirectObject
 from . import DirectObject
 from . import SfxPlayer
 from . import SfxPlayer
+from typing import ClassVar, Optional
 if __debug__:
 if __debug__:
     from direct.showbase import GarbageReport
     from direct.showbase import GarbageReport
     from direct.directutil import DeltaProfiler
     from direct.directutil import DeltaProfiler
@@ -160,8 +162,13 @@ def exitfunc():
 class ShowBase(DirectObject.DirectObject):
 class ShowBase(DirectObject.DirectObject):
 
 
     #: The deprecated `.DConfig` interface for accessing config variables.
     #: 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):
     def __init__(self, fStartDirect=True, windowType=None):
         """Opens a window, sets up a 3-D and several 2-D scene graphs, and
         """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
         self.tkRootCreated = False
 
 
         # This is used for syncing multiple PCs in a distributed cluster
         # This is used for syncing multiple PCs in a distributed cluster
-        try:
+        if hasattr(builtins, 'clusterSyncFlag'):
             # Has the cluster sync variable been set externally?
             # 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
             # Has the clusterSyncFlag been set via a config variable
             self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
             self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
 
 
@@ -424,6 +431,7 @@ class ShowBase(DirectObject.DirectObject):
         #: `.Loader.Loader` object.
         #: `.Loader.Loader` object.
         self.loader = Loader.Loader(self)
         self.loader = Loader.Loader(self)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
+        ShowBaseGlobal.loader = self.loader
 
 
         #: The global event manager, as imported from `.EventManagerGlobal`.
         #: The global event manager, as imported from `.EventManagerGlobal`.
         self.eventMgr = eventMgr
         self.eventMgr = eventMgr
@@ -712,10 +720,9 @@ class ShowBase(DirectObject.DirectObject):
         except Exception:
         except Exception:
             pass
             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):
     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
             # When the user didn't specify an explicit setting, take the value
             # from the config variable. We could just omit the parameter, however
             # from the config variable. We could just omit the parameter, however
             # this way we can keep backward compatibility.
             # this way we can keep backward compatibility.
-            printPipeTypes = ConfigVariableBool("print-pipe-types", True)
+            printPipeTypes = ConfigVariableBool("print-pipe-types", True).value
 
 
         selection = GraphicsPipeSelection.getGlobalPtr()
         selection = GraphicsPipeSelection.getGlobalPtr()
         if printPipeTypes:
         if printPipeTypes:
@@ -3315,8 +3322,9 @@ class ShowBase(DirectObject.DirectObject):
         init_app_for_gui()
         init_app_for_gui()
 
 
         # Disable the Windows message loop, since Tcl wants to handle this all
         # 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):
         if ConfigVariableBool('tk-main-loop', True):
             # Put Tkinter in charge of the main loop.  It really
             # 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
         # Set fWantTk to 0 to avoid starting Tk with this call
         self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
         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`
         """This method runs the :class:`~direct.task.Task.TaskManager`
         when ``self.appRunner is None``, which is to say, when we are
         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
         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
 since ShowBase may not have been created yet; instead, ShowBase dynamically
 adds itself to this module's scope when instantiated."""
 adds itself to this module's scope when instantiated."""
 
 
-__all__ = []
+__all__ = ()
 
 
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify # 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 ConfigPageManager, ConfigVariableManager, ConfigVariableBool
 from panda3d.core import NodePath, PGTop
 from panda3d.core import NodePath, PGTop
 from . import DConfig as config # pylint: disable=unused-import
 from . import DConfig as config # pylint: disable=unused-import
+from .Loader import Loader
 import warnings
 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
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
 #: :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.
 #: A dummy scene graph that is not being rendered by anything.
 hidden = NodePath("hidden")
 hidden = NodePath("hidden")
 
 
+loader: Loader
+
 # Set direct notify categories now that we have config
 # Set direct notify categories now that we have config
 directNotify.setDconfigLevels()
 directNotify.setDconfigLevels()
 
 
@@ -81,7 +86,7 @@ def inspect(anObject):
 
 
 
 
 import builtins
 import builtins
-builtins.inspect = inspect
+builtins.inspect = inspect  # type: ignore[attr-defined]
 
 
 # this also appears in AIBaseGlobal
 # this also appears in AIBaseGlobal
 if (not __debug__) and __dev__:
 if (not __debug__) and __dev__:

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

@@ -37,4 +37,5 @@ del bordercolors
 
 
 def spawnTkLoop():
 def spawnTkLoop():
     """Alias for :meth:`base.spawnTkLoop() <.ShowBase.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.
 the standard output as they happen.
 """
 """
 
 
-__all__ = []
+__all__ = ()
 
 
 
 
 import sys
 import sys

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

@@ -3,4 +3,5 @@
 
 
 def spawnWxLoop():
 def spawnWxLoop():
     """Alias for :meth:`base.spawnWxLoop() <.ShowBase.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;
   i = 0;
   while (PyImport_FrozenModules[i].name != NULL) {
   while (PyImport_FrozenModules[i].name != NULL) {
     if (strcmp(PyImport_FrozenModules[i].name, name) == 0) {
     if (strcmp(PyImport_FrozenModules[i].name, name) == 0) {
-      Py_INCREF(Py_True);
-      return Py_True;
+      Py_RETURN_TRUE;
     }
     }
     ++i;
     ++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,
     WindowProperties,
 )
 )
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase import ShowBaseGlobal
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
 import math
 import math
 import copy
 import copy
@@ -109,7 +110,7 @@ class TexMemWatcher(DirectObject):
         # This is the maximum number of bitmask rows (within
         # This is the maximum number of bitmask rows (within
         # self.limit) to allocate for packing.  This controls the
         # self.limit) to allocate for packing.  This controls the
         # value assigned to self.quantize in repack().
         # 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.
         # The total number of texture bytes tracked, including overflow.
         self.totalSize = 0
         self.totalSize = 0
@@ -122,6 +123,7 @@ class TexMemWatcher(DirectObject):
         self.placedQSize = 0
         self.placedQSize = 0
 
 
         # If no GSG is specified, use the main GSG.
         # If no GSG is specified, use the main GSG.
+        base = ShowBaseGlobal.base
         if gsg is None:
         if gsg is None:
             gsg = base.win.getGsg()
             gsg = base.win.getGsg()
         elif isinstance(gsg, GraphicsOutput):
         elif isinstance(gsg, GraphicsOutput):
@@ -150,7 +152,7 @@ class TexMemWatcher(DirectObject):
         # Set this to tinydisplay if you're running on a machine with
         # Set this to tinydisplay if you're running on a machine with
         # limited texture memory.  That way you won't compete for
         # limited texture memory.  That way you won't compete for
         # texture memory with the main scene.
         # texture memory with the main scene.
-        moduleName = base.config.GetString('tex-mem-pipe', '')
+        moduleName = ConfigVariableString('tex-mem-pipe', '').value
         if moduleName:
         if moduleName:
             self.pipe = base.makeModulePipe(moduleName)
             self.pipe = base.makeModulePipe(moduleName)
 
 
@@ -202,7 +204,7 @@ class TexMemWatcher(DirectObject):
 
 
         # How frequently should the texture memory window check for
         # How frequently should the texture memory window check for
         # state changes?
         # 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.task = taskMgr.doMethodLater(updateInterval, self.updateTextures, 'TexMemWatcher')
 
 
         self.setLimit(limit)
         self.setLimit(limit)
@@ -380,7 +382,7 @@ class TexMemWatcher(DirectObject):
             self.cleanedUp = True
             self.cleanedUp = True
 
 
             # Remove the window.
             # Remove the window.
-            base.graphicsEngine.removeWindow(self.win)
+            self.win.engine.removeWindow(self.win)
             self.win = None
             self.win = None
             self.gsg = None
             self.gsg = None
             self.pipe = None
             self.pipe = None
@@ -756,8 +758,8 @@ class TexMemWatcher(DirectObject):
 
 
         # Sort the regions from largest to smallest to maximize
         # Sort the regions from largest to smallest to maximize
         # packing effectiveness.
         # 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:
         for tr in texRecords:
             self.placeTexture(tr)
             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.
         # We'll put the full-resolution texture on the left.
         cm = CardMaker('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)
         cm.setFrame(l, r, b, t)
         left = cards.attachNewNode(cm.generate())
         left = cards.attachNewNode(cm.generate())
         left.setTexture(self.tex)
         left.setTexture(self.tex)

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

@@ -45,7 +45,7 @@ BasePickler = pickle._Pickler
 BaseUnpickler = pickle._Unpickler
 BaseUnpickler = pickle._Unpickler
 
 
 
 
-class Pickler(BasePickler):
+class Pickler(BasePickler):  # type: ignore[misc, valid-type]
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
         self.bamWriter = BamWriter()
@@ -148,7 +148,7 @@ class Pickler(BasePickler):
         self.save_reduce(obj=obj, *rv)
         self.save_reduce(obj=obj, *rv)
 
 
 
 
-class Unpickler(BaseUnpickler):
+class Unpickler(BaseUnpickler):  # type: ignore[misc, valid-type]
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()
         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
 # Copy these static methods from Panda's Thread object.  These are
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # mode.
 # 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):
 class Thread(ThreadBase):
@@ -123,10 +123,7 @@ class Thread(ThreadBase):
         self.__dict__['ident'] = threadId
         self.__dict__['ident'] = threadId
 
 
     def __del__(self):
     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):
     def is_alive(self):
         thread = self.__thread
         thread = self.__thread
@@ -435,108 +432,3 @@ def setprofile(func):
 
 
 def stack_size(size = None):
 def stack_size(size = None):
     raise ThreadError
     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:
 else:
     # Disable this when using "python -O"
     # Disable this when using "python -O"
-    class _Verbose(object):
+    class _Verbose(object):  # type: ignore[no-redef]
         def __init__(self, verbose=None):
         def __init__(self, verbose=None):
             pass
             pass
         def _note(self, *args):
         def _note(self, *args):
@@ -762,92 +762,3 @@ def main_thread():
 ##     from thread import _local as local
 ##     from thread import _local as local
 ## except ImportError:
 ## except ImportError:
 ##     from _threading_local import local
 ##     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
     done = 0
     cont = 1
     cont = 1
 
 
+    name: str
+
     def __init__(self, callback):
     def __init__(self, callback):
         self.__call__ = 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.
 :ref:`tasks-and-event-handling` page in the programming manual.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['Task', 'TaskManager',
 __all__ = ['Task', 'TaskManager',
            'cont', 'done', 'again', 'pickup', 'exit',
            'cont', 'done', 'again', 'pickup', 'exit',
            'sequence', 'loop', 'pause']
            'sequence', 'loop', 'pause']
@@ -13,6 +15,7 @@ __all__ = ['Task', 'TaskManager',
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Functor, ScratchPad
 from direct.showbase.PythonUtil import Functor, ScratchPad
 from direct.showbase.MessengerGlobal import messenger
 from direct.showbase.MessengerGlobal import messenger
+from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
 import types
 import types
 import random
 import random
 import importlib
 import importlib
@@ -20,11 +23,12 @@ import sys
 
 
 # On Android, there's no use handling SIGINT, and in fact we can't, since we
 # 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.
 # run the application in a separate thread from the main thread.
+signal: types.ModuleType | None
 if hasattr(sys, 'getandroidapilevel'):
 if hasattr(sys, 'getandroidapilevel'):
     signal = None
     signal = None
 else:
 else:
     try:
     try:
-        import _signal as signal
+        import _signal as signal  # type: ignore[import, no-redef]
     except ImportError:
     except ImportError:
         signal = None
         signal = None
 
 
@@ -41,8 +45,15 @@ from panda3d.core import (
 )
 )
 from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-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
     Print the usual traceback information, followed by a listing of all the
     local variables in each frame.
     local variables in each frame.
@@ -50,12 +61,13 @@ def print_exc_plus():
     import traceback
     import traceback
 
 
     tb = sys.exc_info()[2]
     tb = sys.exc_info()[2]
+    assert tb is not None
     while 1:
     while 1:
         if not tb.tb_next:
         if not tb.tb_next:
             break
             break
         tb = tb.tb_next
         tb = tb.tb_next
     stack = []
     stack = []
-    f = tb.tb_frame
+    f: types.FrameType | None = tb.tb_frame
     while f:
     while f:
         stack.append(f)
         stack.append(f)
         f = f.f_back
         f = f.f_back
@@ -82,11 +94,11 @@ def print_exc_plus():
 # these Python names, and define them both at the module level, here,
 # these Python names, and define them both at the module level, here,
 # and at the class level (below).  The preferred access is via the
 # and at the class level (below).  The preferred access is via the
 # class level.
 # 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 aliases to :class:`panda3d.core.PythonTask` for historical purposes.
 Task = PythonTask
 Task = PythonTask
@@ -110,7 +122,7 @@ gather = Task.gather
 shield = Task.shield
 shield = Task.shield
 
 
 
 
-def sequence(*taskList):
+def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('sequence')
     seq = AsyncTaskSequence('sequence')
     for task in taskList:
     for task in taskList:
         seq.addTask(task)
         seq.addTask(task)
@@ -120,7 +132,7 @@ def sequence(*taskList):
 Task.DtoolClassDict['sequence'] = staticmethod(sequence)
 Task.DtoolClassDict['sequence'] = staticmethod(sequence)
 
 
 
 
-def loop(*taskList):
+def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('loop')
     seq = AsyncTaskSequence('loop')
     for task in taskList:
     for task in taskList:
         seq.addTask(task)
         seq.addTask(task)
@@ -140,10 +152,12 @@ class TaskManager:
 
 
     MaxEpochSpeed = 1.0/30.0
     MaxEpochSpeed = 1.0/30.0
 
 
-    def __init__(self):
+    __prevHandler: Any
+
+    def __init__(self) -> None:
         self.mgr = AsyncTaskManager.getGlobalPtr()
         self.mgr = AsyncTaskManager.getGlobalPtr()
 
 
-        self.resumeFunc = None
+        self.resumeFunc: Callable[[], object] | None = None
         self.globalClock = self.mgr.getClock()
         self.globalClock = self.mgr.getClock()
         self.stepping = False
         self.stepping = False
         self.running = False
         self.running = False
@@ -153,12 +167,12 @@ class TaskManager:
         if signal:
         if signal:
             self.__prevHandler = signal.default_int_handler
             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
         # this will be set when it's safe to import StateVar
-        self._profileFrames = None
+        self._profileFrames: Any = None
         self._frameProfiler = None
         self._frameProfiler = None
-        self._profileTasks = None
+        self._profileTasks: Any = None
         self._taskProfiler = None
         self._taskProfiler = None
         self._taskProfileInfo = ScratchPad(
         self._taskProfileInfo = ScratchPad(
             taskId = None,
             taskId = None,
@@ -166,7 +180,7 @@ class TaskManager:
             session = None,
             session = None,
         )
         )
 
 
-    def finalInit(self):
+    def finalInit(self) -> None:
         # This function should be called once during startup, after
         # This function should be called once during startup, after
         # most things are imported.
         # most things are imported.
         from direct.fsm.StatePush import StateVar
         from direct.fsm.StatePush import StateVar
@@ -175,7 +189,7 @@ class TaskManager:
         self._profileFrames = StateVar(False)
         self._profileFrames = StateVar(False)
         self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
         self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
 
 
-    def destroy(self):
+    def destroy(self) -> None:
         # This should be safe to call multiple times.
         # This should be safe to call multiple times.
         self.running = False
         self.running = False
         self.notify.info("TaskManager.destroy()")
         self.notify.info("TaskManager.destroy()")
@@ -183,11 +197,14 @@ class TaskManager:
         self._frameProfileQueue.clear()
         self._frameProfileQueue.clear()
         self.mgr.cleanup()
         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.mgr.setClock(clockObject)
         self.globalClock = clockObject
         self.globalClock = clockObject
 
 
-    clock = property(lambda self: self.mgr.getClock(), setClock)
+    clock = property(__getClock, setClock)
 
 
     def invokeDefaultHandler(self, signalNumber, stackFrame):
     def invokeDefaultHandler(self, signalNumber, stackFrame):
         print('*** allowing mid-frame keyboard interrupt.')
         print('*** allowing mid-frame keyboard interrupt.')
@@ -208,13 +225,13 @@ class TaskManager:
             # Next time around invoke the default handler
             # Next time around invoke the default handler
             signal.signal(signal.SIGINT, self.invokeDefaultHandler)
             signal.signal(signal.SIGINT, self.invokeDefaultHandler)
 
 
-    def getCurrentTask(self):
+    def getCurrentTask(self) -> AsyncTask | None:
         """ Returns the task currently executing on this thread, or
         """ Returns the task currently executing on this thread, or
         None if this is being called outside of the task manager. """
         None if this is being called outside of the task manager. """
 
 
         return Thread.getCurrentThread().getCurrentTask()
         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
         """ Returns true if a task chain with the indicated name has
         already been defined, or false otherwise.  Note that
         already been defined, or false otherwise.  Note that
         setupTaskChain() will implicitly define a task chain if it has
         setupTaskChain() will implicitly define a task chain if it has
@@ -224,9 +241,16 @@ class TaskManager:
 
 
         return self.mgr.findTaskChain(chainName) is not None
         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
         """Defines a new task chain.  Each task chain executes tasks
         potentially in parallel with all of the other task chains (if
         potentially in parallel with all of the other task chains (if
         numThreads is more than zero).  When a new task is created, it
         numThreads is more than zero).  When a new task is created, it
@@ -290,40 +314,50 @@ class TaskManager:
         if timeslicePriority is not None:
         if timeslicePriority is not None:
             chain.setTimeslicePriority(timeslicePriority)
             chain.setTimeslicePriority(timeslicePriority)
 
 
-    def hasTaskNamed(self, taskName):
+    def hasTaskNamed(self, taskName: str) -> bool:
         """Returns true if there is at least one task, active or
         """Returns true if there is at least one task, active or
         sleeping, with the indicated name. """
         sleeping, with the indicated name. """
 
 
         return bool(self.mgr.findTask(taskName))
         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
         """Returns a list of all tasks, active or sleeping, with the
         indicated name. """
         indicated name. """
         return list(self.mgr.findTasks(taskName))
         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
         """Returns a list of all tasks, active or sleeping, with a
         name that matches the pattern, which can include standard
         name that matches the pattern, which can include standard
         shell globbing characters like \\*, ?, and []. """
         shell globbing characters like \\*, ?, and []. """
 
 
         return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
         return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
 
 
-    def getAllTasks(self):
+    def getAllTasks(self) -> list[AsyncTask]:
         """Returns list of all tasks, active and sleeping, in
         """Returns list of all tasks, active and sleeping, in
         arbitrary order. """
         arbitrary order. """
         return list(self.mgr.getTasks())
         return list(self.mgr.getTasks())
 
 
-    def getTasks(self):
+    def getTasks(self) -> list[AsyncTask]:
         """Returns list of all active tasks in arbitrary order. """
         """Returns list of all active tasks in arbitrary order. """
         return list(self.mgr.getActiveTasks())
         return list(self.mgr.getActiveTasks())
 
 
-    def getDoLaters(self):
+    def getDoLaters(self) -> list[AsyncTask]:
         """Returns list of all sleeping tasks in arbitrary order. """
         """Returns list of all sleeping tasks in arbitrary order. """
         return list(self.mgr.getSleepingTasks())
         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.
         """Adds a task to be performed at some time in the future.
         This is identical to `add()`, except that the specified
         This is identical to `add()`, except that the specified
         delayTime is applied to the Task object first, which means
         delayTime is applied to the Task object first, which means
@@ -346,9 +380,19 @@ class TaskManager:
 
 
     do_method_later = doMethodLater
     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
         Add a new task to the taskMgr.  The task will begin executing
         immediately, or next frame if its sort value has already
         immediately, or next frame if its sort value has already
@@ -415,7 +459,18 @@ class TaskManager:
         self.mgr.add(task)
         self.mgr.add(task)
         return 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
         wasTask = False
         if isinstance(funcOrTask, AsyncTask):
         if isinstance(funcOrTask, AsyncTask):
             task = funcOrTask
             task = funcOrTask
@@ -473,7 +528,7 @@ class TaskManager:
 
 
         return task
         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,
         """Removes a task from the task manager.  The task is stopped,
         almost as if it had returned task.done.  (But if the task is
         almost as if it had returned task.done.  (But if the task is
         currently executing, it will finish out its current frame
         currently executing, it will finish out its current frame
@@ -485,13 +540,15 @@ class TaskManager:
         if isinstance(taskOrName, AsyncTask):
         if isinstance(taskOrName, AsyncTask):
             return self.mgr.remove(taskOrName)
             return self.mgr.remove(taskOrName)
         elif isinstance(taskOrName, list):
         elif isinstance(taskOrName, list):
+            count = 0
             for task in taskOrName:
             for task in taskOrName:
-                self.remove(task)
+                count += self.remove(task)
+            return count
         else:
         else:
             tasks = self.mgr.findTasks(taskOrName)
             tasks = self.mgr.findTasks(taskOrName)
             return self.mgr.remove(tasks)
             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
         """Removes all tasks whose names match the pattern, which can
         include standard shell globbing characters like \\*, ?, and [].
         include standard shell globbing characters like \\*, ?, and [].
         See also :meth:`remove()`.
         See also :meth:`remove()`.
@@ -501,7 +558,7 @@ class TaskManager:
         tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
         tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
         return self.mgr.remove(tasks)
         return self.mgr.remove(tasks)
 
 
-    def step(self):
+    def step(self) -> None:
         """Invokes the task manager for one frame, and then returns.
         """Invokes the task manager for one frame, and then returns.
         Normally, this executes each task exactly once, though task
         Normally, this executes each task exactly once, though task
         chains that are in sub-threads or that have frame budgets
         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
         # Replace keyboard interrupt handler during task list processing
         # so we catch the keyboard interrupt but don't handle it until
         # so we catch the keyboard interrupt but don't handle it until
         # after task list processing is complete.
         # after task list processing is complete.
-        self.fKeyboardInterrupt = 0
+        self.fKeyboardInterrupt = False
         self.interruptCount = 0
         self.interruptCount = 0
 
 
         if signal:
         if signal:
@@ -534,7 +591,7 @@ class TaskManager:
         if self.fKeyboardInterrupt:
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
             raise KeyboardInterrupt
 
 
-    def run(self):
+    def run(self) -> None:
         """Starts the task manager running.  Does not return until an
         """Starts the task manager running.  Does not return until an
         exception is encountered (including KeyboardInterrupt). """
         exception is encountered (including KeyboardInterrupt). """
 
 
@@ -560,11 +617,11 @@ class TaskManager:
                     if len(self._frameProfileQueue) > 0:
                     if len(self._frameProfileQueue) > 0:
                         numFrames, session, callback = self._frameProfileQueue.pop(0)
                         numFrames, session, callback = self._frameProfileQueue.pop(0)
 
 
-                        def _profileFunc(numFrames=numFrames):
+                        def _profileFunc(numFrames: int = numFrames) -> None:
                             self._doProfiledFrames(numFrames)
                             self._doProfiledFrames(numFrames)
                         session.setFunc(_profileFunc)
                         session.setFunc(_profileFunc)
                         session.run()
                         session.run()
-                        _profileFunc = None
+                        del _profileFunc
                         if callback:
                         if callback:
                             callback()
                             callback()
                         session.release()
                         session.release()
@@ -617,7 +674,7 @@ class TaskManager:
             message = ioError
             message = ioError
         return code, message
         return code, message
 
 
-    def stop(self):
+    def stop(self) -> None:
         # Set a flag so we will stop before beginning next frame
         # Set a flag so we will stop before beginning next frame
         self.running = False
         self.running = False
 
 
@@ -782,12 +839,12 @@ class TaskManager:
             task = tasks.getTask(i)
             task = tasks.getTask(i)
         return task
         return task
 
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return str(self.mgr)
         return str(self.mgr)
 
 
     # In the event we want to do frame time managment, this is the
     # In the event we want to do frame time managment, this is the
     # function to replace or overload.
     # function to replace or overload.
-    def doYield(self, frameStartTime, nextScheduledTaskTime):
+    def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
         pass
         pass
 
 
     #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
     #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
@@ -801,532 +858,6 @@ class TaskManager:
     #        time.sleep(delta)
     #        time.sleep(delta)
     #        delta = minFinTime - self.globalClock.getRealTime()
     #        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__:
 if __debug__:
     def checkLeak():
     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 SceneGraphExplorer
 from direct.tkwidgets import MemoryExplorer
 from direct.tkwidgets import MemoryExplorer
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
+from direct.showbase import ShowBaseGlobal
 from .TaskManagerPanel import TaskManagerWidget
 from .TaskManagerPanel import TaskManagerWidget
 import Pmw
 import Pmw
 import tkinter as tk
 import tkinter as tk
@@ -44,8 +45,8 @@ class DirectSessionPanel(AppShell):
         AppShell.__init__(self, parent)
         AppShell.__init__(self, parent)
 
 
         # Active light
         # 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.lightMenu.selectitem(name)
             self.selectLightNamed(name)
             self.selectLightNamed(name)
         else:
         else:
@@ -62,14 +63,14 @@ class DirectSessionPanel(AppShell):
         # Initialize state
         # Initialize state
         # Dictionary keeping track of all node paths selected so far
         # Dictionary keeping track of all node paths selected so far
         self.nodePathDict = {}
         self.nodePathDict = {}
-        self.nodePathDict['widget'] = base.direct.widget
+        self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
         self.nodePathNames = ['widget']
         self.nodePathNames = ['widget']
 
 
         # Dictionary keeping track of all jb node paths selected so far
         # Dictionary keeping track of all jb node paths selected so far
         self.jbNodePathDict = {}
         self.jbNodePathDict = {}
         self.jbNodePathDict['none'] = 'No Node Path'
         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']
         self.jbNodePathNames = ['camera', 'selected', 'none']
 
 
         # Set up event hooks
         # Set up event hooks
@@ -93,7 +94,7 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenu('DIRECT', 'Direct Session Panel Operations')
         self.menuBar.addmenu('DIRECT', 'Direct Session Panel Operations')
 
 
         self.directEnabled = tk.BooleanVar()
         self.directEnabled = tk.BooleanVar()
-        self.directEnabled.set(1)
+        self.directEnabled.set(True)
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Enabled',
                                  'DIRECT Enabled',
                                  label = 'Enable',
                                  label = 'Enable',
@@ -101,7 +102,7 @@ class DirectSessionPanel(AppShell):
                                  command = self.toggleDirect)
                                  command = self.toggleDirect)
 
 
         self.directGridEnabled = tk.BooleanVar()
         self.directGridEnabled = tk.BooleanVar()
-        self.directGridEnabled.set(base.direct.grid.isEnabled())
+        self.directGridEnabled.set(ShowBaseGlobal.direct.grid.isEnabled())
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Grid Enabled',
                                  'DIRECT Grid Enabled',
                                  label = 'Enable Grid',
                                  label = 'Enable Grid',
@@ -111,16 +112,16 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenuitem('DIRECT', 'command',
         self.menuBar.addmenuitem('DIRECT', 'command',
                                  'Toggle Object Handles Visability',
                                  'Toggle Object Handles Visability',
                                  label = 'Toggle Widget Viz',
                                  label = 'Toggle Widget Viz',
-                                 command = base.direct.toggleWidgetVis)
+                                 command = ShowBaseGlobal.direct.toggleWidgetVis)
 
 
         self.menuBar.addmenuitem(
         self.menuBar.addmenuitem(
             'DIRECT', 'command',
             'DIRECT', 'command',
             'Toggle Widget Move/COA Mode',
             'Toggle Widget Move/COA Mode',
             label = 'Toggle Widget Mode',
             label = 'Toggle Widget Mode',
-            command = base.direct.manipulationControl.toggleObjectHandlesMode)
+            command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)
 
 
         self.directWidgetOnTop = tk.BooleanVar()
         self.directWidgetOnTop = tk.BooleanVar()
-        self.directWidgetOnTop.set(0)
+        self.directWidgetOnTop.set(False)
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
         self.menuBar.addmenuitem('DIRECT', 'checkbutton',
                                  'DIRECT Widget On Top',
                                  'DIRECT Widget On Top',
                                  label = 'Widget On Top',
                                  label = 'Widget On Top',
@@ -130,7 +131,7 @@ class DirectSessionPanel(AppShell):
         self.menuBar.addmenuitem('DIRECT', 'command',
         self.menuBar.addmenuitem('DIRECT', 'command',
                                  'Deselect All',
                                  'Deselect All',
                                  label = 'Deselect All',
                                  label = 'Deselect All',
-                                 command = base.direct.deselectAll)
+                                 command = ShowBaseGlobal.direct.deselectAll)
 
 
         # Get a handle to the menu frame
         # Get a handle to the menu frame
         menuFrame = self.menuFrame
         menuFrame = self.menuFrame
@@ -150,8 +151,8 @@ class DirectSessionPanel(AppShell):
         self.bind(self.nodePathMenu, 'Select node path to manipulate')
         self.bind(self.nodePathMenu, 'Select node path to manipulate')
 
 
         self.undoButton = tk.Button(menuFrame, text = 'Undo',
         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'
             self.undoButton['state'] = 'normal'
         else:
         else:
             self.undoButton['state'] = 'disabled'
             self.undoButton['state'] = 'disabled'
@@ -159,8 +160,8 @@ class DirectSessionPanel(AppShell):
         self.bind(self.undoButton, 'Undo last operation')
         self.bind(self.undoButton, 'Undo last operation')
 
 
         self.redoButton = tk.Button(menuFrame, text = 'Redo',
         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'
             self.redoButton['state'] = 'normal'
         else:
         else:
             self.redoButton['state'] = 'disabled'
             self.redoButton['state'] = 'disabled'
@@ -177,7 +178,7 @@ class DirectSessionPanel(AppShell):
 
 
         # Scene Graph Explorer
         # Scene Graph Explorer
         self.SGE = SceneGraphExplorer.SceneGraphExplorer(
         self.SGE = SceneGraphExplorer.SceneGraphExplorer(
-            sgeFrame, nodePath = render,
+            sgeFrame, nodePath = ShowBaseGlobal.base.render,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_height = 300)
             scrolledCanvas_hull_height = 300)
         self.SGE.pack(fill = tk.BOTH, expand = 1)
         self.SGE.pack(fill = tk.BOTH, expand = 1)
@@ -218,7 +219,7 @@ class DirectSessionPanel(AppShell):
         tk.Label(drFrame, text = 'Display Region',
         tk.Label(drFrame, text = 'Display Region',
                  font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
                  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(
         self.drMenu = Pmw.ComboBox(
             drFrame, labelpos = tk.W, label_text = 'Display Region:',
             drFrame, labelpos = tk.W, label_text = 'Display Region:',
             entry_width = 20,
             entry_width = 20,
@@ -264,7 +265,7 @@ class DirectSessionPanel(AppShell):
 
 
         frame = tk.Frame(fovFrame)
         frame = tk.Frame(fovFrame)
         self.lockedFov = tk.BooleanVar()
         self.lockedFov = tk.BooleanVar()
-        self.lockedFov.set(1)
+        self.lockedFov.set(True)
         self.lockedFovButton = tk.Checkbutton(
         self.lockedFovButton = tk.Checkbutton(
             frame,
             frame,
             text = 'Locked',
             text = 'Locked',
@@ -289,25 +290,25 @@ class DirectSessionPanel(AppShell):
         self.toggleBackfaceButton = tk.Button(
         self.toggleBackfaceButton = tk.Button(
             toggleFrame,
             toggleFrame,
             text = 'Backface',
             text = 'Backface',
-            command = base.toggleBackface)
+            command = ShowBaseGlobal.base.toggleBackface)
         self.toggleBackfaceButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
         self.toggleBackfaceButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
 
         self.toggleLightsButton = tk.Button(
         self.toggleLightsButton = tk.Button(
             toggleFrame,
             toggleFrame,
             text = 'Lights',
             text = 'Lights',
-            command = base.direct.lights.toggle)
+            command = ShowBaseGlobal.direct.lights.toggle)
         self.toggleLightsButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
         self.toggleLightsButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
 
         self.toggleTextureButton = tk.Button(
         self.toggleTextureButton = tk.Button(
             toggleFrame,
             toggleFrame,
             text = 'Texture',
             text = 'Texture',
-            command = base.toggleTexture)
+            command = ShowBaseGlobal.base.toggleTexture)
         self.toggleTextureButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
         self.toggleTextureButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
 
 
         self.toggleWireframeButton = tk.Button(
         self.toggleWireframeButton = tk.Button(
             toggleFrame,
             toggleFrame,
             text = 'Wireframe',
             text = 'Wireframe',
-            command = base.toggleWireframe)
+            command = ShowBaseGlobal.base.toggleWireframe)
         self.toggleWireframeButton.pack(fill = tk.X, expand = 1)
         self.toggleWireframeButton.pack(fill = tk.X, expand = 1)
         toggleFrame.pack(side = tk.LEFT, 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)
         mainSwitchFrame.pack(fill = tk.X, expand = 0)
 
 
         # Widget to select a light to configure
         # Widget to select a light to configure
-        nameList = base.direct.lights.getNameList()
+        nameList = ShowBaseGlobal.direct.lights.getNameList()
         lightMenuFrame = tk.Frame(lightFrame)
         lightMenuFrame = tk.Frame(lightFrame)
 
 
         self.lightMenu = Pmw.ComboBox(
         self.lightMenu = Pmw.ComboBox(
@@ -510,36 +511,36 @@ class DirectSessionPanel(AppShell):
             gridPage,
             gridPage,
             text = 'Grid Spacing',
             text = 'Grid Spacing',
             min = 0.1,
             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.gridSpacing.pack(fill = tk.X, expand = 0)
 
 
         self.gridSize = Floater.Floater(
         self.gridSize = Floater.Floater(
             gridPage,
             gridPage,
             text = 'Grid Size',
             text = 'Grid Size',
             min = 1.0,
             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.gridSize.pack(fill = tk.X, expand = 0)
 
 
         self.gridSnapAngle = Dial.AngleDial(
         self.gridSnapAngle = Dial.AngleDial(
             gridPage,
             gridPage,
             text = 'Snap Angle',
             text = 'Snap Angle',
             style = 'mini',
             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)
         self.gridSnapAngle.pack(fill = tk.X, expand = 0)
 
 
     def createDevicePage(self, devicePage):
     def createDevicePage(self, devicePage):
         tk.Label(devicePage, text = 'DEVICES',
         tk.Label(devicePage, text = 'DEVICES',
               font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
               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')
             joyboxFrame = tk.Frame(devicePage, borderwidth = 2, relief = 'sunken')
             tk.Label(joyboxFrame, text = 'Joybox',
             tk.Label(joyboxFrame, text = 'Joybox',
                      font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
                      font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
             self.enableJoybox = tk.BooleanVar()
             self.enableJoybox = tk.BooleanVar()
-            self.enableJoybox.set(1)
+            self.enableJoybox.set(True)
             self.enableJoyboxButton = tk.Checkbutton(
             self.enableJoyboxButton = tk.Checkbutton(
                 joyboxFrame,
                 joyboxFrame,
                 text = 'Enabled/Disabled',
                 text = 'Enabled/Disabled',
@@ -581,7 +582,7 @@ class DirectSessionPanel(AppShell):
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 min = 1.0, max = 100.0)
                 min = 1.0, max = 100.0)
             self.jbXyzSF['command'] = (
             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.jbXyzSF.pack(fill = tk.X, expand = 0)
             self.bind(self.jbXyzSF, 'Set joybox XYZ speed multiplier')
             self.bind(self.jbXyzSF, 'Set joybox XYZ speed multiplier')
 
 
@@ -592,7 +593,7 @@ class DirectSessionPanel(AppShell):
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 hull_relief = tk.RIDGE, hull_borderwidth = 2,
                 min = 1.0, max = 100.0)
                 min = 1.0, max = 100.0)
             self.jbHprSF['command'] = (
             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.jbHprSF.pack(fill = tk.X, expand = 0)
             self.bind(self.jbHprSF, 'Set joybox HPR speed multiplier')
             self.bind(self.jbHprSF, 'Set joybox HPR speed multiplier')
 
 
@@ -604,30 +605,30 @@ class DirectSessionPanel(AppShell):
 
 
     def createMemPage(self, memPage):
     def createMemPage(self, memPage):
         self.MemExp = MemoryExplorer.MemoryExplorer(
         self.MemExp = MemoryExplorer.MemoryExplorer(
-            memPage, nodePath = render,
+            memPage, nodePath = ShowBaseGlobal.base.render,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_width = 250,
             scrolledCanvas_hull_height = 250)
             scrolledCanvas_hull_height = 250)
         self.MemExp.pack(fill = tk.BOTH, expand = 1)
         self.MemExp.pack(fill = tk.BOTH, expand = 1)
 
 
     def toggleDirect(self):
     def toggleDirect(self):
         if self.directEnabled.get():
         if self.directEnabled.get():
-            base.direct.enable()
+            ShowBaseGlobal.direct.enable()
         else:
         else:
-            base.direct.disable()
+            ShowBaseGlobal.direct.disable()
 
 
     def toggleDirectGrid(self):
     def toggleDirectGrid(self):
         if self.directGridEnabled.get():
         if self.directGridEnabled.get():
-            base.direct.grid.enable()
+            ShowBaseGlobal.direct.grid.enable()
         else:
         else:
-            base.direct.grid.disable()
+            ShowBaseGlobal.direct.grid.disable()
 
 
     def toggleWidgetOnTop(self):
     def toggleWidgetOnTop(self):
         if self.directWidgetOnTop.get():
         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:
         else:
-            base.direct.widget.clearBin()
-            base.direct.widget.setDepthTest(1)
+            ShowBaseGlobal.direct.widget.clearBin()
+            ShowBaseGlobal.direct.widget.setDepthTest(1)
 
 
     def selectedNodePathHook(self, nodePath):
     def selectedNodePathHook(self, nodePath):
         # Make sure node path is in nodePathDict
         # Make sure node path is in nodePathDict
@@ -657,7 +658,7 @@ class DirectSessionPanel(AppShell):
         # Did we finally get something?
         # Did we finally get something?
         if nodePath is not None:
         if nodePath is not None:
             # Yes, select it!
             # Yes, select it!
-            base.direct.select(nodePath)
+            ShowBaseGlobal.direct.select(nodePath)
 
 
     def addNodePath(self, nodePath):
     def addNodePath(self, nodePath):
         self.addNodePathToDict(nodePath, self.nodePathNames,
         self.addNodePathToDict(nodePath, self.nodePathNames,
@@ -665,25 +666,25 @@ class DirectSessionPanel(AppShell):
 
 
     def selectJBModeNamed(self, name):
     def selectJBModeNamed(self, name):
         if name == 'Joe Mode':
         if name == 'Joe Mode':
-            base.direct.joybox.joeMode()
+            ShowBaseGlobal.direct.joybox.joeMode()
         elif name == 'Drive Mode':
         elif name == 'Drive Mode':
-            base.direct.joybox.driveMode()
+            ShowBaseGlobal.direct.joybox.driveMode()
         elif name == 'Orbit Mode':
         elif name == 'Orbit Mode':
-            base.direct.joybox.orbitMode()
+            ShowBaseGlobal.direct.joybox.orbitMode()
         elif name == 'Look At Mode':
         elif name == 'Look At Mode':
-            base.direct.joybox.lookAtMode()
+            ShowBaseGlobal.direct.joybox.lookAtMode()
         elif name == 'Look Around Mode':
         elif name == 'Look Around Mode':
-            base.direct.joybox.lookAroundMode()
+            ShowBaseGlobal.direct.joybox.lookAroundMode()
         elif name == 'Walkthru Mode':
         elif name == 'Walkthru Mode':
-            base.direct.joybox.walkthruMode()
+            ShowBaseGlobal.direct.joybox.walkthruMode()
         elif name == 'Demo Mode':
         elif name == 'Demo Mode':
-            base.direct.joybox.demoMode()
+            ShowBaseGlobal.direct.joybox.demoMode()
         elif name == 'HPRXYZ Mode':
         elif name == 'HPRXYZ Mode':
-            base.direct.joybox.hprXyzMode()
+            ShowBaseGlobal.direct.joybox.hprXyzMode()
 
 
     def selectJBNodePathNamed(self, name):
     def selectJBNodePathNamed(self, name):
         if name == 'selected':
         if name == 'selected':
-            nodePath = base.direct.selected.last
+            nodePath = ShowBaseGlobal.direct.selected.last
             # Add Combo box entry for this selected object
             # Add Combo box entry for this selected object
             self.addJBNodePath(nodePath)
             self.addJBNodePath(nodePath)
         else:
         else:
@@ -708,9 +709,9 @@ class DirectSessionPanel(AppShell):
         if nodePath is not None:
         if nodePath is not None:
             # Yes, select it!
             # Yes, select it!
             if nodePath == 'No Node Path':
             if nodePath == 'No Node Path':
-                base.direct.joybox.setNodePath(None)
+                ShowBaseGlobal.direct.joybox.setNodePath(None)
             else:
             else:
-                base.direct.joybox.setNodePath(nodePath)
+                ShowBaseGlobal.direct.joybox.setNodePath(nodePath)
 
 
     def addJBNodePath(self, nodePath):
     def addJBNodePath(self, nodePath):
         self.addNodePathToDict(nodePath, self.jbNodePathNames,
         self.addNodePathToDict(nodePath, self.jbNodePathNames,
@@ -741,14 +742,13 @@ class DirectSessionPanel(AppShell):
         self.setBackgroundColorVec((r, g, b))
         self.setBackgroundColorVec((r, g, b))
 
 
     def setBackgroundColorVec(self, color):
     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):
     def selectDisplayRegionNamed(self, name):
         if name.find('Display Region ') >= 0:
         if name.find('Display Region ') >= 0:
             drIndex = int(name[-1:])
             drIndex = int(name[-1:])
-            self.activeDisplayRegion = base.direct.drList[drIndex]
+            self.activeDisplayRegion = ShowBaseGlobal.direct.drList[drIndex]
         else:
         else:
             self.activeDisplayRegion = None
             self.activeDisplayRegion = None
         # Make sure info is current
         # Make sure info is current
@@ -758,13 +758,13 @@ class DirectSessionPanel(AppShell):
         dr = self.activeDisplayRegion
         dr = self.activeDisplayRegion
         if dr:
         if dr:
             dr.camLens.setNear(near)
             dr.camLens.setNear(near)
-            cluster('base.camLens.setNear(%f)' % near, 0)
+            ShowBaseGlobal.direct.cluster('base.camLens.setNear(%f)' % near, 0)
 
 
     def setFar(self, far):
     def setFar(self, far):
         dr = self.activeDisplayRegion
         dr = self.activeDisplayRegion
         if dr:
         if dr:
             dr.camLens.setFar(far)
             dr.camLens.setFar(far)
-            cluster('base.camLens.setFar(%f)' % far, 0)
+            ShowBaseGlobal.direct.cluster('base.camLens.setFar(%f)' % far, 0)
 
 
     def setHFov(self, hFov):
     def setHFov(self, hFov):
         dr = self.activeDisplayRegion
         dr = self.activeDisplayRegion
@@ -802,10 +802,10 @@ class DirectSessionPanel(AppShell):
     # Lights #
     # Lights #
     def selectLightNamed(self, name):
     def selectLightNamed(self, name):
         # See if light exists
         # See if light exists
-        self.activeLight = base.direct.lights[name]
+        self.activeLight = ShowBaseGlobal.direct.lights[name]
         # If not...create new one
         # If not...create new one
         if self.activeLight is None:
         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?
         # Do we have a valid light at this point?
         if self.activeLight:
         if self.activeLight:
             light = self.activeLight.getLight()
             light = self.activeLight.getLight()
@@ -820,28 +820,28 @@ class DirectSessionPanel(AppShell):
         else:
         else:
             # Restore valid data
             # Restore valid data
             listbox = self.lightMenu.component('scrolledlist')
             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
         # Make sure info is current
         self.updateLightInfo()
         self.updateLightInfo()
 
 
     def addAmbient(self):
     def addAmbient(self):
-        return base.direct.lights.create('ambient')
+        return ShowBaseGlobal.direct.lights.create('ambient')
 
 
     def addDirectional(self):
     def addDirectional(self):
-        return base.direct.lights.create('directional')
+        return ShowBaseGlobal.direct.lights.create('directional')
 
 
     def addPoint(self):
     def addPoint(self):
-        return base.direct.lights.create('point')
+        return ShowBaseGlobal.direct.lights.create('point')
 
 
     def addSpot(self):
     def addSpot(self):
-        return base.direct.lights.create('spot')
+        return ShowBaseGlobal.direct.lights.create('spot')
 
 
     def addLight(self, light):
     def addLight(self, light):
         # Make list reflect current list of lights
         # Make list reflect current list of lights
         listbox = self.lightMenu.component('scrolledlist')
         listbox = self.lightMenu.component('scrolledlist')
-        listbox.setlist(base.direct.lights.getNameList())
+        listbox.setlist(ShowBaseGlobal.direct.lights.getNameList())
         # Select the newly added light
         # Select the newly added light
         self.lightMenu.selectitem(light.getName())
         self.lightMenu.selectitem(light.getName())
         # And show corresponding page
         # And show corresponding page
@@ -849,16 +849,16 @@ class DirectSessionPanel(AppShell):
 
 
     def toggleLights(self):
     def toggleLights(self):
         if self.enableLights.get():
         if self.enableLights.get():
-            base.direct.lights.allOn()
+            ShowBaseGlobal.direct.lights.allOn()
         else:
         else:
-            base.direct.lights.allOff()
+            ShowBaseGlobal.direct.lights.allOff()
 
 
     def toggleActiveLight(self):
     def toggleActiveLight(self):
         if self.activeLight:
         if self.activeLight:
             if self.lightActive.get():
             if self.lightActive.get():
-                base.direct.lights.setOn(self.activeLight)
+                ShowBaseGlobal.direct.lights.setOn(self.activeLight)
             else:
             else:
-                base.direct.lights.setOff(self.activeLight)
+                ShowBaseGlobal.direct.lights.setOff(self.activeLight)
 
 
     def setLightColor(self, color):
     def setLightColor(self, color):
         if self.activeLight:
         if self.activeLight:
@@ -893,22 +893,22 @@ class DirectSessionPanel(AppShell):
     ## GRID CONTROLS ##
     ## GRID CONTROLS ##
     def toggleGrid(self):
     def toggleGrid(self):
         if self.enableGrid.get():
         if self.enableGrid.get():
-            base.direct.grid.enable()
+            ShowBaseGlobal.direct.grid.enable()
         else:
         else:
-            base.direct.grid.disable()
+            ShowBaseGlobal.direct.grid.disable()
 
 
     def toggleXyzSnap(self):
     def toggleXyzSnap(self):
-        base.direct.grid.setXyzSnap(self.xyzSnap.get())
+        ShowBaseGlobal.direct.grid.setXyzSnap(self.xyzSnap.get())
 
 
     def toggleHprSnap(self):
     def toggleHprSnap(self):
-        base.direct.grid.setHprSnap(self.hprSnap.get())
+        ShowBaseGlobal.direct.grid.setHprSnap(self.hprSnap.get())
 
 
     ## DEVICE CONTROLS
     ## DEVICE CONTROLS
     def toggleJoybox(self):
     def toggleJoybox(self):
         if self.enableJoybox.get():
         if self.enableJoybox.get():
-            base.direct.joybox.enable()
+            ShowBaseGlobal.direct.joybox.enable()
         else:
         else:
-            base.direct.joybox.disable()
+            ShowBaseGlobal.direct.joybox.disable()
 
 
     ## UPDATE INFO ##
     ## UPDATE INFO ##
     def updateInfo(self, page = 'Environment'):
     def updateInfo(self, page = 'Environment'):
@@ -920,7 +920,7 @@ class DirectSessionPanel(AppShell):
             self.updateGridInfo()
             self.updateGridInfo()
 
 
     def updateEnvironmentInfo(self):
     def updateEnvironmentInfo(self):
-        bkgrdColor = base.getBackgroundColor() * 255.0
+        bkgrdColor = ShowBaseGlobal.base.getBackgroundColor() * 255.0
         self.backgroundColor.set([bkgrdColor[0],
         self.backgroundColor.set([bkgrdColor[0],
                                   bkgrdColor[1],
                                   bkgrdColor[1],
                                   bkgrdColor[2],
                                   bkgrdColor[2],
@@ -936,6 +936,7 @@ class DirectSessionPanel(AppShell):
 
 
     def updateLightInfo(self, page = None):
     def updateLightInfo(self, page = None):
         # Set main lighting button
         # Set main lighting button
+        render = ShowBaseGlobal.base.render
         self.enableLights.set(
         self.enableLights.set(
             render.node().hasAttrib(LightAttrib.getClassType()))
             render.node().hasAttrib(LightAttrib.getClassType()))
 
 
@@ -974,16 +975,16 @@ class DirectSessionPanel(AppShell):
                 self.pQuadraticAttenuation.set(att[2], 0)
                 self.pQuadraticAttenuation.set(att[2], 0)
 
 
     def updateGridInfo(self):
     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
     # UNDO/REDO
     def pushUndo(self, fResetRedo = 1):
     def pushUndo(self, fResetRedo = 1):
-        base.direct.pushUndo([self['nodePath']])
+        ShowBaseGlobal.direct.pushUndo([self['nodePath']])
 
 
     def undoHook(self, nodePathList = []):
     def undoHook(self, nodePathList = []):
         pass
         pass
@@ -997,7 +998,7 @@ class DirectSessionPanel(AppShell):
         self.undoButton.configure(state = 'disabled')
         self.undoButton.configure(state = 'disabled')
 
 
     def pushRedo(self):
     def pushRedo(self):
-        base.direct.pushRedo([self['nodePath']])
+        ShowBaseGlobal.direct.pushRedo([self['nodePath']])
 
 
     def redoHook(self, nodePathList = []):
     def redoHook(self, nodePathList = []):
         pass
         pass

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

@@ -358,8 +358,7 @@ class FSMInspector(AppShell):
 
 
     def printLayout(self):
     def printLayout(self):
         dict = self.stateInspectorDict
         dict = self.stateInspectorDict
-        keys = list(dict.keys())
-        keys.sort()
+        keys = sorted(dict)
         print("ClassicFSM.ClassicFSM('%s', [" % self.name)
         print("ClassicFSM.ClassicFSM('%s', [" % self.name)
         for key in keys[:-1]:
         for key in keys[:-1]:
             si = dict[key]
             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.
 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']
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
 
 
@@ -31,6 +32,8 @@ def inspect(anObject):
 
 
 ### private
 ### private
 
 
+_InspectorMap: dict[str, str]
+
 
 
 def inspectorFor(anObject):
 def inspectorFor(anObject):
     typeName = type(anObject).__name__.capitalize() + 'Type'
     typeName = type(anObject).__name__.capitalize() + 'Type'
@@ -95,9 +98,7 @@ class Inspector:
 
 
     def initializePartsList(self):
     def initializePartsList(self):
         self._partsList = []
         self._partsList = []
-        keys = self.namedParts()
-        keys.sort()
-        for each in keys:
+        for each in sorted(self.namedParts()):
             self._partsList.append(each)
             self._partsList.append(each)
             #if not callable(getattr(self.object, each)):
             #if not callable(getattr(self.object, each)):
             #    self._partsList.append(each)
             #    self._partsList.append(each)
@@ -202,9 +203,7 @@ class DictionaryInspector(Inspector):
 
 
     def initializePartsList(self):
     def initializePartsList(self):
         Inspector.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)
             self._partsList.append(each)
 
 
     def partNumber(self, partNumber):
     def partNumber(self, partNumber):
@@ -400,7 +399,8 @@ class InspectorWindow:
         self.listWidget.component('listbox').focus_set()
         self.listWidget.component('listbox').focus_set()
 
 
     def showHelp(self):
     def showHelp(self):
-        help = tk.Toplevel(base.tkRoot)
+        from direct.showbase import ShowBaseGlobal
+        help = tk.Toplevel(ShowBaseGlobal.base.tkRoot)
         help.title("Inspector Help")
         help.title("Inspector Help")
         frame = tk.Frame(help)
         frame = tk.Frame(help)
         frame.pack()
         frame.pack()

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

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