Browse Source

Merge branch 'release/1.10.x'

rdb 6 years ago
parent
commit
a40228202b
43 changed files with 672 additions and 188 deletions
  1. 4 4
      README.md
  2. 14 7
      direct/src/dist/FreezeTool.py
  3. 56 0
      direct/src/dist/commands.py
  4. 2 1
      direct/src/gui/DirectDialog.py
  5. 9 7
      direct/src/gui/DirectEntry.py
  6. 25 3
      direct/src/gui/DirectEntryScroll.py
  7. 3 4
      direct/src/gui/DirectGuiBase.py
  8. 5 4
      direct/src/gui/DirectScrolledList.py
  9. 199 62
      direct/src/p3d/DeploymentTools.py
  10. 4 1
      direct/src/showbase/ShowBase.py
  11. 1 0
      direct/src/showbase/ShowBaseGlobal.py
  12. 2 1
      direct/src/showbase/Transitions.py
  13. 2 1
      direct/src/tkpanels/MopathRecorder.py
  14. 38 0
      doc/ReleaseNotes
  15. 10 1
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  16. 8 1
      dtool/src/interrogatedb/dtool_super_base.cxx
  17. 1 1
      dtool/src/interrogatedb/py_compat.h
  18. 73 10
      dtool/src/interrogatedb/py_wrappers.cxx
  19. 4 1
      dtool/src/prc/notify.cxx
  20. 27 0
      makepanda/installer.nsi
  21. 3 3
      makepanda/makepackage.py
  22. 4 3
      makepanda/makepanda.py
  23. 5 1
      makepanda/makepandacore.py
  24. 8 2
      makepanda/makewheel.py
  25. 8 3
      makepanda/test_wheel.py
  26. 18 4
      panda/src/bullet/bulletBodyNode.cxx
  27. 6 3
      panda/src/bullet/bulletContactCallbacks.h
  28. 6 4
      panda/src/bullet/bulletDebugNode.cxx
  29. 3 1
      panda/src/bullet/config_bullet.cxx
  30. 0 6
      panda/src/linmath/lmatrix3_src.I
  31. 6 2
      panda/src/ode/odeGeom.cxx
  32. 8 4
      panda/src/ode/odeJoint.cxx
  33. 5 3
      panda/src/ode/odeSpace.cxx
  34. 3 1
      panda/src/ode/odeTriMeshData.I
  35. 44 20
      panda/src/ode/odeTriMeshData.cxx
  36. 9 3
      panda/src/ode/odeWorld.cxx
  37. 2 0
      panda/src/pgraph/nodePath.cxx
  38. 2 0
      panda/src/pgraph/pythonLoaderFileType.h
  39. 3 3
      panda/src/pgraph/textureAttrib.I
  40. 2 13
      panda/src/pgraph/textureAttrib.cxx
  41. 1 0
      pandatool/src/mayaprogs/mayapath.cxx
  42. 1 0
      setup.cfg
  43. 38 0
      tests/gui/test_DirectEntry.py

+ 4 - 4
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-3/).
+[this page](https://www.panda3d.org/download/sdk-1-10-4/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
 the following comand:
 the following comand:
 
 
@@ -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.3/panda3d-1.10.3-tools-win64.zip
-https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win32.zip
+https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-win64.zip
+https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-win32.zip
 
 
 After acquiring these dependencies, you may simply build Panda3D from the
 After acquiring these dependencies, you may simply build Panda3D from the
 command prompt using the following command.  (Change `14.1` to `14` if you are
 command prompt using the following command.  (Change `14.1` to `14` if you are
@@ -135,7 +135,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:

+ 14 - 7
direct/src/dist/FreezeTool.py

@@ -800,29 +800,36 @@ class Freezer:
                     self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
                     self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
         else:
         else:
             self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
             self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
+
+            abi_version = '{0}{1}'.format(*sys.version_info)
+            abi_flags = ''
+            if sys.version_info < (3, 8):
+                abi_flags += 'm'
+
             if 'linux' in self.platform:
             if 'linux' in self.platform:
                 self.moduleSuffixes += [
                 self.moduleSuffixes += [
-                    ('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
-                    ('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
+                    ('.cpython-{0}{1}-x86_64-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
+                    ('.cpython-{0}{1}-i686-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.so', 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
                 ]
             elif 'win' in self.platform:
             elif 'win' in self.platform:
+                # ABI flags are not appended on Windows.
                 self.moduleSuffixes += [
                 self.moduleSuffixes += [
-                    ('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3),
-                    ('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3),
+                    ('.cp{0}-win_amd64.pyd'.format(abi_version), 'rb', 3),
+                    ('.cp{0}-win32.pyd'.format(abi_version), 'rb', 3),
                     ('.pyd', 'rb', 3),
                     ('.pyd', 'rb', 3),
                 ]
                 ]
             elif 'mac' in self.platform:
             elif 'mac' in self.platform:
                 self.moduleSuffixes += [
                 self.moduleSuffixes += [
-                    ('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3),
+                    ('.cpython-{0}{1}-darwin.so'.format(abi_version, abi_flags), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.so', 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
                 ]
             else: # FreeBSD et al.
             else: # FreeBSD et al.
                 self.moduleSuffixes += [
                 self.moduleSuffixes += [
-                    ('.cpython-{0}{1}m.so'.format(*sys.version_info), 'rb', 3),
-                    ('.abi{0}.so'.format(*sys.version_info), 'rb', 3),
+                    ('.cpython-{0}{1}.so'.format(abi_version, abi_flags), 'rb', 3),
+                    ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
                     ('.so', 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
                 ]
 
 

+ 56 - 0
direct/src/dist/commands.py

@@ -14,12 +14,14 @@ import struct
 import imp
 import imp
 import string
 import string
 import time
 import time
+import tempfile
 
 
 import setuptools
 import setuptools
 import distutils.log
 import distutils.log
 
 
 from . import FreezeTool
 from . import FreezeTool
 from . import pefile
 from . import pefile
+from direct.p3d.DeploymentTools import Icon
 import panda3d.core as p3d
 import panda3d.core as p3d
 
 
 
 
@@ -224,6 +226,7 @@ class build_apps(setuptools.Command):
         self.exclude_patterns = []
         self.exclude_patterns = []
         self.include_modules = {}
         self.include_modules = {}
         self.exclude_modules = {}
         self.exclude_modules = {}
+        self.icons = {}
         self.platforms = [
         self.platforms = [
             'manylinux1_x86_64',
             'manylinux1_x86_64',
             'macosx_10_6_x86_64',
             'macosx_10_6_x86_64',
@@ -298,6 +301,7 @@ class build_apps(setuptools.Command):
             key: _parse_list(value)
             key: _parse_list(value)
             for key, value in _parse_dict(self.exclude_modules).items()
             for key, value in _parse_dict(self.exclude_modules).items()
         }
         }
+        self.icons = _parse_dict(self.icons)
         self.platforms = _parse_list(self.platforms)
         self.platforms = _parse_list(self.platforms)
         self.plugins = _parse_list(self.plugins)
         self.plugins = _parse_list(self.plugins)
         self.extra_prc_files = _parse_list(self.extra_prc_files)
         self.extra_prc_files = _parse_list(self.extra_prc_files)
@@ -357,6 +361,18 @@ class build_apps(setuptools.Command):
         tmp.update(self.package_data_dirs)
         tmp.update(self.package_data_dirs)
         self.package_data_dirs = tmp
         self.package_data_dirs = tmp
 
 
+        self.icon_objects = {}
+        for app, iconpaths in self.icons.items():
+            if not isinstance(iconpaths, list) and not isinstance(iconpaths, tuple):
+                iconpaths = (iconpaths,)
+
+            iconobj = Icon()
+            for iconpath in iconpaths:
+                iconobj.addImage(iconpath)
+
+            iconobj.generateMissingImages()
+            self.icon_objects[app] = iconobj
+
     def run(self):
     def run(self):
         self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
         self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
 
 
@@ -433,6 +449,22 @@ class build_apps(setuptools.Command):
 
 
         return wheelpaths
         return wheelpaths
 
 
+    def update_pe_resources(self, appname, runtime):
+        """Update resources (e.g., icons) in windows PE file"""
+
+        icon = self.icon_objects.get(
+            appname,
+            self.icon_objects.get('*', None),
+        )
+
+        if icon is not None:
+            pef = pefile.PEFile()
+            pef.open(runtime, 'r+')
+            pef.add_icon(icon)
+            pef.add_resource_section()
+            pef.write_changes()
+            pef.close()
+
     def bundle_macos_app(self, builddir):
     def bundle_macos_app(self, builddir):
         """Bundle built runtime into a .app for macOS"""
         """Bundle built runtime into a .app for macOS"""
 
 
@@ -474,6 +506,15 @@ class build_apps(setuptools.Command):
             'CFBundleSignature': '', #TODO
             'CFBundleSignature': '', #TODO
             'CFBundleExecutable': self.macos_main_app,
             'CFBundleExecutable': self.macos_main_app,
         }
         }
+
+        icon = self.icon_objects.get(
+            self.macos_main_app,
+            self.icon_objects.get('*', None)
+        )
+        if icon is not None:
+            plist['CFBundleIconFile'] = 'iconfile'
+            icon.makeICNS(os.path.join(resdir, 'iconfile.icns'))
+
         with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
         with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
             if hasattr(plistlib, 'dump'):
             if hasattr(plistlib, 'dump'):
                 plistlib.dump(plist, f)
                 plistlib.dump(plist, f)
@@ -618,6 +659,18 @@ class build_apps(setuptools.Command):
                 stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
                 stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
                 stub_file = open(stub_path, 'rb')
                 stub_file = open(stub_path, 'rb')
 
 
+            # Do we need an icon?  On Windows, we need to add this to the stub
+            # before we add the blob.
+            if 'win' in platform:
+                temp_file = tempfile.NamedTemporaryFile(suffix='-icon.exe', delete=False)
+                temp_file.write(stub_file.read())
+                stub_file.close()
+                temp_file.close()
+                self.update_pe_resources(appname, temp_file.name)
+                stub_file = open(temp_file.name, 'rb')
+            else:
+                temp_file = None
+
             freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
             freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
                 'prc_data': prcexport if self.embed_prc_data else None,
                 'prc_data': prcexport if self.embed_prc_data else None,
                 'default_prc_dir': self.default_prc_dir,
                 'default_prc_dir': self.default_prc_dir,
@@ -633,6 +686,9 @@ class build_apps(setuptools.Command):
             }, self.log_append)
             }, self.log_append)
             stub_file.close()
             stub_file.close()
 
 
+            if temp_file:
+                os.unlink(temp_file.name)
+
             # Copy the dependencies.
             # Copy the dependencies.
             search_path = [builddir]
             search_path = [builddir]
             if use_wheels:
             if use_wheels:

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

@@ -3,6 +3,7 @@
 __all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
 __all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
 
 
 from panda3d.core import *
 from panda3d.core import *
+from direct.showbase import ShowBaseGlobal
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .DirectFrame import *
 from .DirectFrame import *
 from .DirectButton import *
 from .DirectButton import *
@@ -207,7 +208,7 @@ class DirectDialog(DirectFrame):
             image = None
             image = None
         # Get size of text/geom without image (for state 0)
         # Get size of text/geom without image (for state 0)
         if image:
         if image:
-            image.reparentTo(hidden)
+            image.reparentTo(ShowBaseGlobal.hidden)
         bounds = self.stateNodePath[0].getTightBounds()
         bounds = self.stateNodePath[0].getTightBounds()
         if image:
         if image:
             image.reparentTo(self.stateNodePath[0])
             image.reparentTo(self.stateNodePath[0])

+ 9 - 7
direct/src/gui/DirectEntry.py

@@ -4,6 +4,7 @@ text entered using the keyboard."""
 __all__ = ['DirectEntry']
 __all__ = ['DirectEntry']
 
 
 from panda3d.core import *
 from panda3d.core import *
+from direct.showbase import ShowBaseGlobal
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .DirectFrame import *
 from .DirectFrame import *
 from .OnscreenText import OnscreenText
 from .OnscreenText import OnscreenText
@@ -94,7 +95,7 @@ class DirectEntry(DirectFrame):
         self.onscreenText = self.createcomponent(
         self.onscreenText = self.createcomponent(
             'text', (), None,
             'text', (), None,
             OnscreenText,
             OnscreenText,
-            (), parent = hidden,
+            (), parent = ShowBaseGlobal.hidden,
             # Pass in empty text to avoid extra work, since its really
             # Pass in empty text to avoid extra work, since its really
             # The PGEntry which will use the TextNode to generate geometry
             # The PGEntry which will use the TextNode to generate geometry
             text = '',
             text = '',
@@ -215,11 +216,11 @@ class DirectEntry(DirectFrame):
         self._autoCapitalize()
         self._autoCapitalize()
 
 
     def _autoCapitalize(self):
     def _autoCapitalize(self):
-        name = self.get().decode('utf-8')
+        name = self.guiItem.getWtext()
         # capitalize each word, allowing for things like McMutton
         # capitalize each word, allowing for things like McMutton
-        capName = ''
+        capName = u''
         # track each individual word to detect prefixes like Mc
         # track each individual word to detect prefixes like Mc
-        wordSoFar = ''
+        wordSoFar = u''
         # track whether the previous character was part of a word or not
         # track whether the previous character was part of a word or not
         wasNonWordChar = True
         wasNonWordChar = True
         for i, character in enumerate(name):
         for i, character in enumerate(name):
@@ -228,9 +229,9 @@ class DirectEntry(DirectFrame):
             #   This assumes that string.lower and string.upper will return different
             #   This assumes that string.lower and string.upper will return different
             #   values for all unicode letters.
             #   values for all unicode letters.
             # - Don't count apostrophes as a break between words
             # - Don't count apostrophes as a break between words
-            if character.lower() == character.upper() and character != "'":
+            if character.lower() == character.upper() and character != u"'":
                 # we are between words
                 # we are between words
-                wordSoFar = ''
+                wordSoFar = u''
                 wasNonWordChar = True
                 wasNonWordChar = True
             else:
             else:
                 capitalize = False
                 capitalize = False
@@ -254,7 +255,8 @@ class DirectEntry(DirectFrame):
                 wordSoFar += character
                 wordSoFar += character
                 wasNonWordChar = False
                 wasNonWordChar = False
             capName += character
             capName += character
-        self.enterText(capName.encode('utf-8'))
+        self.guiItem.setWtext(capName)
+        self.guiItem.setCursorPosition(self.guiItem.getNumCharacters())
 
 
     def focusOutCommandFunc(self):
     def focusOutCommandFunc(self):
         if self['focusOutCommand']:
         if self['focusOutCommand']:

+ 25 - 3
direct/src/gui/DirectEntryScroll.py

@@ -29,19 +29,41 @@ class DirectEntryScroll(DirectFrame):
            # frameSize = (-0.006, 3.2, -0.015, 0.036),
            # frameSize = (-0.006, 3.2, -0.015, 0.036),
         # if you need to scale the entry scale it's parent instead
         # if you need to scale the entry scale it's parent instead
 
 
-        self.entry = entry
         self.canvas = NodePath(self.guiItem.getCanvasNode())
         self.canvas = NodePath(self.guiItem.getCanvasNode())
-        self.entry.reparentTo(self.canvas)
         self.canvas.setPos(0,0,0)
         self.canvas.setPos(0,0,0)
 
 
-        self.entry.bind(DGG.CURSORMOVE,self.cursorMove)
+        self.entry = None
+        if entry is not None:
+            self.entry = entry
+            self.entry.reparentTo(self.canvas)
+            self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
 
 
         self.canvas.node().setBounds(OmniBoundingVolume())
         self.canvas.node().setBounds(OmniBoundingVolume())
         self.canvas.node().setFinal(1)
         self.canvas.node().setFinal(1)
         self.resetCanvas()
         self.resetCanvas()
 
 
+    def setEntry(self, entry):
+        """
+        Sets a DirectEntry element for this scroll frame. A DirectEntryScroll
+        can only hold one entry at a time, so make sure to not call this
+        function twice or call clearEntry before to make sure no entry
+        is already set.
+        """
+        assert self.entry is None, "An entry was already set for this DirectEntryScroll element"
+        self.entry = entry
+        self.entry.reparentTo(self.canvas)
 
 
+        self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
 
 
+    def clearEntry(self):
+        """
+        detaches and unbinds the entry from the scroll frame and its
+        events. You'll be responsible for destroying it.
+        """
+        if self.entry is None: return
+        self.entry.unbind(DGG.CURSORMOVE)
+        self.entry.detachNode()
+        self.entry = None
 
 
     def cursorMove(self, cursorX, cursorY):
     def cursorMove(self, cursorX, cursorY):
         cursorX = self.entry.guiItem.getCursorX() * self.entry['text_scale'][0]
         cursorX = self.entry.guiItem.getCursorX() * self.entry['text_scale'][0]

+ 3 - 4
direct/src/gui/DirectGuiBase.py

@@ -81,7 +81,6 @@ __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
 from panda3d.core import *
 from panda3d.core import *
 from direct.showbase import ShowBaseGlobal
 from direct.showbase import ShowBaseGlobal
-from direct.showbase.ShowBase import ShowBase
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .OnscreenText import *
 from .OnscreenText import *
 from .OnscreenGeom import *
 from .OnscreenGeom import *
@@ -634,7 +633,7 @@ class DirectGuiBase(DirectObject.DirectObject):
         """
         """
         # Need to tack on gui item specific id
         # Need to tack on gui item specific id
         gEvent = event + self.guiId
         gEvent = event + self.guiId
-        if ShowBase.config.GetBool('debug-directgui-msgs', False):
+        if ShowBaseGlobal.config.GetBool('debug-directgui-msgs', False):
             from direct.showbase.PythonUtil import StackTrace
             from direct.showbase.PythonUtil import StackTrace
             print(gEvent)
             print(gEvent)
             print(StackTrace())
             print(StackTrace())
@@ -663,7 +662,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     # Determine the default initial state for inactive (or
     # Determine the default initial state for inactive (or
     # unclickable) components.  If we are in edit mode, these are
     # unclickable) components.  If we are in edit mode, these are
     # actually clickable by default.
     # actually clickable by default.
-    guiEdit = ShowBase.config.GetBool('direct-gui-edit', False)
+    guiEdit = ShowBaseGlobal.config.GetBool('direct-gui-edit', False)
     if guiEdit:
     if guiEdit:
         inactiveInitState = DGG.NORMAL
         inactiveInitState = DGG.NORMAL
     else:
     else:
@@ -729,7 +728,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             guiObjectCollector.addLevel(1)
             guiObjectCollector.addLevel(1)
             guiObjectCollector.flushLevel()
             guiObjectCollector.flushLevel()
             # track gui items by guiId for tracking down leaks
             # track gui items by guiId for tracking down leaks
-            if ShowBase.config.GetBool('track-gui-items', False):
+            if ShowBaseGlobal.config.GetBool('track-gui-items', False):
                 if not hasattr(ShowBase, 'guiItems'):
                 if not hasattr(ShowBase, 'guiItems'):
                     ShowBase.guiItems = {}
                     ShowBase.guiItems = {}
                 if self.guiId in ShowBase.guiItems:
                 if self.guiId in ShowBase.guiItems:

+ 5 - 4
direct/src/gui/DirectScrolledList.py

@@ -3,6 +3,7 @@
 __all__ = ['DirectScrolledListItem', 'DirectScrolledList']
 __all__ = ['DirectScrolledListItem', 'DirectScrolledList']
 
 
 from panda3d.core import *
 from panda3d.core import *
+from direct.showbase import ShowBaseGlobal
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.task.Task import Task
 from direct.task.Task import Task
@@ -369,7 +370,7 @@ class DirectScrolledList(DirectFrame):
                 del self.currentSelected
                 del self.currentSelected
             self["items"].remove(item)
             self["items"].remove(item)
             if type(item) != type(''):
             if type(item) != type(''):
-                item.reparentTo(hidden)
+                item.reparentTo(ShowBaseGlobal.hidden)
             self.refresh()
             self.refresh()
             return 1
             return 1
         else:
         else:
@@ -387,7 +388,7 @@ class DirectScrolledList(DirectFrame):
                 item.destroy()
                 item.destroy()
             self["items"].remove(item)
             self["items"].remove(item)
             if type(item) != type(''):
             if type(item) != type(''):
-                item.reparentTo(hidden)
+                item.reparentTo(ShowBaseGlobal.hidden)
             self.refresh()
             self.refresh()
             return 1
             return 1
         else:
         else:
@@ -410,7 +411,7 @@ class DirectScrolledList(DirectFrame):
             self["items"].remove(item)
             self["items"].remove(item)
             if type(item) != type(''):
             if type(item) != type(''):
                 #RAU possible leak here, let's try to do the right thing
                 #RAU possible leak here, let's try to do the right thing
-                #item.reparentTo(hidden)
+                #item.reparentTo(ShowBaseGlobal.hidden)
                 item.removeNode()
                 item.removeNode()
             retval = 1
             retval = 1
 
 
@@ -435,7 +436,7 @@ class DirectScrolledList(DirectFrame):
             self["items"].remove(item)
             self["items"].remove(item)
             if type(item) != type(''):
             if type(item) != type(''):
                 #RAU possible leak here, let's try to do the right thing
                 #RAU possible leak here, let's try to do the right thing
-                #item.reparentTo(hidden)
+                #item.reparentTo(ShowBaseGlobal.hidden)
                 item.removeNode()
                 item.removeNode()
             retval = 1
             retval = 1
         if (refresh):
         if (refresh):

+ 199 - 62
direct/src/p3d/DeploymentTools.py

@@ -10,7 +10,7 @@ from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.AppRunnerGlobal import appRunner
 from direct.showbase.AppRunnerGlobal import appRunner
 from panda3d.core import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile
 from panda3d.core import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile
 from panda3d.core import TiXmlDocument, TiXmlDeclaration, TiXmlElement, readXmlStream
 from panda3d.core import TiXmlDocument, TiXmlDeclaration, TiXmlElement, readXmlStream
-from panda3d.core import PNMImage, PNMFileTypeRegistry
+from panda3d.core import PNMImage, PNMFileTypeRegistry, StringStream
 from direct.stdpy.file import *
 from direct.stdpy.file import *
 from direct.p3d.HostInfo import HostInfo
 from direct.p3d.HostInfo import HostInfo
 # This is important for some reason
 # This is important for some reason
@@ -332,6 +332,135 @@ class Icon:
 
 
         return True
         return True
 
 
+    def generateMissingImages(self):
+        """ Generates image sizes that should be present but aren't by scaling
+        from the next higher size. """
+
+        for required_size in (256, 128, 48, 32, 16):
+            if required_size in self.images:
+                continue
+
+            sizes = sorted(self.images.keys())
+            if required_size * 2 in sizes:
+                from_size = required_size * 2
+            else:
+                for from_size in sizes:
+                    if from_size > required_size:
+                        break
+
+            if from_size > required_size:
+                Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size))
+
+                image = PNMImage(required_size, required_size)
+                if self.images[from_size].hasAlpha():
+                    image.addAlpha()
+                image.quickFilterFrom(self.images[from_size])
+                self.images[required_size] = image
+            else:
+                Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size))
+
+    def _write_bitmap(self, fp, image, size, bpp):
+        """ Writes the bitmap header and data of an .ico file. """
+
+        fp.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
+
+        # XOR mask
+        if bpp == 24:
+            # Align rows to 4-byte boundary
+            rowalign = '\0' * (-(size * 3) & 3)
+            for y in xrange(size):
+                for x in xrange(size):
+                    r, g, b = image.getXel(x, size - y - 1)
+                    fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
+                fp.write(rowalign)
+
+        elif bpp == 32:
+            for y in xrange(size):
+                for x in xrange(size):
+                    r, g, b, a = image.getXelA(x, size - y - 1)
+                    fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
+
+        elif bpp == 8:
+            # We'll have to generate a palette of 256 colors.
+            hist = PNMImage.Histogram()
+            if image.hasAlpha():
+                # Make a copy without alpha channel.
+                image2 = PNMImage(image)
+                image2.premultiplyAlpha()
+                image2.removeAlpha()
+            else:
+                image2 = image
+            image2.make_histogram(hist)
+            colors = list(hist.get_pixels())
+            if len(colors) > 256:
+                # Palette too large; remove infrequent colors.
+                colors.sort(key=hist.get_count, reverse=True)
+
+                # Find the closest color on the palette matching each color
+                # that didn't fit.  This is certainly not the best palette
+                # generation code, but it'll do for now.
+                closest_indices = []
+                for color in colors[256:]:
+                    closest_index = 0
+                    closest_diff = 1025
+                    for i, closest_color in enumerate(colors[:256]):
+                        diff = abs(color.get_red() - closest_color.get_red()) \
+                             + abs(color.get_green() - closest_color.get_green()) \
+                             + abs(color.get_blue() - closest_color.get_blue())
+                        if diff < closest_diff:
+                            closest_index = i
+                            closest_diff = diff
+                    assert closest_diff < 100
+                    closest_indices.append(closest_index)
+
+            # Write the palette.
+            i = 0
+            while i < 256 and i < len(colors):
+                r, g, b, a = colors[i]
+                fp.write(struct.pack('<BBBB', b, g, r, 0))
+                i += 1
+            if i < 256:
+                # Fill the rest with zeroes.
+                fp.write(b'\x00' * (4 * (256 - i)))
+
+            # Write indices.  Align rows to 4-byte boundary.
+            rowalign = b'\0' * (-size & 3)
+            for y in xrange(size):
+                for x in xrange(size):
+                    pixel = image2.get_pixel(x, size - y - 1)
+                    index = colors.index(pixel)
+                    if index >= 256:
+                        # Find closest pixel instead.
+                        index = closest_indices[index - 256]
+                    fp.write(struct.pack('<B', index))
+                fp.write(rowalign)
+        else:
+            raise ValueError("Invalid bpp %d" % (bpp))
+
+        # Create an AND mask, aligned to 4-byte boundary
+        if image.hasAlpha() and bpp <= 8:
+            rowalign = b'\0' * (-((size + 7) >> 3) & 3)
+            for y in xrange(size):
+                mask = 0
+                num_bits = 7
+                for x in xrange(size):
+                    a = image.get_alpha_val(x, size - y - 1)
+                    if a <= 1:
+                        mask |= (1 << num_bits)
+                    num_bits -= 1
+                    if num_bits < 0:
+                        fp.write(struct.pack('<B', mask))
+                        mask = 0
+                        num_bits = 7
+                if num_bits < 7:
+                    fp.write(struct.pack('<B', mask))
+                fp.write(rowalign)
+        else:
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            fp.write(b'\x00' * (andsize * size))
+
     def makeICO(self, fn):
     def makeICO(self, fn):
         """ Writes the images to a Windows ICO file.  Returns True on success. """
         """ Writes the images to a Windows ICO file.  Returns True on success. """
 
 
@@ -339,57 +468,71 @@ class Icon:
             fn = Filename.fromOsSpecific(fn)
             fn = Filename.fromOsSpecific(fn)
         fn.setBinary()
         fn.setBinary()
 
 
+        # ICO files only support resolutions up to 256x256.
         count = 0
         count = 0
         for size in self.images.keys():
         for size in self.images.keys():
+            if size < 256:
+                count += 1
             if size <= 256:
             if size <= 256:
                 count += 1
                 count += 1
+        dataoffs = 6 + count * 16
 
 
         ico = open(fn, 'wb')
         ico = open(fn, 'wb')
         ico.write(struct.pack('<HHH', 0, 1, count))
         ico.write(struct.pack('<HHH', 0, 1, count))
 
 
-        # Write the directory
+        # Write 8-bpp image headers for sizes under 256x256.
         for size, image in self.images.items():
         for size, image in self.images.items():
-            if size == 256:
+            if size >= 256:
+                continue
+            ico.write(struct.pack('<BB', size, size))
+
+            # Calculate row sizes
+            xorsize = size
+            if xorsize % 4 != 0:
+                xorsize += 4 - (xorsize % 4)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + 256 * 4 + (xorsize + andsize) * size
+
+            ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs))
+            dataoffs += datasize
+
+        # Write 24/32-bpp image headers.
+        for size, image in self.images.items():
+            if size > 256:
+                continue
+            elif size == 256:
                 ico.write('\0\0')
                 ico.write('\0\0')
             else:
             else:
                 ico.write(struct.pack('<BB', size, size))
                 ico.write(struct.pack('<BB', size, size))
-            bpp = 32 if image.hasAlpha() else 24
-            ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, 0, 0))
 
 
-        # Now write the actual icons
-        ptr = 14
-        for size, image in self.images.items():
-            loc = ico.tell()
-            bpp = 32 if image.hasAlpha() else 24
-            ico.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
-
-            # XOR mask
-            if bpp == 24:
-                # Align rows to 4-byte boundary
-                rowalign = '\0' * (-(size * 3) & 3)
-                for y in xrange(size):
-                    for x in xrange(size):
-                        r, g, b = image.getXel(x, size - y - 1)
-                        ico.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
-                    ico.write(rowalign)
+            # Calculate the size so we can write the offset within the file.
+            if image.hasAlpha():
+                bpp = 32
+                xorsize = size * 4
             else:
             else:
-                for y in xrange(size):
-                    for x in xrange(size):
-                        r, g, b, a = image.getXelA(x, size - y - 1)
-                        ico.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
+                bpp = 24
+                xorsize = size * 3 + (-(size * 3) & 3)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + (xorsize + andsize) * size
+
+            ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs))
+            dataoffs += datasize
 
 
-            # Empty AND mask, aligned to 4-byte boundary
-            #TODO: perhaps we should convert alpha into an AND mask
-            # to support older versions of Windows that don't support alpha.
-            ico.write('\0' * (size * (size / 8 + (-((size / 8) * 3) & 3))))
+        # Now write the actual icon bitmap data.
+        for size, image in self.images.items():
+            if size < 256:
+                self._write_bitmap(ico, image, size, 8)
 
 
-            # Go back to write the location
-            dataend = ico.tell()
-            ico.seek(ptr)
-            ico.write(struct.pack('<II', dataend - loc, loc))
-            ico.seek(dataend)
-            ptr += 16
+        for size, image in self.images.items():
+            if size <= 256:
+                bpp = 32 if image.hasAlpha() else 24
+                self._write_bitmap(ico, image, size, bpp)
 
 
+        assert ico.tell() == dataoffs
         ico.close()
         ico.close()
 
 
         return True
         return True
@@ -401,32 +544,35 @@ class Icon:
             fn = Filename.fromOsSpecific(fn)
             fn = Filename.fromOsSpecific(fn)
         fn.setBinary()
         fn.setBinary()
 
 
-        vfs = VirtualFileSystem.getGlobalPtr()
-        stream = vfs.openWriteFile(fn, False, True)
-        icns = open(stream, 'wb')
+        icns = open(fn, 'wb')
         icns.write(b'icns\0\0\0\0')
         icns.write(b'icns\0\0\0\0')
 
 
-        icon_types = {16: 'is32', 32: 'il32', 48: 'ih32', 128: 'it32'}
-        mask_types = {16: 's8mk', 32: 'l8mk', 48: 'h8mk', 128: 't8mk'}
-        png_types = {256: 'ic08', 512: 'ic09'}
+        icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'}
+        mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'}
+        png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'}
 
 
         pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
         pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
 
 
-        for size, image in self.images.items():
-            if size in png_types:
-                if pngtype is None:
-                    continue
-                icns.write(png_types[size])
-                icns.write(b'\0\0\0\0')
-                start = icns.tell()
-
+        for size, image in sorted(self.images.items(), key=lambda item:item[0]):
+            if size in png_types and pngtype is not None:
+                stream = StringStream()
                 image.write(stream, "", pngtype)
                 image.write(stream, "", pngtype)
-                pngsize = icns.tell() - start
-                icns.seek(start - 4)
-                icns.write(struct.pack('>I', pngsize + 8))
-                icns.seek(start + pngsize)
+                pngdata = stream.data
+
+                icns.write(png_types[size])
+                icns.write(struct.pack('>I', len(pngdata)))
+                icns.write(pngdata)
 
 
             elif size in icon_types:
             elif size in icon_types:
+                # If it has an alpha channel, we write out a mask too.
+                if image.hasAlpha():
+                    icns.write(mask_types[size])
+                    icns.write(struct.pack('>I', size * size + 8))
+
+                    for y in xrange(size):
+                        for x in xrange(size):
+                            icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
+
                 icns.write(icon_types[size])
                 icns.write(icon_types[size])
                 icns.write(struct.pack('>I', size * size * 4 + 8))
                 icns.write(struct.pack('>I', size * size * 4 + 8))
 
 
@@ -435,15 +581,6 @@ class Icon:
                         r, g, b = image.getXel(x, y)
                         r, g, b = image.getXel(x, y)
                         icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
                         icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
 
 
-                if not image.hasAlpha():
-                    continue
-                icns.write(mask_types[size])
-                icns.write(struct.pack('>I', size * size + 8))
-
-                for y in xrange(size):
-                    for x in xrange(size):
-                        icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
-
         length = icns.tell()
         length = icns.tell()
         icns.seek(4)
         icns.seek(4)
         icns.write(struct.pack('>I', length))
         icns.write(struct.pack('>I', length))

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

@@ -207,7 +207,10 @@ class ShowBase(DirectObject.DirectObject):
             # Has the clusterSyncFlag been set via a config variable
             # Has the clusterSyncFlag been set via a config variable
             self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
             self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
 
 
-        self.hidden = NodePath('hidden')
+        # We've already created aspect2d in ShowBaseGlobal, for the
+        # benefit of creating DirectGui elements before ShowBase.
+        from . import ShowBaseGlobal
+        self.hidden = ShowBaseGlobal.hidden
 
 
         ## The global graphics engine, ie. GraphicsEngine.getGlobalPtr()
         ## The global graphics engine, ie. GraphicsEngine.getGlobalPtr()
         self.graphicsEngine = GraphicsEngine.getGlobalPtr()
         self.graphicsEngine = GraphicsEngine.getGlobalPtr()

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

@@ -25,6 +25,7 @@ pandaSystem = PandaSystem.getGlobalPtr()
 
 
 # This is defined here so GUI elements can be instantiated before ShowBase.
 # This is defined here so GUI elements can be instantiated before ShowBase.
 aspect2d = NodePath(PGTop("aspect2d"))
 aspect2d = NodePath(PGTop("aspect2d"))
+hidden = NodePath("hidden")
 
 
 # Set direct notify categories now that we have config
 # Set direct notify categories now that we have config
 directNotify.setDconfigLevels()
 directNotify.setDconfigLevels()

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

@@ -5,6 +5,7 @@ a particular color."""
 __all__ = ['Transitions']
 __all__ = ['Transitions']
 
 
 from panda3d.core import *
 from panda3d.core import *
+from direct.showbase import ShowBaseGlobal
 from direct.gui.DirectGui import DirectFrame
 from direct.gui.DirectGui import DirectFrame
 from direct.gui import DirectGuiGlobals as DGG
 from direct.gui import DirectGuiGlobals as DGG
 from direct.interval.LerpInterval import LerpColorScaleInterval, LerpColorInterval, LerpScaleInterval, LerpPosInterval
 from direct.interval.LerpInterval import LerpColorScaleInterval, LerpColorInterval, LerpScaleInterval, LerpPosInterval
@@ -73,7 +74,7 @@ class Transitions:
             # so that it will also obscure mouse events for objects
             # so that it will also obscure mouse events for objects
             # positioned behind it.
             # positioned behind it.
             self.fade = DirectFrame(
             self.fade = DirectFrame(
-                parent = hidden,
+                parent = ShowBaseGlobal.hidden,
                 guiId = 'fade',
                 guiId = 'fade',
                 relief = None,
                 relief = None,
                 image = self.fadeModel,
                 image = self.fadeModel,

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

@@ -4,6 +4,7 @@ __all__ = ['MopathRecorder']
 
 
 # Import Tkinter, Pmw, and the dial code from this directory tree.
 # Import Tkinter, Pmw, and the dial code from this directory tree.
 from panda3d.core import *
 from panda3d.core import *
+from direct.showbase import ShowBaseGlobal
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
 from direct.tkwidgets.AppShell import *
 from direct.tkwidgets.AppShell import *
@@ -852,7 +853,7 @@ class MopathRecorder(AppShell, DirectObject):
         if self.getVariable('Style', 'Marker').get():
         if self.getVariable('Style', 'Marker').get():
             self.playbackMarker.reparentTo(self.recorderNodePath)
             self.playbackMarker.reparentTo(self.recorderNodePath)
         else:
         else:
-            self.playbackMarker.reparentTo(hidden)
+            self.playbackMarker.reparentTo(ShowBaseGlobal.hidden)
 
 
     def setNumSegs(self, value):
     def setNumSegs(self, value):
         self.numSegs = int(value)
         self.numSegs = int(value)

+ 38 - 0
doc/ReleaseNotes

@@ -1,3 +1,41 @@
+------------------------  RELEASE 1.10.4  -----------------------
+
+This release fixes a regression with DirectScrolledList in 1.10.3,
+fixes various other bugs, and introduces a few minor features.
+
+* Fix exception trying to create DirectScrolledList
+* Fix flickering in DirectScrolledFrame and other scissor issues (#681)
+* Experimental support for Python 3.8
+* Support adding icons to deployed applications
+* Support non-affine (eg. projective) transforms in calc_tight_bounds
+* Allow setting notify-output after initial import
+* Fix macOS issue locating Panda3D using Python 2.7.13+ from python.org
+* Support for Maya 2019
+* On Windows, pip is now installed by the installer (#690)
+* Fix Actor.makeSubpart on models with pre-bound animations (#647)
+* Properly interrupt task manager if first task chain raises error (#692)
+* Fix return value of encrypt_string in Python 3 (#684)
+* Support writing loader plug-ins in Python
+* Fix reading multiple p3d_TextureMatrix[] values from GLSL shaders
+* Fix shader error flag not being set if GLSL compilation failed (#622)
+* Add NodePath.replace_texture() convenience method
+* Fix deadlock when building with SIMPLE_THREADS=1 (#704)
+* Fix DirectOptionMenu cancelFrame not working inside scrolled frame (#658)
+* Fix assertion when calling analyze() on geometry with strip cut index
+* Implement fallback in GL renderer when F_sluminance is not supported (#693)
+* Set reasonable limits for sliders in ParticlePanel
+* Fix for DirectEntry autoCapitalize feature on Python 3 (#628)
+* Fix various DirectGUI items not working before ShowBase is instantiated
+* Work around an MSVC compiler bug in the release build
+* PythonUtil.weightedChoice now raises IndexError on empty list
+* Support changing DirectScrollBar width after initialiation (#699)
+* Workaround for Bullet deadlock when adding shape to a scaled body (#689)
+* Support setting DirectEntryScroll entry after initialization (#702)
+* Fix some missing imports in directtools (#698)
+* Fix undefined behavior issue when using musl-libc
+* Update Eigen in Windows thirdparty packages to 3.3.7
+* Update metadata of pip wheels
+
 ------------------------  RELEASE 1.10.3  -----------------------
 ------------------------  RELEASE 1.10.3  -----------------------
 
 
 This is another bugfix release that addresses a variety of issues
 This is another bugfix release that addresses a variety of issues

+ 10 - 1
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -2878,8 +2878,13 @@ write_module_class(ostream &out, Object *obj) {
 
 
   // destructor tp_dealloc;
   // destructor tp_dealloc;
   out << "    &Dtool_FreeInstance_" << ClassName << ",\n";
   out << "    &Dtool_FreeInstance_" << ClassName << ",\n";
-  // printfunc tp_print;
+
+  out << "#if PY_VERSION_HEX >= 0x03080000\n";
+  out << "    0, // tp_vectorcall_offset\n";
+  out << "#else\n";
   write_function_slot(out, 4, slots, "tp_print");
   write_function_slot(out, 4, slots, "tp_print");
+  out << "#endif\n";
+
   // getattrfunc tp_getattr;
   // getattrfunc tp_getattr;
   write_function_slot(out, 4, slots, "tp_getattr");
   write_function_slot(out, 4, slots, "tp_getattr");
   // setattrfunc tp_setattr;
   // setattrfunc tp_setattr;
@@ -3070,6 +3075,10 @@ write_module_class(ostream &out, Object *obj) {
   out << "#if PY_VERSION_HEX >= 0x03040000\n";
   out << "#if PY_VERSION_HEX >= 0x03040000\n";
   out << "    nullptr, // tp_finalize\n";
   out << "    nullptr, // tp_finalize\n";
   out << "#endif\n";
   out << "#endif\n";
+  // vectorcallfunc tp_vectorcall
+  out << "#if PY_VERSION_HEX >= 0x03080000\n";
+  out << "    nullptr, // tp_vectorcall\n";
+  out << "#endif\n";
   out << "  },\n";
   out << "  },\n";
 
 
   // It's tempting to initialize the type handle here, but this causes static
   // It's tempting to initialize the type handle here, but this causes static

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

@@ -80,7 +80,7 @@ Dtool_PyTypedObject *Dtool_GetSuperBase() {
       sizeof(Dtool_PyInstDef),
       sizeof(Dtool_PyInstDef),
       0, // tp_itemsize
       0, // tp_itemsize
       &Dtool_FreeInstance_DTOOL_SUPER_BASE,
       &Dtool_FreeInstance_DTOOL_SUPER_BASE,
-      nullptr, // tp_print
+      0, // tp_vectorcall_offset
       nullptr, // tp_getattr
       nullptr, // tp_getattr
       nullptr, // tp_setattr
       nullptr, // tp_setattr
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
@@ -129,6 +129,13 @@ Dtool_PyTypedObject *Dtool_GetSuperBase() {
       nullptr, // tp_subclasses
       nullptr, // tp_subclasses
       nullptr, // tp_weaklist
       nullptr, // tp_weaklist
       nullptr, // tp_del
       nullptr, // tp_del
+      0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+      nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+      nullptr, // tp_vectorcall
+#endif
     },
     },
     TypeHandle::none(),
     TypeHandle::none(),
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE,
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE,

+ 1 - 1
dtool/src/interrogatedb/py_compat.h

@@ -139,7 +139,7 @@ typedef long Py_hash_t;
 
 
 /* Python 3.6 */
 /* Python 3.6 */
 
 
-#ifndef _PyObject_CallNoArg
+#if PY_VERSION_HEX < 0x03080000 && !defined(_PyObject_CallNoArg)
 INLINE PyObject *_PyObject_CallNoArg(PyObject *func) {
 INLINE PyObject *_PyObject_CallNoArg(PyObject *func) {
   static PyTupleObject empty_tuple = {PyVarObject_HEAD_INIT(nullptr, 0)};
   static PyTupleObject empty_tuple = {PyVarObject_HEAD_INIT(nullptr, 0)};
 #ifdef Py_TRACE_REFS
 #ifdef Py_TRACE_REFS

+ 73 - 10
dtool/src/interrogatedb/py_wrappers.cxx

@@ -541,7 +541,7 @@ static PyObject *Dtool_MappingWrapper_keys(PyObject *self, PyObject *) {
     sizeof(Dtool_SequenceWrapper),
     sizeof(Dtool_SequenceWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -582,6 +582,13 @@ static PyObject *Dtool_MappingWrapper_keys(PyObject *self, PyObject *) {
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -675,7 +682,7 @@ static PyObject *Dtool_MappingWrapper_values(PyObject *self, PyObject *) {
     sizeof(Dtool_MappingWrapper),
     sizeof(Dtool_MappingWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -716,6 +723,13 @@ static PyObject *Dtool_MappingWrapper_values(PyObject *self, PyObject *) {
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -817,7 +831,7 @@ static PyObject *Dtool_MappingWrapper_items(PyObject *self, PyObject *) {
     sizeof(Dtool_MappingWrapper),
     sizeof(Dtool_MappingWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -858,6 +872,13 @@ static PyObject *Dtool_MappingWrapper_items(PyObject *self, PyObject *) {
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -1192,7 +1213,7 @@ Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name
     sizeof(Dtool_SequenceWrapper),
     sizeof(Dtool_SequenceWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -1233,6 +1254,13 @@ Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -1296,7 +1324,7 @@ Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, co
     sizeof(Dtool_MutableSequenceWrapper),
     sizeof(Dtool_MutableSequenceWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -1337,6 +1365,13 @@ Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, co
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -1404,7 +1439,7 @@ Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name)
     sizeof(Dtool_MappingWrapper),
     sizeof(Dtool_MappingWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -1445,6 +1480,13 @@ Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name)
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -1517,7 +1559,7 @@ Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char
     sizeof(Dtool_MappingWrapper),
     sizeof(Dtool_MappingWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -1558,6 +1600,13 @@ Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   static bool registered = false;
   static bool registered = false;
@@ -1594,7 +1643,7 @@ Dtool_NewGenerator(PyObject *self, iternextfunc gen_next) {
     sizeof(Dtool_GeneratorWrapper),
     sizeof(Dtool_GeneratorWrapper),
     0, // tp_itemsize
     0, // tp_itemsize
     Dtool_WrapperBase_dealloc,
     Dtool_WrapperBase_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_compare
     nullptr, // tp_compare
@@ -1635,6 +1684,13 @@ Dtool_NewGenerator(PyObject *self, iternextfunc gen_next) {
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   if (PyType_Ready(&wrapper_type) < 0) {
   if (PyType_Ready(&wrapper_type) < 0) {
@@ -1663,7 +1719,7 @@ Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
     sizeof(PyGetSetDescrObject),
     sizeof(PyGetSetDescrObject),
     0, // tp_itemsize
     0, // tp_itemsize
     (destructor)Dtool_StaticProperty_dealloc,
     (destructor)Dtool_StaticProperty_dealloc,
-    nullptr, // tp_print
+    0, // tp_vectorcall_offset
     nullptr, // tp_getattr
     nullptr, // tp_getattr
     nullptr, // tp_setattr
     nullptr, // tp_setattr
     nullptr, // tp_reserved
     nullptr, // tp_reserved
@@ -1696,7 +1752,7 @@ Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
     nullptr, // tp_init
     nullptr, // tp_init
     nullptr, // tp_alloc
     nullptr, // tp_alloc
     nullptr, // tp_new
     nullptr, // tp_new
-    nullptr, // tp_del
+    nullptr, // tp_free
     nullptr, // tp_is_gc
     nullptr, // tp_is_gc
     nullptr, // tp_bases
     nullptr, // tp_bases
     nullptr, // tp_mro
     nullptr, // tp_mro
@@ -1704,6 +1760,13 @@ Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
     nullptr, // tp_subclasses
     nullptr, // tp_subclasses
     nullptr, // tp_weaklist
     nullptr, // tp_weaklist
     nullptr, // tp_del
     nullptr, // tp_del
+    0, // tp_version_tag,
+#if PY_VERSION_HEX >= 0x03040000
+    nullptr, // tp_finalize
+#endif
+#if PY_VERSION_HEX >= 0x03080000
+    nullptr, // tp_vectorcall
+#endif
   };
   };
 
 
   if (PyType_Ready(&wrapper_type) < 0) {
   if (PyType_Ready(&wrapper_type) < 0) {

+ 4 - 1
dtool/src/prc/notify.cxx

@@ -19,9 +19,12 @@
 #include "filename.h"
 #include "filename.h"
 #include "config_prc.h"
 #include "config_prc.h"
 
 
-#include <atomic>
 #include <ctype.h>
 #include <ctype.h>
 
 
+#ifdef PHAVE_ATOMIC
+#include <atomic>
+#endif
+
 #ifdef BUILD_IPHONE
 #ifdef BUILD_IPHONE
 #include <fcntl.h>
 #include <fcntl.h>
 #endif
 #endif

+ 27 - 0
makepanda/installer.nsi

@@ -84,6 +84,7 @@ LangString DESC_SecTools ${LANG_ENGLISH} "Useful tools and model converters to h
 LangString DESC_SecGroupPython ${LANG_ENGLISH} "Contains modules that provide Python support for Panda3D."
 LangString DESC_SecGroupPython ${LANG_ENGLISH} "Contains modules that provide Python support for Panda3D."
 LangString DESC_SecPyShared ${LANG_ENGLISH} "Contains the common Python code used by the Panda3D Python bindings."
 LangString DESC_SecPyShared ${LANG_ENGLISH} "Contains the common Python code used by the Panda3D Python bindings."
 LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${INCLUDE_PYVER} preconfigured to make use of Panda3D."
 LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${INCLUDE_PYVER} preconfigured to make use of Panda3D."
+LangString DESC_SecEnsurePip ${LANG_ENGLISH} "Installs the pip package manager into the included Python installation."
 LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D."
 LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D."
 LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D."
 LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D."
 LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D."
 LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D."
@@ -477,6 +478,19 @@ Section "Python ${INCLUDE_PYVER}" SecPython
     SkipRegPath:
     SkipRegPath:
 
 
 SectionEnd
 SectionEnd
+
+Section "Install pip" SecEnsurePip
+    SectionIn 1 2 3
+
+    SetDetailsPrint both
+    DetailPrint "Installing the pip package manager..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR
+    nsExec::ExecToLog '"$INSTDIR\python\python.exe" -m ensurepip --default-pip'
+    Pop $0
+    DetailPrint "Command returned exit status $0"
+SectionEnd
 !endif
 !endif
 
 
 !macro MaybeEnablePyBindingSection PYVER
 !macro MaybeEnablePyBindingSection PYVER
@@ -537,6 +551,18 @@ Function .onSelChange
         IntOp $R0 $R0 | ${SF_SELECTED}
         IntOp $R0 $R0 | ${SF_SELECTED}
         SectionSetFlags ${SecPyShared} $R0
         SectionSetFlags ${SecPyShared} $R0
     ${EndIf}
     ${EndIf}
+
+    !ifdef INCLUDE_PYVER
+        ${If} ${SectionIsSelected} ${SecPython}
+            !insertmacro SectionFlagIsSet ${SecEnsurePip} ${SF_RO} 0 SkipSelectEnsurePip
+            !insertmacro SelectSection ${SecEnsurePip}
+            SkipSelectEnsurePip:
+            !insertmacro ClearSectionFlag ${SecEnsurePip} ${SF_RO}
+        ${Else}
+            !insertmacro UnselectSection ${SecEnsurePip}
+            !insertmacro SetSectionFlag ${SecEnsurePip} ${SF_RO}
+        ${EndIf}
+    !endif
 FunctionEnd
 FunctionEnd
 
 
 !ifdef INCLUDE_PYVER
 !ifdef INCLUDE_PYVER
@@ -917,6 +943,7 @@ SectionEnd
   !endif
   !endif
   !ifdef INCLUDE_PYVER
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecEnsurePip} $(DESC_SecEnsurePip)
   !endif
   !endif
   !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs)
   !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs)
   !ifdef HAVE_SAMPLES
   !ifdef HAVE_SAMPLES

+ 3 - 3
makepanda/makepackage.py

@@ -562,9 +562,9 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
         oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
         oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
         WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D")
         WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D")
 
 
-        # Evidently not all Python 3.7 installations read the above path, so do this for now
-        # See GitHub #502
-        if pyver == "3.7":
+        # Somewhere in Python 2.7.13 and 3.7, the above path was removed from
+        # sys.path of the python.org distribution.  See bpo-28440 and GH #502.
+        if pyver not in ("3.0", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6"):
             dir = "dstroot/pybindings%s/Library/Frameworks/Python.framework/Versions/%s/lib/python%s/site-packages" % (pyver, pyver, pyver)
             dir = "dstroot/pybindings%s/Library/Frameworks/Python.framework/Versions/%s/lib/python%s/site-packages" % (pyver, pyver, pyver)
             oscmd("mkdir -p %s" % (dir))
             oscmd("mkdir -p %s" % (dir))
             WriteFile("%s/Panda3D.pth" % (dir), "/Developer/Panda3D")
             WriteFile("%s/Panda3D.pth" % (dir), "/Developer/Panda3D")

+ 4 - 3
makepanda/makepanda.py

@@ -2903,7 +2903,9 @@ if '__file__' in locals():
 
 
     bindir = os.path.join(os.path.dirname(__file__), '..', 'bin')
     bindir = os.path.join(os.path.dirname(__file__), '..', 'bin')
     if os.path.isdir(bindir):
     if os.path.isdir(bindir):
-        if not os.environ.get('PATH'):
+        if hasattr(os, 'add_dll_directory'):
+            os.add_dll_directory(bindir)
+        elif not os.environ.get('PATH'):
             os.environ['PATH'] = bindir
             os.environ['PATH'] = bindir
         else:
         else:
             os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
             os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
@@ -3210,9 +3212,8 @@ if tp_dir is not None:
 
 
 # Copy over the MSVC runtime.
 # Copy over the MSVC runtime.
 if GetTarget() == 'windows' and "VISUALSTUDIO" in SDK:
 if GetTarget() == 'windows' and "VISUALSTUDIO" in SDK:
-    vsver = "%s%s" % SDK["VISUALSTUDIO_VERSION"]
     vcver = "%s%s" % (SDK["MSVC_VERSION"][0], 0)        # ignore minor version.
     vcver = "%s%s" % (SDK["MSVC_VERSION"][0], 0)        # ignore minor version.
-    crtname = "Microsoft.VC%s.CRT" % (vsver)
+    crtname = "Microsoft.VC%s.CRT" % (vcver)
     if ("VCTOOLSVERSION" in SDK):
     if ("VCTOOLSVERSION" in SDK):
         dir = os.path.join(SDK["VISUALSTUDIO"], "VC", "Redist", "MSVC", SDK["VCTOOLSVERSION"], "onecore", GetTargetArch(), crtname)
         dir = os.path.join(SDK["VISUALSTUDIO"], "VC", "Redist", "MSVC", SDK["VCTOOLSVERSION"], "onecore", GetTargetArch(), crtname)
     else:
     else:

+ 5 - 1
makepanda/makepandacore.py

@@ -105,6 +105,7 @@ MAYAVERSIONINFO = [("MAYA6",   "6.0"),
                    ("MAYA20165","2016.5"),
                    ("MAYA20165","2016.5"),
                    ("MAYA2017","2017"),
                    ("MAYA2017","2017"),
                    ("MAYA2018","2018"),
                    ("MAYA2018","2018"),
+                   ("MAYA2019","2019"),
 ]
 ]
 
 
 MAXVERSIONINFO = [("MAX6", "SOFTWARE\\Autodesk\\3DSMAX\\6.0", "installdir", "maxsdk\\cssdk\\include"),
 MAXVERSIONINFO = [("MAX6", "SOFTWARE\\Autodesk\\3DSMAX\\6.0", "installdir", "maxsdk\\cssdk\\include"),
@@ -2788,7 +2789,7 @@ def SetupVisualStudioEnviron():
 
 
     # Targeting the 7.1 SDK (which is the only way to have Windows XP support)
     # Targeting the 7.1 SDK (which is the only way to have Windows XP support)
     # with Visual Studio 2015 requires use of the Universal CRT.
     # with Visual Studio 2015 requires use of the Universal CRT.
-    if winsdk_ver == '7.1' and SDK["VISUALSTUDIO_VERSION"] >= (14,0):
+    if winsdk_ver in ('7.1', '7.1A') and SDK["VISUALSTUDIO_VERSION"] >= (14,0):
         win_kit = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10")
         win_kit = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10")
 
 
         # Fallback in case we can't read the registry.
         # Fallback in case we can't read the registry.
@@ -3392,6 +3393,9 @@ def GetPythonABI():
 
 
     soabi = 'cpython-%d%d' % (sys.version_info[:2])
     soabi = 'cpython-%d%d' % (sys.version_info[:2])
 
 
+    if sys.version_info >= (3, 8):
+        return soabi
+
     debug_flag = sysconfig.get_config_var('Py_DEBUG')
     debug_flag = sysconfig.get_config_var('Py_DEBUG')
     if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
     if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
         soabi += 'd'
         soabi += 'd'

+ 8 - 2
makepanda/makewheel.py

@@ -29,6 +29,9 @@ def get_abi_tag():
 
 
     soabi = 'cp%d%d' % (sys.version_info[:2])
     soabi = 'cp%d%d' % (sys.version_info[:2])
 
 
+    if sys.version_info >= (3, 8):
+        return soabi
+
     debug_flag = get_config_var('Py_DEBUG')
     debug_flag = get_config_var('Py_DEBUG')
     if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
     if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
         soabi += 'd'
         soabi += 'd'
@@ -168,15 +171,18 @@ questions.
 PANDA3D_TOOLS_INIT = """import os, sys
 PANDA3D_TOOLS_INIT = """import os, sys
 import panda3d
 import panda3d
 
 
+dir = os.path.dirname(panda3d.__file__)
+del panda3d
+
 if sys.platform in ('win32', 'cygwin'):
 if sys.platform in ('win32', 'cygwin'):
     path_var = 'PATH'
     path_var = 'PATH'
+    if hasattr(os, 'add_dll_directory'):
+        os.add_dll_directory(dir)
 elif sys.platform == 'darwin':
 elif sys.platform == 'darwin':
     path_var = 'DYLD_LIBRARY_PATH'
     path_var = 'DYLD_LIBRARY_PATH'
 else:
 else:
     path_var = 'LD_LIBRARY_PATH'
     path_var = 'LD_LIBRARY_PATH'
 
 
-dir = os.path.dirname(panda3d.__file__)
-del panda3d
 if not os.environ.get(path_var):
 if not os.environ.get(path_var):
     os.environ[path_var] = dir
     os.environ[path_var] = dir
 else:
 else:

+ 8 - 3
makepanda/test_wheel.py

@@ -19,9 +19,6 @@ def test_wheel(wheel, verbose=False):
     print("Setting up virtual environment in {0}".format(envdir))
     print("Setting up virtual environment in {0}".format(envdir))
     sys.stdout.flush()
     sys.stdout.flush()
 
 
-    # Make sure pip is up-to-date first.
-    subprocess.call([sys.executable, "-B", "-m", "pip", "install", "-U", "pip"])
-
     # Create a virtualenv.
     # Create a virtualenv.
     if sys.version_info >= (3, 0):
     if sys.version_info >= (3, 0):
         subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir])
         subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir])
@@ -39,6 +36,14 @@ def test_wheel(wheel, verbose=False):
         shutil.rmtree(envdir)
         shutil.rmtree(envdir)
         sys.exit(1)
         sys.exit(1)
 
 
+    # Temp hack to patch issue pypa/pip#6885 in pip 19.2.2 and Python 3.8.
+    if sys.platform == "win32" and "-cp38-cp38-" in wheel and os.path.isdir(os.path.join(envdir, "Lib", "site-packages", "pip-19.2.2.dist-info")):
+        pep425tags = os.path.join(envdir, "Lib", "site-packages", "pip", "_internal", "pep425tags.py")
+        if os.path.isfile(pep425tags):
+            data = open(pep425tags, "r").read()
+            data = data.replace(" m = 'm'\n", " m = ''\n")
+            open(pep425tags, "w").write(data)
+
     # Install pytest into the environment, as well as our wheel.
     # Install pytest into the environment, as well as our wheel.
     if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0:
     if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0:
         shutil.rmtree(envdir)
         shutil.rmtree(envdir)

+ 18 - 4
panda/src/bullet/bulletBodyNode.cxx

@@ -353,9 +353,17 @@ do_add_shape(BulletShape *bullet_shape, const TransformState *ts) {
 
 
   // Reset the shape scaling before we add a shape, and remember the current
   // Reset the shape scaling before we add a shape, and remember the current
   // Scale so we can restore it later...
   // Scale so we can restore it later...
-  NodePath np = NodePath::any_path((PandaNode *)this);
-  LVector3 scale = np.get_scale();
-  np.set_scale(1.0);
+  CPT(TransformState) prev_transform = get_transform();
+  bool scale_changed = false;
+  if (!prev_transform->is_identity() && prev_transform->get_scale() != LVecBase3(1.0, 1.0, 1.0)) {
+    // As a hack, temporarily release the lock, since transform_changed will
+    // otherwise deadlock trying to grab it again.  See GitHub issue #689.
+    LightMutex &lock = BulletWorld::get_global_lock();
+    lock.release();
+    set_transform(prev_transform->set_scale(LVecBase3(1.0, 1.0, 1.0)));
+    lock.acquire();
+    scale_changed = true;
+  }
 
 
   // Root shape
   // Root shape
   btCollisionShape *previous = get_object()->getCollisionShape();
   btCollisionShape *previous = get_object()->getCollisionShape();
@@ -417,7 +425,13 @@ do_add_shape(BulletShape *bullet_shape, const TransformState *ts) {
   _shapes.push_back(bullet_shape);
   _shapes.push_back(bullet_shape);
 
 
   // Restore the local scaling again
   // Restore the local scaling again
-  np.set_scale(scale);
+  if (scale_changed) {
+    CPT(TransformState) transform = get_transform()->set_scale(prev_transform->get_scale());
+    LightMutex &lock = BulletWorld::get_global_lock();
+    lock.release();
+    set_transform(std::move(transform));
+    lock.acquire();
+  }
 
 
   do_shape_changed();
   do_shape_changed();
 }
 }

+ 6 - 3
panda/src/bullet/bulletContactCallbacks.h

@@ -61,7 +61,9 @@ contact_added_callback(btManifoldPoint &cp,
     PT(PandaNode) node1 = (PandaNode *)obj1->getUserPointer();
     PT(PandaNode) node1 = (PandaNode *)obj1->getUserPointer();
 #endif
 #endif
 
 
-    bullet_cat.debug() << "contact added: " << cp.m_userPersistentData << std::endl;
+    if (bullet_cat.is_debug()) {
+      bullet_cat.debug() << "contact added: " << cp.m_userPersistentData << std::endl;
+    }
 
 
     // Gather persistent data
     // Gather persistent data
     UserPersistentData *data = new UserPersistentData();
     UserPersistentData *data = new UserPersistentData();
@@ -123,8 +125,9 @@ contact_processed_callback(btManifoldPoint &cp,
  */
  */
 static bool
 static bool
 contact_destroyed_callback(void *userPersistentData) {
 contact_destroyed_callback(void *userPersistentData) {
-
-  bullet_cat.debug() << "contact removed: " << userPersistentData << std::endl;
+  if (bullet_cat.is_debug()) {
+    bullet_cat.debug() << "contact removed: " << userPersistentData << std::endl;
+  }
 
 
   UserPersistentData *data = (UserPersistentData *)userPersistentData;
   UserPersistentData *data = (UserPersistentData *)userPersistentData;
 
 

+ 6 - 4
panda/src/bullet/bulletDebugNode.cxx

@@ -384,8 +384,9 @@ drawTriangle(const btVector3 &v0, const btVector3 &v1, const btVector3 &v2, cons
  */
  */
 void BulletDebugNode::DebugDraw::
 void BulletDebugNode::DebugDraw::
 drawTriangle(const btVector3 &v0, const btVector3 &v1, const btVector3 &v2, const btVector3 &n0, const btVector3 &n1, const btVector3 &n2, const btVector3 &color, btScalar alpha) {
 drawTriangle(const btVector3 &v0, const btVector3 &v1, const btVector3 &v2, const btVector3 &n0, const btVector3 &n1, const btVector3 &n2, const btVector3 &color, btScalar alpha) {
-
-  bullet_cat.debug() << "drawTriangle(2) - not yet implemented!" << std::endl;
+  if (bullet_cat.is_debug()) {
+    bullet_cat.debug() << "drawTriangle(2) - not yet implemented!" << std::endl;
+  }
 }
 }
 
 
 /**
 /**
@@ -405,8 +406,9 @@ drawContactPoint(const btVector3 &point, const btVector3 &normal, btScalar dista
  */
  */
 void BulletDebugNode::DebugDraw::
 void BulletDebugNode::DebugDraw::
 draw3dText(const btVector3 &location, const char *text) {
 draw3dText(const btVector3 &location, const char *text) {
-
-  bullet_cat.debug() << "draw3dText - not yet implemented!" << std::endl;
+  if (bullet_cat.is_debug()) {
+    bullet_cat.debug() << "draw3dText - not yet implemented!" << std::endl;
+  }
 }
 }
 
 
 /**
 /**

+ 3 - 1
panda/src/bullet/config_bullet.cxx

@@ -207,7 +207,9 @@ init_libbullet() {
 
 
   // Initialize notification category
   // Initialize notification category
   bullet_cat.init();
   bullet_cat.init();
-  bullet_cat.debug() << "initialize module" << std::endl;
+  if (bullet_cat.is_debug()) {
+    bullet_cat.debug() << "initialize module" << std::endl;
+  }
 
 
   // Register the Bullet system
   // Register the Bullet system
   PandaSystem *ps = PandaSystem::get_global_ptr();
   PandaSystem *ps = PandaSystem::get_global_ptr();

+ 0 - 6
panda/src/linmath/lmatrix3_src.I

@@ -132,11 +132,6 @@ FLOATNAME(LMatrix3)(const FLOATNAME(LVecBase3) &row0,
                     const FLOATNAME(LVecBase3) &row2) {
                     const FLOATNAME(LVecBase3) &row2) {
   TAU_PROFILE("LMatrix3::LMatrix3(const LVecBase3 &, ...)", " ", TAU_USER);
   TAU_PROFILE("LMatrix3::LMatrix3(const LVecBase3 &, ...)", " ", TAU_USER);
 
 
-#ifdef HAVE_EIGEN
-  _m.row(0) = row0._v;
-  _m.row(1) = row1._v;
-  _m.row(2) = row2._v;
-#else
   _m(0, 0) = row0._v(0);
   _m(0, 0) = row0._v(0);
   _m(0, 1) = row0._v(1);
   _m(0, 1) = row0._v(1);
   _m(0, 2) = row0._v(2);
   _m(0, 2) = row0._v(2);
@@ -146,7 +141,6 @@ FLOATNAME(LMatrix3)(const FLOATNAME(LVecBase3) &row0,
   _m(2, 0) = row2._v(0);
   _m(2, 0) = row2._v(0);
   _m(2, 1) = row2._v(1);
   _m(2, 1) = row2._v(1);
   _m(2, 2) = row2._v(2);
   _m(2, 2) = row2._v(2);
-#endif  // HAVE_EIGEN
 }
 }
 
 
 /**
 /**

+ 6 - 2
panda/src/ode/odeGeom.cxx

@@ -33,12 +33,16 @@ TypeHandle OdeGeom::_type_handle;
 OdeGeom::
 OdeGeom::
 OdeGeom(dGeomID id) :
 OdeGeom(dGeomID id) :
   _id(id) {
   _id(id) {
-  odegeom_cat.debug() << get_type() << "(" << _id << ")\n";
+  if (odegeom_cat.is_debug()) {
+    odegeom_cat.debug() << get_type() << "(" << _id << ")\n";
+  }
 }
 }
 
 
 OdeGeom::
 OdeGeom::
 ~OdeGeom() {
 ~OdeGeom() {
-  odegeom_cat.debug() << "~" << get_type() << "(" << _id << ")\n";
+  if (odegeom_cat.is_debug()) {
+    odegeom_cat.debug() << "~" << get_type() << "(" << _id << ")\n";
+  }
   /*
   /*
   GeomSurfaceMap::iterator iter = _geom_surface_map.find(this->get_id());
   GeomSurfaceMap::iterator iter = _geom_surface_map.find(this->get_id());
   if (iter != _geom_surface_map.end()) {
   if (iter != _geom_surface_map.end()) {

+ 8 - 4
panda/src/ode/odeJoint.cxx

@@ -31,15 +31,19 @@ TypeHandle OdeJoint::_type_handle;
 OdeJoint::
 OdeJoint::
 OdeJoint() :
 OdeJoint() :
   _id(nullptr) {
   _id(nullptr) {
-  std::ostream &out = odejoint_cat.debug();
-  out << get_type() << "(" << _id  << ")\n";
+  if (odejoint_cat.is_debug()) {
+    std::ostream &out = odejoint_cat.debug();
+    out << get_type() << "(" << _id  << ")\n";
+  }
 }
 }
 
 
 OdeJoint::
 OdeJoint::
 OdeJoint(dJointID id) :
 OdeJoint(dJointID id) :
   _id(id) {
   _id(id) {
-  std::ostream &out = odejoint_cat.debug();
-  out << get_type() << "(" << _id  << ")\n";
+  if (odejoint_cat.is_debug()) {
+    std::ostream &out = odejoint_cat.debug();
+    out << get_type() << "(" << _id  << ")\n";
+  }
 }
 }
 
 
 OdeJoint::
 OdeJoint::

+ 5 - 3
panda/src/ode/odeSpace.cxx

@@ -157,9 +157,11 @@ auto_callback(void *data, dGeomID o1, dGeomID o2) {
   numc = dCollide(o1, o2, OdeSpace::MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
   numc = dCollide(o1, o2, OdeSpace::MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
 
 
   if (numc) {
   if (numc) {
-    odespace_cat.debug() << "collision between geoms " << o1 << " and " << o2 << "\n";
-    odespace_cat.debug() << "collision between body " << b1 << " and " << b2 << "\n";
-    odespace_cat.debug() << "surface1= "<< surface1 << " surface2=" << surface2 << "\n";
+    if (odespace_cat.is_debug()) {
+      odespace_cat.debug() << "collision between geoms " << o1 << " and " << o2 << "\n";
+      odespace_cat.debug() << "collision between body " << b1 << " and " << b2 << "\n";
+      odespace_cat.debug() << "surface1= "<< surface1 << " surface2=" << surface2 << "\n";
+    }
 
 
     PT(OdeCollisionEntry) entry;
     PT(OdeCollisionEntry) entry;
     if (!_static_auto_collide_space->_collision_event.empty()) {
     if (!_static_auto_collide_space->_collision_event.empty()) {

+ 3 - 1
panda/src/ode/odeTriMeshData.I

@@ -38,7 +38,9 @@ get(int data_id) {
 
 
 INLINE void OdeTriMeshData::
 INLINE void OdeTriMeshData::
 build_single(const void* vertices, int vertex_stride, int vertex_count, const void* indices, int index_count, int tri_stride) {
 build_single(const void* vertices, int vertex_stride, int vertex_count, const void* indices, int index_count, int tri_stride) {
-  odetrimeshdata_cat.debug() << "build_single(" << vertices << ", " << vertex_stride << ", " << vertex_count << ", " << indices << ", " << index_count << ", " << tri_stride << ")\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << "build_single(" << vertices << ", " << vertex_stride << ", " << vertex_count << ", " << indices << ", " << index_count << ", " << tri_stride << ")\n";
+  }
   dGeomTriMeshDataBuildSingle(_id, vertices, vertex_stride, vertex_count, indices, index_count, tri_stride);
   dGeomTriMeshDataBuildSingle(_id, vertices, vertex_stride, vertex_count, indices, index_count, tri_stride);
 }
 }
 
 

+ 44 - 20
panda/src/ode/odeTriMeshData.cxx

@@ -20,7 +20,9 @@ OdeTriMeshData::TriMeshDataMap *OdeTriMeshData::_tri_mesh_data_map = nullptr;
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 link_data(dGeomID id, PT(OdeTriMeshData) data) {
 link_data(dGeomID id, PT(OdeTriMeshData) data) {
-  odetrimeshdata_cat.debug() << get_class_type() << "::link_data(" << id << "->" << data << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << get_class_type() << "::link_data(" << id << "->" << data << ")" << "\n";
+  }
   get_tri_mesh_data_map()[id] = data;
   get_tri_mesh_data_map()[id] = data;
 }
 }
 
 
@@ -36,7 +38,9 @@ get_data(dGeomID id) {
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 unlink_data(dGeomID id) {
 unlink_data(dGeomID id) {
-  odetrimeshdata_cat.debug() << get_class_type() << "::unlink_data(" << id << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << get_class_type() << "::unlink_data(" << id << ")" << "\n";
+  }
   nassertv(_tri_mesh_data_map != nullptr);
   nassertv(_tri_mesh_data_map != nullptr);
   TriMeshDataMap::iterator iter = _tri_mesh_data_map->find(id);
   TriMeshDataMap::iterator iter = _tri_mesh_data_map->find(id);
   if (iter != _tri_mesh_data_map->end()) {
   if (iter != _tri_mesh_data_map->end()) {
@@ -46,11 +50,13 @@ unlink_data(dGeomID id) {
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 print_data(const std::string &marker) {
 print_data(const std::string &marker) {
-  odetrimeshdata_cat.debug() << get_class_type() << "::print_data(" << marker << ")\n";
-  const TriMeshDataMap &data_map = get_tri_mesh_data_map();
-  TriMeshDataMap::const_iterator iter = data_map.begin();
-  for (;iter != data_map.end(); ++iter) {
-    odetrimeshdata_cat.debug() << "\t" << iter->first << " : " << iter->second << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << get_class_type() << "::print_data(" << marker << ")\n";
+    const TriMeshDataMap &data_map = get_tri_mesh_data_map();
+    TriMeshDataMap::const_iterator iter = data_map.begin();
+    for (;iter != data_map.end(); ++iter) {
+      odetrimeshdata_cat.debug() << "\t" << iter->first << " : " << iter->second << "\n";
+    }
   }
   }
 }
 }
 
 
@@ -95,11 +101,15 @@ OdeTriMeshData(const NodePath& model, bool use_normals) :
   _normals(nullptr),
   _normals(nullptr),
   _num_vertices(0),
   _num_vertices(0),
   _num_faces(0) {
   _num_faces(0) {
-  odetrimeshdata_cat.debug() << get_type() << "(" << _id << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << get_type() << "(" << _id << ")" << "\n";
+  }
 
 
   process_model(model, use_normals);
   process_model(model, use_normals);
 
 
-  write_faces(odetrimeshdata_cat.debug());
+  if (odetrimeshdata_cat.is_debug()) {
+    write_faces(odetrimeshdata_cat.debug());
+  }
 
 
 #ifdef dSINGLE
 #ifdef dSINGLE
   if (!use_normals) {
   if (!use_normals) {
@@ -131,7 +141,9 @@ OdeTriMeshData(const OdeTriMeshData &other) {
 
 
 OdeTriMeshData::
 OdeTriMeshData::
 ~OdeTriMeshData() {
 ~OdeTriMeshData() {
-  odetrimeshdata_cat.debug() << "~" << get_type() << "(" << _id << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << "~" << get_type() << "(" << _id << ")" << "\n";
+  }
   destroy();
   destroy();
   if (_vertices != nullptr) {
   if (_vertices != nullptr) {
     PANDA_FREE_ARRAY(_vertices);
     PANDA_FREE_ARRAY(_vertices);
@@ -152,7 +164,9 @@ OdeTriMeshData::
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 destroy() {
 destroy() {
-  odetrimeshdata_cat.debug() << get_type() << "::destroy(" << _id << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << get_type() << "::destroy(" << _id << ")" << "\n";
+  }
   if (_id != nullptr) {
   if (_id != nullptr) {
     dGeomTriMeshDataDestroy(_id);
     dGeomTriMeshDataDestroy(_id);
     remove_data(this);
     remove_data(this);
@@ -185,8 +199,10 @@ process_model(const NodePath& model, bool &use_normals) {
     analyze((GeomNode*)geomNodePaths[i].node());
     analyze((GeomNode*)geomNodePaths[i].node());
   }
   }
 
 
-  odetrimeshdata_cat.debug() << "Found " << _num_vertices << " vertices.\n";
-  odetrimeshdata_cat.debug() << "Found " << _num_faces << " faces.\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug() << "Found " << _num_vertices << " vertices.\n";
+    odetrimeshdata_cat.debug() << "Found " << _num_faces << " faces.\n";
+  }
 
 
   _vertices = (StridedVertex *)PANDA_MALLOC_ARRAY(_num_vertices * sizeof(StridedVertex));
   _vertices = (StridedVertex *)PANDA_MALLOC_ARRAY(_num_vertices * sizeof(StridedVertex));
   _faces = (StridedTri *)PANDA_MALLOC_ARRAY(_num_faces * sizeof(StridedTri));
   _faces = (StridedTri *)PANDA_MALLOC_ARRAY(_num_faces * sizeof(StridedTri));
@@ -195,17 +211,23 @@ process_model(const NodePath& model, bool &use_normals) {
 
 
   for (int i = 0; i < geomNodePaths.get_num_paths(); ++i) {
   for (int i = 0; i < geomNodePaths.get_num_paths(); ++i) {
     process_geom_node((GeomNode*)geomNodePaths[i].node());
     process_geom_node((GeomNode*)geomNodePaths[i].node());
-    odetrimeshdata_cat.debug() << "_num_vertices now at " << _num_vertices << "\n";
+    if (odetrimeshdata_cat.is_debug()) {
+      odetrimeshdata_cat.debug() << "_num_vertices now at " << _num_vertices << "\n";
+    }
   }
   }
 
 
-  odetrimeshdata_cat.debug() << "Filled " << _num_faces << " triangles(" \
-                      << _num_vertices << " vertices)\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    odetrimeshdata_cat.debug()
+      << "Filled " << _num_faces << " triangles(" << _num_vertices << " vertices)\n";
+  }
 }
 }
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 process_geom_node(const GeomNode *geomNode) {
 process_geom_node(const GeomNode *geomNode) {
-  ostream &out = odetrimeshdata_cat.debug();
-  out.width(2); out << "" << "process_geom_node(" << *geomNode << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    ostream &out = odetrimeshdata_cat.debug();
+    out.width(2); out << "" << "process_geom_node(" << *geomNode << ")" << "\n";
+  }
   for (int i = 0; i < geomNode->get_num_geoms(); ++i) {
   for (int i = 0; i < geomNode->get_num_geoms(); ++i) {
     process_geom(geomNode->get_geom(i));
     process_geom(geomNode->get_geom(i));
   }
   }
@@ -213,8 +235,10 @@ process_geom_node(const GeomNode *geomNode) {
 
 
 void OdeTriMeshData::
 void OdeTriMeshData::
 process_geom(const Geom *geom) {
 process_geom(const Geom *geom) {
-  ostream &out = odetrimeshdata_cat.debug();
-  out.width(4); out << "" << "process_geom(" << *geom << ")" << "\n";
+  if (odetrimeshdata_cat.is_debug()) {
+    ostream &out = odetrimeshdata_cat.debug();
+    out.width(4); out << "" << "process_geom(" << *geom << ")" << "\n";
+  }
   if (geom->get_primitive_type() != Geom::PT_polygons) {
   if (geom->get_primitive_type() != Geom::PT_polygons) {
     return;
     return;
   }
   }

+ 9 - 3
panda/src/ode/odeWorld.cxx

@@ -20,7 +20,9 @@ TypeHandle OdeWorld::_type_handle;
 OdeWorld::
 OdeWorld::
 OdeWorld() :
 OdeWorld() :
   _id(dWorldCreate()) {
   _id(dWorldCreate()) {
-  odeworld_cat.debug() << get_type() << "(" << _id << ")" << "\n";
+  if (odeworld_cat.is_debug()) {
+    odeworld_cat.debug() << get_type() << "(" << _id << ")" << "\n";
+  }
   _num_surfaces = 0;
   _num_surfaces = 0;
 
 
 }
 }
@@ -34,7 +36,9 @@ OdeWorld(const OdeWorld &copy) :
 
 
 OdeWorld::
 OdeWorld::
 ~OdeWorld() {
 ~OdeWorld() {
-  odeworld_cat.debug() << "~" << get_type() << "(" << _id << ")" << "\n";
+  if (odeworld_cat.is_debug()) {
+    odeworld_cat.debug() << "~" << get_type() << "(" << _id << ")" << "\n";
+  }
 }
 }
 
 
 void OdeWorld::
 void OdeWorld::
@@ -71,7 +75,9 @@ init_surface_table(uint8_t num_surfaces) {
 
 
 void OdeWorld::
 void OdeWorld::
 set_surface(int pos1, int pos2, sSurfaceParams& entry) {
 set_surface(int pos1, int pos2, sSurfaceParams& entry) {
-  odeworld_cat.debug() << " pos1 " << pos1 << " pos2 " << pos2 << " num surfaces " << (int)_num_surfaces << " endline\n";
+  if (odeworld_cat.is_debug()) {
+    odeworld_cat.debug() << " pos1 " << pos1 << " pos2 " << pos2 << " num surfaces " << (int)_num_surfaces << " endline\n";
+  }
   if((_num_surfaces <= pos1) || (_num_surfaces <= pos2)) {
   if((_num_surfaces <= pos1) || (_num_surfaces <= pos2)) {
     odeworld_cat.error() << "surface position exceeds size of surface table, set num_surface in initSurfaceTable higher." << "\n";
     odeworld_cat.error() << "surface position exceeds size of surface table, set num_surface in initSurfaceTable higher." << "\n";
     return;
     return;

+ 2 - 0
panda/src/pgraph/nodePath.cxx

@@ -3139,6 +3139,8 @@ get_texture(TextureStage *stage) const {
 /**
 /**
  * Recursively searches the scene graph for references to the given texture,
  * Recursively searches the scene graph for references to the given texture,
  * and replaces them with the new texture.
  * and replaces them with the new texture.
+ *
+ * @since 1.10.4
  */
  */
 void NodePath::
 void NodePath::
 replace_texture(Texture *tex, Texture *new_tex) {
 replace_texture(Texture *tex, Texture *new_tex) {

+ 2 - 0
panda/src/pgraph/pythonLoaderFileType.h

@@ -24,6 +24,8 @@
  * This defines a Python-based loader plug-in.  An instance of this can be
  * This defines a Python-based loader plug-in.  An instance of this can be
  * constructed by inheritance and explicitly registered, or it can be created
  * constructed by inheritance and explicitly registered, or it can be created
  * by passing in a pkg_resources.EntryPoint instance.
  * by passing in a pkg_resources.EntryPoint instance.
+ *
+ * @since 1.10.4
  */
  */
 class PythonLoaderFileType : public LoaderFileType {
 class PythonLoaderFileType : public LoaderFileType {
 public:
 public:

+ 3 - 3
panda/src/pgraph/textureAttrib.I

@@ -29,12 +29,12 @@ TextureAttrib() {
 INLINE TextureAttrib::
 INLINE TextureAttrib::
 TextureAttrib(const TextureAttrib &copy) :
 TextureAttrib(const TextureAttrib &copy) :
   _on_stages(copy._on_stages),
   _on_stages(copy._on_stages),
-  _render_stages(copy._render_stages),
-  _render_ff_stages(copy._render_ff_stages),
+  _render_stages(),
+  _render_ff_stages(),
   _next_implicit_sort(copy._next_implicit_sort),
   _next_implicit_sort(copy._next_implicit_sort),
   _off_stages(copy._off_stages),
   _off_stages(copy._off_stages),
   _off_all_stages(copy._off_all_stages),
   _off_all_stages(copy._off_all_stages),
-  _sort_seq(copy._sort_seq),
+  _sort_seq(UpdateSeq::old()),
   _filtered_seq(UpdateSeq::old())
   _filtered_seq(UpdateSeq::old())
 {
 {
 }
 }

+ 2 - 13
panda/src/pgraph/textureAttrib.cxx

@@ -116,10 +116,6 @@ add_on_stage(TextureStage *stage, Texture *tex, int override) const {
   (*si)._has_sampler = false;
   (*si)._has_sampler = false;
   ++(attrib->_next_implicit_sort);
   ++(attrib->_next_implicit_sort);
 
 
-  // We now need to re-sort the attrib list.
-  attrib->_sort_seq = UpdateSeq::old();
-  attrib->_filtered_seq = UpdateSeq::old();
-
   return return_new(attrib);
   return return_new(attrib);
 }
 }
 
 
@@ -140,10 +136,6 @@ add_on_stage(TextureStage *stage, Texture *tex, const SamplerState &sampler, int
   (*si)._has_sampler = true;
   (*si)._has_sampler = true;
   ++(attrib->_next_implicit_sort);
   ++(attrib->_next_implicit_sort);
 
 
-  // We now need to re-sort the attrib list.
-  attrib->_sort_seq = UpdateSeq::old();
-  attrib->_filtered_seq = UpdateSeq::old();
-
   return return_new(attrib);
   return return_new(attrib);
 }
 }
 
 
@@ -158,9 +150,6 @@ remove_on_stage(TextureStage *stage) const {
   Stages::iterator si = attrib->_on_stages.find(StageNode(stage));
   Stages::iterator si = attrib->_on_stages.find(StageNode(stage));
   if (si != attrib->_on_stages.end()) {
   if (si != attrib->_on_stages.end()) {
     attrib->_on_stages.erase(si);
     attrib->_on_stages.erase(si);
-
-    attrib->_sort_seq = UpdateSeq::old();
-    attrib->_filtered_seq = UpdateSeq::old();
   }
   }
 
 
   return return_new(attrib);
   return return_new(attrib);
@@ -182,8 +171,6 @@ add_off_stage(TextureStage *stage, int override) const {
     Stages::iterator si = attrib->_on_stages.find(sn);
     Stages::iterator si = attrib->_on_stages.find(sn);
     if (si != attrib->_on_stages.end()) {
     if (si != attrib->_on_stages.end()) {
       attrib->_on_stages.erase(si);
       attrib->_on_stages.erase(si);
-      attrib->_sort_seq = UpdateSeq::old();
-      attrib->_filtered_seq = UpdateSeq::old();
     }
     }
   }
   }
   return return_new(attrib);
   return return_new(attrib);
@@ -253,6 +240,8 @@ unify_texture_stages(TextureStage *stage) const {
 /**
 /**
  * Returns a new TextureAttrib, just like this one, but with all references to
  * Returns a new TextureAttrib, just like this one, but with all references to
  * the given texture replaced with the new texture.
  * the given texture replaced with the new texture.
+ *
+ * @since 1.10.4
  */
  */
 CPT(RenderAttrib) TextureAttrib::
 CPT(RenderAttrib) TextureAttrib::
 replace_texture(Texture *tex, Texture *new_tex) const {
 replace_texture(Texture *tex, Texture *new_tex) const {

+ 1 - 0
pandatool/src/mayaprogs/mayapath.cxx

@@ -106,6 +106,7 @@ struct MayaVerInfo maya_versions[] = {
   { "MAYA20165", "2016.5"},
   { "MAYA20165", "2016.5"},
   { "MAYA2017", "2017"},
   { "MAYA2017", "2017"},
   { "MAYA2018", "2018"},
   { "MAYA2018", "2018"},
+  { "MAYA2019", "2019"},
   { 0, 0 },
   { 0, 0 },
 };
 };
 
 

+ 1 - 0
setup.cfg

@@ -20,6 +20,7 @@ classifiers =
     Programming Language :: Python :: 3.5
     Programming Language :: Python :: 3.5
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
     Programming Language :: Python :: Implementation :: CPython
     Programming Language :: Python :: Implementation :: CPython
     Topic :: Games/Entertainment
     Topic :: Games/Entertainment
     Topic :: Multimedia
     Topic :: Multimedia

+ 38 - 0
tests/gui/test_DirectEntry.py

@@ -0,0 +1,38 @@
+# coding=utf-8
+from direct.gui.DirectEntry import DirectEntry
+import sys
+
+
+def test_entry_get():
+    entry = DirectEntry()
+    assert isinstance(entry.get(), str)
+
+
+def test_entry_auto_capitalize():
+    # Now we can generate the DirectEntry component itself. In normal use, we
+    # would pass "autoCapitalize=1" to DirectEntry's constructor in order for
+    # DirectEntry._autoCapitalize() to be called upon typing into the entry
+    # GUI, however in the case of this unit test where there is no GUI to type
+    # into, we don't need to bother doing that; we're just calling the function
+    # ourselves anyway.
+    entry = DirectEntry()
+
+    # Test DirectEntry._autoCapitalize(). The intended behavior would be that
+    # the first letter of each word in the entry would be capitalized, so that
+    # is what we will check for:
+    entry.set('auto capitalize test')
+    entry._autoCapitalize()
+    assert entry.get() == 'Auto Capitalize Test'
+
+    # Test DirectEntry._autoCapitalize() with a unicode object this time.
+    entry.set(u'àütò çapítalízè ţèsţ')
+    assert entry.get() == u'àütò çapítalízè ţèsţ'
+    entry._autoCapitalize()
+    assert entry.get() == u'Àütò Çapítalízè Ţèsţ'
+
+    # Also test it with a UTF-8 encoded byte string in Python 2.
+    if sys.version_info < (3, 0):
+        entry.set(u'àütò çapítalízè ţèsţ'.encode('utf-8'))
+        assert entry.get() == u'àütò çapítalízè ţèsţ'
+        entry._autoCapitalize()
+        assert entry.get() == u'Àütò Çapítalízè Ţèsţ'