Forráskód Böngészése

Merge branch 'master' into cmake

Sam Edwards 7 éve
szülő
commit
1a17808991

+ 2 - 2
README.md

@@ -55,8 +55,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
 building them from source.
 
-http://rdb.name/thirdparty-vc14-x64.7z
-http://rdb.name/thirdparty-vc14.7z
+https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win64.zip
+https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win32.zip
 
 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

BIN
contrib/src/panda3dtoolsgui/pandaIcon.ico


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

@@ -1,7 +1,7 @@
 
 print('ThreeUpStart: Starting up environment.')
 
-from pandac.PandaModules import *
+from panda3d.core import *
 
 from direct.showbase.PythonUtil import *
 from direct.showbase import ThreeUpShow

+ 3 - 3
direct/src/directscripts/Doxyfile.cxx

@@ -1124,7 +1124,7 @@ HTML_EXTRA_FILES       =
 # Minimum value: 0, maximum value: 359, default value: 220.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_COLORSTYLE_HUE    = 228
+HTML_COLORSTYLE_HUE    = 251
 
 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
 # in the HTML output. For a value of 0 the output will use grayscales only. A
@@ -1132,7 +1132,7 @@ HTML_COLORSTYLE_HUE    = 228
 # Minimum value: 0, maximum value: 255, default value: 100.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_COLORSTYLE_SAT    = 99
+HTML_COLORSTYLE_SAT    = 150
 
 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
 # luminance component of the colors in the HTML output. Values below 100
@@ -1143,7 +1143,7 @@ HTML_COLORSTYLE_SAT    = 99
 # Minimum value: 40, maximum value: 240, default value: 80.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_COLORSTYLE_GAMMA  = 56
+HTML_COLORSTYLE_GAMMA  = 39
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
 # page will contain the date and time when the page was generated. Setting this

+ 3 - 3
direct/src/directscripts/Doxyfile.python

@@ -864,13 +864,13 @@ HTML_STYLESHEET        = $(DOXYGEN_HTML_STYLESHEET)
 # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. 
 # The allowed range is 0 to 359.
 
-HTML_COLORSTYLE_HUE    = 228
+HTML_COLORSTYLE_HUE    = 251
 
 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of 
 # the colors in the HTML output. For a value of 0 the output will use 
 # grayscales only. A value of 255 will produce the most vivid colors.
 
-HTML_COLORSTYLE_SAT    = 99
+HTML_COLORSTYLE_SAT    = 150
 
 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to 
 # the luminance component of the colors in the HTML output. Values below 
@@ -879,7 +879,7 @@ HTML_COLORSTYLE_SAT    = 99
 # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, 
 # and 100 does not change the gamma.
 
-HTML_COLORSTYLE_GAMMA  = 56
+HTML_COLORSTYLE_GAMMA  = 39
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML 
 # page will contain the date and time when the page was generated. Setting 

+ 2 - 1
direct/src/dist/commands.py

@@ -540,6 +540,7 @@ class build_apps(setuptools.Command):
             'pandaegg',
             'p3ffmpeg',
             'p3ptloader',
+            'p3assimp',
         ]
         def parse_prc(prcstr, warn_on_missing_plugin):
             out = []
@@ -1345,7 +1346,7 @@ class bdist_apps(setuptools.Command):
             self.run_command('build_apps')
 
         platforms = build_cmd.platforms
-        build_base = build_cmd.build_base
+        build_base = os.path.abspath(build_cmd.build_base)
         os.makedirs(self.dist_dir, exist_ok=True)
         os.chdir(self.dist_dir)
 

BIN
direct/src/plugin_standalone/panda3d.ico


+ 25 - 12
direct/src/showbase/Audio3DManager.py

@@ -2,7 +2,7 @@
 
 __all__ = ['Audio3DManager']
 
-from panda3d.core import Vec3, VBase3, WeakNodePath
+from panda3d.core import Vec3, VBase3, WeakNodePath, ClockObject
 from direct.task.TaskManagerGlobal import Task, taskMgr
 #
 class Audio3DManager:
@@ -122,9 +122,11 @@ class Audio3DManager:
         This is relative to the sound root (probably render).
         Default: VBase3(0, 0, 0)
         """
+        if isinstance(velocity, tuple) and len(velocity) == 3:
+            velocity = VBase3(*velocity)
         if not isinstance(velocity, VBase3):
             raise TypeError("Invalid argument 1, expected <VBase3>")
-        self.vel_dict[sound]=velocity
+        self.vel_dict[sound] = velocity
 
     def setSoundVelocityAuto(self, sound):
         """
@@ -139,14 +141,22 @@ class Audio3DManager:
         """
         Get the velocity of the sound.
         """
-        if (sound in self.vel_dict):
+        if sound in self.vel_dict:
             vel = self.vel_dict[sound]
-            if (vel!=None):
+            if vel is not None:
                 return vel
-            else:
-                for known_object in list(self.sound_dict.keys()):
-                    if self.sound_dict[known_object].count(sound):
-                        return known_object.getPosDelta(self.root)/globalClock.getDt()
+
+            for known_object in list(self.sound_dict.keys()):
+                if self.sound_dict[known_object].count(sound):
+                    node_path = known_object.getNodePath()
+                    if not node_path:
+                        # The node has been deleted.
+                        del self.sound_dict[known_object]
+                        continue
+
+                    clock = ClockObject.getGlobalClock()
+                    return node_path.getPosDelta(self.root) / clock.getDt()
+
         return VBase3(0, 0, 0)
 
     def setListenerVelocity(self, velocity):
@@ -155,9 +165,11 @@ class Audio3DManager:
         This is relative to the sound root (probably render).
         Default: VBase3(0, 0, 0)
         """
+        if isinstance(velocity, tuple) and len(velocity) == 3:
+            velocity = VBase3(*velocity)
         if not isinstance(velocity, VBase3):
             raise TypeError("Invalid argument 0, expected <VBase3>")
-        self.listener_vel=velocity
+        self.listener_vel = velocity
 
     def setListenerVelocityAuto(self):
         """
@@ -172,10 +184,11 @@ class Audio3DManager:
         """
         Get the velocity of the listener.
         """
-        if (self.listener_vel!=None):
+        if self.listener_vel is not None:
             return self.listener_vel
-        elif (self.listener_target!=None):
-            return self.listener_target.getPosDelta(self.root)/globalClock.getDt()
+        elif self.listener_target is not None:
+            clock = ClockObject.getGlobalClock()
+            return self.listener_target.getPosDelta(self.root) / clock.getDt()
         else:
             return VBase3(0, 0, 0)
 

+ 5 - 0
doc/ReleaseNotes

@@ -1,3 +1,8 @@
+------------------------  RELEASE 1.10.1  -----------------------
+
+* Fix regression with Audio3DManager.setSoundVelocityAuto()
+* Audio3DManager accepts tuple in setSoundVelocity/setListenerVelocity
+
 ------------------------  RELEASE 1.10.0  -----------------------
 
 This is a major release with significant changes.  Please review the

+ 1 - 1
dtool/PandaVersion.pp

@@ -7,7 +7,7 @@
 // place to put this.
 
 // Use spaces to separate the major, minor, and sequence numbers here.
-#define PANDA_VERSION 1 10 0
+#define PANDA_VERSION 1 10 1
 
 // This variable will be defined to false in the CVS repository, but
 // scripts that generate source tarballs and/or binary releases for

+ 2 - 14
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -1532,13 +1532,7 @@ write_module(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
       << "  nullptr, nullptr, nullptr, nullptr\n"
       << "};\n"
       << "\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << def->module_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << def->module_name << "();\n"
-      << "#else\n"
-      << "extern \"C\" PyObject *PyInit_" << def->module_name << "();\n"
-      << "#endif\n"
+      << "extern \"C\" EXPORT_CLASS PyObject *PyInit_" << def->module_name << "();\n"
       << "\n"
       << "PyObject *PyInit_" << def->module_name << "() {\n"
       << "  LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n"
@@ -1549,13 +1543,7 @@ write_module(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
       << "\n"
       << "#else  // Python 2 case\n"
       << "\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) void init" << def->module_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << def->module_name << "();\n"
-      << "#else\n"
-      << "extern \"C\" void init" << def->module_name << "();\n"
-      << "#endif\n"
+      << "extern \"C\" EXPORT_CLASS void init" << def->module_name << "();\n"
       << "\n"
       << "void init" << def->module_name << "() {\n"
       << "  LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n"

+ 3 - 34
dtool/src/interrogate/interrogate_module.cxx

@@ -295,24 +295,10 @@ int write_python_table_native(std::ostream &out) {
 
   out.put('\n');
 
-  out << "#if PY_MAJOR_VERSION >= 3 || !defined(NDEBUG)\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << library_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << library_name << "();\n"
-      << "#else\n"
-      << "extern \"C\" PyObject *PyInit_" << library_name << "();\n"
-      << "#endif\n"
-      << "#endif\n";
-
-  out << "#if PY_MAJOR_VERSION < 3 || !defined(NDEBUG)\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) void init" << library_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << library_name << "();\n"
+  out << "#if PY_MAJOR_VERSION >= 3\n"
+      << "extern \"C\" EXPORT_CLASS PyObject *PyInit_" << library_name << "();\n"
       << "#else\n"
-      << "extern \"C\" void init" << library_name << "();\n"
-      << "#endif\n"
+      << "extern \"C\" EXPORT_CLASS void init" << library_name << "();\n"
       << "#endif\n";
 
   out << "\n"
@@ -361,14 +347,6 @@ int write_python_table_native(std::ostream &out) {
       << "}\n"
       << "\n"
 
-      << "#ifndef NDEBUG\n"
-      << "void init" << library_name << "() {\n"
-      << "  PyErr_SetString(PyExc_ImportError, \"" << module_name << " was "
-      << "compiled for Python \" PY_VERSION \", which is incompatible "
-      << "with Python 2\");\n"
-      << "}\n"
-      << "#endif\n"
-
       << "#else  // Python 2 case\n"
       << "\n"
       << "void init" << library_name << "() {\n";
@@ -402,15 +380,6 @@ int write_python_table_native(std::ostream &out) {
 
   out << "  }\n"
       << "}\n"
-      << "\n"
-      << "#ifndef NDEBUG\n"
-      << "PyObject *PyInit_" << library_name << "() {\n"
-      << "  PyErr_SetString(PyExc_ImportError, \"" << module_name << " was "
-      << "compiled for Python \" PY_VERSION \", which is incompatible "
-      << "with Python 3\");\n"
-      << "  return nullptr;\n"
-      << "}\n"
-      << "#endif\n"
       << "#endif\n"
       << "\n";
 

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

@@ -289,7 +289,7 @@ PyObject *_Dtool_Return(PyObject *value) {
 /**
  * This function converts an int value to the appropriate enum instance.
  */
-PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) {
+static PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) {
   PyObject *arg;
   if (!Dtool_ExtractArg(&arg, args, kwds, "value")) {
     return PyErr_Format(PyExc_TypeError,

+ 10 - 3
makepanda/makepackage.py

@@ -531,6 +531,10 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
         if os.path.isdir(outputdir+"/Pmw"):
             oscmd("cp -R %s/Pmw               dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir)
 
+        # Copy over panda3d.dist-info directory.
+        if os.path.isdir(outputdir + "/panda3d.dist-info"):
+            oscmd("cp -R %s/panda3d.dist-info dstroot/pythoncode/Developer/Panda3D/panda3d.dist-info" % (outputdir))
+
         for base in os.listdir(outputdir+"/panda3d"):
             if base.endswith('.py'):
                 libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base
@@ -554,9 +558,12 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
         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")
 
-        # Copy over panda3d.dist-info directory.
-        if os.path.isdir(outputdir + "/panda3d.dist-info"):
-            oscmd("cp -R %s/panda3d.dist-info dstroot/pybindings%s/Library/Python/%s/site-packages/" % (outputdir, pyver, pyver))
+        # Evidently not all Python 3.7 installations read the above path, so do this for now
+        # See GitHub #502
+        if pyver == "3.7":
+            dir = "dstroot/pybindings%s/Library/Frameworks/Python.framework/Versions/%s/lib/python%s/site-packages" % (pyver, pyver, pyver)
+            oscmd("mkdir -p %s" % (dir))
+            WriteFile("%s/Panda3D.pth" % (dir), "/Developer/Panda3D")
 
     if not PkgSkip("FFMPEG"):
         oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib")

+ 1 - 1
makepanda/makepanda.py

@@ -473,7 +473,7 @@ else:
     target_arch = GetTargetArch()
     if target_arch == 'amd64':
         target_arch = 'x86_64'
-    PLATFORM = '{0}-{1}' % (target, target_arch)
+    PLATFORM = '{0}-{1}'.format(target, target_arch)
 
 
 print("Platform: %s" % PLATFORM)

BIN
makepanda/panda-install.bmp


BIN
models/plugin_images/installer.bmp


BIN
panda/src/configfiles/pandaIcon.ico


+ 1 - 1
panda/src/device/inputDeviceManager.h

@@ -20,7 +20,7 @@
 #include "inputDeviceSet.h"
 
 #ifdef _WIN32
-#include "xinputDevice.h"
+#include "xInputDevice.h"
 class WinRawInputDevice;
 #endif
 

+ 2 - 1
panda/src/device/winInputDeviceManager.cxx

@@ -315,7 +315,8 @@ on_input_device_arrival(HANDLE handle) {
 
   // Is this an XInput device?  If so, handle it via XInput, which allows us
   // to handle independent left/right triggers as well as vibration output.
-  if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr) {
+  if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr &&
+      XInputDevice::init_xinput()) {
     // This is a device we should handle via the XInput API.  Check which of
     // the four players was the lucky one.
     if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) {

+ 41 - 7
panda/src/device/winRawInputDevice.cxx

@@ -334,6 +334,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
     return false;
   }
 
+  if (device_cat.is_debug()) {
+    device_cat.debug()
+      << "Found " << _device_class << " device \"" << _name << "\" with "
+      << caps.NumberInputDataIndices << " data indices, "
+      << caps.NumberInputButtonCaps << " button caps, "
+      << caps.NumberInputValueCaps << " value caps\n";
+  }
+
   // Do we have a button mapping?
   static const ButtonHandle gamepad_buttons_common[] = {
     ButtonHandle::none(),
@@ -374,8 +382,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
   _axes.clear();
 
   USHORT num_button_caps = caps.NumberInputButtonCaps;
-  PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
-  _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
+  PHIDP_BUTTON_CAPS button_caps;
+  if (num_button_caps > 0u) {
+    button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
+    _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
+  }
 
   for (USHORT i = 0; i < num_button_caps; ++i) {
     HIDP_BUTTON_CAPS &cap = button_caps[i];
@@ -396,13 +407,15 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
       if (device_cat.is_debug()) {
         device_cat.debug()
           << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex
-          << ", ReportID=" << dec << (int)cap.ReportID
-          << ", UsagePage=0x" << cap.UsagePage
+          << ", ReportID=" << (int)cap.ReportID
+          << ", UsagePage=0x" << hex << cap.UsagePage
           << ", Usage=0x" << cap.NotRange.Usage
           << dec << "\n";
       }
     }
 
+    nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue;
+
     // Windows will only tell us which buttons in a report are "on", so we
     // need to keep track of which buttons exist in which report so that we
     // can figure out which ones are off.
@@ -429,6 +442,9 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
           handle = MouseButton::button(button);
         }
         break;
+
+      default:
+        continue;
       }
 
       int button_index = _buttons.size();
@@ -439,8 +455,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
   }
 
   USHORT num_value_caps = caps.NumberInputValueCaps;
-  PHIDP_VALUE_CAPS value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
-  _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
+  PHIDP_VALUE_CAPS value_caps;
+  if (num_value_caps > 0u) {
+    value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
+    _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
+  }
 
   _hat_data_index = -1;
 
@@ -464,7 +483,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
       if (device_cat.is_debug()) {
         device_cat.debug()
           << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex
-          << ", ReportID=" << dec << (int)cap.ReportID
+          << ", ReportID=" << (int)cap.ReportID
           << ", UsagePage=0x" << hex << cap.UsagePage
           << ", Usage=0x" << cap.NotRange.Usage
           << dec << ", LogicalMin=" << cap.LogicalMin
@@ -472,6 +491,8 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
       }
     }
 
+    nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue;
+
     for (int j = 0; j <= upper; ++j) {
       USAGE usage = j + cap.Range.UsageMin;
       USHORT data_index = j + cap.Range.DataIndexMin;
@@ -590,6 +611,8 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
 
   _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer);
 
+  nassertr_always(_max_data_count >= 0, false);
+
   _handle = handle;
   _is_connected = true;
   return true;
@@ -619,6 +642,10 @@ on_input(PRAWINPUT input) {
   nassertv(input != nullptr);
   nassertv(_preparsed != nullptr);
 
+  if (_max_data_count == 0) {
+    return;
+  }
+
   BYTE *ptr = input->data.hid.bRawData;
   if (input->data.hid.dwSizeHid == 0) {
     return;
@@ -626,6 +653,12 @@ on_input(PRAWINPUT input) {
 
   LightMutexHolder holder(_lock);
 
+  if (device_cat.is_spam()) {
+    device_cat.spam()
+      << _name << " received " << input->data.hid.dwCount << " reports of size "
+      << input->data.hid.dwSizeHid << "\n";
+  }
+
   for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
     process_report((PCHAR)ptr, input->data.hid.dwSizeHid);
     ptr += input->data.hid.dwSizeHid;
@@ -654,6 +687,7 @@ process_report(PCHAR ptr, size_t size) {
   if (status == HIDP_STATUS_SUCCESS) {
     for (ULONG di = 0; di < count; ++di) {
       if (data[di].DataIndex != _hat_data_index) {
+        nassertd(data[di].DataIndex < _indices.size()) continue;
         const Index &idx = _indices[data[di].DataIndex];
         if (idx._axis >= 0) {
           if (idx._signed) {

+ 0 - 2
panda/src/device/winRawInputDevice.h

@@ -42,10 +42,8 @@ private:
 private:
   const std::string _path;
   HANDLE _handle;
-  DWORD _size;
   void *_preparsed;
   ULONG _max_data_count;
-  ULONG _max_usage_count;
 
   // Indexed by report ID
   pvector<BitArray> _report_buttons;

+ 4 - 0
panda/src/device/xInputDevice.cxx

@@ -249,6 +249,10 @@ detect(InputDeviceManager *mgr) {
  */
 bool XInputDevice::
 init_xinput() {
+  if (_initialized) {
+    return true;
+  }
+
   if (device_cat.is_debug()) {
     device_cat.debug() << "Initializing XInput library.\n";
   }

+ 11 - 5
panda/src/vision/openCVTexture.cxx

@@ -22,20 +22,26 @@
 #include "bamCacheRecord.h"
 
 // This symbol is predefined by the Panda3D build system to select whether we
-// are using the OpenCV 2.3 or later interface, or if it is not defined, we
-// are using the original interface.
-#ifdef OPENCV_VER_23
+// are using the OpenCV 3.x or later interface.
+#if defined(OPENCV_VER_3)
+
+#include <opencv2/core.hpp>
+#include <opencv2/videoio/videoio_c.h>
+
+// This checks for 2.3 or later.
+#elif defined(OPENCV_VER_23)
 
 #include <opencv2/core/core.hpp>
-// #include <opencv2videovideo.hpp>
 #include <opencv2/highgui/highgui.hpp>
 
+// If neither of those are predefined, assume 1.x.
 #else
+
 #include <cv.h>
 #include <cxcore.h>
 #include <highgui.h>
 
-#endif  // OPENCV_VER_23
+#endif  // OPENCV_VER_3
 
 TypeHandle OpenCVTexture::_type_handle;
 

+ 0 - 1
samples/asteroids/requirements.txt

@@ -1,2 +1 @@
---pre --extra-index-url https://archive.panda3d.org/branches/deploy-ng
 panda3d

+ 1 - 1
setup.cfg

@@ -1,6 +1,6 @@
 [metadata]
 name = Panda3D
-version = 1.10.0
+version = 1.10.1
 url = https://www.panda3d.org/
 description = Panda3D is a framework for 3D rendering and game development for Python and C++ programs.
 license = Modified BSD License

+ 13 - 1
tests/build_samples.py

@@ -1,6 +1,7 @@
 import os
 import subprocess
 import sys
+import tempfile
 
 SAMPLES_TO_BUILD = [
     'asteroids',
@@ -8,13 +9,24 @@ SAMPLES_TO_BUILD = [
 SAMPLES_DIR = os.path.join(os.path.dirname(__file__), '..', 'samples')
 
 def main():
+    build_base = tempfile.TemporaryDirectory()
+    dist_dir = tempfile.TemporaryDirectory()
+
     for sample in SAMPLES_TO_BUILD:
         sampledir = os.path.join(SAMPLES_DIR, sample)
         os.chdir(sampledir)
 
+        args = [
+            sys.executable,
+            'setup.py',
+            'bdist_apps',
+            '--build-base', build_base.name,
+            '--dist-dir', dist_dir.name,
+        ]
+
         # This will raise a CalledProcessError if the build fails, which will cause
         # this script to fail
-        subprocess.check_call([sys.executable, 'setup.py', 'bdist_apps'])
+        subprocess.check_call(args)
 
 if __name__ == '__main__':
     main()

+ 0 - 1
tests/requirements.txt

@@ -1,3 +1,2 @@
 pytest==3.2.0
---pre --extra-index-url https://archive.panda3d.org/branches/deploy-ng
 panda3d

+ 55 - 0
tests/showbase/test_Audio3DManager.py

@@ -0,0 +1,55 @@
+from direct.showbase.Audio3DManager import Audio3DManager
+from panda3d import core
+import pytest
+
+
[email protected](scope='session')
+def manager3d():
+    root = core.NodePath("root")
+    manager = core.AudioManager.create_AudioManager()
+    manager3d = Audio3DManager(manager, root=root)
+    yield manager3d
+    del manager3d
+    manager.shutdown()
+
+
+def test_audio3dmanager_velocity(manager3d):
+    sound = manager3d.load_sfx("nonexistent")
+
+    # Generate an object with movement
+    object = core.NodePath("object")
+    object.set_pos(0, 0, 0)
+    object.set_fluid_pos(1, 2, 3)
+    assert object.get_pos_delta() == (1, 2, 3)
+
+    res = manager3d.attach_sound_to_object(sound, object)
+    assert res
+
+    clock = core.ClockObject.get_global_clock()
+    clock.mode = core.ClockObject.M_slave
+    clock.dt = 0.5
+
+    # Test auto velocity calculation
+    manager3d.set_sound_velocity_auto(sound)
+    assert manager3d.get_sound_velocity(sound) == (2, 4, 6)
+
+    # Test setting to fixed value
+    manager3d.set_sound_velocity(sound, (5, 5, 5))
+    assert manager3d.get_sound_velocity(sound) == (5, 5, 5)
+
+
+def test_audio3dmanager_weak(manager3d):
+    sound = manager3d.load_sfx("nonexistent")
+    object = core.NodePath("object")
+
+    res = manager3d.attach_sound_to_object(sound, object)
+    assert res
+    assert object in manager3d.sound_dict
+
+    manager3d.update()
+    assert object in manager3d.sound_dict
+
+    object.remove_node()
+    manager3d.update()
+
+    assert object not in manager3d.sound_dict