Browse Source

Merge branch 'master' into webgl-port

rdb 5 năm trước cách đây
mục cha
commit
f04c2e0e0e
100 tập tin đã thay đổi với 1555 bổ sung662 xóa
  1. 7 10
      .github/workflows/ci.yml
  2. 7 0
      .travis.yml
  3. 1 0
      BACKERS.md
  4. 29 8
      CMakeLists.txt
  5. 3 3
      README.md
  6. 12 2
      cmake/macros/PackageConfig.cmake
  7. 24 0
      cmake/modules/FindFFMPEG.cmake
  8. 3 8
      contrib/src/sceneeditor/SideWindow.py
  9. 0 1
      contrib/src/sceneeditor/collisionWindow.py
  10. 3 8
      contrib/src/sceneeditor/controllerWindow.py
  11. 1 7
      contrib/src/sceneeditor/dataHolder.py
  12. 3 7
      contrib/src/sceneeditor/lightingPanel.py
  13. 1 1
      contrib/src/sceneeditor/quad.py
  14. 2 12
      contrib/src/sceneeditor/sceneEditor.py
  15. 2 8
      contrib/src/sceneeditor/seAnimPanel.py
  16. 2 9
      contrib/src/sceneeditor/seBlendAnimPanel.py
  17. 1 0
      contrib/src/sceneeditor/seCameraControl.py
  18. 2 6
      contrib/src/sceneeditor/seColorEntry.py
  19. 0 1
      contrib/src/sceneeditor/seFileSaver.py
  20. 1 0
      contrib/src/sceneeditor/seGrid.py
  21. 0 1
      contrib/src/sceneeditor/seLights.py
  22. 3 2
      contrib/src/sceneeditor/seManipulation.py
  23. 6 10
      contrib/src/sceneeditor/seMopathRecorder.py
  24. 3 8
      contrib/src/sceneeditor/seParticlePanel.py
  25. 0 1
      contrib/src/sceneeditor/seParticles.py
  26. 3 8
      contrib/src/sceneeditor/sePlacer.py
  27. 3 8
      contrib/src/sceneeditor/seSceneGraphExplorer.py
  28. 2 2
      contrib/src/sceneeditor/seSession.py
  29. 4 8
      contrib/src/sceneeditor/seTree.py
  30. 1 3
      direct/src/dcparser/dcClass_ext.cxx
  31. 1 1
      direct/src/dcparser/dcPacker_ext.cxx
  32. 1 1
      direct/src/directtools/DirectUtil.py
  33. 79 2
      direct/src/dist/FreezeTool.py
  34. 112 182
      direct/src/dist/commands.py
  35. 173 0
      direct/src/dist/installers.py
  36. 1 4
      direct/src/dist/pefile.py
  37. 2 2
      direct/src/distributed/cConnectionRepository.cxx
  38. 3 2
      direct/src/extensions_native/NodePath_extensions.py
  39. 3 2
      direct/src/gui/OnscreenGeom.py
  40. 3 2
      direct/src/gui/OnscreenImage.py
  41. 113 12
      direct/src/gui/OnscreenText.py
  42. 0 38
      direct/src/showbase/showBase.cxx
  43. 0 5
      direct/src/showbase/showBase.h
  44. 37 11
      direct/src/stdpy/pickle.py
  45. 37 15
      direct/src/tkwidgets/EntryScale.py
  46. 28 0
      doc/ReleaseNotes
  47. 0 4
      dtool/Config.cmake
  48. 16 3
      dtool/LocalSetup.cmake
  49. 2 0
      dtool/dtool_config.h.in
  50. 1 1
      dtool/src/dtoolbase/neverFreeMemory.h
  51. 2 0
      dtool/src/dtoolutil/CMakeLists.txt
  52. 7 3
      dtool/src/dtoolutil/executionEnvironment.cxx
  53. 9 0
      dtool/src/dtoolutil/panda_getopt_impl.cxx
  54. 2 0
      dtool/src/dtoolutil/panda_getopt_impl.h
  55. 18 0
      dtool/src/interrogatedb/py_compat.cxx
  56. 23 5
      dtool/src/interrogatedb/py_compat.h
  57. 1 1
      dtool/src/interrogatedb/py_panda.cxx
  58. 1 1
      dtool/src/interrogatedb/py_wrappers.cxx
  59. 30 10
      makepanda/installer.nsi
  60. 3 1
      makepanda/makepackage.py
  61. 181 96
      makepanda/makepanda.py
  62. 34 24
      makepanda/makepandacore.py
  63. 1 1
      makepanda/makewheel.py
  64. 8 0
      panda/src/audio/audioSound.h
  65. 12 1
      panda/src/audiotraits/CMakeLists.txt
  66. 4 2
      panda/src/audiotraits/fmodAudioSound.cxx
  67. 6 4
      panda/src/audiotraits/openalAudioSound.cxx
  68. 3 0
      panda/src/cocoadisplay/CMakeLists.txt
  69. 28 12
      panda/src/collide/collisionPolygon_ext.cxx
  70. 1 1
      panda/src/collide/collisionPolygon_ext.h
  71. 22 12
      panda/src/device/evdevInputDevice.cxx
  72. 2 0
      panda/src/device/inputDeviceManager.cxx
  73. 8 0
      panda/src/display/config_display.cxx
  74. 1 0
      panda/src/display/config_display.h
  75. 8 0
      panda/src/display/graphicsPipe.I
  76. 22 0
      panda/src/display/graphicsPipe.cxx
  77. 5 0
      panda/src/display/graphicsPipe.h
  78. 197 0
      panda/src/display/graphicsStateGuardian.cxx
  79. 7 0
      panda/src/egg/eggTexture.cxx
  80. 1 0
      panda/src/egg/eggTexture.h
  81. 4 0
      panda/src/egg2pg/eggLoader.cxx
  82. 3 0
      panda/src/egg2pg/eggSaver.cxx
  83. 6 6
      panda/src/event/pythonTask.cxx
  84. 3 0
      panda/src/express/zipArchive.h
  85. 1 1
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  86. 1 1
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  87. 50 12
      panda/src/glstuff/glShaderContext_src.cxx
  88. 23 0
      panda/src/gobj/geom.cxx
  89. 2 0
      panda/src/gobj/geom.h
  90. 8 0
      panda/src/gobj/geomPrimitive.cxx
  91. 22 9
      panda/src/gobj/geomTristrips.cxx
  92. 8 8
      panda/src/gobj/geomVertexReader.I
  93. 4 4
      panda/src/gobj/geomVertexReader.h
  94. 8 8
      panda/src/gobj/geomVertexWriter.I
  95. 4 4
      panda/src/gobj/geomVertexWriter.h
  96. 9 0
      panda/src/gobj/shader.h
  97. 3 0
      panda/src/gobj/textureStage.cxx
  98. 2 0
      panda/src/gobj/textureStage.h
  99. 3 3
      panda/src/linmath/lvecBase2_ext_src.I
  100. 3 3
      panda/src/linmath/lvecBase3_ext_src.I

+ 7 - 10
.github/workflows/ci.yml

@@ -97,11 +97,8 @@ jobs:
         mv panda3d-1.10.8/thirdparty thirdparty
         mv panda3d-1.10.8/thirdparty thirdparty
         rmdir panda3d-1.10.8
         rmdir panda3d-1.10.8
 
 
-        mkdir -p build/Frameworks
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework build/Frameworks/Cg.framework
-
-        mkdir -p "build/${{ matrix.config }}/Frameworks"
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework "build/${{ matrix.config }}/Frameworks/Cg.framework"
+        # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
+        install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
 
 
         brew install ccache
         brew install ccache
 
 
@@ -127,16 +124,16 @@ jobs:
       uses: actions/cache@v1
       uses: actions/cache@v1
       with:
       with:
         path: thirdparty
         path: thirdparty
-        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.7-r1
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.8-r3
     - 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.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-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.7/thirdparty -Destination .
+          Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
         }
         }
 
 
     - name: ccache (non-Windows)
     - name: ccache (non-Windows)
@@ -350,9 +347,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.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-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.7/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |

+ 7 - 0
.travis.yml

@@ -0,0 +1,7 @@
+language: cpp
+branches:
+  only:
+    - release/1.10.x
+    - release/1.9.x
+script:
+    - echo "Build disabled on master branch."

+ 1 - 0
BACKERS.md

@@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 
 * Sam Edwards
 * Sam Edwards
 * Max Voss
 * Max Voss
+* Hawkheart
 
 
 ## Enthusiasts
 ## Enthusiasts
 
 

+ 29 - 8
CMakeLists.txt

@@ -32,23 +32,32 @@ a CMake < 3.9. Making a guess if this is a multi-config generator.")
   endif()
   endif()
 endif()
 endif()
 
 
-# Define the type of build we are setting up.
-set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
-if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
-  list(APPEND _configs Coverage)
-endif()
-
+# Set the default CMAKE_BUILD_TYPE before calling project().
 if(IS_MULTICONFIG)
 if(IS_MULTICONFIG)
   message(STATUS "Using multi-configuration generator")
   message(STATUS "Using multi-configuration generator")
 else()
 else()
-  # Set the default CMAKE_BUILD_TYPE before calling project().
   if(NOT CMAKE_BUILD_TYPE)
   if(NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Standard CACHE STRING "Choose the type of build." FORCE)
     set(CMAKE_BUILD_TYPE Standard CACHE STRING "Choose the type of build." FORCE)
     message(STATUS "Using default build type ${CMAKE_BUILD_TYPE}")
     message(STATUS "Using default build type ${CMAKE_BUILD_TYPE}")
   else()
   else()
     message(STATUS "Using build type ${CMAKE_BUILD_TYPE}")
     message(STATUS "Using build type ${CMAKE_BUILD_TYPE}")
   endif()
   endif()
-  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
+# Set defaults for macOS, must be before project().
+if(APPLE)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS version to target")
+  set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
+
+  if(CMAKE_VERSION VERSION_LESS "3.19" AND NOT CMAKE_OSX_SYSROOT)
+    # Older CMake chose SDK based on deployment target, against Apple's recommendations.
+    # However, we need to use the latest to be able to target arm64.
+    if(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk" CACHE STRING "")
+    elseif(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk" CACHE STRING "")
+    endif()
+  endif()
 endif()
 endif()
 
 
 # Figure out the version
 # Figure out the version
@@ -59,6 +68,18 @@ project(Panda3D VERSION ${_version})
 unset(_version)
 unset(_version)
 unset(_s)
 unset(_s)
 
 
+# Determine the possible build types.  Must be *after* calling project().
+set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
+if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
+  list(APPEND _configs Coverage)
+endif()
+
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "${_configs}" CACHE STRING "" FORCE)
+else()
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
 enable_testing()
 enable_testing()
 
 
 string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")
 string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")

+ 3 - 3
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-7/).
+[this page](https://www.panda3d.org/download/sdk-1-10-8/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
 the following command:
 the following command:
 
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-- https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-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

+ 12 - 2
cmake/macros/PackageConfig.cmake

@@ -167,6 +167,12 @@ function(package_option name)
 
 
   set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
   set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
 
 
+  if(${found_as}_FOUND OR ${FOUND_AS}_FOUND)
+    set(PANDA_PACKAGE_FOUND_${name} ON PARENT_SCOPE)
+  else()
+    set(PANDA_PACKAGE_FOUND_${name} OFF PARENT_SCOPE)
+  endif()
+
   # Create the INTERFACE library used to depend on this package.
   # Create the INTERFACE library used to depend on this package.
   add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
   add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
 
 
@@ -279,15 +285,19 @@ function(show_packages)
   foreach(package ${_ALL_CONFIG_PACKAGES})
   foreach(package ${_ALL_CONFIG_PACKAGES})
     set(desc "${PANDA_PACKAGE_DESC_${package}}")
     set(desc "${PANDA_PACKAGE_DESC_${package}}")
     set(note "${PANDA_PACKAGE_NOTE_${package}}")
     set(note "${PANDA_PACKAGE_NOTE_${package}}")
-    if(HAVE_${package})
+
+    if(HAVE_${package} AND PANDA_PACKAGE_FOUND_${package})
       if(NOT note STREQUAL "")
       if(NOT note STREQUAL "")
         message("+ ${desc} (${note})")
         message("+ ${desc} (${note})")
       else()
       else()
         message("+ ${desc}")
         message("+ ${desc}")
       endif()
       endif()
 
 
+    elseif(HAVE_${package})
+      message("! ${desc} (enabled but not found)")
+
     else()
     else()
-      if(NOT ${package}_FOUND)
+      if(NOT PANDA_PACKAGE_FOUND_${package})
         set(reason "not found")
         set(reason "not found")
       elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
       elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
         set(reason "not requested")
         set(reason "not requested")

+ 24 - 0
cmake/modules/FindFFMPEG.cmake

@@ -89,16 +89,36 @@ if(APPLE)
   # When statically built for Apple, FFMPEG may have dependencies on these
   # When statically built for Apple, FFMPEG may have dependencies on these
   # additional frameworks and libraries.
   # additional frameworks and libraries.
 
 
+  find_library(APPLE_AUDIOTOOLBOX_LIBRARY AudioToolbox)
+  if(APPLE_AUDIOTOOLBOX_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_AUDIOTOOLBOX_LIBRARY}")
+  endif()
+
+  find_library(APPLE_COREMEDIA_LIBRARY CoreMedia)
+  if(APPLE_COREMEDIA_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_COREMEDIA_LIBRARY}")
+  endif()
+
   find_library(APPLE_COREVIDEO_LIBRARY CoreVideo)
   find_library(APPLE_COREVIDEO_LIBRARY CoreVideo)
   if(APPLE_COREVIDEO_LIBRARY)
   if(APPLE_COREVIDEO_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_COREVIDEO_LIBRARY}")
     list(APPEND FFMPEG_LIBRARIES "${APPLE_COREVIDEO_LIBRARY}")
   endif()
   endif()
 
 
+  find_library(APPLE_SECURITY_LIBRARY Security)
+  if(APPLE_SECURITY_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_SECURITY_LIBRARY}")
+  endif()
+
   find_library(APPLE_VDA_LIBRARY VideoDecodeAcceleration)
   find_library(APPLE_VDA_LIBRARY VideoDecodeAcceleration)
   if(APPLE_VDA_LIBRARY)
   if(APPLE_VDA_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_VDA_LIBRARY}")
     list(APPEND FFMPEG_LIBRARIES "${APPLE_VDA_LIBRARY}")
   endif()
   endif()
 
 
+  find_library(APPLE_VIDEOTOOLBOX_LIBRARY VideoToolbox)
+  if(APPLE_VIDEOTOOLBOX_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_VIDEOTOOLBOX_LIBRARY}")
+  endif()
+
   find_library(APPLE_ICONV_LIBRARY iconv)
   find_library(APPLE_ICONV_LIBRARY iconv)
   if(APPLE_ICONV_LIBRARY)
   if(APPLE_ICONV_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_ICONV_LIBRARY}")
     list(APPEND FFMPEG_LIBRARIES "${APPLE_ICONV_LIBRARY}")
@@ -108,6 +128,10 @@ if(APPLE)
   if(APPLE_BZ2_LIBRARY)
   if(APPLE_BZ2_LIBRARY)
     list(APPEND FFMPEG_LIBRARIES "${APPLE_BZ2_LIBRARY}")
     list(APPEND FFMPEG_LIBRARIES "${APPLE_BZ2_LIBRARY}")
   endif()
   endif()
+
+  mark_as_advanced(APPLE_AUDIOTOOLBOX_LIBRARY APPLE_COREMEDIA_LIBRARY
+    APPLE_COREVIDEO_LIBRARY APPLE_SECURITY_LIBRARY APPLE_VDA_LIBRARY
+    APPLE_VIDEOTOOLBOX_LIBRARY APPLE_ICONV_LIBRARY APPLE_BZ2_LIBRARY)
 endif()
 endif()
 
 
 mark_as_advanced(FFMPEG_LIBRARY_DIR)
 mark_as_advanced(FFMPEG_LIBRARY_DIR)

+ 3 - 8
contrib/src/sceneeditor/SideWindow.py

@@ -7,14 +7,9 @@ from direct.tkwidgets.VectorWidgets import ColorEntry
 from direct.showbase.TkGlobal import spawnTkLoop
 from direct.showbase.TkGlobal import spawnTkLoop
 import seSceneGraphExplorer
 import seSceneGraphExplorer
 
 
-import Pmw, sys
-
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, IntVar, Checkbutton, Toplevel
-    import tkinter
-else:
-    from Tkinter import Frame, IntVar, Checkbutton, Toplevel
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Frame, IntVar, Checkbutton, Toplevel
+import tkinter
 
 
 
 
 class sideWindow(AppShell):
 class sideWindow(AppShell):

+ 0 - 1
contrib/src/sceneeditor/collisionWindow.py

@@ -9,7 +9,6 @@ from seColorEntry import *
 from direct.tkwidgets import VectorWidgets
 from direct.tkwidgets import VectorWidgets
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
 from direct.tkwidgets import Slider
-import string, math, types
 from panda3d.core import *
 from panda3d.core import *
 
 
 
 

+ 3 - 8
contrib/src/sceneeditor/controllerWindow.py

@@ -4,14 +4,9 @@
 #################################################################
 #################################################################
 
 
 from direct.tkwidgets.AppShell import AppShell
 from direct.tkwidgets.AppShell import AppShell
-import sys, Pmw
-
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, Label, Button
-    import tkinter
-else:
-    from Tkinter import Frame, Label, Button
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Frame, Label, Button
+import tkinter
 
 
 # Define the Category
 # Define the Category
 KEYBOARD = 'Keyboard-'
 KEYBOARD = 'Keyboard-'

+ 1 - 7
contrib/src/sceneeditor/dataHolder.py

@@ -5,12 +5,7 @@ from direct.showbase.TkGlobal import*
 import Pmw
 import Pmw
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Floater
-
-if sys.version_info >= (3, 0):
-    from tkinter.filedialog import askopenfilename
-else:
-    from tkFileDialog import askopenfilename
-
+from tkinter.filedialog import askopenfilename
 
 
 #############################
 #############################
 # Scene Editor Python Files #
 # Scene Editor Python Files #
@@ -27,7 +22,6 @@ from direct.actor import Actor
 # Core Python Modules         #
 # Core Python Modules         #
 ###############################
 ###############################
 import os
 import os
-import string
 import sys
 import sys
 
 
 import seParticleEffect
 import seParticleEffect

+ 3 - 7
contrib/src/sceneeditor/lightingPanel.py

@@ -7,15 +7,11 @@ from direct.tkwidgets.AppShell import AppShell
 from seColorEntry import *
 from seColorEntry import *
 from direct.tkwidgets.VectorWidgets import Vector3Entry
 from direct.tkwidgets.VectorWidgets import Vector3Entry
 from direct.tkwidgets.Slider import Slider
 from direct.tkwidgets.Slider import Slider
-import sys, math, types, Pmw
+import Pmw
 from panda3d.core import *
 from panda3d.core import *
 
 
-if sys.version_info >= (3, 0):
-    from tkinter import Frame, Button, Menubutton, Menu
-    import tkinter
-else:
-    from Tkinter import Frame, Button, Menubutton, Menu
-    import Tkinter as tkinter
+from tkinter import Frame, Button, Menubutton, Menu
+import tkinter
 
 
 
 
 class lightingPanel(AppShell):
 class lightingPanel(AppShell):

+ 1 - 1
contrib/src/sceneeditor/quad.py

@@ -10,7 +10,7 @@ from direct.showbase.ShowBaseGlobal import *
 from direct.interval.IntervalGlobal import *
 from direct.interval.IntervalGlobal import *
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from panda3d.core import *
 from panda3d.core import *
-import math
+from direct.task import Task
 
 
 
 
 class ViewPort:
 class ViewPort:

+ 2 - 12
contrib/src/sceneeditor/sceneEditor.py

@@ -8,13 +8,8 @@ from direct.showbase.ShowBase import ShowBase
 ShowBase()
 ShowBase()
 
 
 from direct.showbase.TkGlobal import spawnTkLoop
 from direct.showbase.TkGlobal import spawnTkLoop
-
-if sys.version_info >= (3, 0):
-    from tkinter import *
-    from tkinter.filedialog import *
-else:
-    from Tkinter import *
-    from tkFileDialog import *
+from tkinter import *
+from tkinter.filedialog import *
 
 
 from direct.directtools.DirectGlobals import *
 from direct.directtools.DirectGlobals import *
 from direct.tkwidgets.AppShell import*
 from direct.tkwidgets.AppShell import*
@@ -36,17 +31,12 @@ from seBlendAnimPanel import *
 from controllerWindow import *
 from controllerWindow import *
 from AlignTool import *
 from AlignTool import *
 
 
-
-
-import os
-import string
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Dial
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
 from direct.tkwidgets import Slider
 from direct.actor import Actor
 from direct.actor import Actor
 import seAnimPanel
 import seAnimPanel
 from direct.task import Task
 from direct.task import Task
-import math
 
 
 #################################################################
 #################################################################
 # All scene and windows object will be stored in here.
 # All scene and windows object will be stored in here.

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

@@ -5,15 +5,9 @@
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
-import string
-import math
-import types
 from direct.task import Task
 from direct.task import Task
 
 
-if sys.version_info >= (3, 0):
-    from tkinter.simpledialog import askfloat
-else:
-    from tkSimpleDialog import askfloat
+from tkinter.simpledialog import askfloat
 
 
 FRAMES = 0
 FRAMES = 0
 SECONDS = 1
 SECONDS = 1
@@ -407,7 +401,7 @@ class AnimPanel(AppShell):
         #################################################################
         #################################################################
         if self.animName in self['animList']:
         if self.animName in self['animList']:
             # Convert scale value to float
             # Convert scale value to float
-            frame = string.atof(frame)
+            frame = float(frame)
             # Now convert t to seconds for offset calculations
             # Now convert t to seconds for offset calculations
             if self.unitsVar.get() == FRAMES:
             if self.unitsVar.get() == FRAMES:
                 frame = frame / self.fps
                 frame = frame / self.fps

+ 2 - 9
contrib/src/sceneeditor/seBlendAnimPanel.py

@@ -5,15 +5,8 @@
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
-import string
-import math
-import types
 from direct.task import Task
 from direct.task import Task
-
-if sys.version_info >= (3, 0):
-    from tkinter.simpledialog import askfloat
-else:
-    from tkSimpleDialog import askfloat
+from tkinter.simpledialog import askfloat
 
 
 FRAMES = 0
 FRAMES = 0
 SECONDS = 1
 SECONDS = 1
@@ -431,7 +424,7 @@ class BlendAnimPanel(AppShell):
         #################################################################
         #################################################################
         if (self.animNameA in self['animList'])and(self.animNameB in self['animList']):
         if (self.animNameA in self['animList'])and(self.animNameB in self['animList']):
             # Convert scale value to float
             # Convert scale value to float
-            frame = string.atof(frame)
+            frame = float(frame)
             # Now convert t to seconds for offset calculations
             # Now convert t to seconds for offset calculations
             if self.unitsVar.get() == FRAMES:
             if self.unitsVar.get() == FRAMES:
                 frame = frame / self.fps
                 frame = frame / self.fps

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

@@ -16,6 +16,7 @@ from direct.directtools.DirectUtil import *
 from seGeometry import *
 from seGeometry import *
 from direct.directtools.DirectGlobals import *
 from direct.directtools.DirectGlobals import *
 from direct.task import Task
 from direct.task import Task
+import math
 
 
 CAM_MOVE_DURATION = 1.2
 CAM_MOVE_DURATION = 1.2
 COA_MARKER_SF = 0.0075
 COA_MARKER_SF = 0.0075

+ 2 - 6
contrib/src/sceneeditor/seColorEntry.py

@@ -12,13 +12,9 @@
 from direct.tkwidgets import Valuator
 from direct.tkwidgets import Valuator
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Floater
 from direct.tkwidgets import Slider
 from direct.tkwidgets import Slider
-import sys, Pmw
+import Pmw
 from direct.tkwidgets.VectorWidgets import VectorEntry
 from direct.tkwidgets.VectorWidgets import VectorEntry
-
-if sys.version_info >= (3, 0):
-    from tkinter.colorchooser import askcolor
-else:
-    from tkColorChooser import askcolor
+from tkinter.colorchooser import askcolor
 
 
 
 
 class seColorEntry(VectorEntry):
 class seColorEntry(VectorEntry):

+ 0 - 1
contrib/src/sceneeditor/seFileSaver.py

@@ -8,7 +8,6 @@ from panda3d.core import *
 from direct.showbase.ShowBaseGlobal import *
 from direct.showbase.ShowBaseGlobal import *
 import os
 import os
 import shutil
 import shutil
-import string
 
 
 ####################################################################################################################################################
 ####################################################################################################################################################
 #### These modules are modified versions of Disney's equivalent modules
 #### These modules are modified versions of Disney's equivalent modules

+ 1 - 0
contrib/src/sceneeditor/seGrid.py

@@ -14,6 +14,7 @@
 from direct.showbase.DirectObject import *
 from direct.showbase.DirectObject import *
 from direct.directtools.DirectUtil import *
 from direct.directtools.DirectUtil import *
 from seGeometry import *
 from seGeometry import *
+import math
 
 
 class DirectGrid(NodePath,DirectObject):
 class DirectGrid(NodePath,DirectObject):
     def __init__(self):
     def __init__(self):

+ 0 - 1
contrib/src/sceneeditor/seLights.py

@@ -5,7 +5,6 @@
 from direct.showbase.DirectObject import *
 from direct.showbase.DirectObject import *
 from direct.directtools import DirectUtil
 from direct.directtools import DirectUtil
 from panda3d.core import *
 from panda3d.core import *
-import string
 
 
 
 
 class seLight(NodePath):
 class seLight(NodePath):

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

@@ -17,6 +17,7 @@ from direct.directtools.DirectGlobals import *
 from direct.directtools.DirectUtil import *
 from direct.directtools.DirectUtil import *
 from seGeometry import *
 from seGeometry import *
 from direct.task import Task
 from direct.task import Task
+import math
 
 
 class DirectManipulationControl(DirectObject):
 class DirectManipulationControl(DirectObject):
     def __init__(self):
     def __init__(self):
@@ -601,7 +602,7 @@ class ObjectHandles(NodePath,DirectObject):
         self.reparentTo(hidden)
         self.reparentTo(hidden)
 
 
     def enableHandles(self, handles):
     def enableHandles(self, handles):
-        if type(handles) == types.ListType:
+        if type(handles) is list:
             for handle in handles:
             for handle in handles:
                 self.enableHandle(handle)
                 self.enableHandle(handle)
         elif handles == 'x':
         elif handles == 'x':
@@ -642,7 +643,7 @@ class ObjectHandles(NodePath,DirectObject):
             self.zDiscGroup.reparentTo(self.zHandles)
             self.zDiscGroup.reparentTo(self.zHandles)
 
 
     def disableHandles(self, handles):
     def disableHandles(self, handles):
-        if type(handles) == types.ListType:
+        if type(handles) is list:
             for handle in handles:
             for handle in handles:
                 self.disableHandle(handle)
                 self.disableHandle(handle)
         elif handles == 'x':
         elif handles == 'x':

+ 6 - 10
contrib/src/sceneeditor/seMopathRecorder.py

@@ -25,16 +25,12 @@ from direct.tkwidgets.Slider import Slider
 from direct.tkwidgets.EntryScale import EntryScale
 from direct.tkwidgets.EntryScale import EntryScale
 from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
 from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
 from direct.tkwidgets.VectorWidgets import ColorEntry
 from direct.tkwidgets.VectorWidgets import ColorEntry
-import os, string, sys, Pmw
+import os, Pmw
+import math
 
 
-if sys.version_info >= (3, 0):
-    from tkinter import Button, Frame, Radiobutton, Checkbutton, Label
-    from tkinter import StringVar, BooleanVar, Entry, Scale
-    import tkinter
-else:
-    from Tkinter import Button, Frame, Radiobutton, Checkbutton, Label
-    from Tkinter import StringVar, BooleanVar, Entry, Scale
-    import Tkinter as tkinter
+from tkinter import Button, Frame, Radiobutton, Checkbutton, Label
+from tkinter import StringVar, BooleanVar, Entry, Scale
+import tkinter
 
 
 
 
 PRF_UTILITIES = [
 PRF_UTILITIES = [
@@ -374,7 +370,7 @@ class MopathRecorder(AppShell, DirectObject):
         self.speedEntry.bind(
         self.speedEntry.bind(
             '<Return>',
             '<Return>',
             lambda e = None, s = self: s.setSpeedScale(
             lambda e = None, s = self: s.setSpeedScale(
-            string.atof(s.speedVar.get())))
+            float(s.speedVar.get())))
         self.speedEntry.pack(side = tkinter.LEFT, expand = 0)
         self.speedEntry.pack(side = tkinter.LEFT, expand = 0)
         frame.pack(fill = tkinter.X, expand = 1)
         frame.pack(fill = tkinter.X, expand = 1)
 
 

+ 3 - 8
contrib/src/sceneeditor/seParticlePanel.py

@@ -2,7 +2,7 @@
 
 
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import AppShell
 from direct.tkwidgets.AppShell import AppShell
-import os, Pmw, sys
+import os, Pmw
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Floater import Floater
 from direct.tkwidgets.Floater import Floater
 from direct.tkwidgets.Slider import Slider
 from direct.tkwidgets.Slider import Slider
@@ -13,13 +13,8 @@ import seForceGroup
 import seParticles
 import seParticles
 import seParticleEffect
 import seParticleEffect
 
 
-
-if sys.version_info >= (3, 0):
-    from tkinter.filedialog import *
-    from tkinter.simpledialog import askstring
-else:
-    from tkFileDialog import *
-    from tkSimpleDialog import askstring
+from tkinter.filedialog import *
+from tkinter.simpledialog import askstring
 
 
 
 
 class ParticlePanel(AppShell):
 class ParticlePanel(AppShell):

+ 0 - 1
contrib/src/sceneeditor/seParticles.py

@@ -3,7 +3,6 @@ from panda3d.physics import *
 from direct.particles.ParticleManagerGlobal import *
 from direct.particles.ParticleManagerGlobal import *
 from direct.showbase.PhysicsManagerGlobal import *
 from direct.showbase.PhysicsManagerGlobal import *
 #import OrientedParticleFactory
 #import OrientedParticleFactory
-import string
 import os
 import os
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 import sys
 import sys

+ 3 - 8
contrib/src/sceneeditor/sePlacer.py

@@ -6,14 +6,9 @@ from direct.tkwidgets.AppShell import AppShell
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Dial import AngleDial
 from direct.tkwidgets.Floater import Floater
 from direct.tkwidgets.Floater import Floater
 from panda3d.core import *
 from panda3d.core import *
-import sys, Pmw
-
-if sys.version_info >= (3, 0):
-    from tkinter import Button, Menubutton, Menu, StringVar
-    import tkinter
-else:
-    from Tkinter import Button, Menubutton, Menu, StringVar
-    import Tkinter as tkinter
+import Pmw
+from tkinter import Button, Menubutton, Menu, StringVar
+import tkinter
 
 
 """
 """
 TODO:
 TODO:

+ 3 - 8
contrib/src/sceneeditor/seSceneGraphExplorer.py

@@ -11,14 +11,9 @@
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from seTree import TreeNode, TreeItem
 from seTree import TreeNode, TreeItem
 
 
-import Pmw, sys
-
-if sys.version_info >= (3, 0):
-    from tkinter import IntVar, Frame, Label
-    import tkinter
-else:
-    from Tkinter import IntVar, Frame, Label
-    import Tkinter as tkinter
+import Pmw
+from tkinter import IntVar, Frame, Label
+import tkinter
 
 
 # changing these strings requires changing sceneEditor.py SGE_ strs too!
 # changing these strings requires changing sceneEditor.py SGE_ strs too!
 # This list of items will be showed on the pop out window when user right click on
 # This list of items will be showed on the pop out window when user right click on

+ 2 - 2
contrib/src/sceneeditor/seSession.py

@@ -20,9 +20,9 @@ from seGeometry import *
 from direct.tkpanels import Placer
 from direct.tkpanels import Placer
 from direct.tkwidgets import Slider
 from direct.tkwidgets import Slider
 from direct.gui import OnscreenText
 from direct.gui import OnscreenText
-import types
-import string
+from direct.task import Task
 from direct.showbase import Loader
 from direct.showbase import Loader
+import math
 
 
 class SeSession(DirectObject):  ### Customized DirectSession
 class SeSession(DirectObject):  ### Customized DirectSession
 
 

+ 4 - 8
contrib/src/sceneeditor/seTree.py

@@ -12,16 +12,12 @@
 #
 #
 #################################################################
 #################################################################
 
 
-import os, sys, string, Pmw
+import os, Pmw
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from panda3d.core import *
 from panda3d.core import *
 
 
-if sys.version_info >= (3, 0):
-    import tkinter
-    from tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
-else:
-    import Tkinter as tkinter
-    from Tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
+import tkinter
+from tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
 
 
 # Initialize icon directory
 # Initialize icon directory
 ICONDIR = getModelPath().findFile(Filename('icons')).toOsSpecific()
 ICONDIR = getModelPath().findFile(Filename('icons')).toOsSpecific()
@@ -221,7 +217,7 @@ class TreeNode:
             self.children[key] = child
             self.children[key] = child
             self.kidKeys.append(key)
             self.kidKeys.append(key)
         # Remove unused children
         # Remove unused children
-        for key in self.children.keys():
+        for key in list(self.children.keys()):
             if key not in self.kidKeys:
             if key not in self.kidKeys:
                 del(self.children[key])
                 del(self.children[key])
         cx = x+20
         cx = x+20

+ 1 - 3
direct/src/dcparser/dcClass_ext.cxx

@@ -400,9 +400,7 @@ pack_required_field(DCPacker &packer, PyObject *distobj,
     PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
     PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
   nassertr(func != nullptr, false);
   nassertr(func != nullptr, false);
 
 
-  PyObject *empty_args = PyTuple_New(0);
-  PyObject *result = PyObject_CallObject(func, empty_args);
-  Py_DECREF(empty_args);
+  PyObject *result = PyObject_CallNoArgs(func);
   Py_DECREF(func);
   Py_DECREF(func);
   if (result == nullptr) {
   if (result == nullptr) {
     // We don't set this as an exception, since presumably the Python method
     // We don't set this as an exception, since presumably the Python method

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

@@ -309,7 +309,7 @@ unpack_class_object(const DCClass *dclass) {
   if (!dclass->has_constructor()) {
   if (!dclass->has_constructor()) {
     // If the class uses a default constructor, go ahead and create the Python
     // If the class uses a default constructor, go ahead and create the Python
     // object for it now.
     // object for it now.
-    object = PyObject_CallObject(class_def, nullptr);
+    object = PyObject_CallNoArgs(class_def);
     if (object == nullptr) {
     if (object == nullptr) {
       return nullptr;
       return nullptr;
     }
     }

+ 1 - 1
direct/src/directtools/DirectUtil.py

@@ -19,7 +19,7 @@ def getTkColorString(color):
     Print out a Tk compatible version of a color string
     Print out a Tk compatible version of a color string
     """
     """
     def toHex(intVal):
     def toHex(intVal):
-        val = int(round(intVal))
+        val = int(intVal)
         if val < 16:
         if val < 16:
             return "0" + hex(val)[2:]
             return "0" + hex(val)[2:]
         else:
         else:

+ 79 - 2
direct/src/dist/FreezeTool.py

@@ -211,7 +211,7 @@ class CompilationEnvironment:
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
 
 
         elif self.platform.startswith('osx_'):
         elif self.platform.startswith('osx_'):
-            # OSX
+            # macOS
             proc = self.platform.split('_', 1)[1]
             proc = self.platform.split('_', 1)[1]
             if proc == 'i386':
             if proc == 'i386':
                 self.arch = '-arch i386'
                 self.arch = '-arch i386'
@@ -219,6 +219,8 @@ class CompilationEnvironment:
                 self.arch = '-arch ppc'
                 self.arch = '-arch ppc'
             elif proc == 'amd64':
             elif proc == 'amd64':
                 self.arch = '-arch x86_64'
                 self.arch = '-arch x86_64'
+            elif proc in ('arm64', 'aarch64'):
+                self.arch = '-arch arm64'
             self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.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.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.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
@@ -636,7 +638,9 @@ okMissing = [
     'EasyDialogs', 'SOCKS', 'ic', 'rourl2path', 'termios', 'vms_lib',
     'EasyDialogs', 'SOCKS', 'ic', 'rourl2path', 'termios', 'vms_lib',
     'OverrideFrom23._Res', 'email', 'email.Utils', 'email.Generator',
     'OverrideFrom23._Res', 'email', 'email.Utils', 'email.Generator',
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
-    'direct.extensions_native.extensions_darwin',
+    'direct.extensions_native.extensions_darwin', '_manylinux',
+    'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
+    'collections.Sequence', 'numpy_distutils',
     ]
     ]
 
 
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -2498,6 +2502,9 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         except ImportError as msg:
         except ImportError as msg:
             self.msg(2, "ImportError:", str(msg))
             self.msg(2, "ImportError:", str(msg))
             self._add_badmodule(name, caller)
             self._add_badmodule(name, caller)
+        except SyntaxError as msg:
+            self.msg(2, "SyntaxError:", str(msg))
+            self._add_badmodule(name, caller)
         else:
         else:
             if fromlist:
             if fromlist:
                 for sub in fromlist:
                 for sub in fromlist:
@@ -2511,6 +2518,76 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                         self.msg(2, "ImportError:", str(msg))
                         self.msg(2, "ImportError:", str(msg))
                         self._add_badmodule(fullname, caller)
                         self._add_badmodule(fullname, caller)
 
 
+    def scan_code(self, co, m):
+        code = co.co_code
+        # This was renamed to scan_opcodes in Python 3.6
+        if hasattr(self, 'scan_opcodes_25'):
+            scanner = self.scan_opcodes_25
+        else:
+            scanner = self.scan_opcodes
+
+        for what, args in scanner(co):
+            if what == "store":
+                name, = args
+                m.globalnames[name] = 1
+            elif what in ("import", "absolute_import"):
+                fromlist, name = args
+                have_star = 0
+                if fromlist is not None:
+                    if "*" in fromlist:
+                        have_star = 1
+                    fromlist = [f for f in fromlist if f != "*"]
+                if what == "absolute_import": level = 0
+                else: level = -1
+                self._safe_import_hook(name, m, fromlist, level=level)
+                if have_star:
+                    # We've encountered an "import *". If it is a Python module,
+                    # the code has already been parsed and we can suck out the
+                    # global names.
+                    mm = None
+                    if m.__path__:
+                        # At this point we don't know whether 'name' is a
+                        # submodule of 'm' or a global module. Let's just try
+                        # the full name first.
+                        mm = self.modules.get(m.__name__ + "." + name)
+                    if mm is None:
+                        mm = self.modules.get(name)
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            elif what == "relative_import":
+                level, fromlist, name = args
+                parent = self.determine_parent(m, level=level)
+                if name:
+                    self._safe_import_hook(name, m, fromlist, level=level)
+                else:
+                    self._safe_import_hook(parent.__name__, None, fromlist, level=0)
+
+                if fromlist and "*" in fromlist:
+                    if name:
+                        mm = self.modules.get(parent.__name__ + "." + name)
+                    else:
+                        mm = self.modules.get(parent.__name__)
+
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            else:
+                # We don't expect anything else from the generator.
+                raise RuntimeError(what)
+
+        for c in co.co_consts:
+            if isinstance(c, type(co)):
+                self.scan_code(c, m)
+
     def find_module(self, name, path=None, parent=None):
     def find_module(self, name, path=None, parent=None):
         """ Finds a module with the indicated name on the given search path
         """ Finds a module with the indicated name on the given search path
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where

+ 112 - 182
direct/src/dist/commands.py

@@ -7,6 +7,7 @@ on how to use these commands.
 import collections
 import collections
 import os
 import os
 import plistlib
 import plistlib
+import pkg_resources
 import sys
 import sys
 import subprocess
 import subprocess
 import zipfile
 import zipfile
@@ -24,20 +25,11 @@ import distutils.log
 
 
 from . import FreezeTool
 from . import FreezeTool
 from . import pefile
 from . import pefile
+from . import installers
 from .icon import Icon
 from .icon import Icon
 import panda3d.core as p3d
 import panda3d.core as p3d
 
 
 
 
-if sys.version_info < (3, 0):
-    # Warn the user.  They might be using Python 2 by accident.
-    print("=================================================================")
-    print("WARNING: You are using Python 2, which has reached the end of its")
-    print("WARNING: life as of January 1, 2020.  Please upgrade to Python 3.")
-    print("=================================================================")
-    sys.stdout.flush()
-    time.sleep(4.0)
-
-
 def _parse_list(input):
 def _parse_list(input):
     if isinstance(input, str):
     if isinstance(input, str):
         input = input.strip().replace(',', '\n')
         input = input.strip().replace(',', '\n')
@@ -213,7 +205,8 @@ class build_apps(setuptools.Command):
             'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll',
             'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll',
             'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll',
             'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll',
             'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll',
             'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll',
-            'setupapi.dll', 'version.dll',
+            'setupapi.dll', 'version.dll', 'userenv.dll', 'netapi32.dll',
+            'crypt32.dll', 'bcrypt.dll',
 
 
             # manylinux1/linux
             # manylinux1/linux
             'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*',
             'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*',
@@ -229,14 +222,45 @@ class build_apps(setuptools.Command):
             '/usr/lib/libSystem.*.dylib',
             '/usr/lib/libSystem.*.dylib',
             '/usr/lib/libbz2.*.dylib',
             '/usr/lib/libbz2.*.dylib',
             '/usr/lib/libedit.*.dylib',
             '/usr/lib/libedit.*.dylib',
+            '/usr/lib/libffi.dylib',
+            '/usr/lib/libauditd.0.dylib',
+            '/usr/lib/libgermantok.dylib',
+            '/usr/lib/liblangid.dylib',
+            '/usr/lib/libarchive.2.dylib',
+            '/usr/lib/libipsec.A.dylib',
+            '/usr/lib/libpanel.5.4.dylib',
+            '/usr/lib/libiodbc.2.1.18.dylib',
+            '/usr/lib/libhunspell-1.2.0.0.0.dylib',
+            '/usr/lib/libsqlite3.dylib',
+            '/usr/lib/libpam.1.dylib',
+            '/usr/lib/libtidy.A.dylib',
+            '/usr/lib/libDHCPServer.A.dylib',
+            '/usr/lib/libpam.2.dylib',
+            '/usr/lib/libXplugin.1.dylib',
+            '/usr/lib/libxslt.1.dylib',
+            '/usr/lib/libiodbcinst.2.1.18.dylib',
+            '/usr/lib/libBSDPClient.A.dylib',
+            '/usr/lib/libsandbox.1.dylib',
+            '/usr/lib/libform.5.4.dylib',
+            '/usr/lib/libbsm.0.dylib',
+            '/usr/lib/libMatch.1.dylib',
+            '/usr/lib/libresolv.9.dylib',
+            '/usr/lib/libcharset.1.dylib',
+            '/usr/lib/libxml2.2.dylib',
+            '/usr/lib/libiconv.2.dylib',
+            '/usr/lib/libScreenReader.dylib',
+            '/usr/lib/libdtrace.dylib',
+            '/usr/lib/libicucore.A.dylib',
+            '/usr/lib/libsasl2.2.dylib',
+            '/usr/lib/libpcap.A.dylib',
+            '/usr/lib/libexslt.0.dylib',
+            '/usr/lib/libcurl.4.dylib',
+            '/usr/lib/libncurses.5.4.dylib',
+            '/usr/lib/libxar.1.dylib',
+            '/usr/lib/libmenu.5.4.dylib',
             '/System/Library/**',
             '/System/Library/**',
         ]
         ]
 
 
-        if sys.version_info >= (3, 5):
-            # Python 3.5+ requires at least Windows Vista to run anyway, so we
-            # shouldn't warn about DLLs that are shipped with Vista.
-            self.exclude_dependencies += ['bcrypt.dll']
-
         self.package_data_dirs = {}
         self.package_data_dirs = {}
         self.hidden_imports = {}
         self.hidden_imports = {}
 
 
@@ -371,7 +395,8 @@ class build_apps(setuptools.Command):
             abi_tag += 'm'
             abi_tag += 'm'
 
 
         whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
         whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
-        os.makedirs(whldir, exist_ok=True)
+        if not os.path.isdir(whldir):
+            os.makedirs(whldir)
 
 
         # Remove any .zip files. These are built from a VCS and block for an
         # Remove any .zip files. These are built from a VCS and block for an
         # interactive prompt on subsequent downloads.
         # interactive prompt on subsequent downloads.
@@ -491,6 +516,7 @@ class build_apps(setuptools.Command):
 
 
         path = sys.path[:]
         path = sys.path[:]
         p3dwhl = None
         p3dwhl = None
+        wheelpaths = []
 
 
         if use_wheels:
         if use_wheels:
             wheelpaths = self.download_wheels(platform)
             wheelpaths = self.download_wheels(platform)
@@ -769,6 +795,7 @@ class build_apps(setuptools.Command):
         for module, source_path in freezer_extras:
         for module, source_path in freezer_extras:
             if source_path is not None:
             if source_path is not None:
                 # Rename panda3d/core.pyd to panda3d.core.pyd
                 # Rename panda3d/core.pyd to panda3d.core.pyd
+                source_path = os.path.normpath(source_path)
                 basename = os.path.basename(source_path)
                 basename = os.path.basename(source_path)
                 if '.' in module:
                 if '.' in module:
                     basename = module.rsplit('.', 1)[0] + '.' + basename
                     basename = module.rsplit('.', 1)[0] + '.' + basename
@@ -778,6 +805,20 @@ class build_apps(setuptools.Command):
                 if len(parts) >= 3 and '-' in parts[-2]:
                 if len(parts) >= 3 and '-' in parts[-2]:
                     parts = parts[:-2] + parts[-1:]
                     parts = parts[:-2] + parts[-1:]
                     basename = '.'.join(parts)
                     basename = '.'.join(parts)
+
+                # Was this not found in a wheel?  Then we may have a problem,
+                # since it may be for the current platform instead of the target
+                # platform.
+                if use_wheels:
+                    found_in_wheel = False
+                    for whl in wheelpaths:
+                        whl = os.path.normpath(whl)
+                        if source_path.lower().startswith(os.path.join(whl, '').lower()):
+                            found_in_wheel = True
+                            break
+
+                    if not found_in_wheel:
+                        self.warn('{} was not found in any downloaded wheel, is a dependency missing from requirements.txt?'.format(basename))
             else:
             else:
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 if module in whl_modules:
                 if module in whl_modules:
@@ -881,6 +922,27 @@ class build_apps(setuptools.Command):
             return check_pattern(fname, include_copy_list) and \
             return check_pattern(fname, include_copy_list) and \
                 not check_pattern(fname, ignore_copy_list)
                 not check_pattern(fname, ignore_copy_list)
 
 
+        def skip_directory(src):
+            # Provides a quick-out for directory checks.  NOT recursive.
+            fn = p3d.Filename.from_os_specific(os.path.normpath(src))
+            path = fn.get_fullpath()
+            fn.make_absolute()
+            abspath = fn.get_fullpath()
+
+            for pattern in ignore_copy_list:
+                if not pattern.pattern.endswith('/*') and \
+                   not pattern.pattern.endswith('/**'):
+                    continue
+
+                pattern_dir = p3d.Filename(pattern.pattern).get_dirname()
+                if abspath.startswith(pattern_dir + '/'):
+                    return True
+
+                if path.startswith(pattern_dir + '/'):
+                    return True
+
+            return False
+
         def copy_file(src, dst):
         def copy_file(src, dst):
             src = os.path.normpath(src)
             src = os.path.normpath(src)
             dst = os.path.normpath(dst)
             dst = os.path.normpath(dst)
@@ -921,6 +983,10 @@ class build_apps(setuptools.Command):
         rootdir = os.getcwd()
         rootdir = os.getcwd()
         for dirname, subdirlist, filelist in os.walk(rootdir):
         for dirname, subdirlist, filelist in os.walk(rootdir):
             dirpath = os.path.relpath(dirname, rootdir)
             dirpath = os.path.relpath(dirname, rootdir)
+            if skip_directory(dirpath):
+                self.announce('skipping directory {}'.format(dirpath))
+                continue
+
             for fname in filelist:
             for fname in filelist:
                 src = os.path.join(dirpath, fname)
                 src = os.path.join(dirpath, fname)
                 dst = os.path.join(builddir, update_path(src))
                 dst = os.path.join(builddir, update_path(src))
@@ -1249,6 +1315,14 @@ class bdist_apps(setuptools.Command):
         # Everything else defaults to ['zip']
         # Everything else defaults to ['zip']
     }
     }
 
 
+    DEFAULT_INSTALLER_FUNCS = {
+        'zip': installers.create_zip,
+        'gztar': installers.create_gztar,
+        'bztar': installers.create_bztar,
+        'xztar': installers.create_xztar,
+        'nsis': installers.create_nsis,
+    }
+
     description = 'bundle built Panda3D applications into distributable forms'
     description = 'bundle built Panda3D applications into distributable forms'
     user_options = build_apps.user_options + [
     user_options = build_apps.user_options + [
         ('dist-dir=', 'd', 'directory to put final built distributions in'),
         ('dist-dir=', 'd', 'directory to put final built distributions in'),
@@ -1262,6 +1336,8 @@ class bdist_apps(setuptools.Command):
         self.installers = {}
         self.installers = {}
         self.dist_dir = os.path.join(os.getcwd(), 'dist')
         self.dist_dir = os.path.join(os.getcwd(), 'dist')
         self.skip_build = False
         self.skip_build = False
+        self.installer_functions = {}
+        self._current_platform = None
         for opt in self._build_apps_options():
         for opt in self._build_apps_options():
             setattr(self, opt, None)
             setattr(self, opt, None)
 
 
@@ -1273,145 +1349,19 @@ class bdist_apps(setuptools.Command):
             for key, value in _parse_dict(self.installers).items()
             for key, value in _parse_dict(self.installers).items()
         }
         }
 
 
-    def _get_archive_basedir(self):
-        return self.distribution.get_name()
+        tmp = self.DEFAULT_INSTALLER_FUNCS.copy()
+        tmp.update(self.installer_functions)
+        tmp.update({
+            entrypoint.name: entrypoint.load()
+            for entrypoint in pkg_resources.iter_entry_points('panda3d.bdist_apps.installers')
+        })
+        self.installer_functions = tmp
 
 
-    def create_zip(self, basename, build_dir):
-        import zipfile
-
-        base_dir = self._get_archive_basedir()
-
-        with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
-            zf.write(build_dir, base_dir)
-
-            for dirpath, dirnames, filenames in os.walk(build_dir):
-                for name in sorted(dirnames):
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    zf.write(path, path.replace(build_dir, base_dir, 1))
-                for name in filenames:
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    if os.path.isfile(path):
-                        zf.write(path, path.replace(build_dir, base_dir, 1))
-
-    def create_tarball(self, basename, build_dir, tar_compression):
-        import tarfile
-
-        base_dir = self._get_archive_basedir()
-        build_cmd = self.get_finalized_command('build_apps')
-        binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
-
-        def tarfilter(tarinfo):
-            if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
-                tarinfo.mode = 0o755
-            else:
-                tarinfo.mode = 0o644
-            return tarinfo
-
-        with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
-            tf.add(build_dir, base_dir, filter=tarfilter)
-
-    def create_nsis(self, basename, build_dir, is_64bit):
-        # Get a list of build applications
-        build_cmd = self.get_finalized_command('build_apps')
-        apps = build_cmd.gui_apps.copy()
-        apps.update(build_cmd.console_apps)
-        apps = [
-            '{}.exe'.format(i)
-            for i in apps
-        ]
-
-        shortname = self.distribution.get_name()
-
-        # Create the .nsi installer script
-        nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
-        nsifile.unlink()
-        nsi = open(nsifile.to_os_specific(), "w")
+    def get_archive_basedir(self):
+        return self.distribution.get_name()
 
 
-        # Some global info
-        nsi.write('Name "%s"\n' % shortname)
-        nsi.write('OutFile "%s"\n' % os.path.join(self.dist_dir, basename+'.exe'))
-        if is_64bit:
-            nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
-        else:
-            nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
-        nsi.write('SetCompress auto\n')
-        nsi.write('SetCompressor lzma\n')
-        nsi.write('ShowInstDetails nevershow\n')
-        nsi.write('ShowUninstDetails nevershow\n')
-        nsi.write('InstType "Typical"\n')
-
-        # Tell Vista that we require admin rights
-        nsi.write('RequestExecutionLevel admin\n')
-        nsi.write('\n')
-
-        # TODO offer run and desktop shortcut after we figure out how to deal
-        # with multiple apps
-
-        nsi.write('!include "MUI2.nsh"\n')
-        nsi.write('!define MUI_ABORTWARNING\n')
-        nsi.write('\n')
-        nsi.write('Var StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
-        # TODO license file
-        nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
-        nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_PAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
-        nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
-        nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
-
-        # This section defines the installer.
-        nsi.write('Section "" SecCore\n')
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        curdir = ""
-        nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
-        build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
-        for root, dirs, files in os.walk(build_dir):
-            for name in files:
-                basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
-                file = p3d.Filename(basefile)
-                file.makeAbsolute()
-                file.makeRelativeTo(nsi_dir)
-                outdir = p3d.Filename(basefile)
-                outdir.makeAbsolute()
-                outdir.makeRelativeTo(build_root_dir)
-                outdir = outdir.getDirname().replace('/', '\\')
-                if curdir != outdir:
-                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
-                    curdir = outdir
-                nsi.write('  File "%s"\n' % (file.toOsSpecific()))
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
-        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
-        for app in apps:
-            nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
-        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
-        nsi.write('SectionEnd\n')
-
-        # This section defines the uninstaller.
-        nsi.write('Section Uninstall\n')
-        nsi.write('  RMDir /r "$INSTDIR"\n')
-        nsi.write('  ; Desktop icon\n')
-        nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
-        nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
-        nsi.write('SectionEnd\n')
-        nsi.close()
-
-        cmd = ['makensis']
-        for flag in ["V2"]:
-            cmd.append(
-                '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
-            )
-        cmd.append(nsifile.to_os_specific())
-        subprocess.check_call(cmd)
+    def get_current_platform(self):
+        return self._current_platform
 
 
     def run(self):
     def run(self):
         build_cmd = self.distribution.get_command_obj('build_apps')
         build_cmd = self.distribution.get_command_obj('build_apps')
@@ -1433,35 +1383,15 @@ class bdist_apps(setuptools.Command):
             build_dir = os.path.join(build_base, platform)
             build_dir = os.path.join(build_base, platform)
             basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
             basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
             installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
             installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
+            self._current_platform = platform
 
 
             for installer in installers:
             for installer in installers:
                 self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
                 self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
+                if installer not in self.installer_functions:
+                    self.announce(
+                        '\tUnknown installer: {}'.format(installer),
+                        distutils.log.ERROR
+                    )
+                    continue
 
 
-                if installer == 'zip':
-                    self.create_zip(basename, build_dir)
-                elif installer in ('gztar', 'bztar', 'xztar'):
-                    compress = installer.replace('tar', '')
-                    if compress == 'bz':
-                        compress = 'bz2'
-
-                    self.create_tarball(basename, build_dir, compress)
-                elif installer == 'nsis':
-                    if not platform.startswith('win'):
-                        self.announce(
-                            '\tNSIS installer not supported for platform: {}'.format(platform),
-                            distutils.log.ERROR
-                        )
-                        continue
-                    try:
-                        subprocess.call(['makensis', '--version'])
-                    except OSError:
-                        self.announce(
-                            '\tCould not find makensis tool that is required to build NSIS installers',
-                            distutils.log.ERROR
-                        )
-                        # continue
-                    is_64bit = platform == 'win_amd64'
-                    self.create_nsis(basename, build_dir, is_64bit)
-
-                else:
-                    self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR)
+                self.installer_functions[installer](self, basename, build_dir)

+ 173 - 0
direct/src/dist/installers.py

@@ -0,0 +1,173 @@
+import distutils.log
+import os
+import subprocess
+import sys
+import tarfile
+import zipfile
+
+import panda3d.core as p3d
+
+def create_zip(command, basename, build_dir):
+    base_dir = command.get_archive_basedir()
+
+    with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
+        zf.write(build_dir, base_dir)
+
+        for dirpath, dirnames, filenames in os.walk(build_dir):
+            for name in sorted(dirnames):
+                path = os.path.normpath(os.path.join(dirpath, name))
+                zf.write(path, path.replace(build_dir, base_dir, 1))
+            for name in filenames:
+                path = os.path.normpath(os.path.join(dirpath, name))
+                if os.path.isfile(path):
+                    zf.write(path, path.replace(build_dir, base_dir, 1))
+
+
+def create_tarball(command, basename, build_dir, tar_compression):
+    base_dir = command.get_archive_basedir()
+    build_cmd = command.get_finalized_command('build_apps')
+    binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
+
+    def tarfilter(tarinfo):
+        if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
+            tarinfo.mode = 0o755
+        else:
+            tarinfo.mode = 0o644
+        return tarinfo
+
+    with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
+        tf.add(build_dir, base_dir, filter=tarfilter)
+
+
+def create_gztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'gz')
+
+
+def create_bztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'bz2')
+
+
+def create_xztar(command, basename, build_dir):
+    return create_tarball(command, basename, build_dir, 'xz')
+
+
+def create_nsis(command, basename, build_dir):
+    platform = command.get_current_platform()
+    if not platform.startswith('win'):
+        command.announce(
+            '\tNSIS installer not supported for platform: {}'.format(platform),
+            distutils.log.ERROR
+        )
+        return
+    try:
+        subprocess.call(['makensis', '--version'])
+    except OSError:
+        command.announce(
+            '\tCould not find makensis tool that is required to build NSIS installers',
+            distutils.log.ERROR
+        )
+        return
+
+    is_64bit = platform == 'win_amd64'
+    # Get a list of build applications
+    build_cmd = command.get_finalized_command('build_apps')
+    apps = build_cmd.gui_apps.copy()
+    apps.update(build_cmd.console_apps)
+    apps = [
+        '{}.exe'.format(i)
+        for i in apps
+    ]
+
+    shortname = command.distribution.get_name()
+
+    # Create the .nsi installer script
+    nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
+    nsifile.unlink()
+    nsi = open(nsifile.to_os_specific(), "w")
+
+    # Some global info
+    nsi.write('Name "%s"\n' % shortname)
+    nsi.write('OutFile "%s"\n' % os.path.join(command.dist_dir, basename+'.exe'))
+    if is_64bit:
+        nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
+    else:
+        nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
+    nsi.write('SetCompress auto\n')
+    nsi.write('SetCompressor lzma\n')
+    nsi.write('ShowInstDetails nevershow\n')
+    nsi.write('ShowUninstDetails nevershow\n')
+    nsi.write('InstType "Typical"\n')
+
+    # Tell Vista that we require admin rights
+    nsi.write('RequestExecutionLevel admin\n')
+    nsi.write('\n')
+
+    # TODO offer run and desktop shortcut after we figure out how to deal
+    # with multiple apps
+
+    nsi.write('!include "MUI2.nsh"\n')
+    nsi.write('!define MUI_ABORTWARNING\n')
+    nsi.write('\n')
+    nsi.write('Var StartMenuFolder\n')
+    nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
+    # TODO license file
+    nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
+    nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
+    nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
+    nsi.write('!insertmacro MUI_PAGE_FINISH\n')
+    nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
+    nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
+    nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
+    nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
+    nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
+
+    # This section defines the installer.
+    nsi.write('Section "" SecCore\n')
+    nsi.write('  SetOutPath "$INSTDIR"\n')
+    curdir = ""
+    nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
+    build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
+    for root, dirs, files in os.walk(build_dir):
+        for name in files:
+            basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
+            file = p3d.Filename(basefile)
+            file.makeAbsolute()
+            file.makeRelativeTo(nsi_dir)
+            outdir = p3d.Filename(basefile)
+            outdir.makeAbsolute()
+            outdir.makeRelativeTo(build_root_dir)
+            outdir = outdir.getDirname().replace('/', '\\')
+            if curdir != outdir:
+                nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
+                curdir = outdir
+            nsi.write('  File "%s"\n' % (file.toOsSpecific()))
+    nsi.write('  SetOutPath "$INSTDIR"\n')
+    nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
+    nsi.write('  ; Start menu items\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
+    nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
+    for app in apps:
+        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
+    nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
+    nsi.write('SectionEnd\n')
+
+    # This section defines the uninstaller.
+    nsi.write('Section Uninstall\n')
+    nsi.write('  RMDir /r "$INSTDIR"\n')
+    nsi.write('  ; Desktop icon\n')
+    nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
+    nsi.write('  ; Start menu items\n')
+    nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
+    nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
+    nsi.write('SectionEnd\n')
+    nsi.close()
+
+    cmd = ['makensis']
+    for flag in ["V2"]:
+        cmd.append(
+            '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
+        )
+    cmd.append(nsifile.to_os_specific())
+    subprocess.check_call(cmd)
+

+ 1 - 4
direct/src/dist/pefile.py

@@ -242,10 +242,7 @@ class VersionInfoResource(object):
         length, value_length = unpack('<HH', data[0:4])
         length, value_length = unpack('<HH', data[0:4])
         offset = 40 + value_length + (value_length & 1)
         offset = 40 + value_length + (value_length & 1)
         dwords = array('I')
         dwords = array('I')
-        if sys.version_info >= (3, 2):
-            dwords.frombytes(bytes(data[40:offset]))
-        else:
-            dwords.fromstring(bytes(data[40:offset]))
+        dwords.frombytes(bytes(data[40:offset]))
 
 
         if len(dwords) > 0:
         if len(dwords) > 0:
             self.signature = dwords[0]
             self.signature = dwords[0]

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

@@ -918,8 +918,8 @@ describe_message(std::ostream &out, const string &prefix,
       PyObject *methodName = PyUnicode_FromString("_getMsgName");
       PyObject *methodName = PyUnicode_FromString("_getMsgName");
       nassertv(methodName != nullptr);
       nassertv(methodName != nullptr);
 
 
-      PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
-                                                    msgId, nullptr);
+      PyObject *result =
+        PyObject_CallMethodOneArg(_python_repository, methodName, msgId);
       nassertv(result != nullptr);
       nassertv(result != nullptr);
 
 
       msgName += string(PyUnicode_AsUTF8(result));
       msgName += string(PyUnicode_AsUTF8(result));

+ 3 - 2
direct/src/extensions_native/NodePath_extensions.py

@@ -432,8 +432,8 @@ def rgbPanel(self, cb = None):
         # 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
-        Slider = importlib.import_module('direct.tkwidgets.Slider')
-        return Slider.rgbPanel(self, cb)
+        Valuator = importlib.import_module('direct.tkwidgets.Valuator')
+        return Valuator.rgbPanel(self, cb)
 
 
 Dtool_funcToMethod(rgbPanel, NodePath)
 Dtool_funcToMethod(rgbPanel, NodePath)
 del rgbPanel
 del rgbPanel
@@ -789,6 +789,7 @@ def r_subdivideCollisions(self, solids, numSolidsInLeaves):
         return newSolids
         return newSolids
 
 
 def r_constructCollisionTree(self, solidTree, parentNode, colName):
 def r_constructCollisionTree(self, solidTree, parentNode, colName):
+        from panda3d.core import CollisionNode
         for item in solidTree:
         for item in solidTree:
             if type(item[0]) == type([]):
             if type(item[0]) == type([]):
                 newNode = parentNode.attachNewNode('%s-branch' % colName)
                 newNode = parentNode.attachNewNode('%s-branch' % colName)

+ 3 - 2
direct/src/gui/OnscreenGeom.py

@@ -42,8 +42,9 @@ class OnscreenGeom(DirectObject, NodePath):
         """
         """
         # We ARE a node path.  Initially, we're an empty node path.
         # We ARE a node path.  Initially, we're an empty node path.
         NodePath.__init__(self)
         NodePath.__init__(self)
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
 
 
         self.setGeom(geom, parent = parent, sort = sort, color = color)
         self.setGeom(geom, parent = parent, sort = sort, color = color)
 
 

+ 3 - 2
direct/src/gui/OnscreenImage.py

@@ -48,8 +48,9 @@ class OnscreenImage(DirectObject, NodePath):
         # We ARE a node path.  Initially, we're an empty node path.
         # We ARE a node path.  Initially, we're an empty node path.
         NodePath.__init__(self)
         NodePath.__init__(self)
 
 
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
         self.setImage(image, parent = parent, sort = sort)
         self.setImage(image, parent = parent, sort = sort)
 
 
         # Adjust pose
         # Adjust pose

+ 113 - 12
direct/src/gui/OnscreenText.py

@@ -103,8 +103,9 @@ class OnscreenText(NodePath):
           direction: this can be set to 'ltr' or 'rtl' to override the
           direction: this can be set to 'ltr' or 'rtl' to override the
               direction of the text.
               direction of the text.
         """
         """
-        if parent == None:
-            parent = aspect2d
+        if parent is None:
+            from direct.showbase import ShowBaseGlobal
+            parent = ShowBaseGlobal.aspect2d
 
 
         # make a text node
         # make a text node
         textNode = TextNode('')
         textNode = TextNode('')
@@ -292,43 +293,139 @@ class OnscreenText(NodePath):
 
 
     text = property(getText, setText)
     text = property(getText, setText)
 
 
+    def setTextX(self, x):
+        """
+        .. versionadded:: 1.10.8
+        """
+        self.setTextPos(x, self.__pos[1])
+
     def setX(self, x):
     def setX(self, x):
-        self.setPos(x, self.__pos[1])
+        """
+        .. deprecated:: 1.11.0
+           Use `.setTextX()` method instead.
+        """
+        self.setTextPos(x, self.__pos[1])
+
+    def setTextY(self, y):
+        """
+        .. versionadded:: 1.10.8
+        """
+        self.setTextPos(self.__pos[0], y)
 
 
     def setY(self, y):
     def setY(self, y):
-        self.setPos(self.__pos[0], y)
+        """
+        .. deprecated:: 1.11.0
+           Use `.setTextY()` method instead.
+        """
+        self.setTextPos(self.__pos[0], y)
+
+    def setTextPos(self, x, y=None):
+        """
+        Position the onscreen text in 2d screen space
+
+        .. versionadded:: 1.10.8
+        """
+        if y is None:
+            self.__pos = tuple(x)
+        else:
+            self.__pos = (x, y)
+        self.updateTransformMat()
+
+    def getTextPos(self):
+        """
+        .. versionadded:: 1.10.8
+        """
+        return self.__pos
+
+    text_pos = property(getTextPos, setTextPos)
 
 
     def setPos(self, x, y):
     def setPos(self, x, y):
         """setPos(self, float, float)
         """setPos(self, float, float)
         Position the onscreen text in 2d screen space
         Position the onscreen text in 2d screen space
+
+        .. deprecated:: 1.11.0
+           Use `.setTextPos()` method or `.text_pos` property instead.
         """
         """
         self.__pos = (x, y)
         self.__pos = (x, y)
         self.updateTransformMat()
         self.updateTransformMat()
 
 
     def getPos(self):
     def getPos(self):
+        """
+        .. deprecated:: 1.11.0
+           Use `.getTextPos()` method or `.text_pos` property instead.
+        """
         return self.__pos
         return self.__pos
 
 
-    pos = property(getPos, setPos)
+    pos = property(getPos)
+
+    def setTextR(self, r):
+        """setTextR(self, float)
+        Rotates the text around the screen's normal.
+
+        .. versionadded:: 1.10.8
+        """
+        self.__roll = -r
+        self.updateTransformMat()
+
+    def getTextR(self):
+        return -self.__roll
+
+    text_r = property(getTextR, setTextR)
 
 
     def setRoll(self, roll):
     def setRoll(self, roll):
         """setRoll(self, float)
         """setRoll(self, float)
-        Rotate the onscreen text around the screen's normal
+        Rotate the onscreen text around the screen's normal.
+
+        .. deprecated:: 1.11.0
+           Use ``setTextR(-roll)`` instead (note the negated sign).
         """
         """
         self.__roll = roll
         self.__roll = roll
         self.updateTransformMat()
         self.updateTransformMat()
 
 
     def getRoll(self):
     def getRoll(self):
+        """
+        .. deprecated:: 1.11.0
+           Use ``-getTextR()`` instead (note the negated sign).
+        """
         return self.__roll
         return self.__roll
 
 
     roll = property(getRoll, setRoll)
     roll = property(getRoll, setRoll)
 
 
+    def setTextScale(self, sx, sy = None):
+        """setTextScale(self, float, float)
+        Scale the text in 2d space.  You may specify either a single
+        uniform scale, or two scales, or a tuple of two scales.
+
+        .. versionadded:: 1.10.8
+        """
+
+        if sy is None:
+            if isinstance(sx, tuple):
+                self.__scale = sx
+            else:
+                self.__scale = (sx, sx)
+        else:
+            self.__scale = (sx, sy)
+        self.updateTransformMat()
+
+    def getTextScale(self):
+        """
+        .. versionadded:: 1.10.8
+        """
+        return self.__scale
+
+    text_scale = property(getTextScale, setTextScale)
+
     def setScale(self, sx, sy = None):
     def setScale(self, sx, sy = None):
         """setScale(self, float, float)
         """setScale(self, float, float)
         Scale the text in 2d space.  You may specify either a single
         Scale the text in 2d space.  You may specify either a single
         uniform scale, or two scales, or a tuple of two scales.
         uniform scale, or two scales, or a tuple of two scales.
+
+        .. deprecated:: 1.11.0
+           Use `.setTextScale()` method or `.text_scale` property instead.
         """
         """
 
 
-        if sy == None:
+        if sy is None:
             if isinstance(sx, tuple):
             if isinstance(sx, tuple):
                 self.__scale = sx
                 self.__scale = sx
             else:
             else:
@@ -337,6 +434,15 @@ class OnscreenText(NodePath):
             self.__scale = (sx, sy)
             self.__scale = (sx, sy)
         self.updateTransformMat()
         self.updateTransformMat()
 
 
+    def getScale(self):
+        """
+        .. deprecated:: 1.11.0
+           Use `.getTextScale()` method or `.text_scale` property instead.
+        """
+        return self.__scale
+
+    scale = property(getScale, setScale)
+
     def updateTransformMat(self):
     def updateTransformMat(self):
         assert(isinstance(self.textNode, TextNode))
         assert(isinstance(self.textNode, TextNode))
         mat = (
         mat = (
@@ -346,11 +452,6 @@ class OnscreenText(NodePath):
             )
             )
         self.textNode.setTransform(mat)
         self.textNode.setTransform(mat)
 
 
-    def getScale(self):
-        return self.__scale
-
-    scale = property(getScale, setScale)
-
     def setWordwrap(self, wordwrap):
     def setWordwrap(self, wordwrap):
         self.__wordwrap = wordwrap
         self.__wordwrap = wordwrap
 
 

+ 0 - 38
direct/src/showbase/showBase.cxx

@@ -91,44 +91,6 @@ init_app_for_gui() {
   */
   */
 }
 }
 
 
-// klunky interface since we cant pass array from python->C++ to use
-// verify_window_sizes directly
-static int num_fullscreen_testsizes = 0;
-#define MAX_FULLSCREEN_TESTS 10
-static int fullscreen_testsizes[MAX_FULLSCREEN_TESTS * 2];
-
-void
-add_fullscreen_testsize(int xsize, int ysize) {
-  if ((xsize == 0) && (ysize == 0)) {
-    num_fullscreen_testsizes = 0;
-    return;
-  }
-
-  // silently fail if maxtests exceeded
-  if (num_fullscreen_testsizes < MAX_FULLSCREEN_TESTS) {
-    fullscreen_testsizes[num_fullscreen_testsizes * 2] = xsize;
-    fullscreen_testsizes[num_fullscreen_testsizes * 2 + 1] = ysize;
-    num_fullscreen_testsizes++;
-  }
-}
-
-void
-runtest_fullscreen_sizes(GraphicsWindow *win) {
-  win->verify_window_sizes(num_fullscreen_testsizes, fullscreen_testsizes);
-}
-
-bool
-query_fullscreen_testresult(int xsize, int ysize) {
-  // stupid linear search that works ok as long as total tests are small
-  int i;
-  for (i=0; i < num_fullscreen_testsizes; i++) {
-    if((fullscreen_testsizes[i * 2] == xsize) &&
-       (fullscreen_testsizes[i * 2 + 1] == ysize))
-      return true;
-  }
-  return false;
-}
-
 void
 void
 store_accessibility_shortcut_keys() {
 store_accessibility_shortcut_keys() {
 #ifdef _WIN32
 #ifdef _WIN32

+ 0 - 5
direct/src/showbase/showBase.h

@@ -40,11 +40,6 @@ EXPCL_DIRECT_SHOWBASE void throw_new_frame();
 
 
 EXPCL_DIRECT_SHOWBASE void init_app_for_gui();
 EXPCL_DIRECT_SHOWBASE void init_app_for_gui();
 
 
-// klunky interface since we cant pass array from python->C++
-EXPCL_DIRECT_SHOWBASE void add_fullscreen_testsize(int xsize, int ysize);
-EXPCL_DIRECT_SHOWBASE void runtest_fullscreen_sizes(GraphicsWindow *win);
-EXPCL_DIRECT_SHOWBASE bool query_fullscreen_testresult(int xsize, int ysize);
-
 // to handle windows stickykeys
 // to handle windows stickykeys
 EXPCL_DIRECT_SHOWBASE void store_accessibility_shortcut_keys();
 EXPCL_DIRECT_SHOWBASE void store_accessibility_shortcut_keys();
 EXPCL_DIRECT_SHOWBASE void allow_accessibility_shortcut_keys(bool allowKeys);
 EXPCL_DIRECT_SHOWBASE void allow_accessibility_shortcut_keys(bool allowKeys);

+ 37 - 11
direct/src/stdpy/pickle.py

@@ -30,19 +30,26 @@ from copyreg import dispatch_table
 # with the local pickle.py.
 # with the local pickle.py.
 pickle = __import__('pickle')
 pickle = __import__('pickle')
 
 
-class Pickler(pickle.Pickler):
+BasePickler = pickle._Pickler
+BaseUnpickler = pickle._Unpickler
+
+
+class _Pickler(BasePickler):
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
         self.bamWriter = BamWriter()
-        pickle.Pickler.__init__(self, *args, **kw)
+        BasePickler.__init__(self, *args, **kw)
 
 
     # We have to duplicate most of the save() method, so we can add
     # We have to duplicate most of the save() method, so we can add
     # support for __reduce_persist__().
     # support for __reduce_persist__().
 
 
-    def save(self, obj):
+    def save(self, obj, save_persistent_id=True):
+        if self.proto >= 4:
+            self.framer.commit_frame()
+
         # Check for persistent id (defined by a subclass)
         # Check for persistent id (defined by a subclass)
         pid = self.persistent_id(obj)
         pid = self.persistent_id(obj)
-        if pid:
+        if pid is not None and save_persistent_id:
             self.save_pers(pid)
             self.save_pers(pid)
             return
             return
 
 
@@ -109,11 +116,12 @@ class Pickler(pickle.Pickler):
         # Save the reduce() output and finally memoize the object
         # Save the reduce() output and finally memoize the object
         self.save_reduce(obj=obj, *rv)
         self.save_reduce(obj=obj, *rv)
 
 
-class Unpickler(pickle.Unpickler):
+
+class Unpickler(BaseUnpickler):
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()
         self.bamReader = BamReader()
-        pickle.Unpickler.__init__(self, *args, **kw)
+        BaseUnpickler.__init__(self, *args, **kw)
 
 
     # Duplicate the load_reduce() function, to provide a special case
     # Duplicate the load_reduce() function, to provide a special case
     # for the reduction function.
     # for the reduction function.
@@ -123,9 +131,10 @@ class Unpickler(pickle.Unpickler):
         args = stack.pop()
         args = stack.pop()
         func = stack[-1]
         func = stack[-1]
 
 
-        # If the function name ends with "Persist", then assume the
+        # If the function name ends with "_persist", then assume the
         # function wants the Unpickler as the first parameter.
         # function wants the Unpickler as the first parameter.
-        if func.__name__.endswith('Persist'):
+        func_name = func.__name__
+        if func_name.endswith('_persist') or func_name.endswith('Persist'):
             value = func(self, *args)
             value = func(self, *args)
         else:
         else:
             # Otherwise, use the existing pickle convention.
             # Otherwise, use the existing pickle convention.
@@ -133,9 +142,26 @@ class Unpickler(pickle.Unpickler):
 
 
         stack[-1] = value
         stack[-1] = value
 
 
-    #FIXME: how to replace in Python 3?
-    if sys.version_info < (3, 0):
-        pickle.Unpickler.dispatch[pickle.REDUCE] = load_reduce
+    BaseUnpickler.dispatch[pickle.REDUCE[0]] = load_reduce
+
+
+if sys.version_info >= (3, 8):
+    # In Python 3.8 and up, we can use the C implementation of Pickler, which
+    # supports a reducer_override method.
+    class Pickler(pickle.Pickler):
+        def __init__(self, *args, **kw):
+            self.bamWriter = BamWriter()
+            pickle.Pickler.__init__(self, *args, **kw)
+
+        def reducer_override(self, obj):
+            reduce = getattr(obj, "__reduce_persist__", None)
+            if reduce:
+                return reduce(self)
+
+            return NotImplemented
+else:
+    # Otherwise, we have to use our custom version that overrides save().
+    Pickler = _Pickler
 
 
 
 
 # Shorthands
 # Shorthands

+ 37 - 15
direct/src/tkwidgets/EntryScale.py

@@ -5,6 +5,7 @@ EntryScale Class: Scale with a label, and a linked and validated entry
 __all__ = ['EntryScale', 'EntryScaleGroup']
 __all__ = ['EntryScale', 'EntryScaleGroup']
 
 
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
+from panda3d.core import Vec4
 import Pmw
 import Pmw
 from tkinter.simpledialog import *
 from tkinter.simpledialog import *
 from tkinter.colorchooser import askcolor
 from tkinter.colorchooser import askcolor
@@ -477,17 +478,24 @@ def rgbPanel(nodePath, callback = None):
     esg.component('menubar').component('EntryScale Group-button')['text'] = (
     esg.component('menubar').component('EntryScale Group-button')['text'] = (
         'RGBA Panel')
         'RGBA Panel')
     # Update menu
     # Update menu
-    menu = esg.component('menubar').component('EntryScale Group-menu')
+    menubar = esg.component('menubar')
+    menubar.deletemenuitems('EntryScale Group', 1, 1)
+
     # Some helper functions
     # Some helper functions
     # Clear color
     # Clear color
-    menu.insert_command(index = 1, label = 'Clear Color',
-                        command = lambda np = nodePath: np.clearColor())
-    # Set Clear Transparency
-    menu.insert_command(index = 2, label = 'Set Transparency',
-                        command = lambda np = nodePath: np.setTransparency(1))
-    menu.insert_command(
-        index = 3, label = 'Clear Transparency',
-        command = lambda np = nodePath: np.clearTransparency())
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Clear Color', command=lambda np=nodePath: np.clearColor())
+
+    # Set/Clear Transparency
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Set Transparency', command=lambda np=nodePath: np.setTransparency(1))
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Clear Transparency',
+        command=lambda np=nodePath: np.clearTransparency())
+
     # System color picker
     # System color picker
     def popupColorPicker(esg = esg):
     def popupColorPicker(esg = esg):
         # Can pass in current color with: color = (255, 0, 0)
         # Can pass in current color with: color = (255, 0, 0)
@@ -497,13 +505,27 @@ def rgbPanel(nodePath, callback = None):
             initialcolor = tuple(esg.get()[:3]))[0]
             initialcolor = tuple(esg.get()[:3]))[0]
         if color:
         if color:
             esg.set((color[0], color[1], color[2], esg.getAt(3)))
             esg.set((color[0], color[1], color[2], esg.getAt(3)))
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
+
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+
     def printToLog(nodePath=nodePath):
     def printToLog(nodePath=nodePath):
-        c=nodePath.getColor()
-        print("Vec4(%.3f, %.3f, %.3f, %.3f)"%(c[0], c[1], c[2], c[3]))
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+        c = nodePath.getColor()
+        print("Vec4(%.3f, %.3f, %.3f, %.3f)" % (c[0], c[1], c[2], c[3]))
+
+    menubar.addmenuitem(
+        'EntryScale Group', 'command',
+        label='Print to log', command=printToLog)
+
+    # Add back the Dismiss item we removed.
+    if esg['fDestroy']:
+        dismissCommand = esg.destroy
+    else:
+        dismissCommand = esg.withdraw
+    menubar.addmenuitem(
+        'EntryScale Group', 'command', 'Dismiss EntryScale Group panel',
+        label='Dismiss', command=dismissCommand)
 
 
     # Set callback
     # Set callback
     def onRelease(r, g, b, a, nodePath = nodePath):
     def onRelease(r, g, b, a, nodePath = nodePath):

+ 28 - 0
doc/ReleaseNotes

@@ -1,3 +1,31 @@
+------------------------  RELEASE 1.10.8  -----------------------
+
+Recommended maintenance release.
+
+* Support building for macOS 11 "Big Sur" and "Apple Silicon" (arm64)
+* Fix a memory leak, particularly noticeable with multithreaded pipeline (#1077)
+* Fix crash on macOS when unplugging device with threading active (#1082)
+* Fix error with build_apps not working with certain versions of Python
+* Fix DirectEntry/PGEntry flickering in the multithreaded pipeline (#1070)
+* Fix sounds resuming on reactivation if stop() was called while inactive (#559)
+* Collision traverser now releases GIL during traversal (#1033)
+* Fix crash caused by some gamepad drivers on Linux (#1066)
+* Add GraphicsPipe::get_display_zoom() for querying system DPI scaling
+* Skinning-enabled shaders can now properly render unskinned models as well
+* BitMask, SparseArray, BitArray types can now be pickled (#886)
+* VFSImporter now properly detects source file encodings in Python 3
+* Workaround for lighting bug with scenes imported using panda3d-gltf/blend2bam
+* Fix compilation error with Bullet 2.90+
+* Assimp library was updated in Windows thirdparty packages (#1020)
+* libCg is now shipped as library instead of framework on macOS (#1079)
+* Fix some erroneous warnings about missing modules in build_apps
+* Add warnings to build_apps when forgetting dependencies in requirements.txt
+* Add experimental TextureStage::M_emission mode
+* Add experimental p3d_TextureNormal, p3d_TextureEmission, etc. GLSL inputs
+* Fix ability to use deployment system when compiling without OpenSSL (#1073)
+* Fix assorted issues with rgbPanel
+* Fix comparison operator of RenderEffects object
+
 ------------------------  RELEASE 1.10.7  -----------------------
 ------------------------  RELEASE 1.10.7  -----------------------
 
 
 This is primarily a bugfix release, but includes a few new features as well.
 This is primarily a bugfix release, but includes a few new features as well.

+ 0 - 4
dtool/Config.cmake

@@ -29,10 +29,6 @@ if(DEFINED CMAKE_CXX_FLAGS_COVERAGE)
   list(APPEND _configs Coverage)
   list(APPEND _configs Coverage)
 endif()
 endif()
 
 
-if(IS_MULTICONFIG)
-  set(CMAKE_CONFIGURATION_TYPES ${_configs})
-endif()
-
 # Are we building with static or dynamic linking?
 # Are we building with static or dynamic linking?
 option(BUILD_SHARED_LIBS
 option(BUILD_SHARED_LIBS
   "Causes subpackages to be built separately -- setup for dynamic linking.
   "Causes subpackages to be built separately -- setup for dynamic linking.

+ 16 - 3
dtool/LocalSetup.cmake

@@ -6,6 +6,7 @@
 # file based on the user's selected configure variables.
 # file based on the user's selected configure variables.
 #
 #
 
 
+include(CheckCXXCompilerFlag)
 include(CheckCXXSourceCompiles)
 include(CheckCXXSourceCompiles)
 include(CheckCSourceRuns)
 include(CheckCSourceRuns)
 include(CheckIncludeFileCXX)
 include(CheckIncludeFileCXX)
@@ -137,8 +138,18 @@ check_include_file_cxx(stdint.h PHAVE_STDINT_H)
 #set(HAVE_POSIX_THREADS ${CMAKE_USE_PTHREADS_INIT})
 #set(HAVE_POSIX_THREADS ${CMAKE_USE_PTHREADS_INIT})
 
 
 # Do we have SSE2 support?
 # Do we have SSE2 support?
-include(CheckCXXCompilerFlag)
-check_cxx_compiler_flag(-msse2 HAVE_SSE2)
+if(MSVC)
+  check_cxx_source_compiles("
+#if !defined(__SSE2__) && !defined(_M_X64) && !defined(_M_AMD64) && !defined(_M_IX86_FP)
+#error no
+#endif
+int main (int argc, char *argv[]) {
+  return 0;
+}
+" HAVE_SSE2)
+else()
+  check_cxx_compiler_flag(-msse2 HAVE_SSE2)
+endif()
 
 
 # Set LINK_ALL_STATIC if we're building everything as static libraries.
 # Set LINK_ALL_STATIC if we're building everything as static libraries.
 # Also set the library type used for "modules" appropriately.
 # Also set the library type used for "modules" appropriately.
@@ -158,7 +169,9 @@ endif()
 show_packages()
 show_packages()
 
 
 message("")
 message("")
-if(INTERROGATE_PYTHON_INTERFACE)
+if(HAVE_PYTHON AND NOT PYTHON_FOUND)
+  message(SEND_ERROR "Configured Panda with Python bindings, but no Python library found.  Disable HAVE_PYTHON to continue.")
+elseif(INTERROGATE_PYTHON_INTERFACE)
   message("Compilation will generate Python interfaces for Python ${PYTHON_VERSION_STRING}.")
   message("Compilation will generate Python interfaces for Python ${PYTHON_VERSION_STRING}.")
 else()
 else()
   message("Configuring Panda WITHOUT Python interfaces.")
   message("Configuring Panda WITHOUT Python interfaces.")

+ 2 - 0
dtool/dtool_config.h.in

@@ -63,7 +63,9 @@
 #cmakedefine HAVE_PNM
 #cmakedefine HAVE_PNM
 
 
 /* Define if we have CG installed.  */
 /* Define if we have CG installed.  */
+#ifndef __aarch64__
 #cmakedefine HAVE_CG
 #cmakedefine HAVE_CG
+#endif
 
 
 /* Define if we have zlib installed.  */
 /* Define if we have zlib installed.  */
 #cmakedefine HAVE_ZLIB
 #cmakedefine HAVE_ZLIB

+ 1 - 1
dtool/src/dtoolbase/neverFreeMemory.h

@@ -57,7 +57,7 @@ private:
     size_t _remaining;
     size_t _remaining;
   };
   };
 
 
-  typedef std::set<Page> Pages;
+  typedef std::multiset<Page> Pages;
   Pages _pages;
   Pages _pages;
 
 
   size_t _total_alloc;
   size_t _total_alloc;

+ 2 - 0
dtool/src/dtoolutil/CMakeLists.txt

@@ -82,6 +82,8 @@ if(APPLE)
   find_library(FOUNDATION_LIBRARY Foundation)
   find_library(FOUNDATION_LIBRARY Foundation)
   find_library(APPKIT_LIBRARY AppKit)
   find_library(APPKIT_LIBRARY AppKit)
   target_link_libraries(p3dtoolutil ${FOUNDATION_LIBRARY} ${APPKIT_LIBRARY})
   target_link_libraries(p3dtoolutil ${FOUNDATION_LIBRARY} ${APPKIT_LIBRARY})
+
+  mark_as_advanced(FOUNDATION_LIBRARY APPKIT_LIBRARY)
 endif()
 endif()
 
 
 # These are all used by executionEnvironment.cxx/filename.cxx
 # These are all used by executionEnvironment.cxx/filename.cxx

+ 7 - 3
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -255,10 +255,14 @@ get_cwd() {
  */
  */
 bool ExecutionEnvironment::
 bool ExecutionEnvironment::
 ns_has_environment_variable(const string &var) const {
 ns_has_environment_variable(const string &var) const {
-#ifdef PREREAD_ENVIRONMENT
-  return _variables.count(var) != 0;
-#else
+  if (_variables.count(var) != 0) {
+    return true;
+  }
+
+#ifndef PREREAD_ENVIRONMENT
   return getenv(var.c_str()) != nullptr;
   return getenv(var.c_str()) != nullptr;
+#else
+  return false;
 #endif
 #endif
 }
 }
 
 

+ 9 - 0
dtool/src/dtoolutil/panda_getopt_impl.cxx

@@ -491,4 +491,13 @@ getopt_long_only(int argc, char *const argv[], const char *optstring,
   return pgetopt->process(opterr, longindex, optarg, optind, optopt);
   return pgetopt->process(opterr, longindex, optarg, optind, optopt);
 }
 }
 
 
+/**
+ * Resets the internal PandaGetopt state.
+ * This is a necessary step to reset getopt state in general.
+ */
+void
+pgetopt_reset() {
+  pgetopt = nullptr;
+}
+
 #endif  // defined(HAVE_GETOPT) && defined(HAVE_GETOPT_LONG_ONLY)
 #endif  // defined(HAVE_GETOPT) && defined(HAVE_GETOPT_LONG_ONLY)

+ 2 - 0
dtool/src/dtoolutil/panda_getopt_impl.h

@@ -62,6 +62,8 @@ getopt_long(int argc, char *const argv[], const char *optstring,
 extern EXPCL_DTOOL_DTOOLUTIL int
 extern EXPCL_DTOOL_DTOOLUTIL int
 getopt_long_only(int argc, char *const argv[], const char *optstring,
 getopt_long_only(int argc, char *const argv[], const char *optstring,
                  const struct option *longopts, int *longindex);
                  const struct option *longopts, int *longindex);
+extern EXPCL_DTOOL_DTOOLUTIL void
+pgetopt_reset();
 
 
 #ifdef  __cplusplus
 #ifdef  __cplusplus
 }
 }

+ 18 - 0
dtool/src/interrogatedb/py_compat.cxx

@@ -43,4 +43,22 @@ size_t PyLongOrInt_AsSize_t(PyObject *vv) {
 }
 }
 #endif
 #endif
 
 
+#if PY_VERSION_HEX < 0x03090000
+/**
+ * Most efficient way to call a function without any arguments.
+ */
+PyObject *PyObject_CallNoArgs(PyObject *func) {
+#if PY_VERSION_HEX >= 0x03080000
+  return _PyObject_Vectorcall(func, nullptr, 0, nullptr);
+#elif PY_VERSION_HEX >= 0x03070000
+  return _PyObject_FastCallDict(func, nullptr, 0, nullptr);
+#elif PY_VERSION_HEX >= 0x03060000
+  return _PyObject_FastCall(func, nullptr, 0);
+#else
+  static PyObject *empty_tuple = PyTuple_New(0);
+  return PyObject_Call(func, empty_tuple, nullptr);
+#endif
+}
+#endif
+
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 23 - 5
dtool/src/interrogatedb/py_compat.h

@@ -140,11 +140,7 @@ typedef long Py_hash_t;
 /* Python 3.6 */
 /* Python 3.6 */
 
 
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_CallNoArg)
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_CallNoArg)
-INLINE PyObject *_PyObject_CallNoArg(PyObject *func) {
-  static PyObject *empty_tuple = PyTuple_New(0);
-  return PyObject_Call(func, empty_tuple, nullptr);
-}
-#  define _PyObject_CallNoArg _PyObject_CallNoArg
+#  define _PyObject_CallNoArg PyObject_CallNoArgs
 #endif
 #endif
 
 
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_FastCall)
 #if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_FastCall)
@@ -208,6 +204,28 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) {
 }
 }
 #endif
 #endif
 
 
+/* Python 3.9 */
+
+#if PY_VERSION_HEX < 0x03090000
+EXPCL_PYPANDA PyObject *PyObject_CallNoArgs(PyObject *func);
+
+INLINE PyObject *PyObject_CallOneArg(PyObject *callable, PyObject *arg) {
+#if PY_VERSION_HEX >= 0x03060000
+  return _PyObject_FastCall(callable, &arg, 1);
+#else
+  return PyObject_CallFunctionObjArgs(callable, arg, nullptr);
+#endif
+}
+
+INLINE PyObject *PyObject_CallMethodNoArgs(PyObject *obj, PyObject *name) {
+  return PyObject_CallMethodObjArgs(obj, name, nullptr);
+}
+
+INLINE PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObject *arg) {
+  return PyObject_CallMethodObjArgs(obj, name, arg, nullptr);
+}
+#endif
+
 /* Other Python implementations */
 /* Other Python implementations */
 
 
 // _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.
 // _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred.

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

@@ -755,7 +755,7 @@ PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) {
  */
  */
 PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) {
 PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) {
   PyObject *callable = (PyObject *)Py_TYPE(self);
   PyObject *callable = (PyObject *)Py_TYPE(self);
-  return _PyObject_FastCall(callable, &self, 1);
+  return PyObject_CallOneArg(callable, self);
 }
 }
 
 
 /**
 /**

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

@@ -30,7 +30,7 @@ static void _register_collection(PyTypeObject *type, const char *abc) {
 #endif
 #endif
         PyObject *sequence = PyDict_GetItemString(dict, abc);
         PyObject *sequence = PyDict_GetItemString(dict, abc);
         if (sequence != nullptr) {
         if (sequence != nullptr) {
-          if (PyObject_CallMethodObjArgs(sequence, register_str, (PyObject *)type, nullptr) == nullptr) {
+          if (PyObject_CallMethodOneArg(sequence, register_str, (PyObject *)type) == nullptr) {
             PyErr_Print();
             PyErr_Print();
           }
           }
         }
         }

+ 30 - 10
makepanda/installer.nsi

@@ -30,6 +30,7 @@ SetCompressor ${COMPRESSOR}
 !include "MUI2.nsh"
 !include "MUI2.nsh"
 !include "Sections.nsh"
 !include "Sections.nsh"
 !include "WinMessages.nsh"
 !include "WinMessages.nsh"
+!include "WinVer.nsh"
 !include "WordFunc.nsh"
 !include "WordFunc.nsh"
 !include "x64.nsh"
 !include "x64.nsh"
 
 
@@ -89,7 +90,6 @@ LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${
 LangString DESC_SecMayaPlugins ${LANG_ENGLISH} "Plug-ins and scripts for Autodesk Maya (${REGVIEW}-bit) that can be used to export models to Panda3D."
 LangString DESC_SecMayaPlugins ${LANG_ENGLISH} "Plug-ins and scripts for Autodesk Maya (${REGVIEW}-bit) that can be used to export models to Panda3D."
 
 
 var READABLE
 var READABLE
-var MANPAGE
 
 
 ; See http://nsis.sourceforge.net/Check_if_a_file_exists_at_compile_time for documentation
 ; See http://nsis.sourceforge.net/Check_if_a_file_exists_at_compile_time for documentation
 !macro !defineifexist _VAR_NAME _FILE_NAME
 !macro !defineifexist _VAR_NAME _FILE_NAME
@@ -373,12 +373,14 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd
         !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd
         !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
         !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
+        !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
     !else
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
         !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd
         !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd
         !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
         !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
+        !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
     !endif
     !endif
 SectionGroupEnd
 SectionGroupEnd
 
 
@@ -483,14 +485,32 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.6-32
         !insertmacro MaybeEnablePyBindingSection 3.6-32
         !insertmacro MaybeEnablePyBindingSection 3.7-32
         !insertmacro MaybeEnablePyBindingSection 3.7-32
         !insertmacro MaybeEnablePyBindingSection 3.8-32
         !insertmacro MaybeEnablePyBindingSection 3.8-32
+        ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9-32
         !insertmacro MaybeEnablePyBindingSection 3.9-32
+        !insertmacro MaybeEnablePyBindingSection 3.10-32
+        ${EndIf}
     !else
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
         !insertmacro MaybeEnablePyBindingSection 3.5
         !insertmacro MaybeEnablePyBindingSection 3.6
         !insertmacro MaybeEnablePyBindingSection 3.6
         !insertmacro MaybeEnablePyBindingSection 3.7
         !insertmacro MaybeEnablePyBindingSection 3.7
         !insertmacro MaybeEnablePyBindingSection 3.8
         !insertmacro MaybeEnablePyBindingSection 3.8
+        ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9
         !insertmacro MaybeEnablePyBindingSection 3.9
+        !insertmacro MaybeEnablePyBindingSection 3.10
+        ${EndIf}
+    !endif
+
+    ; These versions of Python require Windows 8.1 or higher.
+    ${Unless} ${AtLeastWin8}
+    !ifdef SecPyBindings3.9
+        SectionSetFlags ${SecPyBindings3.9} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.9} 0
+    !endif
+    !ifdef SecPyBindings3.10
+        SectionSetFlags ${SecPyBindings3.10} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.10} 0
     !endif
     !endif
+    ${EndUnless}
 FunctionEnd
 FunctionEnd
 
 
 Function .onSelChange
 Function .onSelChange
@@ -599,13 +619,14 @@ Section "Sample programs" SecSamples
 
 
     SetOutPath $INSTDIR
     SetOutPath $INSTDIR
     WriteINIStr $INSTDIR\Website.url "InternetShortcut" "URL" "https://www.panda3d.org/"
     WriteINIStr $INSTDIR\Website.url "InternetShortcut" "URL" "https://www.panda3d.org/"
-    WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php"
-    WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php/Sample_Programs_in_the_Distribution"
+    WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}"
+    WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/index"
     SetOutPath $INSTDIR
     SetOutPath $INSTDIR
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Website"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Website"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Sample Program Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Sample Program Manual"
 
 
+    ${Unless} ${AtLeastWin8}
     FindFirst $0 $1 $INSTDIR\samples\*
     FindFirst $0 $1 $INSTDIR\samples\*
     loop:
     loop:
         StrCmp $1 "" done
         StrCmp $1 "" done
@@ -620,16 +641,10 @@ Section "Sample programs" SecSamples
         Call Capitalize
         Call Capitalize
         Pop $R0
         Pop $R0
         StrCpy $READABLE $R0
         StrCpy $READABLE $R0
-        Push $1
-        Push "-"
-        Push "_"
-        Call StrRep
-        Pop $R0
-        StrCpy $MANPAGE $R0
         DetailPrint "Creating shortcuts for sample program $READABLE"
         DetailPrint "Creating shortcuts for sample program $READABLE"
         CreateDirectory "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE"
         CreateDirectory "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE"
         SetOutPath $INSTDIR\samples\$1
         SetOutPath $INSTDIR\samples\$1
-        WriteINIStr $INSTDIR\samples\$1\ManualPage.url "InternetShortcut" "URL" "https://www.panda3d.org/wiki/index.php/Sample_Programs:_$MANPAGE"
+        WriteINIStr $INSTDIR\samples\$1\ManualPage.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/$1"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\Manual Page.lnk" "$INSTDIR\samples\$1\ManualPage.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Manual Entry on this Sample Program"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\Manual Page.lnk" "$INSTDIR\samples\$1\ManualPage.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Manual Entry on this Sample Program"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\View Source Code.lnk" "$INSTDIR\samples\$1"
         CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\View Source Code.lnk" "$INSTDIR\samples\$1"
         iloop:
         iloop:
@@ -643,6 +658,7 @@ Section "Sample programs" SecSamples
         FindNext $0 $1
         FindNext $0 $1
         Goto loop
         Goto loop
     done:
     done:
+    ${EndUnless}
 SectionEnd
 SectionEnd
 !endif
 !endif
 
 
@@ -803,12 +819,14 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.7-32
         !insertmacro RemovePythonPath 3.7-32
         !insertmacro RemovePythonPath 3.8-32
         !insertmacro RemovePythonPath 3.8-32
         !insertmacro RemovePythonPath 3.9-32
         !insertmacro RemovePythonPath 3.9-32
+        !insertmacro RemovePythonPath 3.10-32
     !else
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
         !insertmacro RemovePythonPath 3.6
         !insertmacro RemovePythonPath 3.7
         !insertmacro RemovePythonPath 3.7
         !insertmacro RemovePythonPath 3.8
         !insertmacro RemovePythonPath 3.8
         !insertmacro RemovePythonPath 3.9
         !insertmacro RemovePythonPath 3.9
+        !insertmacro RemovePythonPath 3.10
     !endif
     !endif
 
 
     SetDetailsPrint both
     SetDetailsPrint both
@@ -876,12 +894,14 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
   !else
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
   !endif
   !endif
   !ifdef INCLUDE_PYVER
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 3 - 1
makepanda/makepackage.py

@@ -153,6 +153,7 @@ def MakeInstallerNSIS(version, file, title, installdir, compressor="lzma", **kwa
         'BUILT'     : '..\\' + outputdir,
         'BUILT'     : '..\\' + outputdir,
         'SOURCE'    : '..',
         'SOURCE'    : '..',
         'REGVIEW'   : regview,
         'REGVIEW'   : regview,
+        'MAJOR_VER' : '.'.join(version.split('.')[:2]),
     }
     }
 
 
     # Are we shipping a version of Python?
     # Are we shipping a version of Python?
@@ -388,7 +389,8 @@ def MakeInstallerOSX(version, python_versions=[], installdir=None, **kwargs):
     oscmd("cp -R %s/models                dstroot/base/%s/models" % (outputdir, installdir))
     oscmd("cp -R %s/models                dstroot/base/%s/models" % (outputdir, installdir))
     oscmd("cp -R doc/LICENSE              dstroot/base/%s/LICENSE" % installdir)
     oscmd("cp -R doc/LICENSE              dstroot/base/%s/LICENSE" % installdir)
     oscmd("cp -R doc/ReleaseNotes         dstroot/base/%s/ReleaseNotes" % installdir)
     oscmd("cp -R doc/ReleaseNotes         dstroot/base/%s/ReleaseNotes" % installdir)
-    oscmd("cp -R %s/Frameworks            dstroot/base/%s/Frameworks" % (outputdir, installdir))
+    if os.path.isdir(outputdir+"/Frameworks") and os.listdir(outputdir+"/Frameworks"):
+        oscmd("cp -R %s/Frameworks            dstroot/base/%s/Frameworks" % (outputdir, installdir))
     if os.path.isdir(outputdir+"/plugins"):
     if os.path.isdir(outputdir+"/plugins"):
         oscmd("cp -R %s/plugins           dstroot/base/%s/plugins" % (outputdir, installdir))
         oscmd("cp -R %s/plugins           dstroot/base/%s/plugins" % (outputdir, installdir))
 
 

+ 181 - 96
makepanda/makepanda.py

@@ -30,6 +30,11 @@ import time
 import os
 import os
 import sys
 import sys
 
 
+try:
+    import zlib
+except:
+    zlib = None
+
 ########################################################################
 ########################################################################
 ##
 ##
 ## PARSING THE COMMAND LINE OPTIONS
 ## PARSING THE COMMAND LINE OPTIONS
@@ -58,7 +63,7 @@ WHLVERSION=None
 RPMRELEASE="1"
 RPMRELEASE="1"
 GIT_COMMIT=None
 GIT_COMMIT=None
 MAJOR_VERSION=None
 MAJOR_VERSION=None
-OSXTARGET=None
+OSX_ARCHS=[]
 global STRDXSDKVERSION, BOOUSEINTELCOMPILER
 global STRDXSDKVERSION, BOOUSEINTELCOMPILER
 STRDXSDKVERSION = 'default'
 STRDXSDKVERSION = 'default'
 WINDOWS_SDK = None
 WINDOWS_SDK = None
@@ -68,9 +73,6 @@ OPENCV_VER_23 = False
 PLATFORM = None
 PLATFORM = None
 COPY_PYTHON = True
 COPY_PYTHON = True
 
 
-if "MACOSX_DEPLOYMENT_TARGET" in os.environ:
-    OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"]
-
 PkgListSet(["PYTHON", "DIRECT",                        # Python support
 PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "GL", "GLES", "GLES2"] + DXVERSIONS + ["TINYDISPLAY", "NVIDIACG", # 3D graphics
   "GL", "GLES", "GLES2"] + DXVERSIONS + ["TINYDISPLAY", "NVIDIACG", # 3D graphics
   "EGL",                                               # OpenGL (ES) integration
   "EGL",                                               # OpenGL (ES) integration
@@ -132,7 +134,7 @@ def usage(problem):
     print("  --distributor X   (short string identifying the distributor of the build)")
     print("  --distributor X   (short string identifying the distributor of the build)")
     print("  --outputdir X     (use the specified directory instead of 'built')")
     print("  --outputdir X     (use the specified directory instead of 'built')")
     print("  --threads N       (use the multithreaded build system. see manual)")
     print("  --threads N       (use the multithreaded build system. see manual)")
-    print("  --osxtarget N     (the macOS version number to build for (macOS only))")
+    print("  --universal       (build universal binaries (macOS 11.0+ only))")
     print("  --override \"O=V\"  (override dtool_config/prc option value)")
     print("  --override \"O=V\"  (override dtool_config/prc option value)")
     print("  --static          (builds libraries for static linking)")
     print("  --static          (builds libraries for static linking)")
     print("  --target X        (experimental cross-compilation (android only))")
     print("  --target X        (experimental cross-compilation (android only))")
@@ -160,7 +162,7 @@ def usage(problem):
 
 
 def parseopts(args):
 def parseopts(args):
     global INSTALLER,WHEEL,RUNTESTS,GENMAN,DISTRIBUTOR,VERSION
     global INSTALLER,WHEEL,RUNTESTS,GENMAN,DISTRIBUTOR,VERSION
-    global COMPRESSOR,THREADCOUNT,OSXTARGET
+    global COMPRESSOR,THREADCOUNT,OSX_ARCHS
     global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT
     global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
     global COPY_PYTHON
     global COPY_PYTHON
@@ -168,24 +170,25 @@ def parseopts(args):
     # Options for which to display a deprecation warning.
     # Options for which to display a deprecation warning.
     removedopts = [
     removedopts = [
         "use-touchinput", "no-touchinput", "no-awesomium", "no-directscripts",
         "use-touchinput", "no-touchinput", "no-awesomium", "no-directscripts",
-        "no-carbon", "universal", "no-physx", "no-rocket", "host"
+        "no-carbon", "no-physx", "no-rocket", "host", "osxtarget=",
         ]
         ]
 
 
     # All recognized options.
     # All recognized options.
     longopts = [
     longopts = [
-        "help","distributor=","verbose","osxtarget=","tests",
+        "help","distributor=","verbose","tests",
         "optimize=","everything","nothing","installer","wheel","rtdist","nocolor",
         "optimize=","everything","nothing","installer","wheel","rtdist","nocolor",
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "static","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "static","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
-        "target=", "arch=", "git-commit=", "no-copy-python",
+        "universal", "target=", "arch=", "git-commit=", "no-copy-python",
         "cggl-incdir=", "cggl-libdir=",
         "cggl-incdir=", "cggl-libdir=",
         ] + removedopts
         ] + removedopts
 
 
     anything = 0
     anything = 0
     optimize = ""
     optimize = ""
     target = None
     target = None
-    target_arch = None
+    target_archs = []
+    universal = False
     clean_build = False
     clean_build = False
     for pkg in PkgListGet():
     for pkg in PkgListGet():
         longopts.append("use-" + pkg.lower())
         longopts.append("use-" + pkg.lower())
@@ -208,9 +211,9 @@ def parseopts(args):
             elif (option=="--nothing"): PkgDisableAll()
             elif (option=="--nothing"): PkgDisableAll()
             elif (option=="--threads"): THREADCOUNT=int(value)
             elif (option=="--threads"): THREADCOUNT=int(value)
             elif (option=="--outputdir"): SetOutputDir(value.strip())
             elif (option=="--outputdir"): SetOutputDir(value.strip())
-            elif (option=="--osxtarget"): OSXTARGET=value.strip()
+            elif (option=="--universal"): universal = True
             elif (option=="--target"): target = value.strip()
             elif (option=="--target"): target = value.strip()
-            elif (option=="--arch"): target_arch = value.strip()
+            elif (option=="--arch"): target_archs.append(value.strip())
             elif (option=="--nocolor"): DisableColors()
             elif (option=="--nocolor"): DisableColors()
             elif (option=="--version"):
             elif (option=="--version"):
                 match = re.match(r'^\d+\.\d+(\.\d+)+', value)
                 match = re.match(r'^\d+\.\d+(\.\d+)+', value)
@@ -239,7 +242,7 @@ def parseopts(args):
             elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True
             elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True
             elif (option=="--clean"): clean_build = True
             elif (option=="--clean"): clean_build = True
             elif (option=="--no-copy-python"): COPY_PYTHON = False
             elif (option=="--no-copy-python"): COPY_PYTHON = False
-            elif (option[2:] in removedopts):
+            elif (option[2:] in removedopts or option[2:]+'=' in removedopts):
                 Warn("Ignoring removed option %s" % (option))
                 Warn("Ignoring removed option %s" % (option))
             else:
             else:
                 for pkg in PkgListGet() + ['CGGL']:
                 for pkg in PkgListGet() + ['CGGL']:
@@ -268,29 +271,17 @@ def parseopts(args):
 
 
     if (optimize==""): optimize = "3"
     if (optimize==""): optimize = "3"
 
 
-    if OSXTARGET:
-        try:
-            maj, min = OSXTARGET.strip().split('.')
-            OSXTARGET = int(maj), int(min)
-            assert OSXTARGET[0] >= 10
-        except:
-            usage("Invalid setting for OSXTARGET")
+    if target is not None or target_archs:
+        SetTarget(target, target_archs[-1] if target_archs else None)
 
 
-        if OSXTARGET < (10, 9):
-            warn_prefix = "%sERROR:%s " % (GetColor("red"), GetColor())
-            print("=========================================================================")
-            print(warn_prefix + "Support for macOS versions before 10.9 has been discontinued.")
-            print(warn_prefix + "For more information, or any questions, please visit:")
-            print("  https://github.com/panda3d/panda3d/issues/300")
-            print("=========================================================================")
-            sys.stdout.flush()
-            time.sleep(1.0)
-            sys.exit(1)
-    else:
-        OSXTARGET = None
+    if universal:
+        if target_archs:
+            exit("--universal is incompatible with --arch")
 
 
-    if target is not None or target_arch is not None:
-        SetTarget(target, target_arch)
+        OSX_ARCHS.append("x86_64")
+        OSX_ARCHS.append("arm64")
+    elif target_archs:
+        OSX_ARCHS = target_archs
 
 
     try:
     try:
         SetOptimize(int(optimize))
         SetOptimize(int(optimize))
@@ -350,8 +341,11 @@ if ("LDFLAGS" in os.environ):
     LDFLAGS = os.environ["LDFLAGS"].strip()
     LDFLAGS = os.environ["LDFLAGS"].strip()
 
 
 os.environ["MAKEPANDA"] = os.path.abspath(sys.argv[0])
 os.environ["MAKEPANDA"] = os.path.abspath(sys.argv[0])
-if GetHost() == "darwin" and OSXTARGET is not None:
-    os.environ["MACOSX_DEPLOYMENT_TARGET"] = "%d.%d" % OSXTARGET
+if GetHost() == "darwin":
+    if tuple(OSX_ARCHS) == ('arm64',):
+        os.environ["MACOSX_DEPLOYMENT_TARGET"] = "11.0"
+    else:
+        os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
 
 
 ########################################################################
 ########################################################################
 ##
 ##
@@ -382,16 +376,30 @@ if target == 'windows':
         PLATFORM = 'win32'
         PLATFORM = 'win32'
 
 
 elif target == 'darwin':
 elif target == 'darwin':
-    if OSXTARGET:
-        osxver = OSXTARGET
+    arch_tag = None
+    if not OSX_ARCHS:
+        arch_tag = GetTargetArch()
+    elif len(OSX_ARCHS) == 1:
+        arch_tag = OSX_ARCHS[0]
+    elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')):
+        arch_tag = 'fat'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386')):
+        arch_tag = 'intel'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'ppc64')):
+        arch_tag = 'fat64'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc')):
+        arch_tag = 'fat32'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc64', 'ppc')):
+        arch_tag = 'universal'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'arm64')):
+        arch_tag = 'universal2'
     else:
     else:
-        maj, min = platform.mac_ver()[0].split('.')[:2]
-        osxver = int(maj), int(min)
-        if osxver < (10, 9):
-            osxver = (10, 9)
+        raise RuntimeError('No arch tag for arch combination %s' % OSX_ARCHS)
 
 
-    arch_tag = GetTargetArch()
-    PLATFORM = 'macosx-{0}.{1}-{2}'.format(osxver[0], osxver[1], arch_tag)
+    if arch_tag == 'arm64':
+        PLATFORM = 'macosx-11.0-' + arch_tag
+    else:
+        PLATFORM = 'macosx-10.9-' + arch_tag
 
 
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
     # This is manylinux1.  A bit of a sloppy check, though.
     # This is manylinux1.  A bit of a sloppy check, though.
@@ -474,7 +482,7 @@ MakeBuildTree()
 SdkLocateDirectX(STRDXSDKVERSION)
 SdkLocateDirectX(STRDXSDKVERSION)
 SdkLocateMaya()
 SdkLocateMaya()
 SdkLocateMax()
 SdkLocateMax()
-SdkLocateMacOSX(OSXTARGET)
+SdkLocateMacOSX(OSX_ARCHS)
 SdkLocatePython(False)
 SdkLocatePython(False)
 SdkLocateWindows(WINDOWS_SDK)
 SdkLocateWindows(WINDOWS_SDK)
 SdkLocateSpeedTree()
 SdkLocateSpeedTree()
@@ -747,6 +755,24 @@ if (COMPILER=="GCC"):
     if GetTarget() != "darwin":
     if GetTarget() != "darwin":
         PkgDisable("COCOA")
         PkgDisable("COCOA")
 
 
+    if GetTarget() == 'darwin':
+        if OSX_ARCHS and 'x86_64' not in OSX_ARCHS and 'i386' not in OSX_ARCHS:
+            # These support only these archs, so don't build them if we're not
+            # targeting any of the supported archs.
+            PkgDisable("FMODEX")
+            PkgDisable("NVIDIACG")
+        elif OSX_ARCHS and 'arm64' in OSX_ARCHS:
+            # We must be using the 11.0 SDK or higher, so can't build FMOD Ex
+            if not PkgSkip("FMODEX"):
+                Warn("thirdparty package fmodex is not supported when targeting arm64, excluding from build")
+            PkgDisable("FMODEX")
+        elif not os.path.isfile(SDK.get("MACOSX", "") + '/usr/lib/libstdc++.6.0.9.tbd') and \
+             not os.path.isfile(SDK.get("MACOSX", "") + '/usr/lib/libstdc++.6.0.9.dylib'):
+            # Also, we can't target FMOD Ex on 10.14 and above
+            if not PkgSkip("FMODEX"):
+                Warn("thirdparty package fmodex requires one of MacOSX 10.9-10.13 SDK, excluding from build")
+            PkgDisable("FMODEX")
+
     #if (PkgSkip("PYTHON")==0):
     #if (PkgSkip("PYTHON")==0):
     #    IncDirectory("PYTHON", SDK["PYTHON"])
     #    IncDirectory("PYTHON", SDK["PYTHON"])
     if (GetHost() == "darwin"):
     if (GetHost() == "darwin"):
@@ -1275,15 +1301,20 @@ def CompileCxx(obj,src,opts):
         # Mac-specific flags.
         # Mac-specific flags.
         if GetTarget() == "darwin":
         if GetTarget() == "darwin":
             cmd += " -Wno-deprecated-declarations"
             cmd += " -Wno-deprecated-declarations"
-            if OSXTARGET is not None:
+            if SDK.get("MACOSX"):
                 cmd += " -isysroot " + SDK["MACOSX"]
                 cmd += " -isysroot " + SDK["MACOSX"]
-                cmd += " -mmacosx-version-min=%d.%d" % (OSXTARGET)
+
+            if tuple(OSX_ARCHS) == ('arm64',):
+                cmd += " -mmacosx-version-min=11.0"
+            else:
+                cmd += " -mmacosx-version-min=10.9"
 
 
             # Use libc++ to enable C++11 features.
             # Use libc++ to enable C++11 features.
             cmd += " -stdlib=libc++"
             cmd += " -stdlib=libc++"
 
 
-            arch = GetTargetArch()
-            cmd += " -arch %s" % arch
+            for arch in OSX_ARCHS:
+                if 'NOARCH:' + arch.upper() not in opts:
+                    cmd += " -arch %s" % arch
 
 
         if "SYSROOT" in SDK:
         if "SYSROOT" in SDK:
             if GetTarget() != "android":
             if GetTarget() != "android":
@@ -1814,15 +1845,20 @@ def CompileLink(dll, obj, opts):
         # macOS specific flags.
         # macOS specific flags.
         if GetTarget() == 'darwin':
         if GetTarget() == 'darwin':
             cmd += " -headerpad_max_install_names"
             cmd += " -headerpad_max_install_names"
-            if OSXTARGET is not None:
+            if SDK.get("MACOSX"):
                 cmd += " -isysroot " + SDK["MACOSX"] + " -Wl,-syslibroot," + SDK["MACOSX"]
                 cmd += " -isysroot " + SDK["MACOSX"] + " -Wl,-syslibroot," + SDK["MACOSX"]
-                cmd += " -mmacosx-version-min=%d.%d" % (OSXTARGET)
+
+            if tuple(OSX_ARCHS) == ('arm64',):
+                cmd += " -mmacosx-version-min=11.0"
+            else:
+                cmd += " -mmacosx-version-min=10.9"
 
 
             # Use libc++ to enable C++11 features.
             # Use libc++ to enable C++11 features.
             cmd += " -stdlib=libc++"
             cmd += " -stdlib=libc++"
 
 
-            arch = GetTargetArch()
-            cmd += " -arch %s" % arch
+            for arch in OSX_ARCHS:
+                if 'NOARCH:' + arch.upper() not in opts:
+                    cmd += " -arch %s" % arch
 
 
         elif GetTarget() == 'android':
         elif GetTarget() == 'android':
             arch = GetTargetArch()
             arch = GetTargetArch()
@@ -1942,7 +1978,11 @@ def CompileEgg(eggfile, src, opts):
         oscmd(flt2egg + ' -ps keep -o ' + BracketNameWithQuotes(eggfile) + ' ' + BracketNameWithQuotes(src))
         oscmd(flt2egg + ' -ps keep -o ' + BracketNameWithQuotes(eggfile) + ' ' + BracketNameWithQuotes(src))
 
 
     if pz:
     if pz:
-        oscmd(pzip + ' ' + BracketNameWithQuotes(eggfile))
+        if zlib:
+            WriteBinaryFile(eggfile + '.pz', zlib.compress(ReadBinaryFile(eggfile)))
+            os.remove(eggfile)
+        else:
+            oscmd(pzip + ' ' + BracketNameWithQuotes(eggfile))
 
 
 ##########################################################################################
 ##########################################################################################
 #
 #
@@ -2517,8 +2557,19 @@ def WriteConfigSettings():
     conf = "/* dtool_config.h.  Generated automatically by makepanda.py */\n"
     conf = "/* dtool_config.h.  Generated automatically by makepanda.py */\n"
     for key in sorted(dtool_config.keys()):
     for key in sorted(dtool_config.keys()):
         val = OverrideValue(key, dtool_config[key])
         val = OverrideValue(key, dtool_config[key])
-        if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
-        else:                conf = conf + "#define " + key + " " + val + "\n"
+
+        if key in ('HAVE_CG', 'HAVE_CGGL', 'HAVE_CGDX9') and val != 'UNDEF':
+            # These are not available for ARM, period.
+            conf = conf + "#ifdef __aarch64__\n"
+            conf = conf + "#undef " + key + "\n"
+            conf = conf + "#else\n"
+            conf = conf + "#define " + key + " " + val + "\n"
+            conf = conf + "#endif\n"
+        elif val == 'UNDEF':
+            conf = conf + "#undef " + key + "\n"
+        else:
+            conf = conf + "#define " + key + " " + val + "\n"
+
     ConditionalWriteFile(GetOutputDir() + '/include/dtool_config.h', conf)
     ConditionalWriteFile(GetOutputDir() + '/include/dtool_config.h', conf)
 
 
     if (PkgSkip("SPEEDTREE")==0):
     if (PkgSkip("SPEEDTREE")==0):
@@ -2876,13 +2927,18 @@ if tp_dir is not None:
     if GetTarget() == 'darwin':
     if GetTarget() == 'darwin':
         # Make a list of all the dylibs we ship, to figure out whether we should use
         # Make a list of all the dylibs we ship, to figure out whether we should use
         # install_name_tool to correct the library reference to point to our copy.
         # install_name_tool to correct the library reference to point to our copy.
-        for lib in glob.glob(tp_dir + "/*/lib/*.dylib"):
-            dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
+        for pkg in PkgListGet():
+            if PkgSkip(pkg):
+                continue
 
 
-        if not PkgSkip("PYTHON"):
-            for lib in glob.glob(tp_dir + "/*/lib/" + SDK["PYTHONVERSION"] + "/*.dylib"):
+            tp_libdir = os.path.join(tp_dir, pkg.lower(), "lib")
+            for lib in glob.glob(os.path.join(tp_libdir, "*.dylib")):
                 dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
                 dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
 
 
+            if not PkgSkip("PYTHON"):
+                for lib in glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.dylib")):
+                    dylibs[os.path.basename(lib)] = os.path.basename(os.path.realpath(lib))
+
     for pkg in PkgListGet():
     for pkg in PkgListGet():
         if PkgSkip(pkg):
         if PkgSkip(pkg):
             continue
             continue
@@ -2895,13 +2951,14 @@ if tp_dir is not None:
                     CopyAllFiles(GetOutputDir() + "/bin/", tp_pkg + "/bin/" + SDK["PYTHONVERSION"] + "/")
                     CopyAllFiles(GetOutputDir() + "/bin/", tp_pkg + "/bin/" + SDK["PYTHONVERSION"] + "/")
 
 
         elif GetTarget() == 'darwin':
         elif GetTarget() == 'darwin':
-            tp_libs = glob.glob(tp_pkg + "/lib/*.dylib")
+            tp_libdir = os.path.join(tp_pkg, "lib")
+            tp_libs = glob.glob(os.path.join(tp_libdir, "*.dylib"))
 
 
             if not PkgSkip("PYTHON"):
             if not PkgSkip("PYTHON"):
-                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.dylib"))
-                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.so"))
+                tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.dylib"))
+                tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.so"))
                 if pkg != 'PYTHON':
                 if pkg != 'PYTHON':
-                    tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.py"))
+                    tp_libs += glob.glob(os.path.join(tp_libdir, SDK["PYTHONVERSION"], "*.py"))
 
 
             for tp_lib in tp_libs:
             for tp_lib in tp_libs:
                 basename = os.path.basename(tp_lib)
                 basename = os.path.basename(tp_lib)
@@ -2942,6 +2999,7 @@ if tp_dir is not None:
                 JustBuilt([target], [tp_lib])
                 JustBuilt([target], [tp_lib])
 
 
             for fwx in glob.glob(tp_pkg + "/*.framework"):
             for fwx in glob.glob(tp_pkg + "/*.framework"):
+                MakeDirectory(GetOutputDir() + "/Frameworks")
                 CopyTree(GetOutputDir() + "/Frameworks/" + os.path.basename(fwx), fwx)
                 CopyTree(GetOutputDir() + "/Frameworks/" + os.path.basename(fwx), fwx)
 
 
         else:  # Linux / FreeBSD case.
         else:  # Linux / FreeBSD case.
@@ -5823,20 +5881,33 @@ if not PkgSkip("PANDATOOL"):
 # DIRECTORY: pandatool/src/mayaprogs/
 # DIRECTORY: pandatool/src/mayaprogs/
 #
 #
 
 
+MAYA_BUILT = False
+
 for VER in MAYAVERSIONS:
 for VER in MAYAVERSIONS:
-  VNUM = VER[4:]
-  if not PkgSkip(VER) and not PkgSkip("PANDATOOL") and not PkgSkip("EGG"):
-    if GetTarget() == 'darwin' and int(VNUM) < 2009:
-      # No x86_64 support.
-      continue
+    VNUM = VER[4:]
+    if PkgSkip(VER) or PkgSkip("PANDATOOL") or PkgSkip("EGG"):
+        continue
+
+    if GetTarget() == 'darwin':
+        if int(VNUM) < 2009:
+            # No x86_64 support.
+            continue
+        if tuple(OSX_ARCHS) == ('arm64',):
+            # No arm64 support.
+            continue
+        ARCH_OPTS = ['NOARCH:ARM64']
+    else:
+        ARCH_OPTS = []
 
 
-    OPTS=['DIR:pandatool/src/mayaprogs', 'DIR:pandatool/src/maya', 'DIR:pandatool/src/mayaegg', 'BUILDING:MISC', VER]
+    MAYA_BUILT = True
+
+    OPTS=['DIR:pandatool/src/mayaprogs', 'DIR:pandatool/src/maya', 'DIR:pandatool/src/mayaegg', 'BUILDING:MISC', VER] + ARCH_OPTS
     TargetAdd('mayaeggimport'+VNUM+'_mayaeggimport.obj', opts=OPTS, input='mayaEggImport.cxx')
     TargetAdd('mayaeggimport'+VNUM+'_mayaeggimport.obj', opts=OPTS, input='mayaEggImport.cxx')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaegg'+VNUM+'_loader.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaegg'+VNUM+'_loader.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaeggimport'+VNUM+'_mayaeggimport.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='mayaeggimport'+VNUM+'_mayaeggimport.obj')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='libpandaegg.dll')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input='libpandaegg.dll')
     TargetAdd('mayaeggimport'+VNUM+'.mll', input=COMMON_PANDA_LIBS)
     TargetAdd('mayaeggimport'+VNUM+'.mll', input=COMMON_PANDA_LIBS)
-    TargetAdd('mayaeggimport'+VNUM+'.mll', opts=['ADVAPI', VER])
+    TargetAdd('mayaeggimport'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
 
     TargetAdd('mayaloader'+VNUM+'_config_mayaloader.obj', opts=OPTS, input='config_mayaloader.cxx')
     TargetAdd('mayaloader'+VNUM+'_config_mayaloader.obj', opts=OPTS, input='config_mayaloader.cxx')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='mayaloader'+VNUM+'_config_mayaloader.obj')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='mayaloader'+VNUM+'_config_mayaloader.obj')
@@ -5860,54 +5931,68 @@ for VER in MAYAVERSIONS:
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libp3pandatoolbase.lib')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libp3pandatoolbase.lib')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libpandaegg.dll')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input='libpandaegg.dll')
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input=COMMON_PANDA_LIBS)
     TargetAdd('libp3mayaloader'+VNUM+'.dll', input=COMMON_PANDA_LIBS)
-    TargetAdd('libp3mayaloader'+VNUM+'.dll', opts=['ADVAPI', VER])
+    TargetAdd('libp3mayaloader'+VNUM+'.dll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
 
     TargetAdd('mayapview'+VNUM+'_mayaPview.obj', opts=OPTS, input='mayaPview.cxx')
     TargetAdd('mayapview'+VNUM+'_mayaPview.obj', opts=OPTS, input='mayaPview.cxx')
     TargetAdd('libmayapview'+VNUM+'.mll', input='mayapview'+VNUM+'_mayaPview.obj')
     TargetAdd('libmayapview'+VNUM+'.mll', input='mayapview'+VNUM+'_mayaPview.obj')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmaya'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libmaya'+VNUM+'.lib')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libp3framework.dll')
     TargetAdd('libmayapview'+VNUM+'.mll', input='libp3framework.dll')
-    if GetTarget() == 'windows':
-      TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
-    TargetAdd('libmayapview'+VNUM+'.mll', opts=['ADVAPI', VER])
-
-    TargetAdd('maya2egg'+VNUM+'_mayaToEgg.obj', opts=OPTS, input='mayaToEgg.cxx')
-    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='maya2egg'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('libmayapview'+VNUM+'.mll', input=COMMON_EGG2X_LIBS)
+    TargetAdd('libmayapview'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
+
+    TargetAdd('mayaprogs'+VNUM+'_eggToMaya.obj', opts=OPTS, input='eggToMaya.cxx')
+    TargetAdd('mayaprogs'+VNUM+'_mayaToEgg.obj', opts=OPTS, input='mayaToEgg.cxx')
+    TargetAdd('mayaprogs_mayaConversionServer.obj', opts=OPTS, input='mayaConversionServer.cxx')
+
+    TargetAdd('maya2egg'+VNUM+'_mayaToEggBin.obj', opts=OPTS, input='mayaToEggBin.cxx')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_eggToMaya.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='mayaprogs_mayaConversionServer.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input='maya2egg'+VNUM+'_mayaToEggBin.obj')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
     TargetAdd('maya2egg'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
-    if GetTarget() == 'windows':
-      TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    TargetAdd('maya2egg'+VNUM+'_bin.exe', opts=['ADVAPI', VER])
-
-    TargetAdd('egg2maya'+VNUM+'_eggToMaya.obj', opts=OPTS, input='eggToMaya.cxx')
-    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='egg2maya'+VNUM+'_eggToMaya.obj')
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('maya2egg'+VNUM+'_bin.exe', opts=['ADVAPI', VER]+ARCH_OPTS)
+
+    TargetAdd('egg2maya'+VNUM+'_eggToMayaBin.obj', opts=OPTS, input='eggToMayaBin.cxx')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_eggToMaya.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs'+VNUM+'_mayaToEgg.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='mayaprogs_mayaConversionServer.obj')
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input='egg2maya'+VNUM+'_eggToMayaBin.obj')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmayaegg'+VNUM+'.lib')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
     TargetAdd('egg2maya'+VNUM+'_bin.exe', input='libmaya'+VNUM+'.lib')
-    if GetTarget() == 'windows':
-      TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    else:
-      TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
-    TargetAdd('egg2maya'+VNUM+'_bin.exe', opts=['ADVAPI', VER])
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', input=COMMON_EGG2X_LIBS)
+    TargetAdd('egg2maya'+VNUM+'_bin.exe', opts=['ADVAPI', VER]+ARCH_OPTS)
 
 
     TargetAdd('mayasavepview'+VNUM+'_mayaSavePview.obj', opts=OPTS, input='mayaSavePview.cxx')
     TargetAdd('mayasavepview'+VNUM+'_mayaSavePview.obj', opts=OPTS, input='mayaSavePview.cxx')
     TargetAdd('libmayasavepview'+VNUM+'.mll', input='mayasavepview'+VNUM+'_mayaSavePview.obj')
     TargetAdd('libmayasavepview'+VNUM+'.mll', input='mayasavepview'+VNUM+'_mayaSavePview.obj')
-    TargetAdd('libmayasavepview'+VNUM+'.mll', opts=['ADVAPI', VER])
+    TargetAdd('libmayasavepview'+VNUM+'.mll', opts=['ADVAPI', VER]+ARCH_OPTS)
 
 
     TargetAdd('mayapath'+VNUM+'.obj', opts=OPTS, input='mayapath.cxx')
     TargetAdd('mayapath'+VNUM+'.obj', opts=OPTS, input='mayapath.cxx')
 
 
     TargetAdd('maya2egg'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('maya2egg'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('maya2egg'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('maya2egg'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('maya2egg'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
     TargetAdd('maya2egg'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
-    TargetAdd('maya2egg'+VNUM+'.exe', opts=['ADVAPI'])
+    TargetAdd('maya2egg'+VNUM+'.exe', opts=['ADVAPI']+ARCH_OPTS)
 
 
     TargetAdd('egg2maya'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('egg2maya'+VNUM+'.exe', input='mayapath'+VNUM+'.obj')
     TargetAdd('egg2maya'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('egg2maya'+VNUM+'.exe', input='libpandaexpress.dll')
     TargetAdd('egg2maya'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
     TargetAdd('egg2maya'+VNUM+'.exe', input=COMMON_DTOOL_LIBS)
-    TargetAdd('egg2maya'+VNUM+'.exe', opts=['ADVAPI'])
+    TargetAdd('egg2maya'+VNUM+'.exe', opts=['ADVAPI']+ARCH_OPTS)
+
+if MAYA_BUILT:
+    TargetAdd('mayaprogs_mayaConversionClient.obj', opts=OPTS, input='mayaConversionClient.cxx')
+
+    TargetAdd('maya2egg_mayaToEggClient.obj', opts=OPTS, input='mayaToEggClient.cxx')
+    TargetAdd('maya2egg_client.exe', input='mayaprogs_mayaConversionClient.obj')
+    TargetAdd('maya2egg_client.exe', input='maya2egg_mayaToEggClient.obj')
+    TargetAdd('maya2egg_client.exe', input=COMMON_EGG2X_LIBS)
+
+    TargetAdd('egg2maya_eggToMayaClient.obj', opts=OPTS, input='eggToMayaClient.cxx')
+    TargetAdd('egg2maya_client.exe', input='mayaprogs_mayaConversionClient.obj')
+    TargetAdd('egg2maya_client.exe', input='egg2maya_eggToMayaClient.obj')
+    TargetAdd('egg2maya_client.exe', input=COMMON_EGG2X_LIBS)
 
 
 #
 #
 # DIRECTORY: contrib/src/ai/
 # DIRECTORY: contrib/src/ai/

+ 34 - 24
makepanda/makepandacore.py

@@ -389,6 +389,9 @@ def SetTarget(target, arch=None):
             else:
             else:
                 arch = 'armv7a'
                 arch = 'armv7a'
 
 
+        if arch == 'arm64':
+            arch = 'aarch64'
+
         # Did we specify an API level?
         # Did we specify an API level?
         global ANDROID_API
         global ANDROID_API
         target, _, api = target.partition('-')
         target, _, api = target.partition('-')
@@ -1251,10 +1254,7 @@ def MakeBuildTree():
     MakeDirectory(OUTPUTDIR + "/pandac/input")
     MakeDirectory(OUTPUTDIR + "/pandac/input")
     MakeDirectory(OUTPUTDIR + "/panda3d")
     MakeDirectory(OUTPUTDIR + "/panda3d")
 
 
-    if GetTarget() == 'darwin':
-        MakeDirectory(OUTPUTDIR + "/Frameworks")
-
-    elif GetTarget() == 'android':
+    if GetTarget() == 'android':
         MakeDirectory(OUTPUTDIR + "/classes")
         MakeDirectory(OUTPUTDIR + "/classes")
 
 
 ########################################################################
 ########################################################################
@@ -2362,30 +2362,40 @@ def SdkLocateWindows(version=None):
     else:
     else:
         print("Using Windows SDK %s" % (version))
         print("Using Windows SDK %s" % (version))
 
 
-def SdkLocateMacOSX(osxtarget = None):
+def SdkLocateMacOSX(archs = []):
     if (GetHost() != "darwin"): return
     if (GetHost() != "darwin"): return
-    if (osxtarget != None):
-        sdkname = "MacOSX%d.%d" % osxtarget
-        if (os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname)):
+
+    handle = os.popen("xcode-select -print-path")
+    xcode_dir = handle.read().strip().rstrip('/')
+    handle.close()
+
+    # Make a list of SDK versions that will work for us, then grab the latest.
+    sdk_versions = []
+    if 'arm64' not in archs:
+        # Prefer pre-10.14 for now so that we can keep building FMOD.
+        sdk_versions += ["10.13", "10.12", "10.11", "10.10", "10.9"]
+
+    sdk_versions += ["11.1", "11.0"]
+
+    if 'arm64' not in archs:
+        sdk_versions += ["10.15", "10.14"]
+
+    for version in sdk_versions:
+        sdkname = "MacOSX" + version
+        if os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname
             SDK["MACOSX"] = "/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%su.sdk" % sdkname)):
-            SDK["MACOSX"] = "/Developer/SDKs/%su.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%s.sdk" % sdkname)):
+            return
+        elif os.path.exists("/Developer/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Developer/SDKs/%s.sdk" % sdkname
             SDK["MACOSX"] = "/Developer/SDKs/%s.sdk" % sdkname
-        elif (os.path.exists("/Developer/SDKs/%s.0.sdk" % sdkname)):
-            SDK["MACOSX"] = "/Developer/SDKs/%s.0.sdk" % sdkname
-        elif (os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname)):
+            return
+        elif os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname):
             SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname
             SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname
-        else:
-            handle = os.popen("xcode-select -print-path")
-            result = handle.read().strip().rstrip('/')
-            handle.close()
-            if (os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (result, sdkname))):
-                SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (result, sdkname)
-            else:
-                exit("Couldn't find any MacOSX SDK for OSX version %s!" % sdkname)
-    else:
-        SDK["MACOSX"] = ""
+            return
+        elif xcode_dir and os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)):
+            SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)
+            return
+
+    exit("Couldn't find any suitable MacOSX SDK!")
 
 
 def SdkLocateSpeedTree():
 def SdkLocateSpeedTree():
     # Look for all of the SpeedTree SDK directories within the
     # Look for all of the SpeedTree SDK directories within the

+ 1 - 1
makepanda/makewheel.py

@@ -458,7 +458,7 @@ class WheelFile(object):
                         self.consider_add_dependency(target_dep, dep)
                         self.consider_add_dependency(target_dep, dep)
 
 
                 subprocess.call(["strip", "-s", temp.name])
                 subprocess.call(["strip", "-s", temp.name])
-                subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name])
+                subprocess.call(["patchelf", "--force-rpath", "--set-rpath", "$ORIGIN", temp.name])
 
 
             source_path = temp.name
             source_path = temp.name
 
 

+ 8 - 0
panda/src/audio/audioSound.h

@@ -123,6 +123,14 @@ PUBLISHED:
   virtual void output(std::ostream &out) const;
   virtual void output(std::ostream &out) const;
   virtual void write(std::ostream &out) const;
   virtual void write(std::ostream &out) const;
 
 
+PUBLISHED:
+  MAKE_PROPERTY(time, get_time, set_time);
+  MAKE_PROPERTY(volume, get_volume, set_volume);
+  MAKE_PROPERTY(balance, get_balance, set_balance);
+  MAKE_PROPERTY(play_rate, get_play_rate, set_play_rate);
+  MAKE_PROPERTY(active, get_active, set_active);
+  MAKE_PROPERTY(name, get_name);
+
 protected:
 protected:
   AudioSound();
   AudioSound();
 
 

+ 12 - 1
panda/src/audiotraits/CMakeLists.txt

@@ -5,7 +5,7 @@ elseif(NOT HAVE_FMODEX AND NOT HAVE_OPENAL)
     "You must have an audio backend for audio support! Turn off HAVE_AUDIO to ignore this.")
     "You must have an audio backend for audio support! Turn off HAVE_AUDIO to ignore this.")
 endif()
 endif()
 
 
-if(HAVE_FMODEX)
+if(HAVE_FMODEX AND NOT (APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64"))
   set(P3FMOD_HEADERS
   set(P3FMOD_HEADERS
     config_fmodAudio.h
     config_fmodAudio.h
     fmodAudioManager.h
     fmodAudioManager.h
@@ -21,6 +21,17 @@ if(HAVE_FMODEX)
   set_target_properties(p3fmod_audio PROPERTIES DEFINE_SYMBOL BUILDING_FMOD_AUDIO)
   set_target_properties(p3fmod_audio PROPERTIES DEFINE_SYMBOL BUILDING_FMOD_AUDIO)
   target_link_libraries(p3fmod_audio panda PKG::FMODEX)
   target_link_libraries(p3fmod_audio panda PKG::FMODEX)
 
 
+  if(APPLE)
+    # The FMOD EX thirdparty library is not available for arm64, so we have to
+    # exclude it from the target architectures.
+    get_target_property(_archs p3fmod_audio OSX_ARCHITECTURES)
+
+    if(_archs AND _archs MATCHES "arm64")
+      list(REMOVE_ITEM _archs "arm64")
+      set_target_properties(p3fmod_audio PROPERTIES OSX_ARCHITECTURES "${_archs}")
+    endif()
+  endif()
+
   install(TARGETS p3fmod_audio
   install(TARGETS p3fmod_audio
     EXPORT FMOD COMPONENT FMOD
     EXPORT FMOD COMPONENT FMOD
     DESTINATION ${CMAKE_INSTALL_LIBDIR}
     DESTINATION ${CMAKE_INSTALL_LIBDIR}

+ 4 - 2
panda/src/audiotraits/fmodAudioSound.cxx

@@ -259,6 +259,7 @@ stop() {
     }
     }
   }
   }
   _start_time = 0.0;
   _start_time = 0.0;
+  _paused = false;
 }
 }
 
 
 
 
@@ -827,12 +828,13 @@ set_active(bool active) {
     } else {
     } else {
       // ...deactivate the sound.
       // ...deactivate the sound.
       if (status() == PLAYING) {
       if (status() == PLAYING) {
+        PN_stdfloat time = get_time();
+        stop();
         if (get_loop_count() == 0) {
         if (get_loop_count() == 0) {
           // ...we're pausing a looping sound.
           // ...we're pausing a looping sound.
           _paused = true;
           _paused = true;
-          _start_time = get_time();
+          _start_time = time;
         }
         }
-        stop();
       }
       }
     }
     }
   }
   }

+ 6 - 4
panda/src/audiotraits/openalAudioSound.cxx

@@ -217,6 +217,8 @@ stop() {
     _stream_queued.resize(0);
     _stream_queued.resize(0);
   }
   }
 
 
+  _paused = false;
+
   _manager->stopping_sound(this);
   _manager->stopping_sound(this);
   release_sound_data(false);
   release_sound_data(false);
 }
 }
@@ -828,13 +830,13 @@ set_active(bool active) {
     } else {
     } else {
       // ...deactivate the sound.
       // ...deactivate the sound.
       if (status()==PLAYING) {
       if (status()==PLAYING) {
-        if (_loop_count==0) {
-          // ...we're pausing a looping sound.
-          _paused=true;
-        }
         // Store off the current time so we can resume from where we paused.
         // Store off the current time so we can resume from where we paused.
         _start_time = get_time();
         _start_time = get_time();
         stop();
         stop();
+        if (_loop_count == 0) {
+          // ...we're pausing a looping sound.
+          _paused = true;
+        }
       }
       }
     }
     }
   }
   }

+ 3 - 0
panda/src/cocoadisplay/CMakeLists.txt

@@ -42,6 +42,9 @@ target_link_libraries(p3cocoadisplay
   ${APPLICATIONSERVICES_LIBRARY} ${APPKIT_LIBRARY} ${CARBON_LIBRARY}
   ${APPLICATIONSERVICES_LIBRARY} ${APPKIT_LIBRARY} ${CARBON_LIBRARY}
   ${CORE_VIDEO_LIBRARY})
   ${CORE_VIDEO_LIBRARY})
 
 
+mark_as_advanced(
+  APPLICATIONSERVICES_LIBRARY APPKIT_LIBRARY CARBON_LIBRARY CORE_VIDEO_LIBRARY)
+
 if(NOT BUILD_METALIBS)
 if(NOT BUILD_METALIBS)
   install(TARGETS p3cocoadisplay EXPORT OpenGL COMPONENT OpenGL DESTINATION ${CMAKE_INSTALL_LIBDIR})
   install(TARGETS p3cocoadisplay EXPORT OpenGL COMPONENT OpenGL DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()
 endif()

+ 28 - 12
panda/src/collide/collisionPolygon_ext.cxx

@@ -29,7 +29,11 @@ extern struct Dtool_PyTypedObject Dtool_LPoint3f;
  */
  */
 bool Extension<CollisionPolygon>::
 bool Extension<CollisionPolygon>::
 verify_points(PyObject *points) {
 verify_points(PyObject *points) {
-  const pvector<LPoint3> vec = convert_points(points);
+  pvector<LPoint3> vec;
+  if (!convert_points(vec, points)) {
+    return false;
+  }
+
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_end = verts_begin + vec.size();
   const LPoint3 *verts_end = verts_begin + vec.size();
 
 
@@ -42,7 +46,16 @@ verify_points(PyObject *points) {
  */
  */
 void Extension<CollisionPolygon>::
 void Extension<CollisionPolygon>::
 setup_points(PyObject *points) {
 setup_points(PyObject *points) {
-  const pvector<LPoint3> vec = convert_points(points);
+  pvector<LPoint3> vec;
+  if (!convert_points(vec, points)) {
+    return;
+  }
+
+  if (vec.size() < 3) {
+    PyErr_SetString(PyExc_ValueError, "expected at least 3 points");
+    return;
+  }
+
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_begin = &vec[0];
   const LPoint3 *verts_end = verts_begin + vec.size();
   const LPoint3 *verts_end = verts_begin + vec.size();
 
 
@@ -52,13 +65,11 @@ setup_points(PyObject *points) {
 /**
 /**
  * Converts a Python sequence to a list of LPoint3 objects.
  * Converts a Python sequence to a list of LPoint3 objects.
  */
  */
-pvector<LPoint3> Extension<CollisionPolygon>::
-convert_points(PyObject *points) {
-  pvector<LPoint3> vec;
+bool Extension<CollisionPolygon>::
+convert_points(pvector<LPoint3> &vec, PyObject *points) {
   PyObject *seq = PySequence_Fast(points, "function expects a sequence");
   PyObject *seq = PySequence_Fast(points, "function expects a sequence");
-
   if (!seq) {
   if (!seq) {
-    return vec;
+    return false;
   }
   }
 
 
   PyObject **items = PySequence_Fast_ITEMS(seq);
   PyObject **items = PySequence_Fast_ITEMS(seq);
@@ -69,18 +80,23 @@ convert_points(PyObject *points) {
 
 
   for (Py_ssize_t i = 0; i < len; ++i) {
   for (Py_ssize_t i = 0; i < len; ++i) {
 #ifdef STDFLOAT_DOUBLE
 #ifdef STDFLOAT_DOUBLE
-    if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3d)) {
+    if (DtoolInstance_Check(items[i]) &&
+        (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3d))) {
 #else
 #else
-    if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3f)) {
+    if (DtoolInstance_Check(items[i]) &&
+        (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3f))) {
 #endif
 #endif
       vec.push_back(*(LPoint3 *)ptr);
       vec.push_back(*(LPoint3 *)ptr);
-    } else {
-      collide_cat.warning() << "Argument must be of LPoint3 type.\n";
+    }
+    else {
+      Dtool_Raise_TypeError("Argument must be of LPoint3 type.");
+      Py_DECREF(seq);
+      return false;
     }
     }
   }
   }
 
 
   Py_DECREF(seq);
   Py_DECREF(seq);
-  return vec;
+  return true;
 }
 }
 
 
 #endif
 #endif

+ 1 - 1
panda/src/collide/collisionPolygon_ext.h

@@ -35,7 +35,7 @@ public:
   void setup_points(PyObject *points);
   void setup_points(PyObject *points);
 
 
 private:
 private:
-  static pvector<LPoint3> convert_points(PyObject *points);
+  static bool convert_points(pvector<LPoint3> &vec, PyObject *points);
 };
 };
 
 
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 22 - 12
panda/src/device/evdevInputDevice.cxx

@@ -806,23 +806,33 @@ process_events() {
         button_changed(_dpad_up_button, events[i].value < 0);
         button_changed(_dpad_up_button, events[i].value < 0);
         button_changed(_dpad_up_button+1, events[i].value > 0);
         button_changed(_dpad_up_button+1, events[i].value > 0);
       }
       }
-      nassertd(code >= 0 && (size_t)code < _axis_indices.size()) break;
-      index = _axis_indices[code];
-      if (index >= 0) {
-        axis_changed(index, events[i].value);
+      if (code >= 0 && (size_t)code < _axis_indices.size()) {
+        index = _axis_indices[code];
+        if (index >= 0) {
+          axis_changed(index, events[i].value);
+        }
+      }
+      else if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Ignoring EV_ABS event with unknown code " << code << "\n";
       }
       }
       break;
       break;
 
 
     case EV_KEY:
     case EV_KEY:
-      nassertd(code >= 0 && (size_t)code < _button_indices.size()) break;
-      index = _button_indices[code];
-      if (index >= 0) {
-        button_changed(index, events[i].value != 0);
+      if (code >= 0 && (size_t)code < _button_indices.size()) {
+        index = _button_indices[code];
+        if (index >= 0) {
+          button_changed(index, events[i].value != 0);
+        }
+        if (code == _ltrigger_code) {
+          axis_changed(_ltrigger_axis, events[i].value);
+        } else if (code == _rtrigger_code) {
+          axis_changed(_ltrigger_axis + 1, events[i].value);
+        }
       }
       }
-      if (code == _ltrigger_code) {
-        axis_changed(_ltrigger_axis, events[i].value);
-      } else if (code == _rtrigger_code) {
-        axis_changed(_ltrigger_axis + 1, events[i].value);
+      else if (device_cat.is_debug()) {
+        device_cat.debug()
+          << "Ignoring EV_KEY event with unknown code " << code << "\n";
       }
       }
       break;
       break;
 
 

+ 2 - 0
panda/src/device/inputDeviceManager.cxx

@@ -111,6 +111,8 @@ add_device(InputDevice *device) {
  */
  */
 void InputDeviceManager::
 void InputDeviceManager::
 remove_device(InputDevice *device) {
 remove_device(InputDevice *device) {
+  // We need to hold a reference, since remove_device decrements the refcount.
+  PT(InputDevice) device_ref = device;
   {
   {
     LightMutexHolder holder(_lock);
     LightMutexHolder holder(_lock);
     _connected_devices.remove_device(device);
     _connected_devices.remove_device(device);

+ 8 - 0
panda/src/display/config_display.cxx

@@ -485,6 +485,14 @@ ConfigVariableBool sync_video
           "cheesy estimate of scene complexity.  Some drivers may ignore "
           "cheesy estimate of scene complexity.  Some drivers may ignore "
           "this request."));
           "this request."));
 
 
+ConfigVariableDouble display_zoom
+("display-zoom", 0.0,
+ PRC_DESC("If this is set to a value other than 0.0, it overrides the detected "
+          "system DPI scaling.  GraphicsPipe::get_display_zoom() will instead "
+          "return whatever was passed in here.  You should generally only "
+          "change this based on a user preference change or to test how the UI "
+          "will look on monitors with different pixel densities."));
+
 /**
 /**
  * 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
  * the functions or classes in this library can be used.  Normally it will be
  * the functions or classes in this library can be used.  Normally it will be

+ 1 - 0
panda/src/display/config_display.h

@@ -109,6 +109,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableDouble pixel_zoom;
 
 
 extern EXPCL_PANDA_DISPLAY ConfigVariableColor background_color;
 extern EXPCL_PANDA_DISPLAY ConfigVariableColor background_color;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool sync_video;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool sync_video;
+extern EXPCL_PANDA_DISPLAY ConfigVariableDouble display_zoom;
 
 
 extern EXPCL_PANDA_DISPLAY void init_libdisplay();
 extern EXPCL_PANDA_DISPLAY void init_libdisplay();
 
 

+ 8 - 0
panda/src/display/graphicsPipe.I

@@ -81,3 +81,11 @@ INLINE GraphicsDevice *GraphicsPipe::
 get_device() const {
 get_device() const {
   return _device;
   return _device;
 }
 }
+
+/**
+ * Called by derived class to set the display zoom factor.
+ */
+INLINE void GraphicsPipe::
+set_detected_display_zoom(PN_stdfloat zoom) {
+  _detected_display_zoom = zoom;
+}

+ 22 - 0
panda/src/display/graphicsPipe.cxx

@@ -116,6 +116,7 @@ GraphicsPipe() :
 
 
   _display_width = 0;
   _display_width = 0;
   _display_height = 0;
   _display_height = 0;
+  _detected_display_zoom = 1.0;
 
 
   _display_information = new DisplayInformation();
   _display_information = new DisplayInformation();
 
 
@@ -266,6 +267,27 @@ make_output(const std::string &name,
   return nullptr;
   return nullptr;
 }
 }
 
 
+/**
+ * Returns the display zoom factor configured in the operating system.  If the
+ * operating system automatically scales windows to match the DPI (such as when
+ * dpi-aware is set to false), this will be 1.0.  Otherwise, this will be set to
+ * a value approximating the density of the monitor divided by the standard
+ * density of the operating system (usually 96), yielding a value like 1.5 or
+ * 2.0.
+ *
+ * @since 1.10.8
+ */
+PN_stdfloat GraphicsPipe::
+get_display_zoom() const {
+  if (display_zoom.get_num_words() > 0) {
+    double override = display_zoom.get_value();
+    if (override != 0.0) {
+      return override;
+    }
+  }
+  return _detected_display_zoom;
+}
+
 /**
 /**
  * Gets the pipe's DisplayInformation.
  * Gets the pipe's DisplayInformation.
  */
  */

+ 5 - 0
panda/src/display/graphicsPipe.h

@@ -91,8 +91,10 @@ PUBLISHED:
 
 
   INLINE int get_display_width() const;
   INLINE int get_display_width() const;
   INLINE int get_display_height() const;
   INLINE int get_display_height() const;
+  PN_stdfloat get_display_zoom() const;
   MAKE_PROPERTY(display_width, get_display_width);
   MAKE_PROPERTY(display_width, get_display_width);
   MAKE_PROPERTY(display_height, get_display_height);
   MAKE_PROPERTY(display_height, get_display_height);
+  MAKE_PROPERTY(display_zoom, get_display_zoom);
 
 
   DisplayInformation *get_display_information();
   DisplayInformation *get_display_information();
   MAKE_PROPERTY(display_information, get_display_information);
   MAKE_PROPERTY(display_information, get_display_information);
@@ -115,6 +117,8 @@ public:
   virtual PT(GraphicsStateGuardian) make_callback_gsg(GraphicsEngine *engine);
   virtual PT(GraphicsStateGuardian) make_callback_gsg(GraphicsEngine *engine);
 
 
 protected:
 protected:
+  INLINE void set_detected_display_zoom(PN_stdfloat zoom);
+
   virtual void close_gsg(GraphicsStateGuardian *gsg);
   virtual void close_gsg(GraphicsStateGuardian *gsg);
 
 
   virtual PT(GraphicsOutput) make_output(const std::string &name,
   virtual PT(GraphicsOutput) make_output(const std::string &name,
@@ -133,6 +137,7 @@ protected:
   int _supported_types;
   int _supported_types;
   int _display_width;
   int _display_width;
   int _display_height;
   int _display_height;
+  PN_stdfloat _detected_display_zoom;
   PT(GraphicsDevice) _device;
   PT(GraphicsDevice) _device;
 
 
   DisplayInformation *_display_information;
   DisplayInformation *_display_information;

+ 197 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -1911,6 +1911,10 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
 PT(Texture) GraphicsStateGuardian::
 PT(Texture) GraphicsStateGuardian::
 fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
 fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
                         int &view) {
                         int &view) {
+
+  static PT(Texture) default_add_tex;
+  static PT(Texture) default_normal_height_tex;
+
   switch (spec._part) {
   switch (spec._part) {
   case Shader::STO_named_input:
   case Shader::STO_named_input:
     // Named texture input.
     // Named texture input.
@@ -2008,6 +2012,199 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler,
     }
     }
     break;
     break;
 
 
+  case Shader::STO_ff_stage_i:
+    {
+      // We get the TextureAttrib directly from the _target_rs, not the
+      // filtered TextureAttrib in _target_texture.
+      const TextureAttrib *texattrib;
+      _target_rs->get_attrib_def(texattrib);
+
+      if (spec._stage < texattrib->get_num_on_ff_stages()) {
+        TextureStage *stage = texattrib->get_on_ff_stage(spec._stage);
+        sampler = texattrib->get_on_sampler(stage);
+        view += stage->get_tex_view_offset();
+        return texattrib->get_on_texture(stage);
+      }
+    }
+    break;
+
+  case Shader::STO_stage_modulate_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_modulate ||
+              mode == TextureStage::M_modulate_glow ||
+              mode == TextureStage::M_modulate_gloss) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_add_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_add) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_add_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-add");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_luminance);
+        tex->set_clear_color(LColor(0, 0, 0, 1));
+        default_add_tex = std::move(tex);
+      }
+      return default_add_tex;
+    }
+    break;
+
+  case Shader::STO_stage_normal_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_normal ||
+              mode == TextureStage::M_normal_height) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_normal_height_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-normal-height");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+        tex->set_clear_color(LColor(0.5, 0.5, 1, 0));
+        default_normal_height_tex = std::move(tex);
+      }
+      return default_normal_height_tex;
+    }
+    break;
+
+  case Shader::STO_stage_gloss_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_gloss ||
+              mode == TextureStage::M_modulate_gloss ||
+              mode == TextureStage::M_normal_gloss) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_height_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_height ||
+              mode == TextureStage::M_normal_height) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+
+      if (default_normal_height_tex == nullptr) {
+        PT(Texture) tex = new Texture("default-normal-height");
+        tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+        tex->set_clear_color(LColor(0.5, 0.5, 1, 0));
+        default_normal_height_tex = std::move(tex);
+      }
+      return default_normal_height_tex;
+    }
+    break;
+
+  case Shader::STO_stage_selector_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_selector) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
+  case Shader::STO_stage_emission_i:
+    {
+      const TextureAttrib *texattrib;
+      if (_target_rs->get_attrib(texattrib)) {
+        size_t si = 0;
+        for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+          TextureStage *stage = texattrib->get_on_stage(i);
+          TextureStage::Mode mode = stage->get_mode();
+
+          if (mode == TextureStage::M_emission) {
+            if (si++ == spec._stage) {
+              sampler = texattrib->get_on_sampler(stage);
+              view += stage->get_tex_view_offset();
+              return texattrib->get_on_texture(stage);
+            }
+          }
+        }
+      }
+    }
+    break;
+
   default:
   default:
     nassertr(false, nullptr);
     nassertr(false, nullptr);
     break;
     break;

+ 7 - 0
panda/src/egg/eggTexture.cxx

@@ -558,6 +558,7 @@ affects_polygon_alpha() const {
   case ET_gloss:
   case ET_gloss:
   case ET_height:
   case ET_height:
   case ET_normal_gloss:
   case ET_normal_gloss:
+  case ET_emission:
     return false;
     return false;
 
 
   case ET_selector:
   case ET_selector:
@@ -882,6 +883,9 @@ string_env_type(const string &string) {
   } else if (cmp_nocase_uh(string, "normal_gloss") == 0) {
   } else if (cmp_nocase_uh(string, "normal_gloss") == 0) {
     return ET_normal_gloss;
     return ET_normal_gloss;
 
 
+  } else if (cmp_nocase_uh(string, "emission") == 0) {
+    return ET_emission;
+
   } else {
   } else {
     return ET_unspecified;
     return ET_unspecified;
   }
   }
@@ -1312,6 +1316,9 @@ ostream &operator << (ostream &out, EggTexture::EnvType type) {
 
 
   case EggTexture::ET_normal_gloss:
   case EggTexture::ET_normal_gloss:
     return out << "normal_gloss";
     return out << "normal_gloss";
+
+  case EggTexture::ET_emission:
+    return out << "emission";
   }
   }
 
 
   nassertr(false, out);
   nassertr(false, out);

+ 1 - 0
panda/src/egg/eggTexture.h

@@ -107,6 +107,7 @@ PUBLISHED:
     ET_height,
     ET_height,
     ET_selector,
     ET_selector,
     ET_normal_gloss,
     ET_normal_gloss,
+    ET_emission,
   };
   };
   enum CombineMode {
   enum CombineMode {
     CM_unspecified,
     CM_unspecified,

+ 4 - 0
panda/src/egg2pg/eggLoader.cxx

@@ -1540,6 +1540,10 @@ make_texture_stage(const EggTexture *egg_tex) {
     stage->set_mode(TextureStage::M_normal_gloss);
     stage->set_mode(TextureStage::M_normal_gloss);
     break;
     break;
 
 
+  case EggTexture::ET_emission:
+    stage->set_mode(TextureStage::M_emission);
+    break;
+
   case EggTexture::ET_unspecified:
   case EggTexture::ET_unspecified:
     break;
     break;
   }
   }

+ 3 - 0
panda/src/egg2pg/eggSaver.cxx

@@ -855,6 +855,9 @@ convert_primitive(const GeomVertexData *vertex_data,
         case TextureStage::M_normal_gloss:
         case TextureStage::M_normal_gloss:
           egg_tex->set_env_type(EggTexture::ET_normal_gloss);
           egg_tex->set_env_type(EggTexture::ET_normal_gloss);
           break;
           break;
+        case TextureStage::M_emission:
+          egg_tex->set_env_type(EggTexture::ET_emission);
+          break;
         default:
         default:
           break;
           break;
         }
         }

+ 6 - 6
panda/src/event/pythonTask.cxx

@@ -266,11 +266,11 @@ exception() const {
     Py_INCREF(Py_None);
     Py_INCREF(Py_None);
     return Py_None;
     return Py_None;
   } else if (_exc_value == nullptr || _exc_value == Py_None) {
   } else if (_exc_value == nullptr || _exc_value == Py_None) {
-    return _PyObject_CallNoArg(_exception);
+    return PyObject_CallNoArgs(_exception);
   } else if (PyTuple_Check(_exc_value)) {
   } else if (PyTuple_Check(_exc_value)) {
     return PyObject_Call(_exception, _exc_value, nullptr);
     return PyObject_Call(_exception, _exc_value, nullptr);
   } else {
   } else {
-    return PyObject_CallFunctionObjArgs(_exception, _exc_value, nullptr);
+    return PyObject_CallOneArg(_exception, _exc_value);
   }
   }
 }*/
 }*/
 
 
@@ -481,7 +481,7 @@ do_python_task() {
 
 
   // Are we waiting for a future to finish?
   // Are we waiting for a future to finish?
   if (_future_done != nullptr) {
   if (_future_done != nullptr) {
-    PyObject *is_done = PyObject_CallObject(_future_done, nullptr);
+    PyObject *is_done = PyObject_CallNoArgs(_future_done);
     if (!PyObject_IsTrue(is_done)) {
     if (!PyObject_IsTrue(is_done)) {
       // Nope, ask again next frame.
       // Nope, ask again next frame.
       Py_DECREF(is_done);
       Py_DECREF(is_done);
@@ -543,12 +543,12 @@ do_python_task() {
       // we need to be able to read the value from a StopIteration exception.
       // we need to be able to read the value from a StopIteration exception.
       PyObject *func = PyObject_GetAttrString(_generator, "send");
       PyObject *func = PyObject_GetAttrString(_generator, "send");
       nassertr(func != nullptr, DS_interrupt);
       nassertr(func != nullptr, DS_interrupt);
-      result = PyObject_CallFunctionObjArgs(func, Py_None, nullptr);
+      result = PyObject_CallOneArg(func, Py_None);
       Py_DECREF(func);
       Py_DECREF(func);
     } else {
     } else {
       // Throw a CancelledError into the generator.
       // Throw a CancelledError into the generator.
       _must_cancel = false;
       _must_cancel = false;
-      PyObject *exc = _PyObject_CallNoArg(Extension<AsyncFuture>::get_cancelled_error_type());
+      PyObject *exc = PyObject_CallNoArgs(Extension<AsyncFuture>::get_cancelled_error_type());
       PyObject *func = PyObject_GetAttrString(_generator, "throw");
       PyObject *func = PyObject_GetAttrString(_generator, "throw");
       result = PyObject_CallFunctionObjArgs(func, exc, nullptr);
       result = PyObject_CallFunctionObjArgs(func, exc, nullptr);
       Py_DECREF(func);
       Py_DECREF(func);
@@ -892,7 +892,7 @@ call_function(PyObject *function) {
   if (function != Py_None) {
   if (function != Py_None) {
     this->ref();
     this->ref();
     PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
     PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
-    PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
+    PyObject *result = PyObject_CallOneArg(function, self);
     Py_XDECREF(result);
     Py_XDECREF(result);
     Py_DECREF(self);
     Py_DECREF(self);
   }
   }

+ 3 - 0
panda/src/express/zipArchive.h

@@ -26,6 +26,9 @@
 #include "pvector.h"
 #include "pvector.h"
 #include "vector_uchar.h"
 #include "vector_uchar.h"
 
 
+// Defined by Cocoa, conflicts with the definition below.
+#undef verify
+
 /**
 /**
  * A file that contains a set of files.
  * A file that contains a set of files.
  */
  */

+ 1 - 1
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -224,7 +224,7 @@ cleanup() {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
     avcodec_free_context(&_audio_ctx);
     avcodec_free_context(&_audio_ctx);
 #else
 #else
-    delete _audio_ctx;
+    av_free(_audio_ctx);
 #endif
 #endif
   }
   }
   _audio_ctx = nullptr;
   _audio_ctx = nullptr;

+ 1 - 1
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -616,7 +616,7 @@ close_stream() {
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
     avcodec_free_context(&_video_ctx);
     avcodec_free_context(&_video_ctx);
 #else
 #else
-    delete _video_ctx;
+    av_free(_video_ctx);
 #endif
 #endif
   }
   }
   _video_ctx = nullptr;
   _video_ctx = nullptr;

+ 50 - 12
panda/src/glstuff/glShaderContext_src.cxx

@@ -963,27 +963,65 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
       _shader->cp_add_mat_spec(bind);
       _shader->cp_add_mat_spec(bind);
       return;
       return;
     }
     }
-    if (size > 7 && noprefix.substr(0, 7) == "Texture") {
+    if (noprefix.compare(0, 7, "Texture") == 0) {
       Shader::ShaderTexSpec bind;
       Shader::ShaderTexSpec bind;
       bind._id = arg_id;
       bind._id = arg_id;
-      bind._part = Shader::STO_stage_i;
-      bind._name = 0;
 
 
-      string tail;
-      bind._stage = string_to_int(noprefix.substr(7), tail);
-      if (!tail.empty()) {
+      if (!get_sampler_texture_type(bind._desired_type, param_type)) {
         GLCAT.error()
         GLCAT.error()
-          << "Error parsing shader input name: unexpected '"
-          << tail << "' in '" << param_name << "'\n";
+          << "Could not bind texture input " << param_name << "\n";
         return;
         return;
       }
       }
 
 
-      if (get_sampler_texture_type(bind._desired_type, param_type)) {
+      if (size > 7 && isdigit(noprefix[7])) {
+        // p3d_Texture0, p3d_Texture1, etc.
+        bind._part = Shader::STO_stage_i;
+
+        string tail;
+        bind._stage = string_to_int(noprefix.substr(7), tail);
+        if (!tail.empty()) {
+          GLCAT.error()
+            << "Error parsing shader input name: unexpected '"
+            << tail << "' in '" << param_name << "'\n";
+          return;
+        }
         _glgsg->_glUniform1i(p, _shader->_tex_spec.size());
         _glgsg->_glUniform1i(p, _shader->_tex_spec.size());
         _shader->_tex_spec.push_back(bind);
         _shader->_tex_spec.push_back(bind);
-      } else {
-        GLCAT.error()
-          << "Could not bind texture input " << param_name << "\n";
+      }
+      else {
+        // p3d_Texture[] or p3d_TextureModulate[], etc.
+        if (size == 7) {
+          bind._part = Shader::STO_stage_i;
+        }
+        else if (noprefix.compare(7, string::npos, "FF") == 0) {
+          bind._part = Shader::STO_ff_stage_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Modulate") == 0) {
+          bind._part = Shader::STO_stage_modulate_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Add") == 0) {
+          bind._part = Shader::STO_stage_add_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Normal") == 0) {
+          bind._part = Shader::STO_stage_normal_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Height") == 0) {
+          bind._part = Shader::STO_stage_height_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Selector") == 0) {
+          bind._part = Shader::STO_stage_selector_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Gloss") == 0) {
+          bind._part = Shader::STO_stage_gloss_i;
+        }
+        else if (noprefix.compare(7, string::npos, "Emission") == 0) {
+          bind._part = Shader::STO_stage_emission_i;
+        }
+
+        for (bind._stage = 0; bind._stage < param_size; ++bind._stage) {
+          _glgsg->_glUniform1i(p + bind._stage, _shader->_tex_spec.size());
+          _shader->_tex_spec.push_back(bind);
+        }
       }
       }
       return;
       return;
     }
     }

+ 23 - 0
panda/src/gobj/geom.cxx

@@ -1294,6 +1294,29 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
   return gc;
   return gc;
 }
 }
 
 
+/**
+ * Returns true if the Geom is within the given view frustum.
+ */
+bool Geom::
+is_in_view(const BoundingVolume *view_frustum, Thread *current_thread) const {
+  CDLockedReader cdata(_cycler, current_thread);
+
+  if (cdata->_user_bounds != nullptr) {
+    const GeometricBoundingVolume *gbv = cdata->_user_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+  else if (!cdata->_internal_bounds_stale) {
+    const GeometricBoundingVolume *gbv = cdata->_internal_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+  else {
+    CDWriter cdataw(((Geom *)this)->_cycler, cdata, false);
+    compute_internal_bounds(cdataw, current_thread);
+    const GeometricBoundingVolume *gbv = cdataw->_internal_bounds->as_geometric_bounding_volume();
+    return view_frustum->contains(gbv) != BoundingVolume::IF_no_intersection;
+  }
+}
+
 /**
 /**
  * Actually draws the Geom with the indicated GSG, using the indicated vertex
  * Actually draws the Geom with the indicated GSG, using the indicated vertex
  * data (which might have been pre-munged to support the GSG's needs).
  * data (which might have been pre-munged to support the GSG's needs).

+ 2 - 0
panda/src/gobj/geom.h

@@ -157,6 +157,8 @@ PUBLISHED:
                            GraphicsStateGuardianBase *gsg);
                            GraphicsStateGuardianBase *gsg);
 
 
 public:
 public:
+  bool is_in_view(const BoundingVolume *view_frustum, Thread *current_thread) const;
+
   bool draw(GraphicsStateGuardianBase *gsg,
   bool draw(GraphicsStateGuardianBase *gsg,
             const GeomVertexData *vertex_data, size_t num_instances,
             const GeomVertexData *vertex_data, size_t num_instances,
             bool force, Thread *current_thread) const;
             bool force, Thread *current_thread) const;

+ 8 - 0
panda/src/gobj/geomPrimitive.cxx

@@ -1627,6 +1627,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
 
 
       for (; i < cdata->_num_vertices; ++i) {
       for (; i < cdata->_num_vertices; ++i) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
         reader.set_row_unsafe(cdata->_first_vertex + i);
+        nassertv(!reader.is_at_end());
+
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
 
 
         min_point.set(min(min_point[0], vertex[0]),
         min_point.set(min(min_point[0], vertex[0]),
@@ -1653,6 +1655,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
 
 
       for (; i < cdata->_num_vertices; ++i) {
       for (; i < cdata->_num_vertices; ++i) {
         reader.set_row_unsafe(cdata->_first_vertex + i);
         reader.set_row_unsafe(cdata->_first_vertex + i);
+        nassertv(!reader.is_at_end());
+
         const LVecBase3 &vertex = reader.get_data3();
         const LVecBase3 &vertex = reader.get_data3();
 
 
         min_point.set(min(min_point[0], vertex[0]),
         min_point.set(min(min_point[0], vertex[0]),
@@ -1696,6 +1700,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
           continue;
           continue;
         }
         }
         reader.set_row_unsafe(ii);
         reader.set_row_unsafe(ii);
+        nassertv(!reader.is_at_end());
+
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
         LPoint3 vertex = mat.xform_point_general(reader.get_data3());
 
 
         min_point.set(min(min_point[0], vertex[0]),
         min_point.set(min(min_point[0], vertex[0]),
@@ -1728,6 +1734,8 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
           continue;
           continue;
         }
         }
         reader.set_row_unsafe(ii);
         reader.set_row_unsafe(ii);
+        nassertv(!reader.is_at_end());
+
         const LVecBase3 &vertex = reader.get_data3();
         const LVecBase3 &vertex = reader.get_data3();
 
 
         min_point.set(min(min_point[0], vertex[0]),
         min_point.set(min(min_point[0], vertex[0]),

+ 22 - 9
panda/src/gobj/geomTristrips.cxx

@@ -482,15 +482,28 @@ requires_unused_vertices() const {
  */
  */
 void GeomTristrips::
 void GeomTristrips::
 append_unused_vertices(GeomVertexArrayData *vertices, int vertex) {
 append_unused_vertices(GeomVertexArrayData *vertices, int vertex) {
-  GeomVertexReader from(vertices, 0);
-  from.set_row_unsafe(vertices->get_num_rows() - 1);
-  int prev = from.get_data1i();
-
-  GeomVertexWriter to(vertices, 0);
-  to.set_row_unsafe(vertices->get_num_rows());
-
-  to.add_data1i(prev);
-  to.add_data1i(vertex);
+  size_t offset = vertices->get_num_rows();
+  vertices->set_num_rows(offset + 2);
+
+  PT(GeomVertexArrayDataHandle) handle = vertices->modify_handle();
+  unsigned char *ptr = handle->get_write_pointer();
+  switch (vertices->get_array_format()->get_stride()) {
+  case 1:
+    ((uint8_t *)ptr)[offset] = ((uint8_t *)ptr)[offset - 1];
+    ((uint8_t *)ptr)[offset + 1] = vertex;
+    break;
+  case 2:
+    ((uint16_t *)ptr)[offset] = ((uint16_t *)ptr)[offset - 1];
+    ((uint16_t *)ptr)[offset + 1] = vertex;
+    break;
+  case 4:
+    ((uint32_t *)ptr)[offset] = ((uint32_t *)ptr)[offset - 1];
+    ((uint32_t *)ptr)[offset + 1] = vertex;
+    break;
+  default:
+    nassert_raise("unsupported index type");
+    break;
+  }
 }
 }
 
 
 /**
 /**

+ 8 - 8
panda/src/gobj/geomVertexReader.I

@@ -29,9 +29,9 @@ GeomVertexReader(Thread *current_thread) :
  * object.
  * object.
  */
  */
 INLINE GeomVertexReader::
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexData *vertex_data,
+GeomVertexReader(CPT(GeomVertexData) vertex_data,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -43,10 +43,10 @@ GeomVertexReader(const GeomVertexData *vertex_data,
  * data type.
  * data type.
  */
  */
 INLINE GeomVertexReader::
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexData *vertex_data,
+GeomVertexReader(CPT(GeomVertexData) vertex_data,
                  CPT_InternalName name,
                  CPT_InternalName name,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -58,9 +58,9 @@ GeomVertexReader(const GeomVertexData *vertex_data,
  * only.
  * only.
  */
  */
 INLINE GeomVertexReader::
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexArrayData *array_data,
+GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -71,9 +71,9 @@ GeomVertexReader(const GeomVertexArrayData *array_data,
  * only.
  * only.
  */
  */
 INLINE GeomVertexReader::
 INLINE GeomVertexReader::
-GeomVertexReader(const GeomVertexArrayData *array_data, int column,
+GeomVertexReader(CPT(GeomVertexArrayData) array_data, int column,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();

+ 4 - 4
panda/src/gobj/geomVertexReader.h

@@ -47,14 +47,14 @@
 class EXPCL_PANDA_GOBJ GeomVertexReader : public GeomEnums {
 class EXPCL_PANDA_GOBJ GeomVertexReader : public GeomEnums {
 PUBLISHED:
 PUBLISHED:
   INLINE GeomVertexReader(Thread *current_thread = Thread::get_current_thread());
   INLINE GeomVertexReader(Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexData *vertex_data,
+  INLINE GeomVertexReader(CPT(GeomVertexData) vertex_data,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexData *vertex_data,
+  INLINE GeomVertexReader(CPT(GeomVertexData) vertex_data,
                           CPT_InternalName name,
                           CPT_InternalName name,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexArrayData *array_data,
+  INLINE GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexReader(const GeomVertexArrayData *array_data,
+  INLINE GeomVertexReader(CPT(GeomVertexArrayData) array_data,
                           int column,
                           int column,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
 
 

+ 8 - 8
panda/src/gobj/geomVertexWriter.I

@@ -29,8 +29,8 @@ GeomVertexWriter(Thread *current_thread) :
  * object.
  * object.
  */
  */
 INLINE GeomVertexWriter::
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexData *vertex_data, Thread *current_thread) :
-  _vertex_data(vertex_data),
+GeomVertexWriter(PT(GeomVertexData) vertex_data, Thread *current_thread) :
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -42,9 +42,9 @@ GeomVertexWriter(GeomVertexData *vertex_data, Thread *current_thread) :
  * data type.
  * data type.
  */
  */
 INLINE GeomVertexWriter::
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexData *vertex_data, CPT_InternalName name,
+GeomVertexWriter(PT(GeomVertexData) vertex_data, CPT_InternalName name,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _vertex_data(vertex_data),
+  _vertex_data(std::move(vertex_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -56,9 +56,9 @@ GeomVertexWriter(GeomVertexData *vertex_data, CPT_InternalName name,
  * only.
  * only.
  */
  */
 INLINE GeomVertexWriter::
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexArrayData *array_data,
+GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();
@@ -69,9 +69,9 @@ GeomVertexWriter(GeomVertexArrayData *array_data,
  * only.
  * only.
  */
  */
 INLINE GeomVertexWriter::
 INLINE GeomVertexWriter::
-GeomVertexWriter(GeomVertexArrayData *array_data, int column,
+GeomVertexWriter(PT(GeomVertexArrayData) array_data, int column,
                  Thread *current_thread) :
                  Thread *current_thread) :
-  _array_data(array_data),
+  _array_data(std::move(array_data)),
   _current_thread(current_thread)
   _current_thread(current_thread)
 {
 {
   initialize();
   initialize();

+ 4 - 4
panda/src/gobj/geomVertexWriter.h

@@ -55,14 +55,14 @@
 class EXPCL_PANDA_GOBJ GeomVertexWriter : public GeomEnums {
 class EXPCL_PANDA_GOBJ GeomVertexWriter : public GeomEnums {
 PUBLISHED:
 PUBLISHED:
   INLINE GeomVertexWriter(Thread *current_thread = Thread::get_current_thread());
   INLINE GeomVertexWriter(Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexData *vertex_data,
+  INLINE GeomVertexWriter(PT(GeomVertexData) vertex_data,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexData *vertex_data,
+  INLINE GeomVertexWriter(PT(GeomVertexData) vertex_data,
                           CPT_InternalName name,
                           CPT_InternalName name,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexArrayData *array_data,
+  INLINE GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
-  INLINE GeomVertexWriter(GeomVertexArrayData *array_data,
+  INLINE GeomVertexWriter(PT(GeomVertexArrayData) array_data,
                           int column,
                           int column,
                           Thread *current_thread = Thread::get_current_thread());
                           Thread *current_thread = Thread::get_current_thread());
 
 

+ 9 - 0
panda/src/gobj/shader.h

@@ -227,6 +227,15 @@ public:
 
 
     STO_stage_i,
     STO_stage_i,
     STO_light_i_shadow_map,
     STO_light_i_shadow_map,
+
+    STO_ff_stage_i,
+    STO_stage_modulate_i,
+    STO_stage_add_i,
+    STO_stage_normal_i,
+    STO_stage_height_i,
+    STO_stage_selector_i,
+    STO_stage_gloss_i,
+    STO_stage_emission_i,
   };
   };
 
 
   enum ShaderArgClass {
   enum ShaderArgClass {

+ 3 - 0
panda/src/gobj/textureStage.cxx

@@ -517,6 +517,9 @@ operator << (ostream &out, TextureStage::Mode mode) {
 
 
   case TextureStage::M_normal_gloss:
   case TextureStage::M_normal_gloss:
     return out << "normal_gloss";
     return out << "normal_gloss";
+
+  case TextureStage::M_emission:
+    return out << "emission";
   }
   }
 
 
   return out << "**invalid Mode(" << (int)mode << ")**";
   return out << "**invalid Mode(" << (int)mode << ")**";

+ 2 - 0
panda/src/gobj/textureStage.h

@@ -63,6 +63,8 @@ PUBLISHED:
     M_height,       // Rarely used: normal_height  is more efficient.
     M_height,       // Rarely used: normal_height  is more efficient.
     M_selector,
     M_selector,
     M_normal_gloss,
     M_normal_gloss,
+
+    M_emission,
   };
   };
 
 
   enum CombineMode {
   enum CombineMode {

+ 3 - 3
panda/src/linmath/lvecBase2_ext_src.I

@@ -221,7 +221,7 @@ __round__(PyObject *self) const {
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);
@@ -241,7 +241,7 @@ __floor__(PyObject *self) const {
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
 #endif
 
 
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);
@@ -260,7 +260,7 @@ __ceil__(PyObject *self) const {
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase2);
 #endif
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     FLOATNAME(LVecBase2) *vec = (FLOATNAME(LVecBase2) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase2));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);

+ 3 - 3
panda/src/linmath/lvecBase3_ext_src.I

@@ -224,7 +224,7 @@ __round__(PyObject *self) const {
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);
@@ -244,7 +244,7 @@ __floor__(PyObject *self) const {
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);
@@ -264,7 +264,7 @@ __ceil__(PyObject *self) const {
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
   extern struct Dtool_PyTypedObject FLOATNAME(Dtool_LVecBase3);
 #endif
 #endif
-  PyObject *py_vec = _PyObject_CallNoArg((PyObject *)DtoolInstance_TYPE(self));
+  PyObject *py_vec = PyObject_CallNoArgs((PyObject *)DtoolInstance_TYPE(self));
   if (py_vec != nullptr) {
   if (py_vec != nullptr) {
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     FLOATNAME(LVecBase3) *vec = (FLOATNAME(LVecBase3) *)DtoolInstance_UPCAST(py_vec, FLOATNAME(Dtool_LVecBase3));
     nassertr(vec != nullptr, nullptr);
     nassertr(vec != nullptr, nullptr);

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác