Browse Source

Merge branch 'shaderpipeline' into vulkan

rdb 1 year ago
parent
commit
a6f0843d8c
100 changed files with 1956 additions and 2322 deletions
  1. 87 65
      .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. 7 1
      BACKERS.md
  7. 1 0
      CMakeLists.txt
  8. 6 6
      README.md
  9. 6 1
      cmake/install/Panda3DConfig.cmake
  10. 11 6
      cmake/macros/Interrogate.cmake
  11. 3 3
      contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py
  12. 1 1
      contrib/src/panda3dtoolsgui/setup.py
  13. 1 4
      contrib/src/sceneeditor/seCameraControl.py
  14. 2 8
      contrib/src/sceneeditor/seManipulation.py
  15. 17 18
      direct/src/actor/Actor.py
  16. 8 9
      direct/src/cluster/ClusterClient.py
  17. 1 1
      direct/src/cluster/ClusterMsgs.py
  18. 6 8
      direct/src/cluster/ClusterServer.py
  19. 4 0
      direct/src/dcparse/CMakeLists.txt
  20. 41 41
      direct/src/dcparser/dcClass_ext.cxx
  21. 3 2
      direct/src/dcparser/dcPacker_ext.cxx
  22. 438 312
      direct/src/dcparser/dcParser.cxx.prebuilt
  23. 61 45
      direct/src/dcparser/dcParser.h.prebuilt
  24. 5 1
      direct/src/dcparser/dcParser.yxx
  25. 1 1
      direct/src/directbase/DirectStart.py
  26. 1 1
      direct/src/directbase/TestStart.py
  27. 1 1
      direct/src/directbase/ThreeUpStart.py
  28. 6 2
      direct/src/directdevices/DirectJoybox.py
  29. 35 27
      direct/src/directnotify/DirectNotify.py
  30. 14 10
      direct/src/directnotify/Logger.py
  31. 46 41
      direct/src/directnotify/Notifier.py
  32. 33 23
      direct/src/directnotify/RotatingLog.py
  33. 187 166
      direct/src/directtools/DirectCameraControl.py
  34. 4 3
      direct/src/directtools/DirectGrid.py
  35. 1 3
      direct/src/directtools/DirectLights.py
  36. 204 180
      direct/src/directtools/DirectManipulation.py
  37. 59 39
      direct/src/directtools/DirectSession.py
  38. 60 34
      direct/src/dist/FreezeTool.py
  39. 16 0
      direct/src/dist/_dist_hooks.py
  40. 26 26
      direct/src/dist/commands.py
  41. 3 1
      direct/src/distributed/AsyncRequest.py
  42. 0 38
      direct/src/distributed/CRDataCache.py
  43. 7 0
      direct/src/distributed/CachedDOData.py
  44. 3 0
      direct/src/distributed/ClientRepositoryBase.py
  45. 31 30
      direct/src/distributed/ConnectionRepository.py
  46. 1 1
      direct/src/distributed/DistributedCartesianGrid.py
  47. 2 2
      direct/src/distributed/DistributedObjectAI.py
  48. 1 1
      direct/src/distributed/DistributedObjectUD.py
  49. 2 8
      direct/src/distributed/DoCollectionManager.py
  50. 5 4
      direct/src/distributed/DoInterestManager.py
  51. 4 2
      direct/src/distributed/GridParent.py
  52. 142 137
      direct/src/distributed/MsgTypes.py
  53. 15 18
      direct/src/distributed/MsgTypesCMU.py
  54. 7 5
      direct/src/distributed/ServerRepository.py
  55. 19 25
      direct/src/distributed/cConnectionRepository.cxx
  56. 12 4
      direct/src/extensions_native/NodePath_extensions.py
  57. 3 1
      direct/src/fsm/ClassicFSM.py
  58. 3 1
      direct/src/fsm/State.py
  59. 4 153
      direct/src/fsm/StatePush.py
  60. 3 1
      direct/src/gui/DirectDialog.py
  61. 1 1
      direct/src/gui/DirectEntry.py
  62. 10 5
      direct/src/gui/DirectGuiBase.py
  63. 1 1
      direct/src/gui/DirectGuiGlobals.py
  64. 1 1
      direct/src/gui/OnscreenText.py
  65. 3 7
      direct/src/interval/FunctionInterval.py
  66. 0 222
      direct/src/interval/IntervalTest.py
  67. 5 1
      direct/src/interval/LerpInterval.py
  68. 3 4
      direct/src/interval/cInterval_ext.cxx
  69. 1 3
      direct/src/leveleditor/HotKeyUI.py
  70. 1 1
      direct/src/leveleditor/LevelEditorStart.py
  71. 28 27
      direct/src/leveleditor/LevelEditorUIBase.py
  72. 3 0
      direct/src/leveleditor/LevelLoader.py
  73. 2 1
      direct/src/leveleditor/testData.py
  74. 3 1
      direct/src/motiontrail/MotionTrail.py
  75. 1 1
      direct/src/particles/ParticleEffect.py
  76. 1 3
      direct/src/showbase/BulletinBoard.py
  77. 4 3
      direct/src/showbase/ContainerLeakDetector.py
  78. 7 10
      direct/src/showbase/ContainerReport.py
  79. 0 144
      direct/src/showbase/CountedResource.py
  80. 1 1
      direct/src/showbase/DConfig.py
  81. 3 29
      direct/src/showbase/DistancePhasedNode.py
  82. 5 14
      direct/src/showbase/ExceptionVarDump.py
  83. 1 2
      direct/src/showbase/GarbageReport.py
  84. 1 3
      direct/src/showbase/JobManager.py
  85. 11 8
      direct/src/showbase/Loader.py
  86. 4 12
      direct/src/showbase/Messenger.py
  87. 3 11
      direct/src/showbase/ObjectPool.py
  88. 4 2
      direct/src/showbase/ObjectReport.py
  89. 1 3
      direct/src/showbase/OnScreenDebug.py
  90. 53 97
      direct/src/showbase/PythonUtil.py
  91. 22 14
      direct/src/showbase/ShowBase.py
  92. 8 3
      direct/src/showbase/ShowBaseGlobal.py
  93. 2 1
      direct/src/showbase/TkGlobal.py
  94. 50 33
      direct/src/showbase/Transitions.py
  95. 1 1
      direct/src/showbase/VerboseImport.py
  96. 2 1
      direct/src/showbase/WxGlobal.py
  97. 2 4
      direct/src/showbase/extend_frozen.c
  98. 8 6
      direct/src/showutil/TexMemWatcher.py
  99. 1 1
      direct/src/showutil/TexViewer.py
  100. 2 2
      direct/src/stdpy/pickle.py

+ 87 - 65
.github/workflows/ci.yml

@@ -40,7 +40,7 @@ jobs:
           eigen: NO
           eigen: NO
 
 
         - profile: macos-eigen-coverage-unity-xcode
         - profile: macos-eigen-coverage-unity-xcode
-          os: macOS-11
+          os: macOS-12
           config: Coverage
           config: Coverage
           unity: YES
           unity: YES
           generator: Xcode
           generator: Xcode
@@ -50,7 +50,7 @@ jobs:
           eigen: YES
           eigen: YES
 
 
         - profile: macos-nometa-standard-makefile
         - profile: macos-nometa-standard-makefile
-          os: macOS-11
+          os: macOS-12
           config: Standard
           config: Standard
           unity: NO
           unity: NO
           generator: Unix Makefiles
           generator: Unix Makefiles
@@ -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
@@ -361,7 +361,7 @@ jobs:
     if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
     if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
     strategy:
     strategy:
       matrix:
       matrix:
-        os: [ubuntu-20.04, windows-2019, macOS-11]
+        os: [ubuntu-20.04, windows-2019, macOS-12]
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
     - uses: actions/checkout@v1
     - uses: actions/checkout@v1
@@ -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,31 @@ 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
-      shell: bash
+        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: |
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        sudo apt-get update
+        sudo apt-get install build-essential ninja-build bison flex
 
 
-    - 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

+ 7 - 1
BACKERS.md

@@ -4,10 +4,11 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 
 ## Bronze Sponsors
 ## Bronze Sponsors
 
 
-[<img src="https://www.panda3d.org/wp-content/uploads/2021/02/changecrab_logo.png" alt="ChangeCrab" height="48">](https://changecrab.com/) ![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
+[<img src="https://www.panda3d.org/wp-content/uploads/2024/08/Route4MeLogo1185x300-2-1-1024x259.png" alt="Route4Me" height="48">](https://route4me.com/)
 
 
 * [Daniel Stokes](https://opencollective.com/daniel-stokes)
 * [Daniel Stokes](https://opencollective.com/daniel-stokes)
 * [David Rose](https://opencollective.com/david-rose)
 * [David Rose](https://opencollective.com/david-rose)
+* [Route4Me](https://route4me.com/)
 
 
 ## Benefactors
 ## Benefactors
 
 
@@ -17,6 +18,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 +31,9 @@ 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
+* dabe
 
 
 ## Backers
 ## Backers
 
 

+ 1 - 0
CMakeLists.txt

@@ -109,6 +109,7 @@ option(BUILD_DIRECT "Build the direct source tree." ON)
 option(BUILD_PANDATOOL "Build the pandatool source tree." ON)
 option(BUILD_PANDATOOL "Build the pandatool source tree." ON)
 option(BUILD_CONTRIB "Build the contrib source tree." ON)
 option(BUILD_CONTRIB "Build the contrib source tree." ON)
 option(BUILD_MODELS "Build/install the built-in models." ON)
 option(BUILD_MODELS "Build/install the built-in models." ON)
+option(BUILD_TOOLS "Build binary tools." ON)
 
 
 # Include Panda3D packages
 # Include Panda3D packages
 if(BUILD_DTOOL)
 if(BUILD_DTOOL)

+ 6 - 6
README.md

@@ -2,11 +2,11 @@
 [![OpenCollective](https://opencollective.com/panda3d/backers/badge.svg)](https://opencollective.com/panda3d)
 [![OpenCollective](https://opencollective.com/panda3d/backers/badge.svg)](https://opencollective.com/panda3d)
 [![OpenCollective](https://opencollective.com/panda3d/sponsors/badge.svg)](https://opencollective.com/panda3d)
 [![OpenCollective](https://opencollective.com/panda3d/sponsors/badge.svg)](https://opencollective.com/panda3d)
 
 
-<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=500" align="right" width="200"/>
-
 Panda3D
 Panda3D
 =======
 =======
 
 
+<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=500" align="right" width="200" />
+
 Panda3D is a game engine, a framework for 3D rendering and game development for
 Panda3D is a game engine, a framework for 3D rendering and game development for
 Python and C++ programs.  Panda3D is open-source and free for any purpose,
 Python and C++ programs.  Panda3D is open-source and free for any purpose,
 including commercial ventures, thanks to its
 including commercial ventures, thanks to its
@@ -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
@@ -168,7 +168,7 @@ Install the appropriate package for it (such as `python37` or `python38`) and
 run the makepanda script with your chosen Python version:
 run the makepanda script with your chosen Python version:
 
 
 ```bash
 ```bash
-python3.7 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
+python3.11 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
 ```
 ```
 
 
 If successful, this will produce a .pkg file in the root of the source
 If successful, this will produce a .pkg file in the root of the source

+ 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
 )
 )
 
 

+ 11 - 6
cmake/macros/Interrogate.cmake

@@ -257,7 +257,7 @@ function(interrogate_sources target output database language_flags)
       make_directory "${output_directory}"
       make_directory "${output_directory}"
     COMMAND ${CMAKE_COMMAND} -E
     COMMAND ${CMAKE_COMMAND} -E
       make_directory "${database_directory}"
       make_directory "${database_directory}"
-    COMMAND host_interrogate
+    COMMAND interrogate
       -oc "${output}"
       -oc "${output}"
       -od "${database}"
       -od "${database}"
       -srcdir "${srcdir}"
       -srcdir "${srcdir}"
@@ -272,7 +272,7 @@ function(interrogate_sources target output database language_flags)
       ${include_flags}
       ${include_flags}
       ${scan_sources}
       ${scan_sources}
 
 
-    DEPENDS host_interrogate ${sources} ${extensions} ${nfiles}
+    DEPENDS interrogate ${sources} ${extensions} ${nfiles}
     COMMENT "Interrogating ${target}")
     COMMENT "Interrogating ${target}")
 
 
   # Propagate the target's compile definitions to the output file
   # Propagate the target's compile definitions to the output file
@@ -341,10 +341,15 @@ function(add_python_module module)
     # Keep command lines short
     # Keep command lines short
     file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
     file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
 
 
+    get_target_property(target_module "${target}" IGATE_MODULE)
+    if(NOT target_module)
+      set(target_module "${module}")
+    endif()
+
     interrogate_sources(${target}
     interrogate_sources(${target}
       "${workdir_abs}/${target}_igate.cxx"
       "${workdir_abs}/${target}_igate.cxx"
       "${workdir_abs}/${target}.in"
       "${workdir_abs}/${target}.in"
-      "-python-native;-module;${module}")
+      "-python-native;-module;${target_module}")
 
 
     get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
     get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
     list(APPEND infiles_rel "${workdir_rel}/${target}.in")
     list(APPEND infiles_rel "${workdir_rel}/${target}.in")
@@ -357,13 +362,13 @@ function(add_python_module module)
     OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
     OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
     COMMAND ${CMAKE_COMMAND} -E
     COMMAND ${CMAKE_COMMAND} -E
       make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
       make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
-    COMMAND host_interrogate_module
+    COMMAND interrogate_module
       -oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
       -oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
-      -module ${modname} -library ${modname}
+      -module ${module} -library ${modname}
       ${import_flags}
       ${import_flags}
       ${INTERROGATE_MODULE_OPTIONS}
       ${INTERROGATE_MODULE_OPTIONS}
       ${IMOD_FLAGS} ${infiles_rel}
       ${IMOD_FLAGS} ${infiles_rel}
-    DEPENDS host_interrogate_module ${infiles_abs}
+    DEPENDS interrogate_module ${infiles_abs}
     COMMENT "Generating module ${module}")
     COMMENT "Generating module ${module}")
 
 
   # CMake chokes on ${CMAKE_CFG_INTDIR} in source paths when unity builds are
   # CMake chokes on ${CMAKE_CFG_INTDIR} in source paths when unity builds are

+ 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

+ 17 - 18
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):
         """
         """
@@ -746,6 +737,13 @@ class Actor(DirectObject, NodePath):
         else:
         else:
             return None
             return None
 
 
+    def getPlayMode(self, animName=None, partName=None):
+        if self.__animControlDict:
+            controls = self.getAnimControls(animName, partName, onlyPlaying=False)
+            if controls:
+                return controls[0].getPlayMode()
+        return None
+
     def hasLOD(self):
     def hasLOD(self):
         """
         """
         Return 1 if the actor has LODs, 0 otherwise
         Return 1 if the actor has LODs, 0 otherwise
@@ -1771,7 +1769,7 @@ class Actor(DirectObject, NodePath):
         return None
         return None
 
 
     def getAnimControls(self, animName=None, partName=None, lodName=None,
     def getAnimControls(self, animName=None, partName=None, lodName=None,
-                        allowAsyncBind = True):
+                        allowAsyncBind = True, onlyPlaying = True):
         """getAnimControls(self, string, string=None, string=None)
         """getAnimControls(self, string, string=None, string=None)
 
 
         Returns a list of the AnimControls that represent the given
         Returns a list of the AnimControls that represent the given
@@ -1849,7 +1847,7 @@ class Actor(DirectObject, NodePath):
                 # get all playing animations
                 # get all playing animations
                 for thisPart, animDict in animDictItems:
                 for thisPart, animDict in animDictItems:
                     for anim in animDict.values():
                     for anim in animDict.values():
-                        if anim.animControl and anim.animControl.isPlaying():
+                        if anim.animControl and (not onlyPlaying or anim.animControl.isPlaying()):
                             controls.append(anim.animControl)
                             controls.append(anim.animControl)
             else:
             else:
                 # get the named animation(s) only.
                 # get the named animation(s) only.
@@ -2669,3 +2667,4 @@ class Actor(DirectObject, NodePath):
     get_base_frame_rate = getBaseFrameRate
     get_base_frame_rate = getBaseFrameRate
     remove_anim_control_dict = removeAnimControlDict
     remove_anim_control_dict = removeAnimControlDict
     load_anims_on_all_lods = loadAnimsOnAllLODs
     load_anims_on_all_lods = loadAnimsOnAllLODs
+    get_play_mode = getPlayMode

+ 8 - 9
direct/src/cluster/ClusterClient.py

@@ -2,14 +2,16 @@
 
 
 from panda3d.core import (
 from panda3d.core import (
     ClockObject,
     ClockObject,
-    ConnectionWriter,
     Point3,
     Point3,
-    QueuedConnectionManager,
-    QueuedConnectionReader,
     VBase3,
     VBase3,
     Vec3,
     Vec3,
     decomposeMatrix,
     decomposeMatrix,
 )
 )
+from panda3d.net import (
+    ConnectionWriter,
+    QueuedConnectionManager,
+    QueuedConnectionReader,
+)
 from .ClusterMsgs import (
 from .ClusterMsgs import (
     CLUSTER_DAEMON_PORT,
     CLUSTER_DAEMON_PORT,
     CLUSTER_NAMED_MOVEMENT_DONE,
     CLUSTER_NAMED_MOVEMENT_DONE,
@@ -169,12 +171,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)

+ 1 - 1
direct/src/cluster/ClusterMsgs.py

@@ -3,7 +3,7 @@
 # This module is intended to supply routines and dataformats common to
 # This module is intended to supply routines and dataformats common to
 # both ClusterClient and ClusterServer.
 # both ClusterClient and ClusterServer.
 
 
-from panda3d.core import NetDatagram
+from panda3d.net import NetDatagram
 from direct.distributed.PyDatagram import PyDatagram
 from direct.distributed.PyDatagram import PyDatagram
 from direct.distributed.PyDatagramIterator import PyDatagramIterator
 from direct.distributed.PyDatagramIterator import PyDatagramIterator
 import time
 import time

+ 6 - 8
direct/src/cluster/ClusterServer.py

@@ -1,12 +1,14 @@
 from panda3d.core import (
 from panda3d.core import (
     ClockObject,
     ClockObject,
+    Vec3,
+)
+from panda3d.net import (
     ConnectionWriter,
     ConnectionWriter,
     NetAddress,
     NetAddress,
     PointerToConnection,
     PointerToConnection,
     QueuedConnectionListener,
     QueuedConnectionListener,
     QueuedConnectionManager,
     QueuedConnectionManager,
     QueuedConnectionReader,
     QueuedConnectionReader,
-    Vec3,
 )
 )
 from .ClusterMsgs import (
 from .ClusterMsgs import (
     CLUSTER_CAM_FRUSTUM,
     CLUSTER_CAM_FRUSTUM,
@@ -137,13 +139,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):

+ 4 - 0
direct/src/dcparse/CMakeLists.txt

@@ -1,3 +1,7 @@
+if(NOT BUILD_TOOLS)
+  return()
+endif()
+
 add_executable(p3dcparse dcparse.cxx)
 add_executable(p3dcparse dcparse.cxx)
 target_link_libraries(p3dcparse p3direct)
 target_link_libraries(p3dcparse p3direct)
 install(TARGETS p3dcparse EXPORT Direct COMPONENT Direct DESTINATION ${CMAKE_INSTALL_BINDIR})
 install(TARGETS p3dcparse EXPORT Direct COMPONENT Direct DESTINATION ${CMAKE_INSTALL_BINDIR})

+ 41 - 41
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);
 }
 }
 
 
 /**
 /**
@@ -530,31 +522,35 @@ client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
   }
   }
 
 
   // Also specify the optional fields.
   // Also specify the optional fields.
-  int num_optional_fields = 0;
   if (PyObject_IsTrue(optional_fields)) {
   if (PyObject_IsTrue(optional_fields)) {
-    num_optional_fields = PySequence_Size(optional_fields);
-  }
-  packer.raw_pack_uint16(num_optional_fields);
-
-  for (int i = 0; i < num_optional_fields; i++) {
-    PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-    std::string field_name = PyUnicode_AsUTF8(py_field_name);
-    Py_XDECREF(py_field_name);
-
-    DCField *field = _this->get_field_by_name(field_name);
-    if (field == nullptr) {
-      std::ostringstream strm;
-      strm << "No field named " << field_name << " in class " << _this->get_name()
-           << "\n";
-      nassert_raise(strm.str());
-      return Datagram();
-    }
-    packer.raw_pack_uint16(field->get_number());
-    packer.begin_pack(field);
-    if (!pack_required_field(packer, distobj, field)) {
-      return Datagram();
+    optional_fields = PySequence_Tuple(optional_fields);
+    Py_ssize_t num_optional_fields = PyTuple_GET_SIZE(optional_fields);
+    packer.raw_pack_uint16(num_optional_fields);
+
+    for (Py_ssize_t i = 0; i < num_optional_fields; i++) {
+      PyObject *py_field_name = PyTuple_GET_ITEM(optional_fields, i);
+      std::string field_name = PyUnicode_AsUTF8(py_field_name);
+
+      DCField *field = _this->get_field_by_name(field_name);
+      if (field == nullptr) {
+        std::ostringstream strm;
+        strm << "No field named " << field_name << " in class " << _this->get_name()
+             << "\n";
+        nassert_raise(strm.str());
+        Py_DECREF(optional_fields);
+        return Datagram();
+      }
+      packer.raw_pack_uint16(field->get_number());
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        Py_DECREF(optional_fields);
+        return Datagram();
+      }
+      packer.end_pack();
     }
     }
-    packer.end_pack();
+    Py_DECREF(optional_fields);
+  } else {
+    packer.raw_pack_uint16(0);
   }
   }
 
 
   return Datagram(packer.get_data(), packer.get_length());
   return Datagram(packer.get_data(), packer.get_length());
@@ -610,13 +606,13 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
 
 
   // Also specify the optional fields.
   // Also specify the optional fields.
   if (has_optional_fields) {
   if (has_optional_fields) {
-    int num_optional_fields = PySequence_Size(optional_fields);
+    optional_fields = PySequence_Tuple(optional_fields);
+    Py_ssize_t num_optional_fields = PyTuple_GET_SIZE(optional_fields);
     packer.raw_pack_uint16(num_optional_fields);
     packer.raw_pack_uint16(num_optional_fields);
 
 
-    for (int i = 0; i < num_optional_fields; ++i) {
-      PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
+    for (Py_ssize_t i = 0; i < num_optional_fields; ++i) {
+      PyObject *py_field_name = PyTuple_GET_ITEM(optional_fields, i);
       std::string field_name = PyUnicode_AsUTF8(py_field_name);
       std::string field_name = PyUnicode_AsUTF8(py_field_name);
-      Py_XDECREF(py_field_name);
 
 
       DCField *field = _this->get_field_by_name(field_name);
       DCField *field = _this->get_field_by_name(field_name);
       if (field == nullptr) {
       if (field == nullptr) {
@@ -624,6 +620,7 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
         strm << "No field named " << field_name << " in class "
         strm << "No field named " << field_name << " in class "
              << _this->get_name() << "\n";
              << _this->get_name() << "\n";
         nassert_raise(strm.str());
         nassert_raise(strm.str());
+        Py_DECREF(optional_fields);
         return Datagram();
         return Datagram();
       }
       }
 
 
@@ -631,10 +628,13 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
 
 
       packer.begin_pack(field);
       packer.begin_pack(field);
       if (!pack_required_field(packer, distobj, field)) {
       if (!pack_required_field(packer, distobj, field)) {
+        Py_DECREF(optional_fields);
         return Datagram();
         return Datagram();
       }
       }
       packer.end_pack();
       packer.end_pack();
     }
     }
+
+    Py_DECREF(optional_fields);
   }
   }
 
 
   return Datagram(packer.get_data(), packer.get_length());
   return Datagram(packer.get_data(), packer.get_length());

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

@@ -136,6 +136,7 @@ pack_object(PyObject *object) {
       // The supplied object is not an instance of the expected class object,
       // The supplied object is not an instance of the expected class object,
       // but it is a sequence.  This is case (2).
       // but it is a sequence.  This is case (2).
       _this->push();
       _this->push();
+      Py_BEGIN_CRITICAL_SECTION(object);
       int size = PySequence_Size(object);
       int size = PySequence_Size(object);
       for (int i = 0; i < size; ++i) {
       for (int i = 0; i < size; ++i) {
         PyObject *element = PySequence_GetItem(object, i);
         PyObject *element = PySequence_GetItem(object, i);
@@ -146,6 +147,7 @@ pack_object(PyObject *object) {
           std::cerr << "Unable to extract item " << i << " from sequence.\n";
           std::cerr << "Unable to extract item " << i << " from sequence.\n";
         }
         }
       }
       }
+      Py_END_CRITICAL_SECTION();
       _this->pop();
       _this->pop();
     } else {
     } else {
       // The supplied object is not a sequence, and we weren't expecting a
       // The supplied object is not a sequence, and we weren't expecting a
@@ -173,8 +175,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.

+ 16 - 0
direct/src/dist/_dist_hooks.py

@@ -0,0 +1,16 @@
+# This module should not import Panda3D modules globally as it contains hooks
+# that may be invoked by setuptools even when Panda3D is not used.  If the
+# Panda3D installation is broken, it should not affect other applications.
+
+__all__ = ('finalize_distribution_options', )
+
+
+def finalize_distribution_options(dist):
+    """Entry point for compatibility with setuptools>=61, see #1394."""
+
+    options = dist.get_option_dict('build_apps')
+    if options.get('gui_apps') or options.get('console_apps'):
+        # Make sure this is set to avoid auto-discovery taking place.
+        if getattr(dist.metadata, 'py_modules', None) is None and \
+           getattr(dist.metadata, 'packages', None) is None:
+            dist.py_modules = []

+ 26 - 26
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
 
 
@@ -24,6 +23,7 @@ from . import FreezeTool
 from . import pefile
 from . import pefile
 from . import installers
 from . import installers
 from .icon import Icon
 from .icon import Icon
+from ._dist_hooks import finalize_distribution_options
 import panda3d.core as p3d
 import panda3d.core as p3d
 
 
 
 
@@ -55,10 +55,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 +472,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 +725,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 +868,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 +1076,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 +1707,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 +1721,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
 
 
@@ -1763,14 +1774,3 @@ class bdist_apps(setuptools.Command):
                     continue
                     continue
 
 
                 self.installer_functions[installer](self, basename, build_dir)
                 self.installer_functions[installer](self, basename, build_dir)
-
-
-def finalize_distribution_options(dist):
-    """Entry point for compatibility with setuptools>=61, see #1394."""
-
-    options = dist.get_option_dict('build_apps')
-    if options.get('gui_apps') or options.get('console_apps'):
-        # Make sure this is set to avoid auto-discovery taking place.
-        if getattr(dist.metadata, 'py_modules', None) is None and \
-           getattr(dist.metadata, 'packages', None) is None:
-            dist.py_modules = []

+ 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())

+ 31 - 30
direct/src/distributed/ConnectionRepository.py

@@ -174,35 +174,6 @@ class ConnectionRepository(
         return retVal
         return retVal
 
 
     def generateGlobalObject(self, doId, dcname, values=None):
     def generateGlobalObject(self, doId, dcname, values=None):
-        def applyFieldValues(distObj, dclass, values):
-            for i in range(dclass.getNumInheritedFields()):
-                field = dclass.getInheritedField(i)
-                if field.asMolecularField() is None:
-                    value = values.get(field.getName(), None)
-                    if value is None and field.isRequired():
-                        # Gee, this could be better.  What would really be
-                        # nicer is to get value from field.getDefaultValue
-                        # or similar, but that returns a binary string, not
-                        # a python tuple, like the following does.  If you
-                        # want to change something better, please go ahead.
-                        packer = DCPacker()
-                        packer.beginPack(field)
-                        packer.packDefaultValue()
-                        packer.endPack()
-
-                        unpacker = DCPacker()
-                        unpacker.setUnpackData(packer.getString())
-                        unpacker.beginUnpack(field)
-                        value = unpacker.unpackObject()
-                        unpacker.endUnpack()
-                    if value is not None:
-                        function = getattr(distObj, field.getName())
-                        if function is not None:
-                            function(*value)
-                        else:
-                            self.notify.error("\n\n\nNot able to find %s.%s"%(
-                                distObj.__class__.__name__, field.getName()))
-
         # Look up the dclass
         # Look up the dclass
         dclass = self.dclassesByName.get(dcname+self.dcSuffix)
         dclass = self.dclassesByName.get(dcname+self.dcSuffix)
         if dclass is None:
         if dclass is None:
@@ -229,7 +200,37 @@ class ConnectionRepository(
         distObj.generateInit()  # Only called when constructed
         distObj.generateInit()  # Only called when constructed
         distObj.generate()
         distObj.generate()
         if values is not None:
         if values is not None:
-            applyFieldValues(distObj, dclass, values)
+            for i in range(dclass.getNumInheritedFields()):
+                field = dclass.getInheritedField(i)
+                if field.asMolecularField() is None:
+                    value = values.get(field.getName(), None)
+                    if value is None and field.isRequired():
+                        # Gee, this could be better.  What would really be
+                        # nicer is to get value from field.getDefaultValue
+                        # or similar, but that returns a binary string, not
+                        # a python tuple, like the following does.  If you
+                        # want to change something better, please go ahead.
+                        packer = DCPacker()
+                        packer.beginPack(field)
+                        packer.packDefaultValue()
+                        packer.endPack()
+
+                        unpacker = DCPacker()
+                        unpacker.setUnpackData(packer.getString())
+                        unpacker.beginUnpack(field)
+                        value = unpacker.unpackObject()
+                        unpacker.endUnpack()
+                    if value is not None:
+                        function = getattr(distObj, field.getName())
+                        if function is not None:
+                            function(*value)
+                        else:
+                            self.notify.error(
+                                "\n\n\nNot able to find %s.%s" % (
+                                    distObj.__class__.__name__,
+                                    field.getName()
+                                )
+                            )
         distObj.announceGenerate()
         distObj.announceGenerate()
         distObj.parentId = 0
         distObj.parentId = 0
         distObj.zoneId = 0
         distObj.zoneId = 0

+ 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)

+ 7 - 5
direct/src/distributed/ServerRepository.py

@@ -4,19 +4,21 @@ from panda3d.core import (
     ConfigVariableBool,
     ConfigVariableBool,
     ConfigVariableDouble,
     ConfigVariableDouble,
     ConfigVariableInt,
     ConfigVariableInt,
-    ConnectionWriter,
     DatagramIterator,
     DatagramIterator,
     Filename,
     Filename,
+    TPLow,
+    UniqueIdAllocator,
+    VirtualFileSystem,
+    getModelPath,
+)
+from panda3d.net import (
+    ConnectionWriter,
     NetAddress,
     NetAddress,
     NetDatagram,
     NetDatagram,
     PointerToConnection,
     PointerToConnection,
     QueuedConnectionListener,
     QueuedConnectionListener,
     QueuedConnectionManager,
     QueuedConnectionManager,
     QueuedConnectionReader,
     QueuedConnectionReader,
-    TPLow,
-    UniqueIdAllocator,
-    VirtualFileSystem,
-    getModelPath,
 )
 )
 from panda3d.direct import DCFile
 from panda3d.direct import DCFile
 from direct.distributed.MsgTypesCMU import (
 from direct.distributed.MsgTypesCMU import (

+ 19 - 25
direct/src/distributed/cConnectionRepository.cxx

@@ -688,11 +688,12 @@ handle_update_field() {
     nassertr(doId2do != nullptr, false);
     nassertr(doId2do != nullptr, false);
 
 
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
-    PyObject *distobj = PyDict_GetItem(doId2do, doId);
+    PyObject *distobj;
+    int result = PyDict_GetItemRef(doId2do, doId, &distobj);
     Py_DECREF(doId);
     Py_DECREF(doId);
     Py_DECREF(doId2do);
     Py_DECREF(doId2do);
 
 
-    if (distobj != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       nassertr(dclass_obj != nullptr, false);
       nassertr(dclass_obj != nullptr, false);
 
 
@@ -711,9 +712,11 @@ handle_update_field() {
         nassertr(neverDisable != nullptr, false);
         nassertr(neverDisable != nullptr, false);
 
 
         unsigned int cNeverDisable = PyLong_AsLong(neverDisable);
         unsigned int cNeverDisable = PyLong_AsLong(neverDisable);
+        Py_DECREF(neverDisable);
         if (!cNeverDisable) {
         if (!cNeverDisable) {
           // in quiet zone and distobj is disable-able drop update on the
           // in quiet zone and distobj is disable-able drop update on the
           // floor
           // floor
+          Py_DECREF(distobj);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
           PyGILState_Release(gstate);
 #endif
 #endif
@@ -721,11 +724,6 @@ handle_update_field() {
         }
         }
       }
       }
 
 
-      // It's a good idea to ensure the reference count to distobj is raised
-      // while we call the update method--otherwise, the update method might
-      // get into trouble if it tried to delete the object from the doId2do
-      // map.
-      Py_INCREF(distobj);
       invoke_extension(dclass).receive_update(distobj, _di);
       invoke_extension(dclass).receive_update(distobj, _di);
       Py_DECREF(distobj);
       Py_DECREF(distobj);
 
 
@@ -777,10 +775,11 @@ handle_update_field_owner() {
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
     PyObject *doId = PyLong_FromUnsignedLong(do_id);
 
 
     // pass the update to the owner view first
     // pass the update to the owner view first
-    PyObject *distobjOV = PyDict_GetItem(doId2ownerView, doId);
+    PyObject *distobjOV;
+    int result = PyDict_GetItemRef(doId2ownerView, doId, &distobjOV);
     Py_DECREF(doId2ownerView);
     Py_DECREF(doId2ownerView);
 
 
-    if (distobjOV != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
       PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
       nassertr(dclass_obj != nullptr, false);
       nassertr(dclass_obj != nullptr, false);
 
 
@@ -798,32 +797,29 @@ handle_update_field_owner() {
       int field_id = packer.raw_unpack_uint16();
       int field_id = packer.raw_unpack_uint16();
       DCField *field = dclass->get_field_by_index(field_id);
       DCField *field = dclass->get_field_by_index(field_id);
       if (field->is_ownrecv()) {
       if (field->is_ownrecv()) {
-        // It's a good idea to ensure the reference count to distobjOV is
-        // raised while we call the update method--otherwise, the update
-        // method might get into trouble if it tried to delete the object from
-        // the doId2do map.
-        Py_INCREF(distobjOV);
         // 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);
         invoke_extension(dclass).receive_update(distobjOV, _odi);
-        Py_DECREF(distobjOV);
 
 
         if (PyErr_Occurred()) {
         if (PyErr_Occurred()) {
+          Py_DECREF(distobjOV);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
           PyGILState_Release(gstate);
 #endif
 #endif
           return false;
           return false;
         }
         }
       }
       }
+      Py_DECREF(distobjOV);
     }
     }
 
 
     // now pass the update to the visible view
     // now pass the update to the visible view
-    PyObject *distobj = PyDict_GetItem(doId2do, doId);
+    PyObject *distobj;
+    result = PyDict_GetItemRef(doId2do, doId, &distobj);
     Py_DECREF(doId);
     Py_DECREF(doId);
     Py_DECREF(doId2do);
     Py_DECREF(doId2do);
 
 
-    if (distobj != nullptr) {
+    if (result > 0) {
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
       nassertr(dclass_obj != nullptr, false);
       nassertr(dclass_obj != nullptr, false);
 
 
@@ -842,21 +838,17 @@ handle_update_field_owner() {
       //int field_id = packer.raw_unpack_uint16();
       //int field_id = packer.raw_unpack_uint16();
       //DCField *field = dclass->get_field_by_index(field_id);
       //DCField *field = dclass->get_field_by_index(field_id);
       if (true) {//field->is_broadcast()) {
       if (true) {//field->is_broadcast()) {
-        // It's a good idea to ensure the reference count to distobj is raised
-        // while we call the update method--otherwise, the update method might
-        // get into trouble if it tried to delete the object from the doId2do
-        // map.
-        Py_INCREF(distobj);
         invoke_extension(dclass).receive_update(distobj, _di);
         invoke_extension(dclass).receive_update(distobj, _di);
-        Py_DECREF(distobj);
 
 
         if (PyErr_Occurred()) {
         if (PyErr_Occurred()) {
+          Py_DECREF(distobj);
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
           PyGILState_Release(gstate);
           PyGILState_Release(gstate);
 #endif
 #endif
           return false;
           return false;
         }
         }
       }
       }
+      Py_DECREF(distobj);
     }
     }
   }
   }
 
 
@@ -956,12 +948,14 @@ describe_message(std::ostream &out, const string &prefix,
       nassertv(doId2do != nullptr);
       nassertv(doId2do != nullptr);
 
 
       PyObject *doId = PyLong_FromUnsignedLong(do_id);
       PyObject *doId = PyLong_FromUnsignedLong(do_id);
-      PyObject *distobj = PyDict_GetItem(doId2do, doId);
+      PyObject *distobj;
+      int result = PyDict_GetItemRef(doId2do, doId, &distobj);
       Py_DECREF(doId);
       Py_DECREF(doId);
       Py_DECREF(doId2do);
       Py_DECREF(doId2do);
 
 
-      if (distobj != nullptr) {
+      if (result > 0) {
         PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
         PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
+        Py_DECREF(distobj);
         nassertv(dclass_obj != nullptr);
         nassertv(dclass_obj != nullptr);
 
 
         PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
         PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");

+ 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()

+ 5 - 1
direct/src/interval/LerpInterval.py

@@ -841,8 +841,12 @@ class LerpFunctionInterval(Interval.Interval):
         self.extraArgs = extraArgs
         self.extraArgs = extraArgs
         # Generate unique name if necessary
         # Generate unique name if necessary
         if name is None:
         if name is None:
+            if hasattr(function, '__name__'):
+                name = function.__name__
+            else:
+                name = '<' + function.__class__.__name__ + '>'
             name = ('LerpFunctionInterval-%s-%d' %
             name = ('LerpFunctionInterval-%s-%d' %
-                    (function.__name__,
+                    (name,
                      LerpFunctionInterval.lerpFunctionIntervalNum))
                      LerpFunctionInterval.lerpFunctionIntervalNum))
             LerpFunctionInterval.lerpFunctionIntervalNum += 1
             LerpFunctionInterval.lerpFunctionIntervalNum += 1
         else:
         else:

+ 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()

+ 50 - 33
direct/src/showbase/Transitions.py

@@ -264,6 +264,54 @@ class Transitions:
             self.iris = base.loader.loadModel(self.IrisModelName)
             self.iris = base.loader.loadModel(self.IrisModelName)
             self.iris.setPos(0, 0, 0)
             self.iris.setPos(0, 0, 0)
 
 
+    def getIrisInIval(self, t=0.5, finishIval=None, blendType='noBlend'):
+        """
+        Returns an interval without starting it.  This is particularly useful in
+        cutscenes, so when the cutsceneIval is escaped out of we can finish the iris immediately
+        """
+        self.noTransitions()
+        self.loadIris()
+
+        scale = 0.18 * max(base.a2dRight, base.a2dTop)
+        transitionIval = Sequence(Func(self.iris.reparentTo, ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX),
+                                  LerpScaleInterval(self.iris, t,
+                                                    scale = scale,
+                                                    startScale = 0.01,
+                                                    blendType=blendType),
+                                 Func(self.iris.detachNode),
+                                 Func(self.__finishTransition),
+                                 name = self.irisTaskName,
+                                 )
+        if finishIval:
+            transitionIval.append(finishIval)
+        return transitionIval
+
+    def getIrisOutIval(self, t=0.5, finishIval=None, blendType='noBlend'):
+        """
+        Create a sequence that lerps the iris out, then
+        parents the iris to hidden
+        """
+        self.noTransitions()
+        self.loadIris()
+        self.loadFade()  # we need this to cover up the hole.
+
+        scale = 0.18 * max(base.a2dRight, base.a2dTop)
+        transitionIval = Sequence(Func(self.iris.reparentTo, ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX),
+                                  LerpScaleInterval(self.iris, t,
+                                                    scale = 0.01,
+                                                    startScale = scale,
+                                                    blendType=blendType),
+                                 Func(self.iris.detachNode),
+                                 # Use the fade to cover up the hole that the iris would leave
+                                 Func(self.fadeOut, 0),
+                                 Func(self.__finishTransition),
+                                 name = self.irisTaskName,
+                                 )
+
+        if finishIval:
+            transitionIval.append(finishIval)
+        return transitionIval
+
     def irisIn(self, t=0.5, finishIval=None, blendType = 'noBlend'):
     def irisIn(self, t=0.5, finishIval=None, blendType = 'noBlend'):
         """
         """
         Play an iris in transition over t seconds.
         Play an iris in transition over t seconds.
@@ -271,28 +319,14 @@ class Transitions:
         of the iris polygon up so it looks like we iris in. When the
         of the iris polygon up so it looks like we iris in. When the
         scale lerp is finished, it parents the iris polygon to hidden.
         scale lerp is finished, it parents the iris polygon to hidden.
         """
         """
-        self.noTransitions()
-        self.loadIris()
         if t == 0:
         if t == 0:
             self.iris.detachNode()
             self.iris.detachNode()
             fut = AsyncFuture()
             fut = AsyncFuture()
             fut.setResult(None)
             fut.setResult(None)
             return fut
             return fut
         else:
         else:
-            self.iris.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
-
-            scale = 0.18 * max(base.a2dRight, base.a2dTop)
-            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
-                                                   scale = scale,
-                                                   startScale = 0.01,
-                                                   blendType=blendType),
-                                 Func(self.iris.detachNode),
-                                 Func(self.__finishTransition),
-                                 name = self.irisTaskName,
-                                 )
+            self.transitionIval = self.getIrisInIval(t, finishIval, blendType)
             self.__transitionFuture = AsyncFuture()
             self.__transitionFuture = AsyncFuture()
-            if finishIval:
-                self.transitionIval.append(finishIval)
             self.transitionIval.start()
             self.transitionIval.start()
             return self.__transitionFuture
             return self.__transitionFuture
 
 
@@ -304,9 +338,6 @@ class Transitions:
         lerp is finished, it leaves the iris polygon covering the
         lerp is finished, it leaves the iris polygon covering the
         aspect2d plane until you irisIn or call noIris.
         aspect2d plane until you irisIn or call noIris.
         """
         """
-        self.noTransitions()
-        self.loadIris()
-        self.loadFade()  # we need this to cover up the hole.
         if t == 0:
         if t == 0:
             self.iris.detachNode()
             self.iris.detachNode()
             self.fadeOut(0)
             self.fadeOut(0)
@@ -314,22 +345,8 @@ class Transitions:
             fut.setResult(None)
             fut.setResult(None)
             return fut
             return fut
         else:
         else:
-            self.iris.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
-
-            scale = 0.18 * max(base.a2dRight, base.a2dTop)
-            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
-                                                   scale = 0.01,
-                                                   startScale = scale,
-                                                   blendType=blendType),
-                                 Func(self.iris.detachNode),
-                                 # Use the fade to cover up the hole that the iris would leave
-                                 Func(self.fadeOut, 0),
-                                 Func(self.__finishTransition),
-                                 name = self.irisTaskName,
-                                 )
+            self.transitionIval = self.getIrisOutIval(t, finishIval, blendType)
             self.__transitionFuture = AsyncFuture()
             self.__transitionFuture = AsyncFuture()
-            if finishIval:
-                self.transitionIval.append(finishIval)
             self.transitionIval.start()
             self.transitionIval.start()
             return self.__transitionFuture
             return self.__transitionFuture
 
 

+ 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()

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