소스 검색

Merge branch 'master' into webgl-port

rdb 2 년 전
부모
커밋
d93942bf3e
100개의 변경된 파일2205개의 추가작업 그리고 1463개의 파일을 삭제
  1. 55 9
      .github/workflows/ci.yml
  2. 2 2
      README.md
  3. 6 1
      cmake/install/Panda3DConfig.cmake
  4. 3 3
      contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py
  5. 1 1
      contrib/src/panda3dtoolsgui/setup.py
  6. 1 4
      contrib/src/sceneeditor/seCameraControl.py
  7. 2 8
      contrib/src/sceneeditor/seManipulation.py
  8. 35 27
      direct/src/directnotify/DirectNotify.py
  9. 14 10
      direct/src/directnotify/Logger.py
  10. 46 41
      direct/src/directnotify/Notifier.py
  11. 29 19
      direct/src/directnotify/RotatingLog.py
  12. 187 166
      direct/src/directtools/DirectCameraControl.py
  13. 4 3
      direct/src/directtools/DirectGrid.py
  14. 204 180
      direct/src/directtools/DirectManipulation.py
  15. 59 39
      direct/src/directtools/DirectSession.py
  16. 59 30
      direct/src/dist/FreezeTool.py
  17. 19 7
      direct/src/dist/commands.py
  18. 1 1
      direct/src/distributed/DistributedCartesianGrid.py
  19. 3 0
      direct/src/distributed/MsgTypes.py
  20. 12 4
      direct/src/extensions_native/NodePath_extensions.py
  21. 1 1
      direct/src/gui/DirectEntry.py
  22. 1 1
      direct/src/gui/OnscreenText.py
  23. 0 6
      direct/src/interval/FunctionInterval.py
  24. 0 222
      direct/src/interval/IntervalTest.py
  25. 28 27
      direct/src/leveleditor/LevelEditorUIBase.py
  26. 3 0
      direct/src/leveleditor/LevelLoader.py
  27. 1 1
      direct/src/particles/ParticleEffect.py
  28. 11 8
      direct/src/showbase/Loader.py
  29. 18 11
      direct/src/showbase/ShowBase.py
  30. 4 1
      direct/src/showbase/ShowBaseGlobal.py
  31. 2 1
      direct/src/showbase/TkGlobal.py
  32. 2 1
      direct/src/showbase/WxGlobal.py
  33. 6 4
      direct/src/showutil/TexMemWatcher.py
  34. 1 1
      direct/src/showutil/TexViewer.py
  35. 2 0
      direct/src/task/MiniTask.py
  36. 104 47
      direct/src/task/Task.py
  37. 90 89
      direct/src/tkpanels/DirectSessionPanel.py
  38. 5 1
      direct/src/tkpanels/Inspector.py
  39. 26 25
      direct/src/tkpanels/Placer.py
  40. 4 5
      direct/src/wxwidgets/WxAppShell.py
  41. 47 43
      direct/src/wxwidgets/WxPandaShell.py
  42. 17 12
      direct/src/wxwidgets/WxSlider.py
  43. 4 4
      dtool/Config.cmake
  44. 26 10
      dtool/Package.cmake
  45. 1 17
      dtool/src/dtoolbase/deletedBufferChain.I
  46. 18 27
      dtool/src/dtoolbase/deletedBufferChain.cxx
  47. 4 12
      dtool/src/dtoolbase/deletedBufferChain.h
  48. 6 0
      dtool/src/dtoolbase/patomic.I
  49. 1 1
      dtool/src/dtoolbase/patomic.cxx
  50. 10 1
      dtool/src/dtoolbase/patomic.h
  51. 1 1
      dtool/src/interrogatedb/dtool_super_base.cxx
  52. 12 0
      dtool/src/interrogatedb/py_compat.h
  53. 1 3
      makepanda/installpanda.py
  54. 32 0
      makepanda/locations.py
  55. 1 2
      makepanda/makepackage.py
  56. 20 9
      makepanda/makepanda.py
  57. 10 10
      makepanda/makepandacore.py
  58. 2 1
      makepanda/makewheel.py
  59. 20 2
      makepanda/test_wheel.py
  60. 3 0
      panda/CMakeLists.txt
  61. 15 2
      panda/src/cocoadisplay/cocoaGraphicsPipe.h
  62. 186 5
      panda/src/cocoadisplay/cocoaGraphicsPipe.mm
  63. 1 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  64. 209 105
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  65. 1 0
      panda/src/cocoadisplay/cocoaPandaWindowDelegate.h
  66. 4 0
      panda/src/cocoadisplay/cocoaPandaWindowDelegate.mm
  67. 1 0
      panda/src/cocoadisplay/config_cocoadisplay.h
  68. 5 0
      panda/src/cocoadisplay/config_cocoadisplay.mm
  69. 0 7
      panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.h
  70. 1 62
      panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.mm
  71. 1 0
      panda/src/cocoagldisplay/cocoaGLGraphicsWindow.h
  72. 9 8
      panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm
  73. 17 9
      panda/src/display/displayInformation.cxx
  74. 5 3
      panda/src/display/displayInformation.h
  75. 31 10
      panda/src/display/graphicsStateGuardian.cxx
  76. 2 2
      panda/src/glstuff/glShaderContext_src.cxx
  77. 11 6
      panda/src/gobj/geomVertexArrayData.cxx
  78. 1 1
      panda/src/gobj/shader.cxx
  79. 11 6
      panda/src/gobj/shaderBuffer.cxx
  80. 1 13
      panda/src/pnmimagetypes/bmp.h
  81. 1 0
      panda/src/pnmimagetypes/pnmFileTypeBMP.h
  82. 80 34
      panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx
  83. 2 2
      panda/src/putil/bitArray_ext.cxx
  84. 1 1
      panda/src/putil/doubleBitMask_ext.I
  85. 127 0
      panda/src/tinydisplay/CMakeLists.txt
  86. 2 0
      panda/src/tinydisplay/srgb_tables.cxx
  87. 5 0
      panda/src/tinydisplay/srgb_tables.h
  88. 2 0
      panda/src/tinydisplay/tinyCocoaGraphicsWindow.h
  89. 15 0
      panda/src/tinydisplay/tinyCocoaGraphicsWindow.mm
  90. 2 0
      panda/src/tinydisplay/zmath.h
  91. 6 0
      panda/src/wgldisplay/wglGraphicsStateGuardian.cxx
  92. 12 3
      panda/src/windisplay/winDetectDx.h
  93. 16 0
      panda/src/windisplay/winGraphicsPipe.cxx
  94. 14 0
      panda/src/x11display/x11GraphicsPipe.cxx
  95. 1 0
      pandatool/CMakeLists.txt
  96. 34 7
      pandatool/src/assimp/loaderFileTypeAssimp.cxx
  97. 1 1
      pandatool/src/gtk-stats/CMakeLists.txt
  98. 53 0
      pandatool/src/mac-stats/CMakeLists.txt
  99. 30 13
      pandatool/src/mac-stats/macStatsTimeline.mm
  100. 2 1
      requirements-test.txt

+ 55 - 9
.github/workflows/ci.yml

@@ -120,16 +120,16 @@ jobs:
       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)
@@ -308,6 +308,38 @@ jobs:
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
+    - name: Setup Python (Python 3.12)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.12'
+    - name: Configure (Python 3.12)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.12 -DHAVE_PYTHON=YES
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
+    - name: Build (Python 3.12)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.12)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
     - name: Upload coverage reports
     - name: Upload coverage reports
       if: always() && matrix.config == 'Coverage'
       if: always() && matrix.config == 'Coverage'
       working-directory: build
       working-directory: build
@@ -343,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: |
@@ -355,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:
@@ -367,7 +413,7 @@ jobs:
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install -r requirements-test.txt
         python -m pip install -r requirements-test.txt
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        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
@@ -381,7 +427,7 @@ jobs:
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install -r requirements-test.txt
         python -m pip install -r requirements-test.txt
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        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
@@ -395,7 +441,7 @@ jobs:
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install -r requirements-test.txt
         python -m pip install -r requirements-test.txt
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        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
@@ -409,7 +455,7 @@ jobs:
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install -r requirements-test.txt
         python -m pip install -r requirements-test.txt
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
 
     - name: Make installer
     - name: Make installer
       run: |
       run: |

+ 2 - 2
README.md

@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win32.zip
 
 
 After acquiring these dependencies, you can build Panda3D from the command
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
 prompt using the following command.  Change the `--msvc-version` option based

+ 6 - 1
cmake/install/Panda3DConfig.cmake

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

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

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

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

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

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

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

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

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

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

+ 29 - 19
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,33 +36,33 @@ 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):
+    def filePath(self) -> str:
         dateString = time.strftime("%Y_%m_%d_%H", time.localtime())
         dateString = time.strftime("%Y_%m_%d_%H", time.localtime())
         for i in range(26):
         for i in range(26):
             limit = self.sizeLimit
             limit = self.sizeLimit
@@ -65,7 +73,7 @@ class RotatingLog:
         # 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().
@@ -88,12 +96,13 @@ class RotatingLog:
             #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

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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):

+ 59 - 30
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)]
 
 
@@ -1316,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')
@@ -1415,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)
@@ -1605,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))
@@ -1887,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)))
@@ -1924,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
@@ -2400,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):
@@ -2415,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)
@@ -2563,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
@@ -2574,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]
@@ -2598,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()
@@ -2752,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:
@@ -2806,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):
@@ -2818,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.

+ 19 - 7
direct/src/dist/commands.py

@@ -13,7 +13,6 @@ import re
 import shutil
 import shutil
 import stat
 import stat
 import struct
 import struct
-import imp
 import string
 import string
 import tempfile
 import tempfile
 
 
@@ -55,10 +54,16 @@ def _register_python_loaders():
 
 
     _register_python_loaders.done = True
     _register_python_loaders.done = True
 
 
-    registry = p3d.LoaderFileTypeRegistry.getGlobalPtr()
+    from importlib.metadata import entry_points
 
 
-    import pkg_resources
-    for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'):
+    eps = entry_points()
+    if isinstance(eps, dict): # Python 3.8 and 3.9
+        loaders = eps.get('panda3d.loaders', ())
+    else:
+        loaders = eps.select(group='panda3d.loaders')
+
+    registry = p3d.LoaderFileTypeRegistry.get_global_ptr()
+    for entry_point in loaders:
         registry.register_deferred_type(entry_point)
         registry.register_deferred_type(entry_point)
 
 
 
 
@@ -717,6 +722,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(
@@ -1067,7 +1073,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():
@@ -1698,7 +1704,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.
@@ -1712,11 +1718,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
 
 

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

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

@@ -42,6 +42,9 @@ CONTROL_ADD_RANGE =                               9002
 CONTROL_REMOVE_RANGE =                            9003
 CONTROL_REMOVE_RANGE =                            9003
 CONTROL_ADD_POST_REMOVE =                         9010
 CONTROL_ADD_POST_REMOVE =                         9010
 CONTROL_CLEAR_POST_REMOVES =                      9011
 CONTROL_CLEAR_POST_REMOVES =                      9011
+CONTROL_SET_CON_NAME =                            9012
+CONTROL_SET_CON_URL =                             9013
+CONTROL_LOG_MESSAGE =                             9014
 
 
 # State Server control messages:
 # State Server control messages:
 STATESERVER_CREATE_OBJECT_WITH_REQUIRED =         2000
 STATESERVER_CREATE_OBJECT_WITH_REQUIRED =         2000

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

+ 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",

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

+ 0 - 6
direct/src/interval/FunctionInterval.py

@@ -10,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

+ 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(base.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(base.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,  # type: ignore[name-defined]
-                         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)  # type: ignore[assignment]
-    # 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)  # type: ignore[assignment]
-    # 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()

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

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

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

+ 18 - 11
direct/src/showbase/ShowBase.py

@@ -123,6 +123,7 @@ import builtins
 builtins.config = DConfig  # type: ignore[attr-defined]
 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:
@@ -3414,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

+ 4 - 1
direct/src/showbase/ShowBaseGlobal.py

@@ -19,9 +19,10 @@ 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
 base: ShowBase
 
 
@@ -61,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()
 
 

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

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

+ 6 - 4
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

+ 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 - 0
direct/src/task/MiniTask.py

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

+ 104 - 47
direct/src/task/Task.py

@@ -6,6 +6,8 @@ For more information about the task system, consult the
 :ref:`tasks-and-event-handling` page in the programming manual.
 :ref:`tasks-and-event-handling` page in the programming manual.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['Task', 'TaskManager',
 __all__ = ['Task', 'TaskManager',
            'cont', 'done', 'again', 'pickup', 'exit',
            'cont', 'done', 'again', 'pickup', 'exit',
            'sequence', 'loop', 'pause']
            'sequence', 'loop', 'pause']
@@ -13,6 +15,7 @@ __all__ = ['Task', 'TaskManager',
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Functor, ScratchPad
 from direct.showbase.PythonUtil import Functor, ScratchPad
 from direct.showbase.MessengerGlobal import messenger
 from direct.showbase.MessengerGlobal import messenger
+from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
 import types
 import types
 import random
 import random
 import importlib
 import importlib
@@ -20,6 +23,7 @@ import sys
 
 
 # On Android, there's no use handling SIGINT, and in fact we can't, since we
 # On Android, there's no use handling SIGINT, and in fact we can't, since we
 # run the application in a separate thread from the main thread.
 # run the application in a separate thread from the main thread.
+signal: types.ModuleType | None
 if hasattr(sys, 'getandroidapilevel'):
 if hasattr(sys, 'getandroidapilevel'):
     signal = None
     signal = None
 else:
 else:
@@ -41,8 +45,15 @@ from panda3d.core import (
 )
 )
 from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
 from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
 
 
+# The following variables are typing constructs used in annotations
+# to succinctly express all the types that can be converted into tasks.
+_T = TypeVar('_T', covariant=True)
+_TaskCoroutine = Union[Coroutine[Any, None, _T], Generator[Any, None, _T]]
+_TaskFunction = Callable[..., Union[int, _TaskCoroutine[Union[int, None]], None]]
+_FuncOrTask = Union[_TaskFunction, _TaskCoroutine[Any], AsyncTask]
+
 
 
-def print_exc_plus():
+def print_exc_plus() -> None:
     """
     """
     Print the usual traceback information, followed by a listing of all the
     Print the usual traceback information, followed by a listing of all the
     local variables in each frame.
     local variables in each frame.
@@ -50,12 +61,13 @@ def print_exc_plus():
     import traceback
     import traceback
 
 
     tb = sys.exc_info()[2]
     tb = sys.exc_info()[2]
+    assert tb is not None
     while 1:
     while 1:
         if not tb.tb_next:
         if not tb.tb_next:
             break
             break
         tb = tb.tb_next
         tb = tb.tb_next
     stack = []
     stack = []
-    f = tb.tb_frame
+    f: types.FrameType | None = tb.tb_frame
     while f:
     while f:
         stack.append(f)
         stack.append(f)
         f = f.f_back
         f = f.f_back
@@ -82,11 +94,11 @@ def print_exc_plus():
 # these Python names, and define them both at the module level, here,
 # these Python names, and define them both at the module level, here,
 # and at the class level (below).  The preferred access is via the
 # and at the class level (below).  The preferred access is via the
 # class level.
 # class level.
-done = AsyncTask.DSDone
-cont = AsyncTask.DSCont
-again = AsyncTask.DSAgain
-pickup = AsyncTask.DSPickup
-exit = AsyncTask.DSExit
+done: Final = AsyncTask.DSDone
+cont: Final = AsyncTask.DSCont
+again: Final = AsyncTask.DSAgain
+pickup: Final = AsyncTask.DSPickup
+exit: Final = AsyncTask.DSExit
 
 
 #: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
 #: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
 Task = PythonTask
 Task = PythonTask
@@ -110,7 +122,7 @@ gather = Task.gather
 shield = Task.shield
 shield = Task.shield
 
 
 
 
-def sequence(*taskList):
+def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('sequence')
     seq = AsyncTaskSequence('sequence')
     for task in taskList:
     for task in taskList:
         seq.addTask(task)
         seq.addTask(task)
@@ -120,7 +132,7 @@ def sequence(*taskList):
 Task.DtoolClassDict['sequence'] = staticmethod(sequence)
 Task.DtoolClassDict['sequence'] = staticmethod(sequence)
 
 
 
 
-def loop(*taskList):
+def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
     seq = AsyncTaskSequence('loop')
     seq = AsyncTaskSequence('loop')
     for task in taskList:
     for task in taskList:
         seq.addTask(task)
         seq.addTask(task)
@@ -140,10 +152,12 @@ class TaskManager:
 
 
     MaxEpochSpeed = 1.0/30.0
     MaxEpochSpeed = 1.0/30.0
 
 
-    def __init__(self):
+    __prevHandler: Any
+
+    def __init__(self) -> None:
         self.mgr = AsyncTaskManager.getGlobalPtr()
         self.mgr = AsyncTaskManager.getGlobalPtr()
 
 
-        self.resumeFunc = None
+        self.resumeFunc: Callable[[], object] | None = None
         self.globalClock = self.mgr.getClock()
         self.globalClock = self.mgr.getClock()
         self.stepping = False
         self.stepping = False
         self.running = False
         self.running = False
@@ -153,12 +167,12 @@ class TaskManager:
         if signal:
         if signal:
             self.__prevHandler = signal.default_int_handler
             self.__prevHandler = signal.default_int_handler
 
 
-        self._frameProfileQueue = []
+        self._frameProfileQueue: list[tuple[int, Any, Callable[[], object] | None]] = []
 
 
         # this will be set when it's safe to import StateVar
         # this will be set when it's safe to import StateVar
-        self._profileFrames = None
+        self._profileFrames: Any = None
         self._frameProfiler = None
         self._frameProfiler = None
-        self._profileTasks = None
+        self._profileTasks: Any = None
         self._taskProfiler = None
         self._taskProfiler = None
         self._taskProfileInfo = ScratchPad(
         self._taskProfileInfo = ScratchPad(
             taskId = None,
             taskId = None,
@@ -166,7 +180,7 @@ class TaskManager:
             session = None,
             session = None,
         )
         )
 
 
-    def finalInit(self):
+    def finalInit(self) -> None:
         # This function should be called once during startup, after
         # This function should be called once during startup, after
         # most things are imported.
         # most things are imported.
         from direct.fsm.StatePush import StateVar
         from direct.fsm.StatePush import StateVar
@@ -175,7 +189,7 @@ class TaskManager:
         self._profileFrames = StateVar(False)
         self._profileFrames = StateVar(False)
         self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
         self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
 
 
-    def destroy(self):
+    def destroy(self) -> None:
         # This should be safe to call multiple times.
         # This should be safe to call multiple times.
         self.running = False
         self.running = False
         self.notify.info("TaskManager.destroy()")
         self.notify.info("TaskManager.destroy()")
@@ -183,11 +197,14 @@ class TaskManager:
         self._frameProfileQueue.clear()
         self._frameProfileQueue.clear()
         self.mgr.cleanup()
         self.mgr.cleanup()
 
 
-    def setClock(self, clockObject):
+    def __getClock(self) -> ClockObject:
+        return self.mgr.getClock()
+
+    def setClock(self, clockObject: ClockObject) -> None:
         self.mgr.setClock(clockObject)
         self.mgr.setClock(clockObject)
         self.globalClock = clockObject
         self.globalClock = clockObject
 
 
-    clock = property(lambda self: self.mgr.getClock(), setClock)
+    clock = property(__getClock, setClock)
 
 
     def invokeDefaultHandler(self, signalNumber, stackFrame):
     def invokeDefaultHandler(self, signalNumber, stackFrame):
         print('*** allowing mid-frame keyboard interrupt.')
         print('*** allowing mid-frame keyboard interrupt.')
@@ -208,13 +225,13 @@ class TaskManager:
             # Next time around invoke the default handler
             # Next time around invoke the default handler
             signal.signal(signal.SIGINT, self.invokeDefaultHandler)
             signal.signal(signal.SIGINT, self.invokeDefaultHandler)
 
 
-    def getCurrentTask(self):
+    def getCurrentTask(self) -> AsyncTask | None:
         """ Returns the task currently executing on this thread, or
         """ Returns the task currently executing on this thread, or
         None if this is being called outside of the task manager. """
         None if this is being called outside of the task manager. """
 
 
         return Thread.getCurrentThread().getCurrentTask()
         return Thread.getCurrentThread().getCurrentTask()
 
 
-    def hasTaskChain(self, chainName):
+    def hasTaskChain(self, chainName: str) -> bool:
         """ Returns true if a task chain with the indicated name has
         """ Returns true if a task chain with the indicated name has
         already been defined, or false otherwise.  Note that
         already been defined, or false otherwise.  Note that
         setupTaskChain() will implicitly define a task chain if it has
         setupTaskChain() will implicitly define a task chain if it has
@@ -224,9 +241,16 @@ class TaskManager:
 
 
         return self.mgr.findTaskChain(chainName) is not None
         return self.mgr.findTaskChain(chainName) is not None
 
 
-    def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
-                       threadPriority = None, frameBudget = None,
-                       frameSync = None, timeslicePriority = None):
+    def setupTaskChain(
+        self,
+        chainName: str,
+        numThreads: int | None = None,
+        tickClock: bool | None = None,
+        threadPriority: int | None = None,
+        frameBudget: float | None = None,
+        frameSync: bool | None = None,
+        timeslicePriority: bool | None = None,
+    ) -> None:
         """Defines a new task chain.  Each task chain executes tasks
         """Defines a new task chain.  Each task chain executes tasks
         potentially in parallel with all of the other task chains (if
         potentially in parallel with all of the other task chains (if
         numThreads is more than zero).  When a new task is created, it
         numThreads is more than zero).  When a new task is created, it
@@ -290,40 +314,50 @@ class TaskManager:
         if timeslicePriority is not None:
         if timeslicePriority is not None:
             chain.setTimeslicePriority(timeslicePriority)
             chain.setTimeslicePriority(timeslicePriority)
 
 
-    def hasTaskNamed(self, taskName):
+    def hasTaskNamed(self, taskName: str) -> bool:
         """Returns true if there is at least one task, active or
         """Returns true if there is at least one task, active or
         sleeping, with the indicated name. """
         sleeping, with the indicated name. """
 
 
         return bool(self.mgr.findTask(taskName))
         return bool(self.mgr.findTask(taskName))
 
 
-    def getTasksNamed(self, taskName):
+    def getTasksNamed(self, taskName: str) -> list[AsyncTask]:
         """Returns a list of all tasks, active or sleeping, with the
         """Returns a list of all tasks, active or sleeping, with the
         indicated name. """
         indicated name. """
         return list(self.mgr.findTasks(taskName))
         return list(self.mgr.findTasks(taskName))
 
 
-    def getTasksMatching(self, taskPattern):
+    def getTasksMatching(self, taskPattern: GlobPattern | str) -> list[AsyncTask]:
         """Returns a list of all tasks, active or sleeping, with a
         """Returns a list of all tasks, active or sleeping, with a
         name that matches the pattern, which can include standard
         name that matches the pattern, which can include standard
         shell globbing characters like \\*, ?, and []. """
         shell globbing characters like \\*, ?, and []. """
 
 
         return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
         return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
 
 
-    def getAllTasks(self):
+    def getAllTasks(self) -> list[AsyncTask]:
         """Returns list of all tasks, active and sleeping, in
         """Returns list of all tasks, active and sleeping, in
         arbitrary order. """
         arbitrary order. """
         return list(self.mgr.getTasks())
         return list(self.mgr.getTasks())
 
 
-    def getTasks(self):
+    def getTasks(self) -> list[AsyncTask]:
         """Returns list of all active tasks in arbitrary order. """
         """Returns list of all active tasks in arbitrary order. """
         return list(self.mgr.getActiveTasks())
         return list(self.mgr.getActiveTasks())
 
 
-    def getDoLaters(self):
+    def getDoLaters(self) -> list[AsyncTask]:
         """Returns list of all sleeping tasks in arbitrary order. """
         """Returns list of all sleeping tasks in arbitrary order. """
         return list(self.mgr.getSleepingTasks())
         return list(self.mgr.getSleepingTasks())
 
 
-    def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
-                      sort = None, priority = None, taskChain = None,
-                      uponDeath = None, appendTask = False, owner = None):
+    def doMethodLater(
+        self,
+        delayTime: float,
+        funcOrTask: _FuncOrTask,
+        name: str | None,
+        extraArgs: Sequence | None = None,
+        sort: int | None = None,
+        priority: int | None = None,
+        taskChain: str | None = None,
+        uponDeath: Callable[[], object] | None = None,
+        appendTask: bool = False,
+        owner = None,
+    ) -> AsyncTask:
         """Adds a task to be performed at some time in the future.
         """Adds a task to be performed at some time in the future.
         This is identical to `add()`, except that the specified
         This is identical to `add()`, except that the specified
         delayTime is applied to the Task object first, which means
         delayTime is applied to the Task object first, which means
@@ -346,9 +380,19 @@ class TaskManager:
 
 
     do_method_later = doMethodLater
     do_method_later = doMethodLater
 
 
-    def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
-            priority = None, uponDeath = None, appendTask = False,
-            taskChain = None, owner = None, delay = None):
+    def add(
+        self,
+        funcOrTask: _FuncOrTask,
+        name: str | None = None,
+        sort: int | None = None,
+        extraArgs: Sequence | None = None,
+        priority: int | None = None,
+        uponDeath: Callable[[], object] | None = None,
+        appendTask: bool = False,
+        taskChain: str | None = None,
+        owner = None,
+        delay: float | None = None,
+    ) -> AsyncTask:
         """
         """
         Add a new task to the taskMgr.  The task will begin executing
         Add a new task to the taskMgr.  The task will begin executing
         immediately, or next frame if its sort value has already
         immediately, or next frame if its sort value has already
@@ -415,7 +459,18 @@ class TaskManager:
         self.mgr.add(task)
         self.mgr.add(task)
         return task
         return task
 
 
-    def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
+    def __setupTask(
+        self,
+        funcOrTask: _FuncOrTask,
+        name: str | None,
+        priority: int | None,
+        sort: int | None,
+        extraArgs: Sequence | None,
+        taskChain: str | None,
+        appendTask: bool,
+        owner,
+        uponDeath: Callable[[], object] | None,
+    ) -> AsyncTask:
         wasTask = False
         wasTask = False
         if isinstance(funcOrTask, AsyncTask):
         if isinstance(funcOrTask, AsyncTask):
             task = funcOrTask
             task = funcOrTask
@@ -473,7 +528,7 @@ class TaskManager:
 
 
         return task
         return task
 
 
-    def remove(self, taskOrName):
+    def remove(self, taskOrName: AsyncTask | str | list[AsyncTask | str]) -> int:
         """Removes a task from the task manager.  The task is stopped,
         """Removes a task from the task manager.  The task is stopped,
         almost as if it had returned task.done.  (But if the task is
         almost as if it had returned task.done.  (But if the task is
         currently executing, it will finish out its current frame
         currently executing, it will finish out its current frame
@@ -485,13 +540,15 @@ class TaskManager:
         if isinstance(taskOrName, AsyncTask):
         if isinstance(taskOrName, AsyncTask):
             return self.mgr.remove(taskOrName)
             return self.mgr.remove(taskOrName)
         elif isinstance(taskOrName, list):
         elif isinstance(taskOrName, list):
+            count = 0
             for task in taskOrName:
             for task in taskOrName:
-                self.remove(task)
+                count += self.remove(task)
+            return count
         else:
         else:
             tasks = self.mgr.findTasks(taskOrName)
             tasks = self.mgr.findTasks(taskOrName)
             return self.mgr.remove(tasks)
             return self.mgr.remove(tasks)
 
 
-    def removeTasksMatching(self, taskPattern):
+    def removeTasksMatching(self, taskPattern: GlobPattern | str) -> int:
         """Removes all tasks whose names match the pattern, which can
         """Removes all tasks whose names match the pattern, which can
         include standard shell globbing characters like \\*, ?, and [].
         include standard shell globbing characters like \\*, ?, and [].
         See also :meth:`remove()`.
         See also :meth:`remove()`.
@@ -501,7 +558,7 @@ class TaskManager:
         tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
         tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
         return self.mgr.remove(tasks)
         return self.mgr.remove(tasks)
 
 
-    def step(self):
+    def step(self) -> None:
         """Invokes the task manager for one frame, and then returns.
         """Invokes the task manager for one frame, and then returns.
         Normally, this executes each task exactly once, though task
         Normally, this executes each task exactly once, though task
         chains that are in sub-threads or that have frame budgets
         chains that are in sub-threads or that have frame budgets
@@ -512,7 +569,7 @@ class TaskManager:
         # Replace keyboard interrupt handler during task list processing
         # Replace keyboard interrupt handler during task list processing
         # so we catch the keyboard interrupt but don't handle it until
         # so we catch the keyboard interrupt but don't handle it until
         # after task list processing is complete.
         # after task list processing is complete.
-        self.fKeyboardInterrupt = 0
+        self.fKeyboardInterrupt = False
         self.interruptCount = 0
         self.interruptCount = 0
 
 
         if signal:
         if signal:
@@ -534,7 +591,7 @@ class TaskManager:
         if self.fKeyboardInterrupt:
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
             raise KeyboardInterrupt
 
 
-    def run(self):
+    def run(self) -> None:
         """Starts the task manager running.  Does not return until an
         """Starts the task manager running.  Does not return until an
         exception is encountered (including KeyboardInterrupt). """
         exception is encountered (including KeyboardInterrupt). """
 
 
@@ -560,11 +617,11 @@ class TaskManager:
                     if len(self._frameProfileQueue) > 0:
                     if len(self._frameProfileQueue) > 0:
                         numFrames, session, callback = self._frameProfileQueue.pop(0)
                         numFrames, session, callback = self._frameProfileQueue.pop(0)
 
 
-                        def _profileFunc(numFrames=numFrames):
+                        def _profileFunc(numFrames: int = numFrames) -> None:
                             self._doProfiledFrames(numFrames)
                             self._doProfiledFrames(numFrames)
                         session.setFunc(_profileFunc)
                         session.setFunc(_profileFunc)
                         session.run()
                         session.run()
-                        _profileFunc = None
+                        del _profileFunc
                         if callback:
                         if callback:
                             callback()
                             callback()
                         session.release()
                         session.release()
@@ -617,7 +674,7 @@ class TaskManager:
             message = ioError
             message = ioError
         return code, message
         return code, message
 
 
-    def stop(self):
+    def stop(self) -> None:
         # Set a flag so we will stop before beginning next frame
         # Set a flag so we will stop before beginning next frame
         self.running = False
         self.running = False
 
 
@@ -782,12 +839,12 @@ class TaskManager:
             task = tasks.getTask(i)
             task = tasks.getTask(i)
         return task
         return task
 
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return str(self.mgr)
         return str(self.mgr)
 
 
     # In the event we want to do frame time managment, this is the
     # In the event we want to do frame time managment, this is the
     # function to replace or overload.
     # function to replace or overload.
-    def doYield(self, frameStartTime, nextScheduledTaskTime):
+    def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
         pass
         pass
 
 
     #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
     #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):

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

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

+ 5 - 1
direct/src/tkpanels/Inspector.py

@@ -11,6 +11,7 @@ so that I can just type: ``inspect(anObject)`` any time.
 See :ref:`inspection-utilities` for more information.
 See :ref:`inspection-utilities` for more information.
 """
 """
 
 
+from __future__ import annotations
 
 
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
 __all__ = ['inspect', 'inspectorFor', 'Inspector', 'ModuleInspector', 'ClassInspector', 'InstanceInspector', 'FunctionInspector', 'InstanceMethodInspector', 'CodeInspector', 'ComplexInspector', 'DictionaryInspector', 'SequenceInspector', 'SliceInspector', 'InspectorWindow']
 
 
@@ -31,6 +32,8 @@ def inspect(anObject):
 
 
 ### private
 ### private
 
 
+_InspectorMap: dict[str, str]
+
 
 
 def inspectorFor(anObject):
 def inspectorFor(anObject):
     typeName = type(anObject).__name__.capitalize() + 'Type'
     typeName = type(anObject).__name__.capitalize() + 'Type'
@@ -396,7 +399,8 @@ class InspectorWindow:
         self.listWidget.component('listbox').focus_set()
         self.listWidget.component('listbox').focus_set()
 
 
     def showHelp(self):
     def showHelp(self):
-        help = tk.Toplevel(base.tkRoot)
+        from direct.showbase import ShowBaseGlobal
+        help = tk.Toplevel(ShowBaseGlobal.base.tkRoot)
         help.title("Inspector Help")
         help.title("Inspector Help")
         frame = tk.Frame(help)
         frame = tk.Frame(help)
         frame.pack()
         frame.pack()

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

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

+ 4 - 5
direct/src/wxwidgets/WxAppShell.py

@@ -79,13 +79,12 @@ class WxAppShell(wx.Frame):
         self.onDestroy(event)
         self.onDestroy(event)
 
 
         # to close Panda
         # to close Panda
-        try:
-            base
-        except NameError:
+        from direct.showbase import ShowBaseGlobal
+        if hasattr(ShowBaseGlobal, 'base'):
+            ShowBaseGlobal.base.userExit()
+        else:
             sys.exit()
             sys.exit()
 
 
-        base.userExit()
-
     ### USER METHODS ###
     ### USER METHODS ###
     # To be overridden
     # To be overridden
     def appInit(self):
     def appInit(self):

+ 47 - 43
direct/src/wxwidgets/WxPandaShell.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import wx
 import wx
 from wx.lib.agw import fourwaysplitter as FWS
 from wx.lib.agw import fourwaysplitter as FWS
 
 
@@ -25,7 +27,7 @@ class WxPandaShell(WxAppShell):
     copyright       = ('Copyright 2010 Disney Online Studios.' +
     copyright       = ('Copyright 2010 Disney Online Studios.' +
                        '\nAll Rights Reserved.')
                        '\nAll Rights Reserved.')
 
 
-    MENU_TEXTS = {
+    MENU_TEXTS: dict[int, tuple[str, str | None]] = {
         ID_FOUR_VIEW: ("Four Views", None),
         ID_FOUR_VIEW: ("Four Views", None),
         ID_TOP_VIEW: ("Top View", None),
         ID_TOP_VIEW: ("Top View", None),
         ID_FRONT_VIEW: ("Front View", None),
         ID_FRONT_VIEW: ("Front View", None),
@@ -114,6 +116,7 @@ class WxPandaShell(WxAppShell):
         self.wxStep()
         self.wxStep()
         ViewportManager.initializeAll()
         ViewportManager.initializeAll()
         # Position the camera
         # Position the camera
+        base = ShowBaseGlobal.base
         if base.trackball is not None:
         if base.trackball is not None:
             base.trackball.node().setPos(0, 30, 0)
             base.trackball.node().setPos(0, 30, 0)
             base.trackball.node().setHpr(0, 15, 0)
             base.trackball.node().setHpr(0, 15, 0)
@@ -125,33 +128,34 @@ class WxPandaShell(WxAppShell):
         # initializing direct
         # initializing direct
         if self.fStartDirect:
         if self.fStartDirect:
             base.startDirect(fWantTk = 0, fWantWx = 0)
             base.startDirect(fWantTk = 0, fWantWx = 0)
-
-            base.direct.disableMouseEvents()
-            newMouseEvents = ["_le_per_%s"%x for x in base.direct.mouseEvents] +\
-                             ["_le_fro_%s"%x for x in base.direct.mouseEvents] +\
-                             ["_le_lef_%s"%x for x in base.direct.mouseEvents] +\
-                             ["_le_top_%s"%x for x in base.direct.mouseEvents]
-            base.direct.mouseEvents = newMouseEvents
-            base.direct.enableMouseEvents()
-
-            base.direct.disableKeyEvents()
-            keyEvents = ["_le_per_%s"%x for x in base.direct.keyEvents] +\
-                             ["_le_fro_%s"%x for x in base.direct.keyEvents] +\
-                             ["_le_lef_%s"%x for x in base.direct.keyEvents] +\
-                             ["_le_top_%s"%x for x in base.direct.keyEvents]
-            base.direct.keyEvents = keyEvents
-            base.direct.enableKeyEvents()
-
-            base.direct.disableModifierEvents()
-            modifierEvents = ["_le_per_%s"%x for x in base.direct.modifierEvents] +\
-                             ["_le_fro_%s"%x for x in base.direct.modifierEvents] +\
-                             ["_le_lef_%s"%x for x in base.direct.modifierEvents] +\
-                             ["_le_top_%s"%x for x in base.direct.modifierEvents]
-            base.direct.modifierEvents = modifierEvents
-            base.direct.enableModifierEvents()
-
-            base.direct.cameraControl.lockRoll = True
-            base.direct.setFScaleWidgetByCam(1)
+            direct = ShowBaseGlobal.direct
+
+            direct.disableMouseEvents()
+            newMouseEvents = ["_le_per_%s"%x for x in direct.mouseEvents] +\
+                             ["_le_fro_%s"%x for x in direct.mouseEvents] +\
+                             ["_le_lef_%s"%x for x in direct.mouseEvents] +\
+                             ["_le_top_%s"%x for x in direct.mouseEvents]
+            direct.mouseEvents = newMouseEvents
+            direct.enableMouseEvents()
+
+            direct.disableKeyEvents()
+            keyEvents = ["_le_per_%s"%x for x in direct.keyEvents] +\
+                             ["_le_fro_%s"%x for x in direct.keyEvents] +\
+                             ["_le_lef_%s"%x for x in direct.keyEvents] +\
+                             ["_le_top_%s"%x for x in direct.keyEvents]
+            direct.keyEvents = keyEvents
+            direct.enableKeyEvents()
+
+            direct.disableModifierEvents()
+            modifierEvents = ["_le_per_%s"%x for x in direct.modifierEvents] +\
+                             ["_le_fro_%s"%x for x in direct.modifierEvents] +\
+                             ["_le_lef_%s"%x for x in direct.modifierEvents] +\
+                             ["_le_top_%s"%x for x in direct.modifierEvents]
+            direct.modifierEvents = modifierEvents
+            direct.enableModifierEvents()
+
+            direct.cameraControl.lockRoll = True
+            direct.setFScaleWidgetByCam(1)
 
 
             unpickables = [
             unpickables = [
                 "z-guide",
                 "z-guide",
@@ -172,31 +176,31 @@ class WxPandaShell(WxAppShell):
                 "Sphere",]
                 "Sphere",]
 
 
             for unpickable in unpickables:
             for unpickable in unpickables:
-                base.direct.addUnpickable(unpickable)
+                direct.addUnpickable(unpickable)
 
 
-            base.direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
-            base.direct.manipulationControl.fAllowMarquee = 1
-            base.direct.manipulationControl.supportMultiView()
-            base.direct.cameraControl.useMayaCamControls = 1
-            base.direct.cameraControl.perspCollPlane = self.perspView.collPlane
-            base.direct.cameraControl.perspCollPlane2 = self.perspView.collPlane2
+            direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
+            direct.manipulationControl.fAllowMarquee = 1
+            direct.manipulationControl.supportMultiView()
+            direct.cameraControl.useMayaCamControls = 1
+            direct.cameraControl.perspCollPlane = self.perspView.collPlane
+            direct.cameraControl.perspCollPlane2 = self.perspView.collPlane2
 
 
-            for widget in base.direct.manipulationControl.widgetList:
+            for widget in direct.manipulationControl.widgetList:
                 widget.setBin('gui-popup', 0)
                 widget.setBin('gui-popup', 0)
                 widget.setDepthTest(0)
                 widget.setDepthTest(0)
 
 
             # [gjeon] to intercept messages here
             # [gjeon] to intercept messages here
-            base.direct.ignore('DIRECT-delete')
-            base.direct.ignore('DIRECT-select')
-            base.direct.ignore('DIRECT-preDeselectAll')
-            base.direct.ignore('DIRECT-toggleWidgetVis')
-            base.direct.fIgnoreDirectOnlyKeyMap = 1
+            direct.ignore('DIRECT-delete')
+            direct.ignore('DIRECT-select')
+            direct.ignore('DIRECT-preDeselectAll')
+            direct.ignore('DIRECT-toggleWidgetVis')
+            direct.fIgnoreDirectOnlyKeyMap = 1
 
 
             # [gjeon] do not use the old way of finding current DR
             # [gjeon] do not use the old way of finding current DR
-            base.direct.drList.tryToGetCurrentDr = False
+            direct.drList.tryToGetCurrentDr = False
 
 
         else:
         else:
-            base.direct=None
+            base.direct = None
         #base.closeWindow(base.win)
         #base.closeWindow(base.win)
         base.win = base.winList[3]
         base.win = base.winList[3]
 
 

+ 17 - 12
direct/src/wxwidgets/WxSlider.py

@@ -16,7 +16,7 @@ class WxSlider(wx.Slider):
 
 
         self.maxValue = maxValue
         self.maxValue = maxValue
         self.minValue = minValue
         self.minValue = minValue
-        intVal = 100.0 / (self.maxValue - self.minValue) * (value - self.minValue)
+        intVal = int(100.0 / (self.maxValue - self.minValue) * (value - self.minValue))
 
 
         intMin = 0
         intMin = 0
         intMax = 100
         intMax = 100
@@ -35,33 +35,37 @@ class WxSlider(wx.Slider):
                                              textSize, wx.TE_CENTER | wx.TE_PROCESS_ENTER)
                                              textSize, wx.TE_CENTER | wx.TE_PROCESS_ENTER)
 
 
                 self.textValue.Disable()
                 self.textValue.Disable()
-                newPos = (pos[0], pos[1] + 20)
+                pos = (pos[0], pos[1] + 20)
         else:
         else:
             newStyle = wx.SL_VERTICAL
             newStyle = wx.SL_VERTICAL
-            newPos = (pos[0], pos[1] + 40)
+            pos = (pos[0], pos[1] + 40)
 
 
         if style & wx.SL_AUTOTICKS:
         if style & wx.SL_AUTOTICKS:
             newStyle |= wx.SL_AUTOTICKS
             newStyle |= wx.SL_AUTOTICKS
 
 
-        wx.Slider.__init__(self, parent, id, intVal, intMin, intMax, newPos, size, style=newStyle)
+        wx.Slider.__init__(self, parent, id, intVal, intMin, intMax, pos, size, style=newStyle)
         self.Disable()
         self.Disable()
 
 
     def GetValue(self):
     def GetValue(self):
         # overriding wx.Slider.GetValue()
         # overriding wx.Slider.GetValue()
-        #return (wx.Slider.GetValue(self) * (self.maxValue - self.minValue) / 100.0 + self.minValue)
-        return float(self.textValue.GetValue()) # [gjeon] since the value from the slider is not as precise as the value entered by the user
+        if self.textValue is not None: # Horizontal with labels
+            return float(self.textValue.GetValue()) # [gjeon] since the value from the slider is not as precise as the value entered by the user
+        else:
+            return (wx.Slider.GetValue(self) * (self.maxValue - self.minValue) / 100.0 + self.minValue)
 
 
     def SetValue(self, value):
     def SetValue(self, value):
         # overriding wx.Slider.SetValue()
         # overriding wx.Slider.SetValue()
-        self.textValue.SetValue("%.2f" % value)
+        if self.textValue is not None:
+            self.textValue.SetValue("%.2f" % value)
         intVal = 100.0 / (self.maxValue - self.minValue) * (value - self.minValue)
         intVal = 100.0 / (self.maxValue - self.minValue) * (value - self.minValue)
         wx.Slider.SetValue(self, intVal)
         wx.Slider.SetValue(self, intVal)
 
 
     def onChange(self, event):
     def onChange(self, event):
         # update textValue from slider
         # update textValue from slider
-        self.textValue.Clear()
-        floatVal = wx.Slider.GetValue(self) * (self.maxValue - self.minValue) / 100.0 + self.minValue
-        self.textValue.WriteText("%.2f" % floatVal)
+        if self.textValue is not None:
+            self.textValue.Clear()
+            floatVal = wx.Slider.GetValue(self) * (self.maxValue - self.minValue) / 100.0 + self.minValue
+            self.textValue.WriteText("%.2f" % floatVal)
         if self.updateCB: # callback function sould receive event as the argument
         if self.updateCB: # callback function sould receive event as the argument
             self.updateCB(event)
             self.updateCB(event)
         event.Skip()
         event.Skip()
@@ -82,13 +86,14 @@ class WxSlider(wx.Slider):
     def Disable(self):
     def Disable(self):
         # overriding wx.Slider.Disable()
         # overriding wx.Slider.Disable()
         wx.Slider.Disable(self)
         wx.Slider.Disable(self)
-        self.textValue.Disable()
+        if self.textValue is not None:
+            self.textValue.Disable()
 
 
     def Enable(self):
     def Enable(self):
         # overriding wx.Slider.Enable()
         # overriding wx.Slider.Enable()
         wx.Slider.Enable(self)
         wx.Slider.Enable(self)
         self.Bind(wx.EVT_SLIDER, self.onChange)
         self.Bind(wx.EVT_SLIDER, self.onChange)
 
 
-        if not self.textValue is None:
+        if self.textValue is not None:
             self.textValue.Enable()
             self.textValue.Enable()
             self.textValue.Bind(wx.EVT_TEXT_ENTER, self.onEnter)
             self.textValue.Bind(wx.EVT_TEXT_ENTER, self.onEnter)

+ 4 - 4
dtool/Config.cmake

@@ -453,10 +453,10 @@ on DirectX rendering." OFF)
 mark_as_advanced(SUPPORT_FIXED_FUNCTION)
 mark_as_advanced(SUPPORT_FIXED_FUNCTION)
 
 
 # Should build tinydisplay?
 # Should build tinydisplay?
-#option(HAVE_TINYDISPLAY
-#  "Builds TinyDisplay, a light software renderer based on TinyGL,
-#that is built into Panda. TinyDisplay is not as full-featured as Mesa
-#but is many times faster." ON)
+option(HAVE_TINYDISPLAY
+  "Builds TinyDisplay, a light software renderer based on TinyGL,
+that is built into Panda. TinyDisplay is not as full-featured as Mesa
+but is many times faster." ON)
 
 
 # Is SDL installed, and where?
 # Is SDL installed, and where?
 set(Threads_FIND_QUIETLY TRUE) # Fix for builtin FindSDL
 set(Threads_FIND_QUIETLY TRUE) # Fix for builtin FindSDL

+ 26 - 10
dtool/Package.cmake

@@ -257,16 +257,32 @@ if(HAVE_PYTHON)
     set(_ARCH_DIR ".")
     set(_ARCH_DIR ".")
 
 
   elseif(PYTHON_EXECUTABLE)
   elseif(PYTHON_EXECUTABLE)
-    execute_process(
-      COMMAND ${PYTHON_EXECUTABLE}
-        -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(False))"
-      OUTPUT_VARIABLE _LIB_DIR
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-    execute_process(
-      COMMAND ${PYTHON_EXECUTABLE}
-        -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(True))"
-      OUTPUT_VARIABLE _ARCH_DIR
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
+    # Python 3.12 drops the distutils module, so we have to use the newer
+    # sysconfig module instead.  Earlier versions of Python had the newer
+    # module too, but it was broken in Debian/Ubuntu, see #1230
+    if(PYTHON_VERSION_STRING VERSION_LESS "3.12")
+      execute_process(
+        COMMAND ${PYTHON_EXECUTABLE}
+          -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(False))"
+        OUTPUT_VARIABLE _LIB_DIR
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+      execute_process(
+        COMMAND ${PYTHON_EXECUTABLE}
+          -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(True))"
+        OUTPUT_VARIABLE _ARCH_DIR
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+    else()
+      execute_process(
+        COMMAND ${PYTHON_EXECUTABLE}
+          -c "import sysconfig; print(sysconfig.get_path('purelib'))"
+        OUTPUT_VARIABLE _LIB_DIR
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+      execute_process(
+        COMMAND ${PYTHON_EXECUTABLE}
+          -c "import sysconfig; print(sysconfig.get_path('platlib'))"
+        OUTPUT_VARIABLE _ARCH_DIR
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+    endif()
 
 
   else()
   else()
     set(_LIB_DIR "")
     set(_LIB_DIR "")

+ 1 - 17
dtool/src/dtoolbase/deletedBufferChain.I

@@ -16,7 +16,7 @@
  * size.
  * size.
  */
  */
 constexpr DeletedBufferChain::
 constexpr DeletedBufferChain::
-DeletedBufferChain(size_t buffer_size) : _buffer_size(buffer_size) {
+DeletedBufferChain(size_t buffer_size) noexcept : _buffer_size(buffer_size) {
 }
 }
 
 
 /**
 /**
@@ -77,22 +77,6 @@ operator < (const DeletedBufferChain &other) const {
   return _buffer_size < other._buffer_size;
   return _buffer_size < other._buffer_size;
 }
 }
 
 
-/**
- * Returns a deleted chain of the given size.
- */
-INLINE DeletedBufferChain *DeletedBufferChain::
-get_deleted_chain(size_t buffer_size) {
-  // We must allocate at least this much space for bookkeeping reasons.
-  buffer_size = (std::max)(buffer_size, sizeof(ObjectNode));
-
-  size_t index = ((buffer_size + sizeof(void *) - 1) / sizeof(void *)) - 1;
-  if (index < num_small_deleted_chains) {
-    return &_small_deleted_chains[index];
-  } else {
-    return get_large_deleted_chain((index + 1) * sizeof(void *));
-  }
-}
-
 /**
 /**
  * Casts an ObjectNode* to a void* buffer.
  * Casts an ObjectNode* to a void* buffer.
  */
  */

+ 18 - 27
dtool/src/dtoolbase/deletedBufferChain.cxx

@@ -16,32 +16,10 @@
 
 
 #include <set>
 #include <set>
 
 
-DeletedBufferChain DeletedBufferChain::_small_deleted_chains[DeletedBufferChain::num_small_deleted_chains] = {
-  DeletedBufferChain(sizeof(void *)),
-  DeletedBufferChain(sizeof(void *) * 2),
-  DeletedBufferChain(sizeof(void *) * 3),
-  DeletedBufferChain(sizeof(void *) * 4),
-  DeletedBufferChain(sizeof(void *) * 5),
-  DeletedBufferChain(sizeof(void *) * 6),
-  DeletedBufferChain(sizeof(void *) * 7),
-  DeletedBufferChain(sizeof(void *) * 8),
-  DeletedBufferChain(sizeof(void *) * 9),
-  DeletedBufferChain(sizeof(void *) * 10),
-  DeletedBufferChain(sizeof(void *) * 11),
-  DeletedBufferChain(sizeof(void *) * 12),
-  DeletedBufferChain(sizeof(void *) * 13),
-  DeletedBufferChain(sizeof(void *) * 14),
-  DeletedBufferChain(sizeof(void *) * 15),
-  DeletedBufferChain(sizeof(void *) * 16),
-  DeletedBufferChain(sizeof(void *) * 17),
-  DeletedBufferChain(sizeof(void *) * 18),
-  DeletedBufferChain(sizeof(void *) * 19),
-  DeletedBufferChain(sizeof(void *) * 20),
-  DeletedBufferChain(sizeof(void *) * 21),
-  DeletedBufferChain(sizeof(void *) * 22),
-  DeletedBufferChain(sizeof(void *) * 23),
-  DeletedBufferChain(sizeof(void *) * 24),
-};
+// This array stores the deleted chains for smaller sizes, starting with
+// sizeof(void *) and increasing in multiples thereof.
+static const size_t num_small_deleted_chains = 24;
+static DeletedBufferChain small_deleted_chains[num_small_deleted_chains] = {};
 
 
 /**
 /**
  * Allocates the memory for a new buffer of the indicated size (which must be
  * Allocates the memory for a new buffer of the indicated size (which must be
@@ -49,6 +27,8 @@ DeletedBufferChain DeletedBufferChain::_small_deleted_chains[DeletedBufferChain:
  */
  */
 void *DeletedBufferChain::
 void *DeletedBufferChain::
 allocate(size_t size, TypeHandle type_handle) {
 allocate(size_t size, TypeHandle type_handle) {
+  assert(_buffer_size > 0);
+
 #ifdef USE_DELETED_CHAIN
 #ifdef USE_DELETED_CHAIN
   // TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", "
   // TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", "
   // ", TAU_USER);
   // ", TAU_USER);
@@ -161,7 +141,18 @@ deallocate(void *ptr, TypeHandle type_handle) {
  * Returns a new DeletedBufferChain.
  * Returns a new DeletedBufferChain.
  */
  */
 DeletedBufferChain *DeletedBufferChain::
 DeletedBufferChain *DeletedBufferChain::
-get_large_deleted_chain(size_t buffer_size) {
+get_deleted_chain(size_t buffer_size) {
+  // Common, smaller sized chains avoid the expensive locking and set
+  // manipulation code further down.
+  size_t index = ((buffer_size + sizeof(void *) - 1) / sizeof(void *));
+  buffer_size = index * sizeof(void *);
+  index--;
+  if (index < num_small_deleted_chains) {
+    DeletedBufferChain *chain = &small_deleted_chains[index];
+    chain->_buffer_size = buffer_size;
+    return chain;
+  }
+
   static MutexImpl lock;
   static MutexImpl lock;
   lock.lock();
   lock.lock();
   static std::set<DeletedBufferChain> deleted_chains;
   static std::set<DeletedBufferChain> deleted_chains;

+ 4 - 12
dtool/src/dtoolbase/deletedBufferChain.h

@@ -57,10 +57,9 @@ enum DeletedChainFlag : unsigned int {
  * Use MemoryHook to get a new DeletedBufferChain of a particular size.
  * Use MemoryHook to get a new DeletedBufferChain of a particular size.
  */
  */
 class EXPCL_DTOOL_DTOOLBASE DeletedBufferChain {
 class EXPCL_DTOOL_DTOOLBASE DeletedBufferChain {
-protected:
-  constexpr explicit DeletedBufferChain(size_t buffer_size);
-
 public:
 public:
+  constexpr DeletedBufferChain() = default;
+  constexpr explicit DeletedBufferChain(size_t buffer_size) noexcept;
   INLINE DeletedBufferChain(DeletedBufferChain &&from) noexcept;
   INLINE DeletedBufferChain(DeletedBufferChain &&from) noexcept;
   INLINE DeletedBufferChain(const DeletedBufferChain &copy);
   INLINE DeletedBufferChain(const DeletedBufferChain &copy);
 
 
@@ -72,11 +71,9 @@ public:
 
 
   INLINE bool operator < (const DeletedBufferChain &other) const;
   INLINE bool operator < (const DeletedBufferChain &other) const;
 
 
-  static INLINE DeletedBufferChain *get_deleted_chain(size_t buffer_size);
+  static DeletedBufferChain *get_deleted_chain(size_t buffer_size);
 
 
 private:
 private:
-  static DeletedBufferChain *get_large_deleted_chain(size_t buffer_size);
-
   class ObjectNode {
   class ObjectNode {
   public:
   public:
 #ifdef USE_DELETEDCHAINFLAG
 #ifdef USE_DELETEDCHAINFLAG
@@ -99,7 +96,7 @@ private:
   ObjectNode *_deleted_chain = nullptr;
   ObjectNode *_deleted_chain = nullptr;
 
 
   MutexImpl _lock;
   MutexImpl _lock;
-  const size_t _buffer_size;
+  size_t _buffer_size = 0;
 
 
 #ifndef USE_DELETEDCHAINFLAG
 #ifndef USE_DELETEDCHAINFLAG
   // Without DELETEDCHAINFLAG, we don't even store the _flag member at all.
   // Without DELETEDCHAINFLAG, we don't even store the _flag member at all.
@@ -110,11 +107,6 @@ private:
   static const size_t flag_reserved_bytes = sizeof(AtomicAdjust::Integer);
   static const size_t flag_reserved_bytes = sizeof(AtomicAdjust::Integer);
 #endif  // USE_DELETEDCHAINFLAG
 #endif  // USE_DELETEDCHAINFLAG
 
 
-  // This array stores the deleted chains for smaller sizes, starting with
-  // sizeof(void *) and increasing in multiples thereof.
-  static const size_t num_small_deleted_chains = 24;
-  static DeletedBufferChain _small_deleted_chains[num_small_deleted_chains];
-
   friend class MemoryHook;
   friend class MemoryHook;
 };
 };
 
 

+ 6 - 0
dtool/src/dtoolbase/patomic.I

@@ -433,6 +433,8 @@ patomic_wait(const volatile uint32_t *value, uint32_t old) {
   while (*value == old) {
   while (*value == old) {
     _patomic_wait_func((volatile void *)value, &old, sizeof(uint32_t), INFINITE);
     _patomic_wait_func((volatile void *)value, &old, sizeof(uint32_t), INFINITE);
   }
   }
+#elif defined(__APPLE__)
+  __ulock_wait(UL_COMPARE_AND_WAIT, (void *)value, old, 0);
 #elif defined(HAVE_POSIX_THREADS)
 #elif defined(HAVE_POSIX_THREADS)
   _patomic_wait(value, old);
   _patomic_wait(value, old);
 #else
 #else
@@ -451,6 +453,8 @@ patomic_notify_one(volatile uint32_t *value) {
 //  WakeByAddressSingle((void *)value);
 //  WakeByAddressSingle((void *)value);
 #elif defined(_WIN32)
 #elif defined(_WIN32)
   _patomic_wake_one_func((void *)value);
   _patomic_wake_one_func((void *)value);
+#elif defined(__APPLE__)
+  __ulock_wake(UL_COMPARE_AND_WAIT, (void *)value, 0);
 #elif defined(HAVE_POSIX_THREADS)
 #elif defined(HAVE_POSIX_THREADS)
   _patomic_notify_all(value);
   _patomic_notify_all(value);
 #endif
 #endif
@@ -467,6 +471,8 @@ patomic_notify_all(volatile uint32_t *value) {
 //  WakeByAddressAll((void *)value);
 //  WakeByAddressAll((void *)value);
 #elif defined(_WIN32)
 #elif defined(_WIN32)
   _patomic_wake_all_func((void *)value);
   _patomic_wake_all_func((void *)value);
+#elif defined(__APPLE__)
+  __ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, (void *)value, 0);
 #elif defined(HAVE_POSIX_THREADS)
 #elif defined(HAVE_POSIX_THREADS)
   _patomic_notify_all(value);
   _patomic_notify_all(value);
 #endif
 #endif

+ 1 - 1
dtool/src/dtoolbase/patomic.cxx

@@ -125,7 +125,7 @@ initialize_wait(volatile VOID *addr, PVOID cmp, SIZE_T size, DWORD timeout) {
   return emulated_wait(addr, cmp, size, timeout);
   return emulated_wait(addr, cmp, size, timeout);
 }
 }
 
 
-#elif !defined(CPPPARSER) && !defined(__linux__) && defined(HAVE_POSIX_THREADS)
+#elif !defined(CPPPARSER) && !defined(__linux__) && !defined(__APPLE__) && defined(HAVE_POSIX_THREADS)
 
 
 // Same as above, but using pthreads.
 // Same as above, but using pthreads.
 struct alignas(64) WaitTableEntry {
 struct alignas(64) WaitTableEntry {

+ 10 - 1
dtool/src/dtoolbase/patomic.h

@@ -32,6 +32,15 @@
 #include <unistd.h>
 #include <unistd.h>
 #endif
 #endif
 
 
+#ifdef __APPLE__
+// Undocumented API, see https://outerproduct.net/futex-dictionary.html
+#define UL_COMPARE_AND_WAIT 1
+#define ULF_WAKE_ALL 0x00000100
+
+extern "C" int __ulock_wait(uint32_t op, void *addr, uint64_t value, uint32_t timeout);
+extern "C" int __ulock_wake(uint32_t op, void *addr, uint64_t wake_value);
+#endif
+
 #if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
 #if defined(THREAD_DUMMY_IMPL) || defined(THREAD_SIMPLE_IMPL)
 
 
 /**
 /**
@@ -164,7 +173,7 @@ ALWAYS_INLINE void patomic_notify_all(volatile uint32_t *value);
 EXPCL_DTOOL_DTOOLBASE extern BOOL (__stdcall *_patomic_wait_func)(volatile VOID *, PVOID, SIZE_T, DWORD);
 EXPCL_DTOOL_DTOOLBASE extern BOOL (__stdcall *_patomic_wait_func)(volatile VOID *, PVOID, SIZE_T, DWORD);
 EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_one_func)(PVOID);
 EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_one_func)(PVOID);
 EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_all_func)(PVOID);
 EXPCL_DTOOL_DTOOLBASE extern void (__stdcall *_patomic_wake_all_func)(PVOID);
-#elif !defined(__linux__) && defined(HAVE_POSIX_THREADS)
+#elif !defined(__linux__) && !defined(__APPLE__) && defined(HAVE_POSIX_THREADS)
 EXPCL_DTOOL_DTOOLBASE void _patomic_wait(const volatile uint32_t *value, uint32_t old);
 EXPCL_DTOOL_DTOOLBASE void _patomic_wait(const volatile uint32_t *value, uint32_t old);
 EXPCL_DTOOL_DTOOLBASE void _patomic_notify_all(volatile uint32_t *value);
 EXPCL_DTOOL_DTOOLBASE void _patomic_notify_all(volatile uint32_t *value);
 #endif
 #endif

+ 1 - 1
dtool/src/interrogatedb/dtool_super_base.cxx

@@ -21,7 +21,7 @@ static PyMemberDef standard_type_members[] = {
   {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"},
   {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"},
 // {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature),
 // {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature),
 // READONLY, (char *)"A type check signature"},
 // READONLY, (char *)"A type check signature"},
-  {(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
+  {(char *)"this_metatype", T_OBJECT_EX, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
   {nullptr}  /* Sentinel */
   {nullptr}  /* Sentinel */
 };
 };
 
 

+ 12 - 0
dtool/src/interrogatedb/py_compat.h

@@ -241,6 +241,18 @@ INLINE PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObje
 }
 }
 #endif
 #endif
 
 
+/* Python 3.12 */
+
+#if PY_VERSION_HEX < 0x030C0000
+#  define PyLong_IsNonNegative(value) (Py_SIZE((value)) >= 0)
+#else
+INLINE bool PyLong_IsNonNegative(PyObject *value) {
+  int overflow = 0;
+  long longval = PyLong_AsLongAndOverflow(value, &overflow);
+  return overflow == 1 || longval >= 0;
+}
+#endif
+
 /* Other Python implementations */
 /* Other Python implementations */
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 1 - 3
makepanda/installpanda.py

@@ -12,9 +12,7 @@ import os
 import sys
 import sys
 from optparse import OptionParser
 from optparse import OptionParser
 from makepandacore import *
 from makepandacore import *
-
-# DO NOT CHANGE TO sysconfig - see GitHub issue #1230
-from distutils.sysconfig import get_python_lib
+from locations import get_python_lib
 
 
 
 
 MIME_INFO = (
 MIME_INFO = (

+ 32 - 0
makepanda/locations.py

@@ -0,0 +1,32 @@
+__all__ = [
+    'get_python_inc',
+    'get_config_var',
+    'get_python_version',
+    'PREFIX',
+    'get_python_lib',
+    'get_config_vars',
+]
+
+import sys
+
+if sys.version_info < (3, 12):
+    from distutils.sysconfig import *
+else:
+    from sysconfig import *
+
+    PREFIX = get_config_var('prefix')
+
+    def get_python_inc(plat_specific=False):
+        path_name = 'platinclude' if plat_specific else 'include'
+        return get_path(path_name)
+
+    def get_python_lib(plat_specific=False, standard_lib=False):
+        if standard_lib:
+            path_name = 'stdlib'
+            if plat_specific:
+                path_name = 'plat' + path_name
+        elif plat_specific:
+            path_name = 'platlib'
+        else:
+            path_name = 'purelib'
+        return get_path(path_name)

+ 1 - 2
makepanda/makepackage.py

@@ -942,8 +942,7 @@ def MakeInstallerAndroid(version, **kwargs):
                     shutil.copy(os.path.join(source_dir, base), target)
                     shutil.copy(os.path.join(source_dir, base), target)
 
 
     # Copy the Python standard library to the .apk as well.
     # Copy the Python standard library to the .apk as well.
-    # DO NOT CHANGE TO sysconfig - see #1230
-    from distutils.sysconfig import get_python_lib
+    from locations import get_python_lib
     stdlib_source = get_python_lib(False, True)
     stdlib_source = get_python_lib(False, True)
     stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
     stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
     copy_python_tree(stdlib_source, stdlib_target)
     copy_python_tree(stdlib_source, stdlib_target)

+ 20 - 9
makepanda/makepanda.py

@@ -30,12 +30,10 @@ except:
     print("Please install the development package of Python and try again.")
     print("Please install the development package of Python and try again.")
     exit(1)
     exit(1)
 
 
-if sys.version_info >= (3, 10):
-    from sysconfig import get_platform
-else:
-    from distutils.util import get_platform
 from makepandacore import *
 from makepandacore import *
 
 
+from sysconfig import get_platform
+
 try:
 try:
     import zlib
     import zlib
 except:
 except:
@@ -366,6 +364,12 @@ if GetHost() == "darwin":
 if VERSION is None:
 if VERSION is None:
     # Take the value from the setup.cfg file.
     # Take the value from the setup.cfg file.
     VERSION = GetMetadataValue('version')
     VERSION = GetMetadataValue('version')
+    match = re.match(r'^\d+\.\d+(\.\d+)+', VERSION)
+    if not match:
+        exit("Invalid version %s in setup.cfg, three digits are required" % (VERSION))
+    if WHLVERSION is None:
+        WHLVERSION = VERSION
+    VERSION = match.group()
 
 
 if WHLVERSION is None:
 if WHLVERSION is None:
     WHLVERSION = VERSION
     WHLVERSION = VERSION
@@ -2776,11 +2780,18 @@ template class CheckPandaVersion<void>;
 
 
 
 
 def CreatePandaVersionFiles():
 def CreatePandaVersionFiles():
-    version1=int(VERSION.split(".")[0])
-    version2=int(VERSION.split(".")[1])
-    version3=int(VERSION.split(".")[2])
-    nversion=version1*1000000+version2*1000+version3
-    if (DISTRIBUTOR != "cmu"):
+    parts = VERSION.split(".", 2)
+    version1 = int(parts[0])
+    version2 = int(parts[1])
+    version3 = 0
+    if len(parts) > 2:
+        for c in parts[2]:
+            if c.isdigit():
+                version3 = version3 * 10 + ord(c) - 48
+            else:
+                break
+    nversion = version1 * 1000000 + version2 * 1000 + version3
+    if DISTRIBUTOR != "cmu":
         # Subtract 1 if we are not an official version.
         # Subtract 1 if we are not an official version.
         nversion -= 1
         nversion -= 1
 
 

+ 10 - 10
makepanda/makepandacore.py

@@ -6,7 +6,6 @@
 ########################################################################
 ########################################################################
 
 
 import configparser
 import configparser
-from distutils import sysconfig # DO NOT CHANGE to sysconfig - see #1230
 import fnmatch
 import fnmatch
 import getpass
 import getpass
 import glob
 import glob
@@ -21,6 +20,7 @@ import sys
 import threading
 import threading
 import _thread as thread
 import _thread as thread
 import time
 import time
+import locations
 
 
 SUFFIX_INC = [".cxx",".cpp",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"]
 SUFFIX_INC = [".cxx",".cpp",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"]
 SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"]
 SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"]
@@ -2218,7 +2218,7 @@ def SdkLocatePython(prefer_thirdparty_python=False):
         # On macOS, search for the Python framework directory matching the
         # On macOS, search for the Python framework directory matching the
         # version number of our current Python version.
         # version number of our current Python version.
         sysroot = SDK.get("MACOSX", "")
         sysroot = SDK.get("MACOSX", "")
-        version = sysconfig.get_python_version()
+        version = locations.get_python_version()
 
 
         py_fwx = "{0}/System/Library/Frameworks/Python.framework/Versions/{1}".format(sysroot, version)
         py_fwx = "{0}/System/Library/Frameworks/Python.framework/Versions/{1}".format(sysroot, version)
 
 
@@ -2243,19 +2243,19 @@ def SdkLocatePython(prefer_thirdparty_python=False):
         LibDirectory("PYTHON", py_fwx + "/lib")
         LibDirectory("PYTHON", py_fwx + "/lib")
 
 
     #elif GetTarget() == 'windows':
     #elif GetTarget() == 'windows':
-    #    SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc())
-    #    SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
+    #    SDK["PYTHON"] = os.path.dirname(locations.get_python_inc())
+    #    SDK["PYTHONVERSION"] = "python" + locations.get_python_version()
     #    SDK["PYTHONEXEC"] = sys.executable
     #    SDK["PYTHONEXEC"] = sys.executable
 
 
     else:
     else:
-        SDK["PYTHON"] = sysconfig.get_python_inc()
-        SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags
+        SDK["PYTHON"] = locations.get_python_inc()
+        SDK["PYTHONVERSION"] = "python" + locations.get_python_version() + abiflags
         SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
         SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
 
 
     if CrossCompiling():
     if CrossCompiling():
         # We need a version of Python we can run.
         # We need a version of Python we can run.
         SDK["PYTHONEXEC"] = sys.executable
         SDK["PYTHONEXEC"] = sys.executable
-        host_version = "python" + sysconfig.get_python_version() + abiflags
+        host_version = "python" + locations.get_python_version() + abiflags
         if SDK["PYTHONVERSION"] != host_version:
         if SDK["PYTHONVERSION"] != host_version:
             exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
             exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
 
 
@@ -3422,7 +3422,7 @@ def GetExtensionSuffix():
 
 
 def GetPythonABI():
 def GetPythonABI():
     if not CrossCompiling():
     if not CrossCompiling():
-        soabi = sysconfig.get_config_var('SOABI')
+        soabi = locations.get_config_var('SOABI')
         if soabi:
         if soabi:
             return soabi
             return soabi
 
 
@@ -3552,8 +3552,8 @@ def GetCurrentPythonVersionInfo():
         "soabi": GetPythonABI(),
         "soabi": GetPythonABI(),
         "ext_suffix": GetExtensionSuffix(),
         "ext_suffix": GetExtensionSuffix(),
         "executable": sys.executable,
         "executable": sys.executable,
-        "purelib": sysconfig.get_python_lib(False),
-        "platlib": sysconfig.get_python_lib(True),
+        "purelib": locations.get_python_lib(False),
+        "platlib": locations.get_python_lib(True),
     }
     }
 
 
 
 

+ 2 - 1
makepanda/makewheel.py

@@ -11,10 +11,11 @@ import tempfile
 import subprocess
 import subprocess
 import time
 import time
 import struct
 import struct
-from sysconfig import get_platform, get_config_var
 from optparse import OptionParser
 from optparse import OptionParser
 from base64 import urlsafe_b64encode
 from base64 import urlsafe_b64encode
 from makepandacore import LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue, CrossCompiling, GetThirdpartyDir, SDK, GetStrip
 from makepandacore import LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue, CrossCompiling, GetThirdpartyDir, SDK, GetStrip
+from locations import get_config_var
+from sysconfig import get_platform
 
 
 
 
 def get_abi_tag():
 def get_abi_tag():

+ 20 - 2
makepanda/test_wheel.py

@@ -41,7 +41,7 @@ def test_wheel(wheel, verbose=False):
     if sys.version_info >= (3, 10):
     if sys.version_info >= (3, 10):
         packages += ["pytest>=6.2.4"]
         packages += ["pytest>=6.2.4"]
     else:
     else:
-        packages += ["pytest"]
+        packages += ["pytest>=3.9.0"]
 
 
     if sys.version_info[0:2] == (3, 4):
     if sys.version_info[0:2] == (3, 4):
         if sys.platform == "win32":
         if sys.platform == "win32":
@@ -50,6 +50,9 @@ def test_wheel(wheel, verbose=False):
         # See https://github.com/python-attrs/attrs/pull/807
         # See https://github.com/python-attrs/attrs/pull/807
         packages += ["attrs<21"]
         packages += ["attrs<21"]
 
 
+    if sys.version_info >= (3, 12):
+        packages += ["setuptools"]
+
     if subprocess.call([python, "-m", "pip", "install"] + packages) != 0:
     if subprocess.call([python, "-m", "pip", "install"] + packages) != 0:
         shutil.rmtree(envdir)
         shutil.rmtree(envdir)
         sys.exit(1)
         sys.exit(1)
@@ -59,7 +62,22 @@ def test_wheel(wheel, verbose=False):
     if verbose:
     if verbose:
         test_cmd.append("--verbose")
         test_cmd.append("--verbose")
 
 
-    exit_code = subprocess.call(test_cmd)
+    # Put the location of the python DLL on the path, for deploy-stub test
+    # This is needed because venv does not install a copy of the python DLL
+    env = None
+    if sys.platform == "win32":
+        deploy_libs = os.path.join(envdir, "Lib", "site-packages", "deploy_libs")
+        if os.path.isdir(deploy_libs):
+            # We have to do this dance because os.environ is case insensitive
+            env = dict(os.environ)
+            for key, value in env.items():
+                if key.upper() == "PATH":
+                    env[key] = deploy_libs + ";" + value
+                    break
+            else:
+                env["PATH"] = deploy_libs
+
+    exit_code = subprocess.call(test_cmd, env=env)
     shutil.rmtree(envdir)
     shutil.rmtree(envdir)
 
 
     if exit_code != 0:
     if exit_code != 0:

+ 3 - 0
panda/CMakeLists.txt

@@ -60,6 +60,9 @@ add_subdirectory(src/wgldisplay)
 add_subdirectory(src/windisplay)
 add_subdirectory(src/windisplay)
 add_subdirectory(src/x11display)
 add_subdirectory(src/x11display)
 
 
+# Creates a metalib, so should go after
+add_subdirectory(src/tinydisplay)
+
 # For other components
 # For other components
 # bullet
 # bullet
 add_subdirectory(src/bullet)
 add_subdirectory(src/bullet)

+ 15 - 2
panda/src/cocoadisplay/cocoaGraphicsPipe.h

@@ -16,8 +16,10 @@
 
 
 #include "pandabase.h"
 #include "pandabase.h"
 #include "graphicsPipe.h"
 #include "graphicsPipe.h"
+#include "patomic.h"
 
 
 #include <ApplicationServices/ApplicationServices.h>
 #include <ApplicationServices/ApplicationServices.h>
+#include <CoreVideo/CoreVideo.h>
 
 
 /**
 /**
  * This graphics pipe represents the base class for pipes that create
  * This graphics pipe represents the base class for pipes that create
@@ -28,17 +30,28 @@ public:
   CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   virtual ~CocoaGraphicsPipe();
   virtual ~CocoaGraphicsPipe();
 
 
+  virtual PreferredWindowThread get_preferred_window_thread() const;
+
   INLINE CGDirectDisplayID get_display_id() const;
   INLINE CGDirectDisplayID get_display_id() const;
 
 
-public:
-  virtual PreferredWindowThread get_preferred_window_thread() const;
+  bool init_vsync(uint32_t &counter);
+  void wait_vsync(uint32_t &counter, bool adaptive=false);
 
 
 private:
 private:
+  static CVReturn display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
+                                  const CVTimeStamp *output_time,
+                                  CVOptionFlags flags_in, CVOptionFlags *flags_out,
+                                  void *context);
+
   void load_display_information();
   void load_display_information();
 
 
   // This is the Quartz display identifier.
   // This is the Quartz display identifier.
   CGDirectDisplayID _display;
   CGDirectDisplayID _display;
 
 
+  CVDisplayLinkRef _display_link = nullptr;
+  patomic<int> _last_wait_frame {0};
+  uint32_t _vsync_counter = 0;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 186 - 5
panda/src/cocoadisplay/cocoaGraphicsPipe.mm

@@ -40,18 +40,49 @@ CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
   [thread start];
   [thread start];
   [thread autorelease];
   [thread autorelease];
 
 
+  // If the application is dpi-aware, iterate over all the screens to find the
+  // one with our display ID and get the backing scale factor to configure the
+  // detected display zoom. Otherwise the detected display zoom keeps its
+  // default value of 1.0
+
+  if (dpi_aware) {
+    NSScreen *screen;
+    NSEnumerator *e = [[NSScreen screens] objectEnumerator];
+    while (screen = (NSScreen *) [e nextObject]) {
+      NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
+      if (_display == (CGDirectDisplayID) [num longValue]) {
+        set_detected_display_zoom([screen backingScaleFactor]);
+        if (cocoadisplay_cat.is_debug()) {
+          cocoadisplay_cat.debug()
+            << "Display zoom is " << [screen backingScaleFactor] << "\n";
+        }
+        break;
+      }
+    }
+  }
+
   // We used to also obtain the corresponding NSScreen here, but this causes
   // We used to also obtain the corresponding NSScreen here, but this causes
   // the application icon to start bouncing, which may be undesirable for
   // the application icon to start bouncing, which may be undesirable for
   // apps that will never open a window.
   // apps that will never open a window.
 
 
-  _display_width = CGDisplayPixelsWide(_display);
-  _display_height = CGDisplayPixelsHigh(_display);
+  // Although the name of these functions mention pixels, they actually return
+  // display points, we use the detected display zoom to transform the values
+  // into pixels.
+  _display_width = CGDisplayPixelsWide(_display) * _detected_display_zoom;
+  _display_height = CGDisplayPixelsHigh(_display) * _detected_display_zoom;
   load_display_information();
   load_display_information();
 
 
   if (cocoadisplay_cat.is_debug()) {
   if (cocoadisplay_cat.is_debug()) {
     cocoadisplay_cat.debug()
     cocoadisplay_cat.debug()
       << "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
       << "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
   }
   }
+
+  // It takes a while to fire up the display link, so let's fire it up now if
+  // we expect to need VSync.
+  if (sync_video) {
+    uint32_t counter;
+    init_vsync(counter);
+  }
 }
 }
 
 
 /**
 /**
@@ -64,19 +95,44 @@ load_display_information() {
   // _display_information->_device_id = CGDisplaySerialNumber(_display);
   // _display_information->_device_id = CGDisplaySerialNumber(_display);
 
 
   // Display modes
   // Display modes
+  CFDictionaryRef options = NULL;
+  const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
+  const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
+  options = CFDictionaryCreate(NULL,
+                               (const void **)dictkeys,
+                               (const void **)dictvalues,
+                               1,
+                               &kCFCopyStringDictionaryKeyCallBacks,
+                               &kCFTypeDictionaryValueCallBacks);
   size_t num_modes = 0;
   size_t num_modes = 0;
-  CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, NULL);
+  int32_t current_mode_id = -1;
+  CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, options);
   if (modes != NULL) {
   if (modes != NULL) {
     num_modes = CFArrayGetCount(modes);
     num_modes = CFArrayGetCount(modes);
     _display_information->_total_display_modes = num_modes;
     _display_information->_total_display_modes = num_modes;
     _display_information->_display_mode_array = new DisplayMode[num_modes];
     _display_information->_display_mode_array = new DisplayMode[num_modes];
+
+    // Get information about the current mode.
+    CGDisplayModeRef mode = CGDisplayCopyDisplayMode(_display);
+    if (mode) {
+      current_mode_id = CGDisplayModeGetIODisplayModeID(mode);
+      CGDisplayModeRelease(mode);
+    }
+  }
+  if (options != NULL) {
+    CFRelease(options);
   }
   }
 
 
   for (size_t i = 0; i < num_modes; ++i) {
   for (size_t i = 0; i < num_modes; ++i) {
     CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
     CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
 
 
-    _display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
-    _display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
+    if (dpi_aware) {
+      _display_information->_display_mode_array[i].width = CGDisplayModeGetPixelWidth(mode);
+      _display_information->_display_mode_array[i].height = CGDisplayModeGetPixelHeight(mode);
+    } else {
+      _display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
+      _display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
+    }
     _display_information->_display_mode_array[i].refresh_rate = CGDisplayModeGetRefreshRate(mode);
     _display_information->_display_mode_array[i].refresh_rate = CGDisplayModeGetRefreshRate(mode);
     _display_information->_display_mode_array[i].fullscreen_only = false;
     _display_information->_display_mode_array[i].fullscreen_only = false;
 
 
@@ -105,6 +161,14 @@ load_display_information() {
       // pixel can be deduced from the string length.  Nifty!
       // pixel can be deduced from the string length.  Nifty!
       _display_information->_display_mode_array[i].bits_per_pixel = CFStringGetLength(encoding);
       _display_information->_display_mode_array[i].bits_per_pixel = CFStringGetLength(encoding);
     }
     }
+
+    if (current_mode_id >= 0 && current_mode_id == CGDisplayModeGetIODisplayModeID(mode)) {
+      _display_information->_current_display_mode_index = i;
+
+      // Stop checking
+      current_mode_id = -1;
+    }
+
     CFRelease(encoding);
     CFRelease(encoding);
   }
   }
   if (modes != nullptr) {
   if (modes != nullptr) {
@@ -130,6 +194,14 @@ load_display_information() {
  */
  */
 CocoaGraphicsPipe::
 CocoaGraphicsPipe::
 ~CocoaGraphicsPipe() {
 ~CocoaGraphicsPipe() {
+  if (_display_link != nil) {
+    CVDisplayLinkRelease(_display_link);
+    _display_link = nil;
+
+    // Unblock any threads that may be waiting on the VSync counter.
+    __atomic_fetch_add(&_vsync_counter, 1u, __ATOMIC_SEQ_CST);
+    patomic_notify_all(&_vsync_counter);
+  }
 }
 }
 
 
 /**
 /**
@@ -143,3 +215,112 @@ CocoaGraphicsPipe::get_preferred_window_thread() const {
   // only be called from the main thread!
   // only be called from the main thread!
   return PWT_app;
   return PWT_app;
 }
 }
+
+/**
+ * Ensures a CVDisplayLink is created, which tells us when the display will
+ * want a frame, to avoid tearing (vertical blanking interval).
+ * Initializes the counter with the value that can be passed to wait_vsync
+ * to wait for the next interval.
+ */
+bool CocoaGraphicsPipe::
+init_vsync(uint32_t &counter) {
+  if (_display_link != nil) {
+    // Already set up.
+    __atomic_load(&_vsync_counter, &counter, __ATOMIC_SEQ_CST);
+    return true;
+  }
+
+  counter = 0;
+  _vsync_counter = 0;
+
+  CVDisplayLinkRef display_link;
+  CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
+    display_link = nil;
+    return false;
+  }
+
+  result = CVDisplayLinkSetCurrentCGDisplay(display_link, _display);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
+    CVDisplayLinkRelease(display_link);
+    display_link = nil;
+    return false;
+  }
+
+  result = CVDisplayLinkSetOutputCallback(display_link, &display_link_cb, this);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
+    CVDisplayLinkRelease(display_link);
+    display_link = nil;
+    return false;
+  }
+
+  result = CVDisplayLinkStart(display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
+    CVDisplayLinkRelease(display_link);
+    display_link = nil;
+    return false;
+  }
+
+  _display_link = display_link;
+  return true;
+}
+
+/**
+ * The first time this method is called in a frame, waits for the vertical
+ * blanking interval.  If init_vsync has not first been called, does nothing.
+ *
+ * The given counter will be updated with the vblank counter.  If adaptive is
+ * true and the value differs from the current, no wait will occur.
+ */
+void CocoaGraphicsPipe::
+wait_vsync(uint32_t &counter, bool adaptive) {
+  if (_display_link == nil) {
+    return;
+  }
+
+  // Use direct atomic operations since we need this to be thread-safe even
+  // when compiling without thread support.
+  uint32_t current_count = __atomic_load_n(&_vsync_counter, __ATOMIC_SEQ_CST);
+  uint32_t diff = current_count - counter;
+  if (diff > 0) {
+    if (cocoadisplay_cat.is_spam()) {
+      cocoadisplay_cat.spam()
+        << "Missed vertical blanking interval by " << diff << " frames.\n";
+    }
+    if (adaptive) {
+      counter = current_count;
+      return;
+    }
+  }
+
+  // We only wait for the first window that gets flipped in a single frame,
+  // otherwise we end up halving our FPS when we have multiple windows!
+  int cur_frame = ClockObject::get_global_clock()->get_frame_count();
+  if (_last_wait_frame.exchange(cur_frame) == cur_frame) {
+    counter = current_count;
+    return;
+  }
+
+  patomic_wait(&_vsync_counter, current_count);
+  __atomic_load(&_vsync_counter, &counter, __ATOMIC_SEQ_CST);
+}
+
+/**
+ * Called whenever a display wants a frame.  The context argument contains the
+ * applicable CocoaGraphicsPipe.
+ */
+CVReturn CocoaGraphicsPipe::
+display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
+                const CVTimeStamp *output_time, CVOptionFlags flags_in,
+                CVOptionFlags *flags_out, void *context) {
+
+  CocoaGraphicsPipe *pipe = (CocoaGraphicsPipe *)context;
+  __atomic_fetch_add(&pipe->_vsync_counter, 1u, __ATOMIC_SEQ_CST);
+  patomic_notify_all(&pipe->_vsync_counter);
+
+  return kCVReturnSuccess;
+}

+ 1 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -63,6 +63,7 @@ public:
   void handle_minimize_event(bool minimized);
   void handle_minimize_event(bool minimized);
   void handle_maximize_event(bool maximized);
   void handle_maximize_event(bool maximized);
   void handle_foreground_event(bool foreground);
   void handle_foreground_event(bool foreground);
+  void handle_backing_change_event();
   bool handle_close_request();
   bool handle_close_request();
   void handle_close_event();
   void handle_close_event();
   void handle_key_event(NSEvent *event);
   void handle_key_event(NSEvent *event);

+ 209 - 105
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -132,13 +132,20 @@ move_pointer(int device, int x, int y) {
     return true;
     return true;
   }
   }
 
 
+  // Mouse position is expressed in screen points and not pixels, but in Panda3D
+  // we are using pixel coordinates.
+  // Instead of using convertPointFromBacking and have complex logic to cope with
+  // the change of coordinate system, we cheat and directly use the contents scale
+  // of the view layer to convert pixel coordinates into screen point coordinates.
+  CGFloat contents_scale = _view.layer.contentsScale;
   if (device == 0) {
   if (device == 0) {
     CGPoint point;
     CGPoint point;
     if (_properties.get_fullscreen()) {
     if (_properties.get_fullscreen()) {
-      point = CGPointMake(x, y);
+      point = CGPointMake(float(x) / contents_scale,
+                          float(y) / contents_scale);
     } else {
     } else {
-      point = CGPointMake(x + _properties.get_x_origin(),
-                          y + _properties.get_y_origin());
+      point = CGPointMake((float(x) + _properties.get_x_origin()) / contents_scale,
+                          (float(y) + _properties.get_y_origin()) / contents_scale);
     }
     }
 
 
     if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
     if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
@@ -302,35 +309,89 @@ open_window() {
     }
     }
   }
   }
 
 
+  // Configure the origin and the size of the window.
+  // On macOS, screen coordinates are expressed in "points" which are independant
+  // of the pixel density of the screen. Panda3D however, expresses the size and
+  // origin of a window in pixels.
+  // So, when opening a window, we need to convert the origin and size from pixel
+  // units into point units. However, this conversion depends on the pixel density
+  // of the screen, the backing scale factor.
+  // As the origin and size of a window depends on the size of the screen of the
+  // parent view, their size must be converted first from points to pixels.
+  // If a window (or a view) is not configured to support high-dpi screen, macOS
+  // will upscale the window (or view) when displayed on a high-dpi screen.
+  // Therefore its backing scale factor will always be 1.0
+  // In a Panda3D application, windows are always configured as high resolution
+  // capable, but the view is only configured as high resolution if the dpi-aware
+  // configuration flag is set.
+  // If the app is not dpi-aware, we must upscale its size and origin from points
+  // into pixels as the window is always high resolution capable.
+
   // Center the window if coordinates were set to -1 or -2 TODO: perhaps in
   // Center the window if coordinates were set to -1 or -2 TODO: perhaps in
   // future, in the case of -1, it should use the origin used in a previous
   // future, in the case of -1, it should use the origin used in a previous
   // run of Panda
   // run of Panda
+
+  // Size of the requested window
+  NSSize size = NSMakeSize(_properties.get_x_size(), _properties.get_y_size());
   NSRect container;
   NSRect container;
+  CGFloat backing_scale_factor = screen.backingScaleFactor;
   if (parent_nsview != NULL) {
   if (parent_nsview != NULL) {
-    container = [parent_nsview bounds];
+    // Convert parent view bounds into pixel units.
+    container = [parent_nsview convertRectToBacking:[parent_nsview bounds]];
+    // If the app is not dpi-aware, we must convert its size from points into
+    // pixels as the window is always high resolution capable
+    if (!dpi_aware) {
+      size = [parent_nsview convertSizeToBacking:size];
+    }
   } else {
   } else {
     container = [screen frame];
     container = [screen frame];
     container.origin = NSMakePoint(0, 0);
     container.origin = NSMakePoint(0, 0);
+    container = [screen convertRectToBacking:container];
+    if (!dpi_aware) {
+      // Weirdly NSScreen does not respond to convertSizeToBacking, so we have to
+      // create a dummy rect just for converting the window size.
+      NSRect rect;
+      rect.origin = NSMakePoint(0, 0);
+      rect.size = size;
+      rect = [screen convertRectToBacking:rect];
+      size = rect.size;
+    }
   }
   }
   int x = _properties.get_x_origin();
   int x = _properties.get_x_origin();
   int y = _properties.get_y_origin();
   int y = _properties.get_y_origin();
 
 
+  // As we are converting a single value and the view is not created yet, it's
+  // easier to simply use the backing  scale factor and don't bother with
+  // coordinate system transformations.
   if (x < 0) {
   if (x < 0) {
-    x = floor(container.size.width / 2 - _properties.get_x_size() / 2);
+    x = floor(container.size.width / 2 - size.width / 2);
+  } else if (!dpi_aware) {
+    x *= backing_scale_factor;
   }
   }
   if (y < 0) {
   if (y < 0) {
-    y = floor(container.size.height / 2 - _properties.get_y_size() / 2);
+    y = floor(container.size.height / 2 - size.height / 2);
+  } else if (!dpi_aware) {
+    y *= backing_scale_factor;
+  }
+  if (dpi_aware) {
+    _properties.set_origin(x, y);
+  } else {
+    _properties.set_origin(x / backing_scale_factor, y / backing_scale_factor);
   }
   }
-  _properties.set_origin(x, y);
 
 
   if (_parent_window_handle == (WindowHandle *)NULL) {
   if (_parent_window_handle == (WindowHandle *)NULL) {
     // Content rectangle
     // Content rectangle
     NSRect rect;
     NSRect rect;
     if (_properties.get_fullscreen()) {
     if (_properties.get_fullscreen()) {
-      rect = container;
+      rect = [screen convertRectFromBacking:container];
     } else {
     } else {
-      rect = NSMakeRect(x, container.size.height - _properties.get_y_size() - y,
-                        _properties.get_x_size(), _properties.get_y_size());
+      rect = NSMakeRect(x, container.size.height - size.height - y,
+                        size.width, size.height);
+      if (parent_nsview != NULL) {
+        rect = [parent_nsview convertRectFromBacking:rect];
+      } else {
+        rect = [screen convertRectFromBacking:rect];
+      }
     }
     }
 
 
     // Configure the window decorations
     // Configure the window decorations
@@ -388,8 +449,10 @@ open_window() {
     _parent_window_handle->attach_child(_window_handle);
     _parent_window_handle->attach_child(_window_handle);
   }
   }
 
 
-  // Always disable application HiDPI support, Cocoa will do the eventual upscaling for us.
-  [_view setWantsBestResolutionOpenGLSurface:NO];
+  // Configure the view to be high resolution capable using the dpi-aware
+  // configuration flag. If dpi-aware is false, macOS will upscale the view
+  // for us.
+  [_view setWantsBestResolutionOpenGLSurface:dpi_aware];
   if (_properties.has_icon_filename()) {
   if (_properties.has_icon_filename()) {
     NSImage *image = load_image(_properties.get_icon_filename());
     NSImage *image = load_image(_properties.get_icon_filename());
     if (image != nil) {
     if (image != nil) {
@@ -596,22 +659,6 @@ set_properties_now(WindowProperties &properties) {
           }
           }
 
 
           if (switched) {
           if (switched) {
-            if (_window != nil) {
-              // For some reason, setting the style mask makes it give up its
-              // first-responder status.  And for some reason, we need to first
-              // restore the window to normal level before we switch fullscreen,
-              // otherwise we may get a black bar if we're currently on Z_top.
-              if (_properties.get_z_order() != WindowProperties::Z_normal) {
-                [_window setLevel: NSNormalWindowLevel];
-              }
-              if ([_window respondsToSelector:@selector(setStyleMask:)]) {
-                [_window setStyleMask:NSBorderlessWindowMask];
-              }
-              [_window makeFirstResponder:_view];
-              [_window setLevel:CGShieldingWindowLevel()];
-              [_window makeKeyAndOrderFront:nil];
-            }
-
             // We've already set the size property this way; clear it.
             // We've already set the size property this way; clear it.
             properties.clear_size();
             properties.clear_size();
             _properties.set_size(width, height);
             _properties.set_size(width, height);
@@ -684,6 +731,9 @@ set_properties_now(WindowProperties &properties) {
                                 NSMiniaturizableWindowMask | NSResizableWindowMask ];
                                 NSMiniaturizableWindowMask | NSResizableWindowMask ];
         }
         }
         [_window makeFirstResponder:_view];
         [_window makeFirstResponder:_view];
+        // Resize event fired by makeFirstResponder has an invalid backing scale factor
+        // The actual size must be reset afterward
+        handle_resize_event();
       }
       }
     }
     }
 
 
@@ -705,6 +755,9 @@ set_properties_now(WindowProperties &properties) {
                                NSMiniaturizableWindowMask | NSResizableWindowMask ];
                                NSMiniaturizableWindowMask | NSResizableWindowMask ];
       }
       }
       [_window makeFirstResponder:_view];
       [_window makeFirstResponder:_view];
+      // Resize event fired by makeFirstResponder has an invalid backing scale factor
+      // The actual size must be reset afterward
+      handle_resize_event();
     }
     }
 
 
     properties.clear_undecorated();
     properties.clear_undecorated();
@@ -715,10 +768,13 @@ set_properties_now(WindowProperties &properties) {
     int height = properties.get_y_size();
     int height = properties.get_y_size();
 
 
     if (!_properties.get_fullscreen()) {
     if (!_properties.get_fullscreen()) {
+      // We use the view, not the window, to convert the frame size, expressed
+      // in pixels, into points as the "dpi awareness" is managed by the view.
+      NSSize size = [_view convertSizeFromBacking:NSMakeSize(width, height)];
       if (_window != nil) {
       if (_window != nil) {
-        [_window setContentSize:NSMakeSize(width, height)];
+        [_window setContentSize:size];
       }
       }
-      [_view setFrameSize:NSMakeSize(width, height)];
+      [_view setFrameSize:size];
 
 
       if (cocoadisplay_cat.is_debug()) {
       if (cocoadisplay_cat.is_debug()) {
         cocoadisplay_cat.debug()
         cocoadisplay_cat.debug()
@@ -768,12 +824,14 @@ set_properties_now(WindowProperties &properties) {
     // Get the frame for the screen
     // Get the frame for the screen
     NSRect frame;
     NSRect frame;
     NSRect container;
     NSRect container;
+    // Note again that we are using the view to convert the frame and container
+    // size from points into pixels.
     if (_window != nil) {
     if (_window != nil) {
       NSRect window_frame = [_window frame];
       NSRect window_frame = [_window frame];
-      frame = [_window contentRectForFrameRect:window_frame];
+      frame = [_view convertRectToBacking:[_window contentRectForFrameRect:window_frame]];
       NSScreen *screen = [_window screen];
       NSScreen *screen = [_window screen];
       nassertv(screen != nil);
       nassertv(screen != nil);
-      container = [screen frame];
+      container = [_view convertRectToBacking:[screen frame]];
 
 
       // Prevent the centering from overlapping the Dock
       // Prevent the centering from overlapping the Dock
       if (y < 0) {
       if (y < 0) {
@@ -783,8 +841,8 @@ set_properties_now(WindowProperties &properties) {
         }
         }
       }
       }
     } else {
     } else {
-      frame = [_view frame];
-      container = [[_view superview] frame];
+      frame = [_view convertRectToBacking:[_view frame]];
+      container = [[_view superview] convertRectToBacking:[[_view superview] frame]];
     }
     }
 
 
     if (x < 0) {
     if (x < 0) {
@@ -795,22 +853,22 @@ set_properties_now(WindowProperties &properties) {
     }
     }
     _properties.set_origin(x, y);
     _properties.set_origin(x, y);
 
 
-    if (!_properties.get_fullscreen()) {
-      // Remember, Mac OS X coordinates are flipped in the vertical axis.
-      frame.origin.x = x;
-      frame.origin.y = container.size.height - y - frame.size.height;
+    frame.origin.x = x;
+    // Y coordinate in backing store is not flipped, but origin is still at the bottom left
+    frame.origin.y = y - container.size.height;
 
 
-      if (cocoadisplay_cat.is_debug()) {
-        cocoadisplay_cat.debug()
-          << "Setting window content origin to "
-          << frame.origin.x << ", " << frame.origin.y << "\n";
-      }
+    if (cocoadisplay_cat.is_debug()) {
+      cocoadisplay_cat.debug()
+        << "Setting window content origin to "
+        << frame.origin.x << ", " << frame.origin.y << "\n";
+    }
 
 
-      if (_window != nil) {
-        [_window setFrame:[_window frameRectForContentRect:frame] display:NO];
-      } else {
-        [_view setFrame:frame];
-      }
+    if (_window != nil) {
+      frame = [_view convertRectFromBacking:frame];
+      [_window setFrame:[_window frameRectForContentRect:frame] display:NO];
+    } else {
+      frame = [_view convertRectFromBacking:frame];
+      [_view setFrame:frame];
     }
     }
     properties.clear_origin();
     properties.clear_origin();
   }
   }
@@ -957,24 +1015,20 @@ unbind_context() {
 CFMutableArrayRef CocoaGraphicsWindow::
 CFMutableArrayRef CocoaGraphicsWindow::
 find_display_modes(int width, int height) {
 find_display_modes(int width, int height) {
   CFDictionaryRef options = NULL;
   CFDictionaryRef options = NULL;
-  // On macOS 10.15+ (Catalina), we want to select the display mode with the
-  // samescaling factor as the current view to avoid cropping or scaling issues.
-  // This is a workaround until HiDPI display or scaling factor is properly
-  // handled. CGDisplayCopyAllDisplayModes() does not return upscaled display
-  // mode unless explicitly asked with kCGDisplayShowDuplicateLowResolutionModes
+  // We want to select the display mode with the same scaling factor as the
+  // current view to avoid cropping or scaling issues.
+  // CGDisplayCopyAllDisplayModes() does not return upscaled display modes
+  // nor the current mode, unless explicitly asked with
+  // kCGDisplayShowDuplicateLowResolutionModes
   // (which is undocumented...).
   // (which is undocumented...).
-  bool macos_10_15_or_higher = false;
-  if (@available(macOS 10.15, *)) {
-    const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
-    const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
-    options = CFDictionaryCreate(NULL,
-                                 (const void **)dictkeys,
-                                 (const void **)dictvalues,
-                                 1,
-                                 &kCFCopyStringDictionaryKeyCallBacks,
-                                 &kCFTypeDictionaryValueCallBacks);
-    macos_10_15_or_higher = true;
-  }
+  const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
+  const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
+  options = CFDictionaryCreate(NULL,
+                               (const void **)dictkeys,
+                               (const void **)dictvalues,
+                               1,
+                               &kCFCopyStringDictionaryKeyCallBacks,
+                               &kCFTypeDictionaryValueCallBacks);
   CFMutableArrayRef valid_modes;
   CFMutableArrayRef valid_modes;
   valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
   valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 
 
@@ -985,29 +1039,39 @@ find_display_modes(int width, int height) {
 
 
   size_t num_modes = CFArrayGetCount(modes);
   size_t num_modes = CFArrayGetCount(modes);
   CGDisplayModeRef mode;
   CGDisplayModeRef mode;
-
-  // Get the current refresh rate and pixel encoding.
-  CFStringRef current_pixel_encoding;
-  double refresh_rate;
   mode = CGDisplayCopyDisplayMode(_display);
   mode = CGDisplayCopyDisplayMode(_display);
 
 
+  // Calculate requested display size and pixel size
+  CGSize display_size;
+  CGSize pixel_size;
+  if (dpi_aware) {
+    pixel_size = NSMakeSize(width, height);
+    display_size = [_view convertSizeFromBacking:pixel_size];
+  } else {
+    display_size = NSMakeSize(width, height);
+    // Calculate the pixel width and height of the fullscreen mode we want using
+    // the current display mode dimensions and pixel dimensions.
+    size_t pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
+    size_t pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
+    pixel_size = NSMakeSize(pixel_width, pixel_height);
+  }
+
   // First check if the current mode is adequate.
   // First check if the current mode is adequate.
-  // This test not done for macOS 10.15 and above as the mode resolution is
-  // not enough to identify a mode.
-  if (!macos_10_15_or_higher &&
-      CGDisplayModeGetWidth(mode) == width &&
-      CGDisplayModeGetHeight(mode) == height) {
+  if (CGDisplayModeGetWidth(mode) == display_size.width &&
+      CGDisplayModeGetHeight(mode) == display_size.height &&
+      CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
+      CGDisplayModeGetPixelHeight(mode) == pixel_size.height) {
     CFArrayAppendValue(valid_modes, mode);
     CFArrayAppendValue(valid_modes, mode);
     CGDisplayModeRelease(mode);
     CGDisplayModeRelease(mode);
     return valid_modes;
     return valid_modes;
   }
   }
 
 
+  // Get the current refresh rate and pixel encoding.
+  CFStringRef current_pixel_encoding;
+  double refresh_rate;
+
   current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
   current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
   refresh_rate = CGDisplayModeGetRefreshRate(mode);
   refresh_rate = CGDisplayModeGetRefreshRate(mode);
-  // Calculate the pixel width and height of the fullscreen mode we want using
-  // the currentdisplay mode dimensions and pixel dimensions.
-  size_t expected_pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
-  size_t expected_pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
   CGDisplayModeRelease(mode);
   CGDisplayModeRelease(mode);
 
 
   for (size_t i = 0; i < num_modes; ++i) {
   for (size_t i = 0; i < num_modes; ++i) {
@@ -1015,17 +1079,15 @@ find_display_modes(int width, int height) {
 
 
     CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
     CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
 
 
-    // As explained above, we want to select the fullscreen display mode using
-    // the same scaling factor, but only for MacOS 10.15+ To do this we check
-    // the mode width and height but also actual pixel widh and height.
-    if (CGDisplayModeGetWidth(mode) == width &&
-        CGDisplayModeGetHeight(mode) == height &&
+    // We select the fullscreen display mode using he same scaling factor
+    // To do this we check the mode width and height but also actual pixel widh
+    // and height.
+    if (CGDisplayModeGetWidth(mode) == display_size.width &&
+        CGDisplayModeGetHeight(mode) == display_size.height &&
         (int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
         (int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
-        (!macos_10_15_or_higher ||
-        (CGDisplayModeGetPixelWidth(mode) == expected_pixel_width &&
-         CGDisplayModeGetPixelHeight(mode) == expected_pixel_height)) &&
+        CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
+        CGDisplayModeGetPixelHeight(mode) == pixel_size.height &&
         CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
         CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
-
       if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
       if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
         // Exact match for refresh rate, prioritize this.
         // Exact match for refresh rate, prioritize this.
         CFArrayInsertValueAtIndex(valid_modes, 0, mode);
         CFArrayInsertValueAtIndex(valid_modes, 0, mode);
@@ -1103,14 +1165,25 @@ do_switch_fullscreen(CGDisplayModeRef mode) {
 
 
     NSRect frame = [[[_view window] screen] frame];
     NSRect frame = [[[_view window] screen] frame];
     if (cocoadisplay_cat.is_debug()) {
     if (cocoadisplay_cat.is_debug()) {
-      NSString *str = NSStringFromRect(frame);
+      NSString *str = NSStringFromSize([_view convertSizeToBacking:frame.size]);
       cocoadisplay_cat.debug()
       cocoadisplay_cat.debug()
-        << "Switched to fullscreen, screen rect is now " << [str UTF8String] << "\n";
+        << "Switched to fullscreen, screen size is now " << [str UTF8String] << "\n";
     }
     }
 
 
     if (_window != nil) {
     if (_window != nil) {
+      // For some reason, setting the style mask makes it give up its
+      // first-responder status.
+      if ([_window respondsToSelector:@selector(setStyleMask:)]) {
+        [_window setStyleMask:NSBorderlessWindowMask];
+      }
+      [_window makeFirstResponder:_view];
+      [_window setLevel:CGShieldingWindowLevel()];
+      [_window makeKeyAndOrderFront:nil];
+
+      // Window and view frame must be updated *after* the window reconfiguration
+      // or the size is not set properly !
       [_window setFrame:frame display:YES];
       [_window setFrame:frame display:YES];
-      [_view setFrame:NSMakeRect(0, 0, frame.size.width, frame.size.height)];
+      [_view setFrame:frame];
       [_window update];
       [_window update];
     }
     }
   }
   }
@@ -1252,19 +1325,25 @@ load_cursor(const Filename &filename) {
  */
  */
 void CocoaGraphicsWindow::
 void CocoaGraphicsWindow::
 handle_move_event() {
 handle_move_event() {
-  // Remember, Mac OS X uses flipped coordinates
   NSRect frame;
   NSRect frame;
+  NSRect container;
   int x, y;
   int x, y;
+  // Again, we are using the view to convert the frame and container size from
+  // points to pixels.
   if (_window == nil) {
   if (_window == nil) {
-    frame = [_view frame];
-    x = frame.origin.x;
-    y = [[_view superview] bounds].size.height - frame.origin.y - frame.size.height;
+    frame = [_view convertRectToBacking:[_view frame]];
+    container = [_view convertRectToBacking:[[_view superview] frame]];
   } else {
   } else {
-    frame = [_window contentRectForFrameRect:[_window frame]];
-    x = frame.origin.x;
-    y = [[_window screen] frame].size.height - frame.origin.y - frame.size.height;
+    frame = [_view convertRectToBacking:[_window contentRectForFrameRect:[_window frame]]];
+    NSScreen *screen = [_window screen];
+    nassertv(screen != nil);
+    container = [_view convertRectToBacking:[screen frame]];
   }
   }
 
 
+  // Y coordinate in backing store is not flipped, but origin is still at the bottom left
+  x = frame.origin.x;
+  y = container.size.height + frame.origin.y;
+
   if (x != _properties.get_x_origin() ||
   if (x != _properties.get_x_origin() ||
       y != _properties.get_y_origin()) {
       y != _properties.get_y_origin()) {
 
 
@@ -1290,7 +1369,7 @@ handle_resize_event() {
     [_view setFrameSize:contentRect.size];
     [_view setFrameSize:contentRect.size];
   }
   }
 
 
-  NSRect frame = [_view convertRect:[_view bounds] toView:nil];
+  NSRect frame = [_view convertRectToBacking:[_view bounds]];
 
 
   WindowProperties properties;
   WindowProperties properties;
   bool changed = false;
   bool changed = false;
@@ -1403,6 +1482,22 @@ handle_foreground_event(bool foreground) {
   }
   }
 }
 }
 
 
+
+/**
+ * Called by the window delegate when the properties of backing store of the
+ * window have changed.
+ */
+void CocoaGraphicsWindow::
+handle_backing_change_event() {
+  if (cocoadisplay_cat.is_debug()) {
+    cocoadisplay_cat.debug() << "Backing store properties have changed\n";
+  }
+  // Trigger a resize event to update the window size in case the backing scale
+  // factor did change.
+  handle_resize_event();
+}
+
+
 /**
 /**
  * Called by the window delegate when the user requests to close the window.
  * Called by the window delegate when the user requests to close the window.
  * This may not always be called, which is why there is also a
  * This may not always be called, which is why there is also a
@@ -1693,6 +1788,13 @@ void CocoaGraphicsWindow::
 handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
 handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
   double nx, ny;
   double nx, ny;
 
 
+  // Mouse position is received in screen points and not pixels, but in Panda3D
+  // we want to have the coordinates expressed in pixels.
+  // Instead of using convertPointFrom/toBackingStore and have complex logic to
+  // cope with the change of coordinate system, we cheat and directly use the
+  // contents scale of the view layer to convert screen point into pixels and
+  // vice-versa.
+  CGFloat contents_scale = _view.layer.contentsScale;
   if (absolute) {
   if (absolute) {
     if (cocoadisplay_cat.is_spam()) {
     if (cocoadisplay_cat.is_spam()) {
       if (in_window != _input->get_pointer().get_in_window()) {
       if (in_window != _input->get_pointer().get_in_window()) {
@@ -1704,14 +1806,14 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
       }
       }
     }
     }
 
 
-    nx = x;
-    ny = y;
+    nx = x * contents_scale;
+    ny = y * contents_scale;
 
 
   } else {
   } else {
     // We received deltas, so add it to the current mouse position.
     // We received deltas, so add it to the current mouse position.
     PointerData md = _input->get_pointer();
     PointerData md = _input->get_pointer();
-    nx = md.get_x() + x;
-    ny = md.get_y() + y;
+    nx = md.get_x() + x * contents_scale;
+    ny = md.get_y() + y * contents_scale;
   }
   }
 
 
   if (_properties.get_mouse_mode() == WindowProperties::M_confined
   if (_properties.get_mouse_mode() == WindowProperties::M_confined
@@ -1721,11 +1823,13 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
     nx = std::max(0., std::min((double) get_x_size() - 1, nx));
     nx = std::max(0., std::min((double) get_x_size() - 1, nx));
     ny = std::max(0., std::min((double) get_y_size() - 1, ny));
     ny = std::max(0., std::min((double) get_y_size() - 1, ny));
 
 
+    // Convert back mouse position to screen space using point units
     if (_properties.get_fullscreen()) {
     if (_properties.get_fullscreen()) {
-      point = CGPointMake(nx, ny);
+      point = CGPointMake(nx / contents_scale,
+                          ny / contents_scale);
     } else {
     } else {
-      point = CGPointMake(nx + _properties.get_x_origin(),
-                          ny + _properties.get_y_origin());
+      point = CGPointMake((nx + _properties.get_x_origin()) / contents_scale,
+                          (ny + _properties.get_y_origin()) / contents_scale);
     }
     }
 
 
     if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
     if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {

+ 1 - 0
panda/src/cocoadisplay/cocoaPandaWindowDelegate.h

@@ -29,6 +29,7 @@ class CocoaGraphicsWindow;
 - (void)windowDidDeminiaturize:(NSNotification *)notification;
 - (void)windowDidDeminiaturize:(NSNotification *)notification;
 - (void)windowDidBecomeKey:(NSNotification *)notification;
 - (void)windowDidBecomeKey:(NSNotification *)notification;
 - (void)windowDidResignKey:(NSNotification *)notification;
 - (void)windowDidResignKey:(NSNotification *)notification;
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification;
 - (BOOL)windowShouldClose:(id)sender;
 - (BOOL)windowShouldClose:(id)sender;
 - (void)windowWillClose:(id)sender;
 - (void)windowWillClose:(id)sender;
 
 

+ 4 - 0
panda/src/cocoadisplay/cocoaPandaWindowDelegate.mm

@@ -51,6 +51,10 @@
   _graphicsWindow->handle_foreground_event(false);
   _graphicsWindow->handle_foreground_event(false);
 }
 }
 
 
+- (void) windowDidChangeBackingProperties:(NSNotification *)notification {
+  _graphicsWindow->handle_backing_change_event();
+}
+
 - (BOOL) windowShouldClose:(id)sender {
 - (BOOL) windowShouldClose:(id)sender {
   if (cocoadisplay_cat.is_debug()) {
   if (cocoadisplay_cat.is_debug()) {
     cocoadisplay_cat.debug()
     cocoadisplay_cat.debug()

+ 1 - 0
panda/src/cocoadisplay/config_cocoadisplay.h

@@ -21,6 +21,7 @@
 NotifyCategoryDecl(cocoadisplay, EXPCL_PANDA_COCOADISPLAY, EXPTP_PANDA_COCOADISPLAY);
 NotifyCategoryDecl(cocoadisplay, EXPCL_PANDA_COCOADISPLAY, EXPTP_PANDA_COCOADISPLAY);
 
 
 extern ConfigVariableBool cocoa_invert_wheel_x;
 extern ConfigVariableBool cocoa_invert_wheel_x;
+extern ConfigVariableBool dpi_aware;
 
 
 extern EXPCL_PANDA_COCOADISPLAY void init_libcocoadisplay();
 extern EXPCL_PANDA_COCOADISPLAY void init_libcocoadisplay();
 
 

+ 5 - 0
panda/src/cocoadisplay/config_cocoadisplay.mm

@@ -32,6 +32,11 @@ ConfigVariableBool cocoa_invert_wheel_x
 ("cocoa-invert-wheel-x", false,
 ("cocoa-invert-wheel-x", false,
  PRC_DESC("Set this to true to swap the wheel_left and wheel_right mouse "
  PRC_DESC("Set this to true to swap the wheel_left and wheel_right mouse "
           "button events, to restore to the pre-1.10.12 behavior."));
           "button events, to restore to the pre-1.10.12 behavior."));
+ConfigVariableBool dpi_aware
+("dpi-aware", false,
+ PRC_DESC("The default behavior on macOS is for Panda3D to use upscaling on"
+          "high DPI screen. Set this to true to let the application use the"
+          "actual pixel density of the screen."));
 
 
 /**
 /**
  * Initializes the library.  This must be called at least once before any of
  * Initializes the library.  This must be called at least once before any of

+ 0 - 7
panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.h

@@ -20,7 +20,6 @@
 
 
 #import <AppKit/NSOpenGL.h>
 #import <AppKit/NSOpenGL.h>
 #import <OpenGL/OpenGL.h>
 #import <OpenGL/OpenGL.h>
-#import <CoreVideo/CoreVideo.h>
 
 
 /**
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -39,7 +38,6 @@ public:
                                CocoaGLGraphicsStateGuardian *share_with);
                                CocoaGLGraphicsStateGuardian *share_with);
 
 
   virtual ~CocoaGLGraphicsStateGuardian();
   virtual ~CocoaGLGraphicsStateGuardian();
-  bool setup_vsync();
 
 
   INLINE void lock_context();
   INLINE void lock_context();
   INLINE void unlock_context();
   INLINE void unlock_context();
@@ -49,11 +47,6 @@ public:
   NSOpenGLPixelFormat *_format = nullptr;
   NSOpenGLPixelFormat *_format = nullptr;
   FrameBufferProperties _fbprops;
   FrameBufferProperties _fbprops;
 
 
-  CVDisplayLinkRef _display_link = nullptr;
-  TrueMutexImpl _swap_lock;
-  TrueConditionVarImpl _swap_condition;
-  AtomicAdjust::Integer _last_wait_frame = 0;
-
 protected:
 protected:
   virtual void query_gl_version();
   virtual void query_gl_version();
   virtual void *do_get_extension_func(const char *name);
   virtual void *do_get_extension_func(const char *name);

+ 1 - 62
panda/src/cocoagldisplay/cocoaGLGraphicsStateGuardian.mm

@@ -28,21 +28,6 @@
 #define NSAppKitVersionNumber10_7 1138
 #define NSAppKitVersionNumber10_7 1138
 #endif
 #endif
 
 
-/**
- * Called whenever a display wants a frame.  The context argument contains the
- * applicable CocoaGLGraphicsStateGuardian.
- */
-static CVReturn
-display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
-                const CVTimeStamp* output_time, CVOptionFlags flags_in,
-                CVOptionFlags *flags_out, void *context) {
-  CocoaGLGraphicsStateGuardian *gsg = (CocoaGLGraphicsStateGuardian *)context;
-  gsg->_swap_lock.lock();
-  gsg->_swap_condition.notify();
-  gsg->_swap_lock.unlock();
-  return kCVReturnSuccess;
-}
-
 TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
 TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
 
 
 /**
 /**
@@ -51,8 +36,7 @@ TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
 CocoaGLGraphicsStateGuardian::
 CocoaGLGraphicsStateGuardian::
 CocoaGLGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
 CocoaGLGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
                            CocoaGLGraphicsStateGuardian *share_with) :
                            CocoaGLGraphicsStateGuardian *share_with) :
-  GLGraphicsStateGuardian(engine, pipe),
-  _swap_condition(_swap_lock)
+  GLGraphicsStateGuardian(engine, pipe)
 {
 {
   _share_context = nil;
   _share_context = nil;
   _context = nil;
   _context = nil;
@@ -71,57 +55,12 @@ CocoaGLGraphicsStateGuardian::
   if (_format != nil) {
   if (_format != nil) {
     [_format release];
     [_format release];
   }
   }
-  if (_display_link != nil) {
-    CVDisplayLinkRelease(_display_link);
-    _display_link = nil;
-    _swap_lock.lock();
-    _swap_condition.notify();
-    _swap_lock.unlock();
-  }
   if (_context != nil) {
   if (_context != nil) {
     [_context clearDrawable];
     [_context clearDrawable];
     [_context release];
     [_context release];
   }
   }
 }
 }
 
 
-/**
- * Creates a CVDisplayLink, which tells us when the display the window is on
- * will want a frame.
- */
-bool CocoaGLGraphicsStateGuardian::
-setup_vsync() {
-  if (_display_link != nil) {
-    // Already set up.
-    return true;
-  }
-
-  CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
-  if (result != kCVReturnSuccess) {
-    cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
-    return false;
-  }
-
-  result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
-  if (result != kCVReturnSuccess) {
-    cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
-    return false;
-  }
-
-  result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
-  if (result != kCVReturnSuccess) {
-    cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
-    return false;
-  }
-
-  result = CVDisplayLinkStart(_display_link);
-  if (result != kCVReturnSuccess) {
-    cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
-    return false;
-  }
-
-  return true;
-}
-
 /**
 /**
  * Gets the FrameBufferProperties to match the indicated config.
  * Gets the FrameBufferProperties to match the indicated config.
  */
  */

+ 1 - 0
panda/src/cocoagldisplay/cocoaGLGraphicsWindow.h

@@ -42,6 +42,7 @@ protected:
 
 
 private:
 private:
   bool _vsync_enabled = false;
   bool _vsync_enabled = false;
+  uint32_t _vsync_counter = 0;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 9 - 8
panda/src/cocoagldisplay/cocoaGLGraphicsWindow.mm

@@ -159,13 +159,16 @@ end_flip() {
     CocoaGLGraphicsStateGuardian *cocoagsg;
     CocoaGLGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
     DCAST_INTO_V(cocoagsg, _gsg);
 
 
-    if (_vsync_enabled) {
-      AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count();
-      if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) {
-        cocoagsg->_swap_lock.lock();
-        cocoagsg->_swap_condition.wait();
-        cocoagsg->_swap_lock.unlock();
+    if (sync_video) {
+      CocoaGraphicsPipe *cocoapipe = (CocoaGraphicsPipe *)_pipe.p();
+      if (!_vsync_enabled) {
+        // If this fails, we don't keep trying.
+        cocoapipe->init_vsync(_vsync_counter);
+        _vsync_enabled = true;
       }
       }
+      cocoapipe->wait_vsync(_vsync_counter);
+    } else {
+      _vsync_enabled = false;
     }
     }
 
 
     cocoagsg->lock_context();
     cocoagsg->lock_context();
@@ -237,8 +240,6 @@ open_window() {
   }
   }
   _fb_properties = cocoagsg->get_fb_properties();
   _fb_properties = cocoagsg->get_fb_properties();
 
 
-  _vsync_enabled = sync_video && cocoagsg->setup_vsync();
-
   return true;
   return true;
 }
 }
 
 

+ 17 - 9
panda/src/display/displayInformation.cxx

@@ -75,7 +75,6 @@ DisplayInformation::
 DisplayInformation::
 DisplayInformation::
 DisplayInformation() {
 DisplayInformation() {
   DisplayInformation::DetectionState state;
   DisplayInformation::DetectionState state;
-  int get_adapter_display_mode_state;
   int get_device_caps_state;
   int get_device_caps_state;
   int window_width;
   int window_width;
   int window_height;
   int window_height;
@@ -88,7 +87,6 @@ DisplayInformation() {
   uint64_t available_physical_memory;
   uint64_t available_physical_memory;
 
 
   state = DisplayInformation::DS_unknown;
   state = DisplayInformation::DS_unknown;
-  get_adapter_display_mode_state = false;
   get_device_caps_state = false;
   get_device_caps_state = false;
   window_width = 0;
   window_width = 0;
   window_height = 0;
   window_height = 0;
@@ -101,7 +99,7 @@ DisplayInformation() {
   available_physical_memory = 0;
   available_physical_memory = 0;
 
 
   _state = state;
   _state = state;
-  _get_adapter_display_mode_state = get_adapter_display_mode_state;
+  _current_display_mode_index = -1;
   _get_device_caps_state = get_device_caps_state;
   _get_device_caps_state = get_device_caps_state;
   _maximum_window_width = window_width;
   _maximum_window_width = window_width;
   _maximum_window_height = window_height;
   _maximum_window_height = window_height;
@@ -210,7 +208,17 @@ get_display_mode(int display_index) {
 }
 }
 
 
 /**
 /**
- *
+ * Returns the index of the current display mode (determined at the time of
+ * application start) in the display mode array, or -1 if this could not be
+ * determined.
+ */
+int DisplayInformation::
+get_current_display_mode_index() const {
+  return _current_display_mode_index;
+}
+
+/**
+ * @deprecated use get_display_mode instead.
  */
  */
 int DisplayInformation::
 int DisplayInformation::
 get_display_mode_width (int display_index) {
 get_display_mode_width (int display_index) {
@@ -225,7 +233,7 @@ get_display_mode_width (int display_index) {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated use get_display_mode instead.
  */
  */
 int DisplayInformation::
 int DisplayInformation::
 get_display_mode_height (int display_index) {
 get_display_mode_height (int display_index) {
@@ -240,7 +248,7 @@ get_display_mode_height (int display_index) {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated use get_display_mode instead.
  */
  */
 int DisplayInformation::
 int DisplayInformation::
 get_display_mode_bits_per_pixel (int display_index) {
 get_display_mode_bits_per_pixel (int display_index) {
@@ -255,9 +263,9 @@ get_display_mode_bits_per_pixel (int display_index) {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated use get_display_mode instead.
  */
  */
-int DisplayInformation::
+double DisplayInformation::
 get_display_mode_refresh_rate (int display_index) {
 get_display_mode_refresh_rate (int display_index) {
   int value;
   int value;
 
 
@@ -270,7 +278,7 @@ get_display_mode_refresh_rate (int display_index) {
 }
 }
 
 
 /**
 /**
- *
+ * @deprecated use get_display_mode instead.
  */
  */
 int DisplayInformation::
 int DisplayInformation::
 get_display_mode_fullscreen_only (int display_index) {
 get_display_mode_fullscreen_only (int display_index) {

+ 5 - 3
panda/src/display/displayInformation.h

@@ -21,7 +21,7 @@ PUBLISHED:
   int width;
   int width;
   int height;
   int height;
   int bits_per_pixel;
   int bits_per_pixel;
-  int refresh_rate;
+  double refresh_rate;
   int fullscreen_only;
   int fullscreen_only;
 
 
   bool operator == (const DisplayMode &other) const;
   bool operator == (const DisplayMode &other) const;
@@ -56,11 +56,13 @@ PUBLISHED:
   const DisplayMode &get_display_mode(int display_index);
   const DisplayMode &get_display_mode(int display_index);
   MAKE_SEQ(get_display_modes, get_total_display_modes, get_display_mode);
   MAKE_SEQ(get_display_modes, get_total_display_modes, get_display_mode);
 
 
+  int get_current_display_mode_index() const;
+
   // Older interface for display modes.
   // Older interface for display modes.
   int get_display_mode_width(int display_index);
   int get_display_mode_width(int display_index);
   int get_display_mode_height(int display_index);
   int get_display_mode_height(int display_index);
   int get_display_mode_bits_per_pixel(int display_index);
   int get_display_mode_bits_per_pixel(int display_index);
-  int get_display_mode_refresh_rate(int display_index);
+  double get_display_mode_refresh_rate(int display_index);
   int get_display_mode_fullscreen_only(int display_index);
   int get_display_mode_fullscreen_only(int display_index);
 
 
   GraphicsStateGuardian::ShaderModel get_shader_model();
   GraphicsStateGuardian::ShaderModel get_shader_model();
@@ -115,7 +117,7 @@ PUBLISHED:
 
 
 public:
 public:
   DetectionState _state;
   DetectionState _state;
-  int _get_adapter_display_mode_state;
+  int _current_display_mode_index;
   int _get_device_caps_state;
   int _get_device_caps_state;
   int _maximum_window_width;
   int _maximum_window_width;
   int _maximum_window_height;
   int _maximum_window_height;

+ 31 - 10
panda/src/display/graphicsStateGuardian.cxx

@@ -64,6 +64,11 @@
 
 
 using std::string;
 using std::string;
 
 
+static const LMatrix4 shadow_bias_mat(0.5f, 0.0f, 0.0f, 0.0f,
+                                      0.0f, 0.5f, 0.0f, 0.0f,
+                                      0.0f, 0.0f, 0.5f, 0.0f,
+                                      0.5f, 0.5f, 0.5f, 1.0f);
+
 //PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Buffer switch:Vertex");
 //PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Buffer switch:Vertex");
 //PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Buffer switch:Index");
 //PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Buffer switch:Index");
 //PStatCollector GraphicsStateGuardian::_shader_buffer_switch_pcollector("Buffer switch:Shader");
 //PStatCollector GraphicsStateGuardian::_shader_buffer_switch_pcollector("Buffer switch:Shader");
@@ -1543,9 +1548,30 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     }
     }
 
 
     const NodePath &np = _target_shader->get_shader_input_nodepath(name->get_parent());
     const NodePath &np = _target_shader->get_shader_input_nodepath(name->get_parent());
-    nassertv(!np.is_empty());
+    const PandaNode *node = np.node();
 
 
-    fetch_specified_member(np, name->get_basename(), into[0]);
+    // This is the only matrix member we support from NodePath inputs.
+    if (node != nullptr && node->is_of_type(LensNode::get_class_type()) &&
+        name->get_basename() == "shadowViewMatrix") {
+      const LensNode *lnode = (const LensNode *)node;
+      const Lens *lens = lnode->get_lens();
+
+      LMatrix4 t = _inv_cs_transform->get_mat() *
+        _scene_setup->get_camera_transform()->get_mat() *
+        np.get_net_transform()->get_inverse()->get_mat() *
+        LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
+
+      if (!lnode->is_of_type(PointLight::get_class_type())) {
+        t *= lens->get_projection_mat() * shadow_bias_mat;
+      }
+      *(LMatrix4f *)into = LCAST(float, t);
+    }
+    else {
+      display_cat.error()
+        << "Shader input " << *name << " requests invalid attribute "
+        << name->get_basename() << " from node " << np << "\n";
+      *(LMatrix4f *)into = LMatrix4f::ident_mat();
+    }
     return;
     return;
   }
   }
   case Shader::SMO_vec_constant_x_attrib: {
   case Shader::SMO_vec_constant_x_attrib: {
@@ -1588,12 +1614,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     }
     }
     return;
     return;
   }
   }
-  case Shader::SMO_apiview_to_apiclip_light_source_i: {
-    static const LMatrix4 biasmat(0.5f, 0.0f, 0.0f, 0.0f,
-                                  0.0f, 0.5f, 0.0f, 0.0f,
-                                  0.0f, 0.0f, 0.5f, 0.0f,
-                                  0.5f, 0.5f, 0.5f, 1.0f);
-
+  case Shader::SMO_apiview_to_apiclip_light_source_i: { // shadowViewMatrix
     const LightAttrib *target_light;
     const LightAttrib *target_light;
     _target_rs->get_attrib_def(target_light);
     _target_rs->get_attrib_def(target_light);
 
 
@@ -1616,14 +1637,14 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
         LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
         LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
 
 
       if (!lnode->is_of_type(PointLight::get_class_type())) {
       if (!lnode->is_of_type(PointLight::get_class_type())) {
-        t *= lens->get_projection_mat() * biasmat;
+        t *= lens->get_projection_mat() * shadow_bias_mat;
       }
       }
       ((LMatrix4f *)into)[i] = LCAST(float, t);
       ((LMatrix4f *)into)[i] = LCAST(float, t);
     }
     }
 
 
     // Apply just the bias matrix otherwise.
     // Apply just the bias matrix otherwise.
     for (; i < (size_t)count; ++i) {
     for (; i < (size_t)count; ++i) {
-      ((LMatrix4f *)into)[i] = LCAST(float, biasmat);
+      ((LMatrix4f *)into)[i] = LCAST(float, shadow_bias_mat);
     }
     }
     return;
     return;
   }
   }

+ 2 - 2
panda/src/glstuff/glShaderContext_src.cxx

@@ -1570,8 +1570,8 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
             bind._func = Shader::SMF_compose;
             bind._func = Shader::SMF_compose;
             bind._part[0] = Shader::SMO_model_to_apiview;
             bind._part[0] = Shader::SMO_model_to_apiview;
             bind._arg[0] = nullptr;
             bind._arg[0] = nullptr;
-            bind._part[1] = Shader::SMO_apiview_to_apiclip_light_source_i;
-            bind._arg[1] = nullptr;
+            bind._part[1] = Shader::SMO_mat_constant_x_attrib;
+            bind._arg[1] = iname->get_parent()->append("shadowViewMatrix");
           } else {
           } else {
             bind._part[0] = Shader::SMO_mat_constant_x_attrib;
             bind._part[0] = Shader::SMO_mat_constant_x_attrib;
             bind._arg[0] = InternalName::make(param_name);
             bind._arg[0] = InternalName::make(param_name);

+ 11 - 6
panda/src/gobj/geomVertexArrayData.cxx

@@ -215,19 +215,24 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 VertexBufferContext *GeomVertexArrayData::
 VertexBufferContext *GeomVertexArrayData::
 prepare_now(PreparedGraphicsObjects *prepared_objects,
 prepare_now(PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
             GraphicsStateGuardianBase *gsg) {
-  if (_contexts == nullptr) {
+  if (_contexts != nullptr) {
+    Contexts::const_iterator ci;
+    ci = _contexts->find(prepared_objects);
+    if (ci != _contexts->end()) {
+      return (*ci).second;
+    }
+  } else {
     _contexts = new Contexts;
     _contexts = new Contexts;
   }
   }
-  Contexts::const_iterator ci;
-  ci = _contexts->find(prepared_objects);
-  if (ci != _contexts->end()) {
-    return (*ci).second;
-  }
 
 
   VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
   VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
   if (vbc != nullptr) {
   if (vbc != nullptr) {
     (*_contexts)[prepared_objects] = vbc;
     (*_contexts)[prepared_objects] = vbc;
   }
   }
+  else if (_contexts->empty()) {
+    delete _contexts;
+    _contexts = nullptr;
+  }
   return vbc;
   return vbc;
 }
 }
 
 

+ 1 - 1
panda/src/gobj/shader.cxx

@@ -652,7 +652,6 @@ cp_add_mat_spec(ShaderMatSpec &spec) {
       case SMO_pixel_size:
       case SMO_pixel_size:
       case SMO_texpad_x:
       case SMO_texpad_x:
       case SMO_texpix_x:
       case SMO_texpix_x:
-      case SMO_attr_material:
       case SMO_attr_color:
       case SMO_attr_color:
       case SMO_attr_colorscale:
       case SMO_attr_colorscale:
       case SMO_satten_x:
       case SMO_satten_x:
@@ -684,6 +683,7 @@ cp_add_mat_spec(ShaderMatSpec &spec) {
         break;
         break;
 
 
       case SMO_identity:
       case SMO_identity:
+      case SMO_attr_material:
       case SMO_alight_x:
       case SMO_alight_x:
       case SMO_dlight_x:
       case SMO_dlight_x:
       case SMO_plight_x:
       case SMO_plight_x:

+ 11 - 6
panda/src/gobj/shaderBuffer.cxx

@@ -76,19 +76,24 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 BufferContext *ShaderBuffer::
 BufferContext *ShaderBuffer::
 prepare_now(PreparedGraphicsObjects *prepared_objects,
 prepare_now(PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
             GraphicsStateGuardianBase *gsg) {
-  if (_contexts == nullptr) {
+  if (_contexts != nullptr) {
+    Contexts::const_iterator ci;
+    ci = _contexts->find(prepared_objects);
+    if (ci != _contexts->end()) {
+      return (*ci).second;
+    }
+  } else {
     _contexts = new Contexts;
     _contexts = new Contexts;
   }
   }
-  Contexts::const_iterator ci;
-  ci = _contexts->find(prepared_objects);
-  if (ci != _contexts->end()) {
-    return (*ci).second;
-  }
 
 
   BufferContext *vbc = prepared_objects->prepare_shader_buffer_now(this, gsg);
   BufferContext *vbc = prepared_objects->prepare_shader_buffer_now(this, gsg);
   if (vbc != nullptr) {
   if (vbc != nullptr) {
     (*_contexts)[prepared_objects] = vbc;
     (*_contexts)[prepared_objects] = vbc;
   }
   }
+  else if (_contexts->empty()) {
+    delete _contexts;
+    _contexts = nullptr;
+  }
   return vbc;
   return vbc;
 }
 }
 
 

+ 1 - 13
panda/src/pnmimagetypes/bmp.h

@@ -92,19 +92,7 @@ BMPlenrgbtable(int classv, unsigned long bitcount)
                 pm_error(er_internal, "BMPlenrgbtable");
                 pm_error(er_internal, "BMPlenrgbtable");
                 return 0;
                 return 0;
         }
         }
-        switch (classv)
-        {
-        case C_WIN:
-                lenrgb = 4;
-                break;
-        case C_OS2:
-                lenrgb = 3;
-                break;
-        default:
-                pm_error(er_internal, "BMPlenrgbtable");
-                return 0;
-        }
-
+        lenrgb = (classv == C_OS2) ? 3 : 4;
         return (1 << bitcount) * lenrgb;
         return (1 << bitcount) * lenrgb;
 }
 }
 
 

+ 1 - 0
panda/src/pnmimagetypes/pnmFileTypeBMP.h

@@ -55,6 +55,7 @@ public:
     unsigned long offBits;
     unsigned long offBits;
 
 
     unsigned short  cBitCount;
     unsigned short  cBitCount;
+    unsigned short  cCompression;
     int             indexed;
     int             indexed;
     int             classv;
     int             classv;
 
 

+ 80 - 34
panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx

@@ -177,6 +177,7 @@ BMPreadinfoheader(
         unsigned long  *pcx,
         unsigned long  *pcx,
         unsigned long  *pcy,
         unsigned long  *pcy,
         unsigned short *pcBitCount,
         unsigned short *pcBitCount,
+        unsigned short *pcCompression,
         int            *pclassv)
         int            *pclassv)
 {
 {
         unsigned long   cbFix;
         unsigned long   cbFix;
@@ -185,6 +186,7 @@ BMPreadinfoheader(
         unsigned long   cx = 0;
         unsigned long   cx = 0;
         unsigned long   cy = 0;
         unsigned long   cy = 0;
         unsigned short  cBitCount = 0;
         unsigned short  cBitCount = 0;
+        unsigned long   cCompression = 0;
         int             classv = 0;
         int             classv = 0;
 
 
         cbFix = GetLong(fp);
         cbFix = GetLong(fp);
@@ -229,7 +231,9 @@ BMPreadinfoheader(
          * for the required total.
          * for the required total.
          */
          */
         if (classv != C_OS2) {
         if (classv != C_OS2) {
-            for (int i = 0; i < (int)cbFix - 16; i += 4) {
+            cCompression = GetLong(fp);
+
+            for (int i = 0; i < (int)cbFix - 20; i += 4) {
                 GetLong(fp);
                 GetLong(fp);
             }
             }
         }
         }
@@ -273,11 +277,13 @@ BMPreadinfoheader(
         pm_message("cy: %d", cy);
         pm_message("cy: %d", cy);
         pm_message("cPlanes: %d", cPlanes);
         pm_message("cPlanes: %d", cPlanes);
         pm_message("cBitCount: %d", cBitCount);
         pm_message("cBitCount: %d", cBitCount);
+        pm_message("cCompression: %d", cCompression);
 #endif
 #endif
 
 
         *pcx = cx;
         *pcx = cx;
         *pcy = cy;
         *pcy = cy;
         *pcBitCount = cBitCount;
         *pcBitCount = cBitCount;
+        *pcCompression = cCompression;
         *pclassv = classv;
         *pclassv = classv;
 
 
         *ppos += cbFix;
         *ppos += cbFix;
@@ -401,45 +407,84 @@ BMPreadbits(xel *array, xelval *alpha_array,
         unsigned long   cx,
         unsigned long   cx,
         unsigned long   cy,
         unsigned long   cy,
         unsigned short  cBitCount,
         unsigned short  cBitCount,
-        int             /* classv */,
+        unsigned long   cCompression,
         int             indexed,
         int             indexed,
         pixval         *R,
         pixval         *R,
         pixval         *G,
         pixval         *G,
         pixval         *B)
         pixval         *B)
 {
 {
-        long            y;
-
-        readto(fp, ppos, offBits);
+  long y;
 
 
-        if(cBitCount > 24 && cBitCount != 32)
-        {
-                pm_error("%s: cannot handle cBitCount: %d"
-                         ,ifname
-                         ,cBitCount);
-        }
+  readto(fp, ppos, offBits);
 
 
-        /*
-         * The picture is stored bottom line first, top line last
-         */
+  if (cBitCount > 24 && cBitCount != 32) {
+    pm_error("%s: cannot handle cBitCount: %d", ifname, cBitCount);
+  }
 
 
-        for (y = (long)cy - 1; y >= 0; y--)
-        {
-                int rc;
-                rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B);
-                if(rc == -1)
-                {
-                        pm_error("%s: couldn't read row %d"
-                                 ,ifname
-                                 ,y);
-                }
-                if(rc%4)
-                {
-                        pm_error("%s: row had bad number of bytes: %d"
-                                 ,ifname
-                                 ,rc);
-                }
+  if (cCompression == 1) {
+    // RLE8 compression
+    xel *row = array + (cy - 1) * cx;
+    xel *p = row;
+    unsigned long nbyte = 0;
+    while (true) {
+      int first = GetByte(fp);
+      int second = GetByte(fp);
+      nbyte += 2;
+
+      if (first != 0) {
+        // Repeated index.
+        for (int i = 0; i < first; ++i) {
+          PPM_ASSIGN(*p, R[second], G[second], B[second]);
+          ++p;
         }
         }
-
+      }
+      else if (second == 0) {
+        // End of line.
+        row -= cx;
+        p = row;
+      }
+      else if (second == 1) {
+        // End of image.
+        break;
+      }
+      else if (second == 2) {
+        // Delta.
+        int xoffset = GetByte(fp);
+        int yoffset = GetByte(fp);
+        nbyte += 2;
+        row -= cx * yoffset;
+        p += xoffset - cx * yoffset;
+      }
+      else {
+        // Absolute run.
+        for (int i = 0; i < second; ++i) {
+          int v = GetByte(fp);
+          ++nbyte;
+          PPM_ASSIGN(*p, R[v], G[v], B[v]);
+          ++p;
+        }
+        nbyte += second;
+        if (second % 2) {
+          // Pad to 16-bit boundary.
+          GetByte(fp);
+          ++nbyte;
+        }
+      }
+    }
+    *ppos += nbyte;
+  }
+  else {
+    // The picture is stored bottom line first, top line last
+    for (y = (long)cy - 1; y >= 0; y--) {
+      int rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B);
+      if (rc == -1) {
+        pm_error("%s: couldn't read row %d", ifname, y);
+      }
+      if (rc % 4) {
+        pm_error("%s: row had bad number of bytes: %d", ifname, rc);
+      }
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -474,7 +519,7 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
   pos = 0;
   pos = 0;
 
 
   BMPreadfileheader(file, &pos, &offBits);
   BMPreadfileheader(file, &pos, &offBits);
-  BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &classv);
+  BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &cCompression, &classv);
 
 
   if (offBits != BMPoffbits(classv, cBitCount)) {
   if (offBits != BMPoffbits(classv, cBitCount)) {
     pnmimage_bmp_cat.warning()
     pnmimage_bmp_cat.warning()
@@ -523,9 +568,10 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
 int PNMFileTypeBMP::Reader::
 int PNMFileTypeBMP::Reader::
 read_data(xel *array, xelval *alpha_array) {
 read_data(xel *array, xelval *alpha_array) {
   BMPreadbits(array, alpha_array, _file, &pos, offBits, _x_size, _y_size,
   BMPreadbits(array, alpha_array, _file, &pos, offBits, _x_size, _y_size,
-              cBitCount, classv, indexed, R, G, B);
+              cBitCount, cCompression, indexed, R, G, B);
 
 
-  if (pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) {
+  if (cCompression != 1 &&
+      pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) {
     pnmimage_bmp_cat.warning()
     pnmimage_bmp_cat.warning()
       << "Read " << pos << " bytes, expected to read "
       << "Read " << pos << " bytes, expected to read "
       << BMPlenfile(classv, cBitCount, _x_size, _y_size) << " bytes\n";
       << BMPlenfile(classv, cBitCount, _x_size, _y_size) << " bytes\n";

+ 2 - 2
panda/src/putil/bitArray_ext.cxx

@@ -20,7 +20,7 @@
  */
  */
 void Extension<BitArray>::
 void Extension<BitArray>::
 __init__(PyObject *init_value) {
 __init__(PyObject *init_value) {
-  if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) {
+  if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) {
     PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer");
     PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer");
     return;
     return;
   }
   }
@@ -76,7 +76,7 @@ __getstate__() const {
  */
  */
 void Extension<BitArray>::
 void Extension<BitArray>::
 __setstate__(PyObject *state) {
 __setstate__(PyObject *state) {
-  if (Py_SIZE(state) >= 0) {
+  if (PyLong_IsNonNegative(state)) {
     __init__(state);
     __init__(state);
   } else {
   } else {
     PyObject *inverted = PyNumber_Invert(state);
     PyObject *inverted = PyNumber_Invert(state);

+ 1 - 1
panda/src/putil/doubleBitMask_ext.I

@@ -17,7 +17,7 @@
 template<class BMType>
 template<class BMType>
 INLINE void Extension<DoubleBitMask<BMType> >::
 INLINE void Extension<DoubleBitMask<BMType> >::
 __init__(PyObject *init_value) {
 __init__(PyObject *init_value) {
-  if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) {
+  if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) {
     PyErr_SetString(PyExc_ValueError, "DoubleBitMask constructor requires a positive integer");
     PyErr_SetString(PyExc_ValueError, "DoubleBitMask constructor requires a positive integer");
     return;
     return;
   }
   }

+ 127 - 0
panda/src/tinydisplay/CMakeLists.txt

@@ -0,0 +1,127 @@
+if (NOT HAVE_TINYDISPLAY)
+  return()
+endif()
+
+set(P3TINYDISPLAY_HEADERS
+  config_tinydisplay.h
+  tinyGeomMunger.I tinyGeomMunger.h
+  tinySDLGraphicsPipe.I tinySDLGraphicsPipe.h
+  tinySDLGraphicsWindow.I tinySDLGraphicsWindow.h
+  tinyGraphicsBuffer.I tinyGraphicsBuffer.h
+  tinyGraphicsStateGuardian.I tinyGraphicsStateGuardian.h
+  tinyTextureContext.I tinyTextureContext.h
+  tinyWinGraphicsPipe.I tinyWinGraphicsPipe.h
+  tinyWinGraphicsWindow.I tinyWinGraphicsWindow.h
+  tinyXGraphicsPipe.I tinyXGraphicsPipe.h
+  tinyXGraphicsWindow.I tinyXGraphicsWindow.h
+  tinyOffscreenGraphicsPipe.I tinyOffscreenGraphicsPipe.h
+  srgb_tables.h
+  zbuffer.h
+  zfeatures.h
+  zgl.h
+  zline.h
+  zmath.h
+  ztriangle.h
+  ztriangle_two.h
+  ztriangle_code_1.h
+  ztriangle_code_2.h
+  ztriangle_code_3.h
+  ztriangle_code_4.h
+  ztriangle_table.h
+  store_pixel.h
+  store_pixel_code.h
+  store_pixel_table.h
+)
+
+set(P3TINYDISPLAY_SOURCES
+  clip.cxx
+  config_tinydisplay.cxx
+  error.cxx
+  image_util.cxx
+  init.cxx
+  td_light.cxx
+  memory.cxx
+  specbuf.cxx
+  store_pixel.cxx
+  td_texture.cxx
+  tinyGeomMunger.cxx
+  tinyGraphicsBuffer.cxx
+  tinyGraphicsStateGuardian.cxx
+  tinyOffscreenGraphicsPipe.cxx
+  tinySDLGraphicsPipe.cxx
+  tinySDLGraphicsWindow.cxx
+  tinyTextureContext.cxx
+  tinyWinGraphicsPipe.cxx
+  tinyWinGraphicsWindow.cxx
+  tinyXGraphicsPipe.cxx
+  tinyXGraphicsWindow.cxx
+  vertex.cxx
+  srgb_tables.cxx
+  zbuffer.cxx
+  zdither.cxx
+  zline.cxx
+  zmath.cxx
+)
+
+set(P3TINYDISPLAY_ZTRIANGLE_SOURCES
+  ztriangle_1.cxx
+  ztriangle_2.cxx
+  ztriangle_3.cxx
+  ztriangle_4.cxx
+  ztriangle_table.cxx
+)
+
+set_source_files_properties(${P3TINYDISPLAY_ZTRIANGLE_SOURCES}
+  PROPERTIES SKIP_UNITY_BUILD_INCLUSION YES)
+
+if(NOT MSVC)
+  set_source_files_properties(${P3TINYDISPLAY_ZTRIANGLE_SOURCES}
+    PROPERTIES COMPILE_FLAGS "-Wno-unused-but-set-variable")
+endif()
+
+if(HAVE_COCOA)
+  set(P3TINYDISPLAY_HEADERS ${P3TINYDISPLAY_HEADERS}
+    tinyCocoaGraphicsPipe.I tinyCocoaGraphicsPipe.h
+    tinyCocoaGraphicsWindow.I tinyCocoaGraphicsWindow.h)
+
+  set(P3TINYDISPLAY_SOURCES ${P3TINYDISPLAY_SOURCES}
+    tinyCocoaGraphicsPipe.cxx
+    tinyCocoaGraphicsWindow.mm)
+
+  set_source_files_properties(tinyCocoaGraphicsWindow.mm
+    PROPERTIES SKIP_UNITY_BUILD_INCLUSION YES)
+
+  add_compile_definitions(HAVE_COCOA)
+endif()
+
+composite_sources(p3tinydisplay P3TINYDISPLAY_SOURCES)
+
+# Determine the additional components to link in.
+set(COCOADISPLAY_LINK_TARGETS)
+
+if(WIN32)
+  list(APPEND COCOADISPLAY_LINK_TARGETS p3windisplay)
+endif()
+
+if(HAVE_X11)
+  list(APPEND COCOADISPLAY_LINK_TARGETS p3x11display)
+endif()
+
+if(HAVE_COCOA)
+  list(APPEND COCOADISPLAY_LINK_TARGETS p3cocoadisplay)
+endif()
+
+set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "TinyDisplay")
+add_metalib(p3tinydisplay ${MODULE_TYPE}
+  ${P3TINYDISPLAY_HEADERS} ${P3TINYDISPLAY_SOURCES} ${P3TINYDISPLAY_ZTRIANGLE_SOURCES}
+  COMPONENTS ${COCOADISPLAY_LINK_TARGETS})
+unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
+
+set_target_properties(p3tinydisplay PROPERTIES DEFINE_SYMBOL BUILDING_TINYDISPLAY)
+
+install(TARGETS p3tinydisplay
+  EXPORT TinyDisplay COMPONENT TinyDisplay
+  DESTINATION ${MODULE_DESTINATION}
+  ARCHIVE COMPONENT TinyDisplayDevel)
+
+export_targets(TinyDisplay NAMESPACE "Panda3D::TinyDisplay::" COMPONENT TinyDisplayDevel)

+ 2 - 0
panda/src/tinydisplay/srgb_tables.cxx

@@ -1,3 +1,5 @@
+#include "srgb_tables.h"
+
 const unsigned short decode_sRGB[256] = { 0x0000, 0x0013, 0x0027, 0x003b,
 const unsigned short decode_sRGB[256] = { 0x0000, 0x0013, 0x0027, 0x003b,
   0x004f, 0x0063, 0x0077, 0x008b, 0x009f, 0x00b3, 0x00c6, 0x00db, 0x00f0,
   0x004f, 0x0063, 0x0077, 0x008b, 0x009f, 0x00b3, 0x00c6, 0x00db, 0x00f0,
   0x0107, 0x011f, 0x0139, 0x0153, 0x016f, 0x018c, 0x01aa, 0x01ca, 0x01eb,
   0x0107, 0x011f, 0x0139, 0x0153, 0x016f, 0x018c, 0x01aa, 0x01ca, 0x01eb,

+ 5 - 0
panda/src/tinydisplay/srgb_tables.h

@@ -1,6 +1,11 @@
+#ifndef SRGB_TABLES_H
+#define SRGB_TABLES_H
+
 /* 8-bit sRGB to 16-bit linear */
 /* 8-bit sRGB to 16-bit linear */
 extern const unsigned short decode_sRGB[256];
 extern const unsigned short decode_sRGB[256];
 
 
 /* 12-bit linear to 8-bit sRGB.  I used 12-bit because it can
 /* 12-bit linear to 8-bit sRGB.  I used 12-bit because it can
    represent all possible 8-bit sRGB values. */
    represent all possible 8-bit sRGB values. */
 extern const unsigned char encode_sRGB[4096];
 extern const unsigned char encode_sRGB[4096];
+
+#endif

+ 2 - 0
panda/src/tinydisplay/tinyCocoaGraphicsWindow.h

@@ -62,6 +62,8 @@ private:
 
 
   small_vector<SwapBuffer, 2> _swap_chain;
   small_vector<SwapBuffer, 2> _swap_chain;
   int _swap_index = 0;
   int _swap_index = 0;
+  uint32_t _vsync_counter = 0;
+  bool _vsync_enabled = false;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 15 - 0
panda/src/tinydisplay/tinyCocoaGraphicsWindow.mm

@@ -114,6 +114,21 @@ end_flip() {
   if (_flip_ready) {
   if (_flip_ready) {
     do_present();
     do_present();
     _swap_index = (_swap_index + 1) % _swap_chain.size();
     _swap_index = (_swap_index + 1) % _swap_chain.size();
+
+    // We don't really support proper VSync because we just update the backing
+    // store and let the OS update it when needed, but we still need to wait
+    // for VBlank so that the frame rate is appropriately limited.
+    if (sync_video) {
+      CocoaGraphicsPipe *cocoapipe = (CocoaGraphicsPipe *)_pipe.p();
+      if (!_vsync_enabled) {
+        // If this fails, we don't keep trying.
+        cocoapipe->init_vsync(_vsync_counter);
+        _vsync_enabled = true;
+      }
+      cocoapipe->wait_vsync(_vsync_counter);
+    } else {
+      _vsync_enabled = false;
+    }
   }
   }
 
 
   GraphicsWindow::end_flip();
   GraphicsWindow::end_flip();

+ 2 - 0
panda/src/tinydisplay/zmath.h

@@ -1,6 +1,8 @@
 #ifndef __ZMATH__
 #ifndef __ZMATH__
 #define __ZMATH__
 #define __ZMATH__
 
 
+#include "numeric_types.h"
+
 /* Matrix & Vertex */
 /* Matrix & Vertex */
 
 
 typedef struct {
 typedef struct {

+ 6 - 0
panda/src/wgldisplay/wglGraphicsStateGuardian.cxx

@@ -336,6 +336,8 @@ choose_pixel_format(const FrameBufferProperties &properties,
   _supports_pixel_format = has_extension("WGL_ARB_pixel_format");
   _supports_pixel_format = has_extension("WGL_ARB_pixel_format");
   _supports_wgl_multisample = has_extension("WGL_ARB_multisample");
   _supports_wgl_multisample = has_extension("WGL_ARB_multisample");
 
 
+  bool supports_pixel_format_float = _supports_pixel_format && has_extension("WGL_ARB_pixel_format_float");
+
   if (has_extension("WGL_ARB_create_context")) {
   if (has_extension("WGL_ARB_create_context")) {
     _wglCreateContextAttribsARB =
     _wglCreateContextAttribsARB =
       (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
       (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
@@ -381,6 +383,10 @@ choose_pixel_format(const FrameBufferProperties &properties,
     iattrib_list[ni++] = WGL_PIXEL_TYPE_ARB;
     iattrib_list[ni++] = WGL_PIXEL_TYPE_ARB;
     iattrib_list[ni++] = WGL_TYPE_RGBA_ARB;
     iattrib_list[ni++] = WGL_TYPE_RGBA_ARB;
   }
   }
+  else if (supports_pixel_format_float) {
+    iattrib_list[ni++] = WGL_PIXEL_TYPE_ARB;
+    iattrib_list[ni++] = WGL_TYPE_RGBA_FLOAT_ARB;
+  }
 
 
   if (need_pbuffer) {
   if (need_pbuffer) {
     iattrib_list[ni++] = WGL_DRAW_TO_PBUFFER_ARB;
     iattrib_list[ni++] = WGL_DRAW_TO_PBUFFER_ARB;

+ 12 - 3
panda/src/windisplay/winDetectDx.h

@@ -99,7 +99,6 @@ static int get_display_information (DisplaySearchParameters &display_search_para
 
 
   int success;
   int success;
   DisplayInformation::DetectionState state;
   DisplayInformation::DetectionState state;
-  int get_adapter_display_mode_state;
   int get_device_caps_state;
   int get_device_caps_state;
 
 
   GraphicsStateGuardian::ShaderModel shader_model;
   GraphicsStateGuardian::ShaderModel shader_model;
@@ -117,6 +116,7 @@ static int get_display_information (DisplaySearchParameters &display_search_para
   int window_height;
   int window_height;
   int window_bits_per_pixel;
   int window_bits_per_pixel;
   int total_display_modes;
   int total_display_modes;
+  int current_display_mode_index;
   DisplayMode *display_mode_array;
   DisplayMode *display_mode_array;
 
 
   uint64_t physical_memory;
   uint64_t physical_memory;
@@ -139,6 +139,7 @@ static int get_display_information (DisplaySearchParameters &display_search_para
   window_height = 0;
   window_height = 0;
   window_bits_per_pixel = 0;
   window_bits_per_pixel = 0;
   total_display_modes = 0;
   total_display_modes = 0;
+  current_display_mode_index = -1;
   display_mode_array = nullptr;
   display_mode_array = nullptr;
 
 
   minimum_width = display_search_parameters._minimum_width;
   minimum_width = display_search_parameters._minimum_width;
@@ -153,7 +154,6 @@ static int get_display_information (DisplaySearchParameters &display_search_para
   texture_memory = 0;
   texture_memory = 0;
 
 
   state = DisplayInformation::DS_unknown;
   state = DisplayInformation::DS_unknown;
-  get_adapter_display_mode_state = false;
   get_device_caps_state = false;
   get_device_caps_state = false;
 
 
   physical_memory = 0;
   physical_memory = 0;
@@ -193,6 +193,7 @@ static int get_display_information (DisplaySearchParameters &display_search_para
         device_type = D3DDEVTYPE_HAL;
         device_type = D3DDEVTYPE_HAL;
 
 
         // windowed mode max res and format
         // windowed mode max res and format
+        bool get_adapter_display_mode_state = false;
         if (direct_3d -> GetAdapterDisplayMode (adapter, &current_d3d_display_mode) == D3D_OK) {
         if (direct_3d -> GetAdapterDisplayMode (adapter, &current_d3d_display_mode) == D3D_OK) {
           if (debug) {
           if (debug) {
             printf ("current mode  w = %d h = %d r = %d f = %d \n",
             printf ("current mode  w = %d h = %d r = %d f = %d \n",
@@ -428,6 +429,14 @@ static int get_display_information (DisplaySearchParameters &display_search_para
                       display_mode -> refresh_rate = d3d_display_mode.RefreshRate;
                       display_mode -> refresh_rate = d3d_display_mode.RefreshRate;
                       display_mode -> fullscreen_only = display_format_array [format_index].fullscreen_only;
                       display_mode -> fullscreen_only = display_format_array [format_index].fullscreen_only;
 
 
+                      if (get_adapter_display_mode_state &&
+                          d3d_display_mode.Width == current_d3d_display_mode.Width &&
+                          d3d_display_mode.Height == current_d3d_display_mode.Height &&
+                          d3d_display_mode.RefreshRate == current_d3d_display_mode.RefreshRate &&
+                          d3d_display_mode.Format == current_d3d_display_mode.Format) {
+                        current_display_mode_index = total_display_modes;
+                      }
+
                       total_display_modes++;
                       total_display_modes++;
                     }
                     }
                   }
                   }
@@ -587,7 +596,7 @@ static int get_display_information (DisplaySearchParameters &display_search_para
 
 
   if (success) {
   if (success) {
     display_information -> _state = state;
     display_information -> _state = state;
-    display_information -> _get_adapter_display_mode_state = get_adapter_display_mode_state;
+    display_information -> _current_display_mode_index = current_display_mode_index;
     display_information -> _get_device_caps_state = get_device_caps_state;
     display_information -> _get_device_caps_state = get_device_caps_state;
     display_information -> _maximum_window_width = window_width;
     display_information -> _maximum_window_width = window_width;
     display_information -> _maximum_window_height = window_height;
     display_information -> _maximum_window_height = window_height;

+ 16 - 0
panda/src/windisplay/winGraphicsPipe.cxx

@@ -328,9 +328,21 @@ WinGraphicsPipe() {
     if (windisplay_cat.is_debug()) {
     if (windisplay_cat.is_debug()) {
       windisplay_cat.debug() << "Using EnumDisplaySettings to fetch display information.\n";
       windisplay_cat.debug() << "Using EnumDisplaySettings to fetch display information.\n";
     }
     }
+
     pvector<DisplayMode> display_modes;
     pvector<DisplayMode> display_modes;
+    DisplayMode current_mode = {0};
+    int current_mode_index = -1;
     DEVMODE dm{};
     DEVMODE dm{};
     dm.dmSize = sizeof(dm);
     dm.dmSize = sizeof(dm);
+
+    if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &dm) != 0) {
+      current_mode.width = dm.dmPelsWidth;
+      current_mode.height = dm.dmPelsHeight;
+      current_mode.bits_per_pixel = dm.dmBitsPerPel;
+      current_mode.refresh_rate = dm.dmDisplayFrequency;
+      current_mode.fullscreen_only = 0;
+    }
+
     for (int i = 0; EnumDisplaySettings(nullptr, i, &dm) != 0; ++i) {
     for (int i = 0; EnumDisplaySettings(nullptr, i, &dm) != 0; ++i) {
       DisplayMode mode;
       DisplayMode mode;
       mode.width = dm.dmPelsWidth;
       mode.width = dm.dmPelsWidth;
@@ -339,6 +351,9 @@ WinGraphicsPipe() {
       mode.refresh_rate = dm.dmDisplayFrequency;
       mode.refresh_rate = dm.dmDisplayFrequency;
       mode.fullscreen_only = 0;
       mode.fullscreen_only = 0;
       if (i == 0 || mode != display_modes.back()) {
       if (i == 0 || mode != display_modes.back()) {
+        if (current_mode_index < 0 && mode == current_mode) {
+          current_mode_index = (int)display_modes.size();
+        }
         display_modes.push_back(mode);
         display_modes.push_back(mode);
       }
       }
     }
     }
@@ -346,6 +361,7 @@ WinGraphicsPipe() {
     // Copy this information to the DisplayInformation object.
     // Copy this information to the DisplayInformation object.
     _display_information->_total_display_modes = display_modes.size();
     _display_information->_total_display_modes = display_modes.size();
     if (!display_modes.empty()) {
     if (!display_modes.empty()) {
+      _display_information->_current_display_mode_index = current_mode_index;
       _display_information->_display_mode_array = new DisplayMode[display_modes.size()];
       _display_information->_display_mode_array = new DisplayMode[display_modes.size()];
       std::copy(display_modes.begin(), display_modes.end(),
       std::copy(display_modes.begin(), display_modes.end(),
                 _display_information->_display_mode_array);
                 _display_information->_display_mode_array);

+ 14 - 0
panda/src/x11display/x11GraphicsPipe.cxx

@@ -283,11 +283,25 @@ x11GraphicsPipe(const std::string &display) :
         x11display_cat.debug()
         x11display_cat.debug()
           << "Using XRRScreenResources to obtain display modes\n";
           << "Using XRRScreenResources to obtain display modes\n";
       }
       }
+
+      // Query current configuration, we just grab the first CRTC for now,
+      // since we don't have a way to represent multiple monitors.
+      RRMode current_mode_id = 0;
+      if (res->ncrtc > 0) {
+        if (auto info = get_crtc_info(res.get(), res->crtcs[0])) {
+          current_mode_id = info->mode;
+        }
+      }
+
       _display_information->_total_display_modes = res->nmode;
       _display_information->_total_display_modes = res->nmode;
       _display_information->_display_mode_array = new DisplayMode[res->nmode];
       _display_information->_display_mode_array = new DisplayMode[res->nmode];
       for (int i = 0; i < res->nmode; ++i) {
       for (int i = 0; i < res->nmode; ++i) {
         XRRModeInfo &mode = res->modes[i];
         XRRModeInfo &mode = res->modes[i];
 
 
+        if (mode.id == current_mode_id) {
+          _display_information->_current_display_mode_index = i;
+        }
+
         DisplayMode *dm = _display_information->_display_mode_array + i;
         DisplayMode *dm = _display_information->_display_mode_array + i;
         dm->width = mode.width;
         dm->width = mode.width;
         dm->height = mode.height;
         dm->height = mode.height;

+ 1 - 0
pandatool/CMakeLists.txt

@@ -28,6 +28,7 @@ add_subdirectory(src/imageprogs)
 add_subdirectory(src/lwo)
 add_subdirectory(src/lwo)
 add_subdirectory(src/lwoegg)
 add_subdirectory(src/lwoegg)
 add_subdirectory(src/lwoprogs)
 add_subdirectory(src/lwoprogs)
+add_subdirectory(src/mac-stats)
 add_subdirectory(src/miscprogs)
 add_subdirectory(src/miscprogs)
 add_subdirectory(src/objegg)
 add_subdirectory(src/objegg)
 add_subdirectory(src/objprogs)
 add_subdirectory(src/objprogs)

+ 34 - 7
pandatool/src/assimp/loaderFileTypeAssimp.cxx

@@ -57,22 +57,49 @@ get_extension() const {
  */
  */
 string LoaderFileTypeAssimp::
 string LoaderFileTypeAssimp::
 get_additional_extensions() const {
 get_additional_extensions() const {
+  // This may be called at static init time, so ensure it is constructed now.
+  static ConfigVariableString assimp_disable_extensions
+  ("assimp-disable-extensions", "gltf glb",
+   PRC_DESC("A list of extensions (without preceding dot) that should not be "
+            "loaded via the Assimp loader, even if Assimp supports these "
+            "formats.  It is useful to set this for eg. gltf and glb files "
+            "to prevent them from being accidentally loaded via the Assimp "
+            "plug-in instead of via a superior plug-in like panda3d-gltf."));
+
+  bool has_disabled_exts = !assimp_disable_extensions.empty();
+
   aiString aexts;
   aiString aexts;
   aiGetExtensionList(&aexts);
   aiGetExtensionList(&aexts);
 
 
+  char *buffer = (char *)alloca(aexts.length + 2);
+  char *p = buffer;
+
   // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot
   // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot
-  std::string ext;
   char *sub = strtok(aexts.data, ";");
   char *sub = strtok(aexts.data, ";");
   while (sub != nullptr) {
   while (sub != nullptr) {
-    ext += sub + 2;
-    sub = strtok(nullptr, ";");
-
-    if (sub != nullptr) {
-      ext += ' ';
+    bool enabled = true;
+    if (has_disabled_exts) {
+      for (size_t i = 0; i < assimp_disable_extensions.get_num_words(); ++i) {
+        std::string disabled_ext = assimp_disable_extensions.get_word(i);
+        if (strcmp(sub + 2, disabled_ext.c_str()) == 0) {
+          enabled = false;
+          break;
+        }
+      }
+    }
+    if (enabled) {
+      *(p++) = ' ';
+      size_t len = strlen(sub + 2);
+      memcpy(p, sub + 2, len);
+      p += len;
     }
     }
+
+    sub = strtok(nullptr, ";");
   }
   }
 
 
-  return ext;
+  // Strip first space
+  ++buffer;
+  return std::string(buffer, p - buffer);
 }
 }
 
 
 /**
 /**

+ 1 - 1
pandatool/src/gtk-stats/CMakeLists.txt

@@ -34,7 +34,7 @@ target_link_libraries(gtk-stats p3progbase p3pstatserver PKG::GTK3)
 
 
 # This program is NOT actually called gtk-stats. It's pstats-gtk on Win32 and
 # This program is NOT actually called gtk-stats. It's pstats-gtk on Win32 and
 # pstats everywhere else (as the Win32 GUI is not built).
 # pstats everywhere else (as the Win32 GUI is not built).
-if(WIN32)
+if(WIN32 OR APPLE)
   set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats-gtk")
   set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats-gtk")
 else()
 else()
   set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats")
   set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats")

+ 53 - 0
pandatool/src/mac-stats/CMakeLists.txt

@@ -0,0 +1,53 @@
+if(NOT APPLE OR NOT HAVE_NET)
+  return()
+endif()
+
+set(MACSTATS_HEADERS
+  macStats.h
+  macStatsAppDelegate.h
+  macStatsChartMenu.h
+  macStatsChartMenuDelegate.h
+  macStatsFlameGraph.h
+  macStatsGraph.h
+  macStatsGraphView.h
+  macStatsGraphViewController.h
+  macStatsLabel.h
+  macStatsLabelStack.h
+  macStatsMonitor.h
+  macStatsPianoRoll.h
+  macStatsScaleArea.h
+  macStatsServer.h
+  macStatsStripChart.h
+  macStatsTimeline.h
+)
+
+set(MACSTATS_SOURCES
+  macStats.mm
+  macStatsAppDelegate.mm
+  macStatsChartMenu.mm
+  macStatsChartMenuDelegate.mm
+  macStatsFlameGraph.mm
+  macStatsGraph.mm
+  macStatsGraphView.mm
+  macStatsGraphViewController.mm
+  macStatsLabel.mm
+  macStatsLabelStack.mm
+  macStatsMonitor.mm
+  macStatsPianoRoll.mm
+  macStatsScaleArea.mm
+  macStatsServer.mm
+  macStatsStripChart.mm
+  macStatsTimeline.mm
+)
+
+composite_sources(mac-stats MACSTATS_SOURCES)
+add_executable(mac-stats ${MACSTATS_HEADERS} ${MACSTATS_SOURCES})
+target_link_libraries(mac-stats p3progbase p3pstatserver)
+target_link_libraries(mac-stats "-framework Cocoa")
+target_link_libraries(mac-stats "-framework Carbon")
+target_link_libraries(mac-stats "-framework Quartz")
+
+# This program is NOT actually called win-stats. It's just pstats
+set_target_properties(mac-stats PROPERTIES OUTPUT_NAME "pstats")
+
+install(TARGETS mac-stats EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})

+ 30 - 13
pandatool/src/mac-stats/macStatsTimeline.mm

@@ -232,9 +232,6 @@ draw_bar(int row, int from_x, int to_x, int collector_index,
 
 
     if ((to_x - from_x) >= scale * 4) {
     if ((to_x - from_x) >= scale * 4) {
       // Only bother drawing the text if we've got some space to draw on.
       // Only bother drawing the text if we've got some space to draw on.
-      const PStatClientData *client_data = monitor->get_client_data();
-      const PStatCollectorDef &def = client_data->get_collector_def(collector_index);
-
       const CFStringRef keys[] = {
       const CFStringRef keys[] = {
         (__bridge CFStringRef)NSForegroundColorAttributeName,
         (__bridge CFStringRef)NSForegroundColorAttributeName,
         (__bridge CFStringRef)NSFontAttributeName,
         (__bridge CFStringRef)NSFontAttributeName,
@@ -245,7 +242,7 @@ draw_bar(int row, int from_x, int to_x, int collector_index,
       };
       };
       CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
       CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 
 
-      CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, def._name.c_str(), kCFStringEncodingUTF8);
+      CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, collector_name.c_str(), kCFStringEncodingUTF8);
       CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
       CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
 
 
       CTLineRef line = CTLineCreateWithAttributedString(astr);
       CTLineRef line = CTLineCreateWithAttributedString(astr);
@@ -260,23 +257,43 @@ draw_bar(int row, int from_x, int to_x, int collector_index,
       double text_left = std::max(from_x, 0) + scale / 2.0;
       double text_left = std::max(from_x, 0) + scale / 2.0;
       double text_right = std::min(to_x, get_xsize()) - scale / 2.0;
       double text_right = std::min(to_x, get_xsize()) - scale / 2.0;
       double text_top = top + (bottom - top - text_height) / 2.0 + text_height;
       double text_top = top + (bottom - top - text_height) / 2.0 + text_height;
-/*
+
       if (text_width >= text_right - text_left) {
       if (text_width >= text_right - text_left) {
         size_t c = collector_name.rfind(':');
         size_t c = collector_name.rfind(':');
         if (text_right - text_left < scale * 6) {
         if (text_right - text_left < scale * 6) {
           // It's a really tiny space.  Draw a single letter.
           // It's a really tiny space.  Draw a single letter.
-          const char *ch = collector_name.data() + (c != std::string::npos ? c + 1 : 0);
-          pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
-          pango_layout_set_text(layout, ch, 1);
-        } else {
+          UniChar ch = *(collector_name.data() + (c != std::string::npos ? c + 1 : 0));
+
+          CFStringRef str = CFStringCreateWithCharacters(kCFAllocatorDefault, &ch, 1);
+          CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
+
+          CTLineRef new_line = CTLineCreateWithAttributedString((CFAttributedStringRef)astr);
+          bounds = CTLineGetImageBounds(new_line, _ctx);
+          text_width = bounds.size.width;
+
+          CFRelease(line);
+          CFRelease(astr);
+          CFRelease(str);
+          line = new_line;
+        }
+        else {
           // Maybe just use everything after the last colon.
           // Maybe just use everything after the last colon.
           if (c != std::string::npos) {
           if (c != std::string::npos) {
-            pango_layout_set_text(layout, collector_name.data() + c + 1,
-                                          collector_name.size() - c - 1);
-            pango_layout_get_pixel_size(layout, &text_width, &text_height);
+            const char *short_name = collector_name.data() + c + 1;
+            CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, short_name, kCFStringEncodingUTF8);
+            CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
+
+            CTLineRef new_line = CTLineCreateWithAttributedString((CFAttributedStringRef)astr);
+            bounds = CTLineGetImageBounds(new_line, _ctx);
+            text_width = bounds.size.width;
+
+            CFRelease(line);
+            CFRelease(astr);
+            CFRelease(str);
+            line = new_line;
           }
           }
         }
         }
-      }*/
+      }
 
 
       if (text_width >= text_right - text_left) {
       if (text_width >= text_right - text_left) {
         // Have CoreText truncate to the correct length.
         // Have CoreText truncate to the correct length.

+ 2 - 1
requirements-test.txt

@@ -1,4 +1,5 @@
-pytest
+pytest>=3.9.0
 pytest-cov
 pytest-cov
 
 
 Pmw-py3==2.1
 Pmw-py3==2.1
+setuptools>=15.2

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.