Selaa lähdekoodia

Merge branch 'master' into webgl-port

rdb 6 vuotta sitten
vanhempi
sitoutus
7c313c8ce0
100 muutettua tiedostoa jossa 2646 lisäystä ja 2936 poistoa
  1. 2 0
      .github/FUNDING.yml
  2. 4 1
      .gitignore
  3. 5 0
      .travis.yml
  4. 35 0
      BACKERS.md
  5. 50 20
      README.md
  6. 11 0
      contrib/src/ai/aiBehaviors.cxx
  7. 3 0
      contrib/src/ai/aiBehaviors.h
  8. 0 1
      contrib/src/ai/aiGlobals.h
  9. 3 0
      contrib/src/ai/arrival.cxx
  10. 2 0
      contrib/src/ai/obstacleAvoidance.cxx
  11. 2 0
      contrib/src/ai/pathFind.cxx
  12. 14 0
      contrib/src/ai/pathFollow.cxx
  13. 1 1
      contrib/src/rplight/shadowAtlas.h
  14. 3 2
      direct/src/actor/Actor.py
  15. 0 84
      direct/src/controls/GravityWalker.py
  16. 0 45
      direct/src/controls/PhysicsWalker.py
  17. 5 689
      direct/src/dcparser/dcClass.cxx
  18. 58 44
      direct/src/dcparser/dcClass.h
  19. 665 0
      direct/src/dcparser/dcClass_ext.cxx
  20. 93 0
      direct/src/dcparser/dcClass_ext.h
  21. 6 313
      direct/src/dcparser/dcField.cxx
  22. 13 15
      direct/src/dcparser/dcField.h
  23. 305 0
      direct/src/dcparser/dcField_ext.cxx
  24. 48 0
      direct/src/dcparser/dcField_ext.h
  25. 0 508
      direct/src/dcparser/dcPacker.cxx
  26. 17 14
      direct/src/dcparser/dcPacker.h
  27. 508 0
      direct/src/dcparser/dcPacker_ext.cxx
  28. 45 0
      direct/src/dcparser/dcPacker_ext.h
  29. 0 42
      direct/src/dcparser/dcPython.h
  30. 3 0
      direct/src/dcparser/p3dcparser_ext_composite.cxx
  31. 14 4
      direct/src/directscripts/Doxyfile.cxx
  32. 1 1
      direct/src/directscripts/Doxyfile.python
  33. 3 3
      direct/src/directscripts/gendocs.py
  34. 2 0
      direct/src/directtools/DirectUtil.py
  35. 19 12
      direct/src/dist/FreezeTool.py
  36. 71 2
      direct/src/dist/commands.py
  37. 4 3
      direct/src/distributed/cConnectionRepository.cxx
  38. 1 1
      direct/src/distributed/cConnectionRepository.h
  39. 4 0
      direct/src/distributed/cDistributedSmoothNodeBase.cxx
  40. 1 2
      direct/src/distributed/cDistributedSmoothNodeBase.h
  41. 4 4
      direct/src/distributed/config_distributed.h
  42. 14 5
      direct/src/filter/FilterManager.py
  43. 2 1
      direct/src/gui/DirectDialog.py
  44. 9 7
      direct/src/gui/DirectEntry.py
  45. 25 3
      direct/src/gui/DirectEntryScroll.py
  46. 3 3
      direct/src/gui/DirectGuiBase.py
  47. 19 3
      direct/src/gui/DirectOptionMenu.py
  48. 12 5
      direct/src/gui/DirectScrolledFrame.py
  49. 5 4
      direct/src/gui/DirectScrolledList.py
  50. 1 19
      direct/src/interval/SoundInterval.py
  51. 4 0
      direct/src/interval/cLerpNodePathInterval.I
  52. 2 2
      direct/src/interval/cLerpNodePathInterval.cxx
  53. 1 1
      direct/src/leveleditor/ActionMgr.py
  54. 1 1
      direct/src/leveleditor/CurveEditor.py
  55. 1 1
      direct/src/leveleditor/FileMgr.py
  56. 1 1
      direct/src/leveleditor/LayerEditorUI.py
  57. 2 2
      direct/src/leveleditor/LevelEditor.py
  58. 2 2
      direct/src/leveleditor/LevelEditorBase.py
  59. 1 1
      direct/src/leveleditor/LevelEditorStart.py
  60. 4 4
      direct/src/leveleditor/LevelEditorUIBase.py
  61. 1 1
      direct/src/leveleditor/ObjectMgrBase.py
  62. 1 1
      direct/src/leveleditor/ObjectPaletteUI.py
  63. 1 1
      direct/src/leveleditor/ObjectPropertyUI.py
  64. 2 2
      direct/src/leveleditor/PaletteTreeCtrl.py
  65. 1 1
      direct/src/leveleditor/ProtoObjsUI.py
  66. 3 3
      direct/src/leveleditor/ProtoPaletteUI.py
  67. 14 14
      direct/src/leveleditor/SceneGraphUIBase.py
  68. 1 1
      direct/src/leveleditor/testData.py
  69. 199 62
      direct/src/p3d/DeploymentTools.py
  70. 35 38
      direct/src/particles/ParticleEffect.py
  71. 1 0
      direct/src/particles/Particles.py
  72. 18 0
      direct/src/showbase/BufferViewer.py
  73. 36 44
      direct/src/showbase/EventManager.py
  74. 0 100
      direct/src/showbase/FindCtaPaths.py
  75. 59 21
      direct/src/showbase/Loader.py
  76. 3 2
      direct/src/showbase/Messenger.py
  77. 9 6
      direct/src/showbase/PythonUtil.py
  78. 2 3
      direct/src/showbase/SfxPlayer.py
  79. 23 8
      direct/src/showbase/ShowBase.py
  80. 1 0
      direct/src/showbase/ShowBaseGlobal.py
  81. 2 1
      direct/src/showbase/Transitions.py
  82. 9 7
      direct/src/showutil/TexMemWatcher.py
  83. 0 23
      direct/src/stdpy/threading.py
  84. 4 7
      direct/src/task/Task.py
  85. 8 4
      direct/src/tkpanels/AnimPanel.py
  86. 2 1
      direct/src/tkpanels/MopathRecorder.py
  87. 40 31
      direct/src/tkpanels/ParticlePanel.py
  88. 33 17
      direct/src/tkwidgets/Valuator.py
  89. 4 4
      direct/src/wxwidgets/WxPandaShell.py
  90. 0 108
      dmodels/src/level_editor/donaldsDockColors.txt
  91. 0 64
      dmodels/src/level_editor/donaldsDockStyles.txt
  92. BIN
      dmodels/src/level_editor/donalds_dock_layout.flt
  93. 0 61
      dmodels/src/level_editor/minniesMelodyLandColors.txt
  94. 0 79
      dmodels/src/level_editor/minniesMelodyLandStyles.txt
  95. BIN
      dmodels/src/level_editor/minnies_melody_land_layout.flt
  96. 0 65
      dmodels/src/level_editor/theBurrrghColors.txt
  97. 0 90
      dmodels/src/level_editor/theBurrrghStyles.txt
  98. BIN
      dmodels/src/level_editor/the_burrrgh_layout.flt
  99. 0 106
      dmodels/src/level_editor/toontownCentralColors.txt
  100. 0 84
      dmodels/src/level_editor/toontownCentralStyles.txt

+ 2 - 0
.github/FUNDING.yml

@@ -0,0 +1,2 @@
+open_collective: panda3d
+

+ 4 - 1
.gitignore

@@ -4,11 +4,12 @@
 /targetroot/
 /targetroot/
 /dstroot/
 /dstroot/
 
 
-# Core dumps
+# Core dumps and traces
 core
 core
 core.*
 core.*
 vgcore.*
 vgcore.*
 *.core
 *.core
+*.trace
 
 
 # Editor files/directories
 # Editor files/directories
 *.save
 *.save
@@ -26,6 +27,7 @@ vgcore.*
 /+DESC
 /+DESC
 /+MANIFEST
 /+MANIFEST
 /pkg-plist
 /pkg-plist
+/debug.ks
 
 
 # Produced installer/executables
 # Produced installer/executables
 /*.exe
 /*.exe
@@ -36,6 +38,7 @@ vgcore.*
 /*.dmg
 /*.dmg
 /*.whl
 /*.whl
 /*.txz
 /*.txz
+/*.apk
 
 
 # CMake
 # CMake
 /build/
 /build/

+ 5 - 0
.travis.yml

@@ -54,3 +54,8 @@ notifications:
     on_failure: always
     on_failure: always
     use_notice: true
     use_notice: true
     skip_join: false
     skip_join: false
+  webhooks:
+    urls:
+      - https://www.panda3d.org/webhooks/travis-ci.php
+    on_success: change
+    on_failure: always

+ 35 - 0
BACKERS.md

@@ -0,0 +1,35 @@
+# Panda3D Backers
+
+This is a list of all the people who are contributing financially to Panda3D.  If you'd like to join them, visit [our campaign on OpenCollective](https://opencollective.com/panda3d)!
+
+## Gold Sponsors
+
+![Gold Sponsors](https://opencollective.com/panda3d/tiers/gold-sponsor.svg?avatarHeight=48&width=600)
+
+* [tcdude](https://opencollective.com/tizilogic)
+
+## Bronze Sponsors
+
+![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
+
+* [Mitchell Stokes](https://opencollective.com/mitchell-stokes)
+* [Daniel Stokes](https://opencollective.com/daniel-stokes)
+* [David Rose](https://opencollective.com/david-rose)
+* [Carnetsoft](https://cs-driving-simulator.com/)
+
+## Benefactors
+
+![Benefactors](https://opencollective.com/panda3d/tiers/benefactor.svg?avatarHeight=48&width=600)
+
+* Sam Edwards
+* Max Voss
+
+## Enthusiasts
+
+![Benefactors](https://opencollective.com/panda3d/tiers/enthusiast.svg?avatarHeight=48&width=600)
+
+* Eric Thomson
+
+## Backers
+
+![Backers](https://opencollective.com/panda3d/tiers/backer.svg?avatarHeight=48&width=600)

+ 50 - 20
README.md

@@ -1,6 +1,8 @@
 [![Build Status](https://travis-ci.org/panda3d/panda3d.svg?branch=master)](https://travis-ci.org/panda3d/panda3d)
 [![Build Status](https://travis-ci.org/panda3d/panda3d.svg?branch=master)](https://travis-ci.org/panda3d/panda3d)
+[![OpenCollective](https://opencollective.com/panda3d/backers/badge.svg)](https://opencollective.com/panda3d)
+[![OpenCollective](https://opencollective.com/panda3d/sponsors/badge.svg)](https://opencollective.com/panda3d)
 
 
-<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=200" align="right" />
+<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=500" align="right" width="200"/>
 
 
 Panda3D
 Panda3D
 =======
 =======
@@ -8,12 +10,12 @@ Panda3D
 Panda3D is a game engine, a framework for 3D rendering and game development for
 Panda3D is a game engine, a framework for 3D rendering and game development for
 Python and C++ programs.  Panda3D is open-source and free for any purpose,
 Python and C++ programs.  Panda3D is open-source and free for any purpose,
 including commercial ventures, thanks to its
 including commercial ventures, thanks to its
-[liberal license](https://www.panda3d.org/license.php).  To learn more about
-Panda3D's capabilities, visit the [gallery](https://www.panda3d.org/gallery.php)
-and the [feature list](https://www.panda3d.org/features.php).  To learn how to
-use Panda3D, check the [documentation](https://www.panda3d.org/documentation.php)
+[liberal license](https://www.panda3d.org/license/). To learn more about
+Panda3D's capabilities, visit the [gallery](https://www.panda3d.org/gallery/)
+and the [feature list](https://www.panda3d.org/features/).  To learn how to
+use Panda3D, check the [documentation](https://www.panda3d.org/documentation/)
 resources. If you get stuck, ask for help from our active
 resources. If you get stuck, ask for help from our active
-[community](https://www.panda3d.org/community.php).
+[community](https://discourse.panda3d.org).
 
 
 Panda3D is licensed under the Modified BSD License.  See the LICENSE file for
 Panda3D is licensed under the Modified BSD License.  See the LICENSE file for
 more details.
 more details.
@@ -21,7 +23,16 @@ more details.
 Installing Panda3D
 Installing Panda3D
 ==================
 ==================
 
 
-By far, the easiest way to install the latest development build of Panda3D
+The latest Panda3D SDK can be downloaded from
+[this page](https://www.panda3d.org/download/sdk-1-10-4-1/).
+If you are familiar with installing Python packages, you can use
+the following comand:
+
+```bash
+pip install panda3d
+```
+
+The easiest way to install the latest development build of Panda3D
 into an existing Python installation is using the following command:
 into an existing Python installation is using the following command:
 
 
 ```bash
 ```bash
@@ -31,9 +42,7 @@ pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
 If this command fails, please make sure your version of pip is up-to-date.
 If this command fails, please make sure your version of pip is up-to-date.
 
 
 If you prefer to install the full SDK with all tools, the latest development
 If you prefer to install the full SDK with all tools, the latest development
-builds can be obtained from this page:
-
-https://www.panda3d.org/download.php?sdk&version=devel
+builds can be obtained from [this page](https://www.panda3d.org/download.php?version=devel&sdk).
 
 
 These are automatically kept up-to-date with the latest GitHub version of Panda.
 These are automatically kept up-to-date with the latest GitHub version of Panda.
 
 
@@ -55,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.0/panda3d-1.10.0-tools-win64.zip
-https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win32.zip
+https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip
+https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-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
@@ -96,14 +105,14 @@ python makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-g
 You will probably see some warnings saying that it's unable to find several
 You will probably see some warnings saying that it's unable to find several
 dependency packages.  You should determine which ones you want to include in
 dependency packages.  You should determine which ones you want to include in
 your build and install the respective development packages.  You may visit
 your build and install the respective development packages.  You may visit
-[this manual page](https://www.panda3d.org/manual/index.php/Dependencies)
+[this manual page](https://www.panda3d.org/manual/?title=Third-party_dependencies_and_license_info)
 for an overview of the various dependencies.
 for an overview of the various dependencies.
 
 
 If you are on Ubuntu, this command should cover the most frequently
 If you are on Ubuntu, this command should cover the most frequently
 used third-party packages:
 used third-party packages:
 
 
 ```bash
 ```bash
-sudo apt-get install build-essential pkg-config python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev libassimp-dev libopenexr-dev
+sudo apt-get install build-essential pkg-config fakeroot python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev libassimp-dev libopenexr-dev
 ```
 ```
 
 
 Once Panda3D has built, you can either install the .deb or .rpm package that
 Once Panda3D has built, you can either install the .deb or .rpm package that
@@ -126,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.9.4/panda3d-1.9.4-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-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:
@@ -135,9 +144,8 @@ you may build Panda3D using a command like the following:
 python makepanda/makepanda.py --everything --installer
 python makepanda/makepanda.py --everything --installer
 ```
 ```
 
 
-In order to make a universal build, pass the --universal flag.  You may also
-target a specific minimum macOS version using the --osxtarget flag followed
-by the release number, eg. 10.6 or 10.7.
+You may target a specific minimum macOS version using the --osxtarget flag
+followed by the release number, eg. 10.7 or 10.9.
 
 
 If the build was successful, makepanda will have generated a .dmg file in
 If the build was successful, makepanda will have generated a .dmg file in
 the source directory containing the installer.  Simply open it and run the
 the source directory containing the installer.  Simply open it and run the
@@ -151,7 +159,7 @@ install the requisite packages using the system package manager.  To install
 the recommended set of dependencies, you can use this command:
 the recommended set of dependencies, you can use this command:
 
 
 ```bash
 ```bash
-pkg install pkgconf png jpeg-turbo tiff freetype2 eigen squish openal opusfile libvorbis libX11 libGL ode bullet assimp openexr
+pkg install pkgconf bison png jpeg-turbo tiff freetype2 harfbuzz eigen squish openal opusfile libvorbis libX11 mesa-libs ode bullet assimp openexr
 ```
 ```
 
 
 You will also need to choose which version of Python you want to use.
 You will also need to choose which version of Python you want to use.
@@ -183,7 +191,7 @@ pkg install python-dev termux-tools ndk-stl ndk-sysroot clang libvorbis-dev libo
 Then, you can build and install the .apk right away using these commands:
 Then, you can build and install the .apk right away using these commands:
 
 
 ```bash
 ```bash
-python makepanda/makepanda.py --everything --target android-21 --installer
+python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
 xdg-open panda3d.apk
 xdg-open panda3d.apk
 ```
 ```
 
 
@@ -222,3 +230,25 @@ models that are necessary for the developers to reproduce the issue.
 
 
 If you're not sure whether you've encountered a bug, feel free to ask about
 If you're not sure whether you've encountered a bug, feel free to ask about
 it in the forums or the IRC channel first.
 it in the forums or the IRC channel first.
+
+Supporting the Project
+======================
+
+If you would like to support the project financially, visit
+[our campaign on OpenCollective](https://opencollective.com/panda3d).  Your
+contributions help us accelerate the development of Panda3D.
+
+For the list of backers, see the [BACKERS.md](BACKERS.md) file or visit the
+[Sponsors page](https://www.panda3d.org/sponsors) on our web site.  Thank you
+to everyone who has donated!
+
+<a href="https://opencollective.com/panda3d" target="_blank">
+  <img src="https://opencollective.com/panda3d/contribute/[email protected]?color=blue" width=300 />
+</a>
+
+### Gold Sponsors
+[![](https://opencollective.com/panda3d/tiers/gold-sponsor/0/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/0/website)
+[![](https://opencollective.com/panda3d/tiers/gold-sponsor/1/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/1/website)
+[![](https://opencollective.com/panda3d/tiers/gold-sponsor/2/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/2/website)
+[![](https://opencollective.com/panda3d/tiers/gold-sponsor/3/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/3/website)
+[![](https://opencollective.com/panda3d/tiers/gold-sponsor/4/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/4/website)

+ 11 - 0
contrib/src/ai/aiBehaviors.cxx

@@ -13,6 +13,17 @@
 
 
 #include "aiBehaviors.h"
 #include "aiBehaviors.h"
 
 
+#include "arrival.h"
+#include "evade.h"
+#include "flee.h"
+#include "flock.h"
+#include "obstacleAvoidance.h"
+#include "pathFind.h"
+#include "pathFollow.h"
+#include "pursue.h"
+#include "seek.h"
+#include "wander.h"
+
 using std::cout;
 using std::cout;
 using std::endl;
 using std::endl;
 using std::string;
 using std::string;

+ 3 - 0
contrib/src/ai/aiBehaviors.h

@@ -28,6 +28,9 @@ class PathFollow;
 class PathFind;
 class PathFind;
 class ObstacleAvoidance;
 class ObstacleAvoidance;
 
 
+#include "flee.h"
+#include "evade.h"
+
 typedef std::list<Flee, std::allocator<Flee> > ListFlee;
 typedef std::list<Flee, std::allocator<Flee> > ListFlee;
 typedef std::list<Evade, std::allocator<Evade> > ListEvade;
 typedef std::list<Evade, std::allocator<Evade> > ListEvade;
 
 

+ 0 - 1
contrib/src/ai/aiGlobals.h

@@ -15,7 +15,6 @@
 #define _AI_GLOBALS_H
 #define _AI_GLOBALS_H
 
 
 #include "config_ai.h"
 #include "config_ai.h"
-#include "pandaFramework.h"
 #include "textNode.h"
 #include "textNode.h"
 #include "pandaSystem.h"
 #include "pandaSystem.h"
 
 

+ 3 - 0
contrib/src/ai/arrival.cxx

@@ -13,6 +13,9 @@
 
 
 #include "arrival.h"
 #include "arrival.h"
 
 
+#include "pursue.h"
+#include "seek.h"
+
 Arrival::Arrival(AICharacter *ai_ch, double distance) {
 Arrival::Arrival(AICharacter *ai_ch, double distance) {
   _ai_char = ai_ch;
   _ai_char = ai_ch;
 
 

+ 2 - 0
contrib/src/ai/obstacleAvoidance.cxx

@@ -13,6 +13,8 @@
 
 
 #include "obstacleAvoidance.h"
 #include "obstacleAvoidance.h"
 
 
+#include "aiWorld.h"
+
 ObstacleAvoidance::
 ObstacleAvoidance::
 ObstacleAvoidance(AICharacter *ai_char, float feeler_length) {
 ObstacleAvoidance(AICharacter *ai_char, float feeler_length) {
   _ai_char = ai_char;
   _ai_char = ai_char;

+ 2 - 0
contrib/src/ai/pathFind.cxx

@@ -13,6 +13,8 @@
 
 
 #include "pathFind.h"
 #include "pathFind.h"
 
 
+#include "pathFollow.h"
+
 using std::cout;
 using std::cout;
 using std::endl;
 using std::endl;
 using std::string;
 using std::string;

+ 14 - 0
contrib/src/ai/pathFollow.cxx

@@ -1,6 +1,20 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pathFind.cxx
+ * @author Deepak, John, Navin
+ * @date 2009-10-24
+ */
 
 
 #include "pathFollow.h"
 #include "pathFollow.h"
 
 
+#include "pathFind.h"
+
 PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
 PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
     _follow_weight = follow_wt;
     _follow_weight = follow_wt;
   _curr_path_waypoint = -1;
   _curr_path_waypoint = -1;

+ 1 - 1
contrib/src/rplight/shadowAtlas.h

@@ -28,7 +28,7 @@
 #define SHADOWATLAS_H
 #define SHADOWATLAS_H
 
 
 #include "pandabase.h"
 #include "pandabase.h"
-#include "lvecBase4.h"
+#include "luse.h"
 
 
 NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
 NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
 
 

+ 3 - 2
direct/src/actor/Actor.py

@@ -67,7 +67,7 @@ class Actor(DirectObject, NodePath):
 
 
         def __init__(self, filename = None, animBundle = None):
         def __init__(self, filename = None, animBundle = None):
             self.filename = filename
             self.filename = filename
-            self.animBundle = None
+            self.animBundle = animBundle
             self.animControl = None
             self.animControl = None
 
 
         def makeCopy(self):
         def makeCopy(self):
@@ -1947,6 +1947,7 @@ class Actor(DirectObject, NodePath):
                     animName = acc.getAnimName(i)
                     animName = acc.getAnimName(i)
 
 
                     animDef = Actor.AnimDef()
                     animDef = Actor.AnimDef()
+                    animDef.animBundle = animControl.getAnim()
                     animDef.animControl = animControl
                     animDef.animControl = animControl
                     self.__animControlDict[lodName][partName][animName] = animDef
                     self.__animControlDict[lodName][partName][animName] = animDef
 
 
@@ -2507,7 +2508,7 @@ class Actor(DirectObject, NodePath):
         else:
         else:
             animNames = [animName]
             animNames = [animName]
         for animName in animNames:
         for animName in animNames:
-            if animName is 'nothing':
+            if animName == 'nothing':
                 continue
                 continue
             thisAnim = ''
             thisAnim = ''
             totalEffect = 0.
             totalEffect = 0.

+ 0 - 84
direct/src/controls/GravityWalker.py

@@ -71,90 +71,6 @@ class GravityWalker(DirectObject.DirectObject):
         self.isAirborne = 0
         self.isAirborne = 0
         self.highMark = 0
         self.highMark = 0
 
 
-    """
-    def spawnTest(self):
-        assert self.notify.debugStateCall(self)
-        if not self.wantDebugIndicator:
-            return
-        from pandac.PandaModules import *
-        from direct.interval.IntervalGlobal import *
-        from toontown.coghq import MovingPlatform
-
-        if hasattr(self, "platform"):
-            # Remove the prior instantiation:
-            self.moveIval.pause()
-            del self.moveIval
-            self.platform.destroy()
-            del self.platform
-            self.platform2.destroy()
-            del self.platform2
-
-        model = loader.loadModel('phase_9/models/cogHQ/platform1')
-        fakeId = id(self)
-        self.platform = MovingPlatform.MovingPlatform()
-        self.platform.setupCopyModel(fakeId, model, 'platformcollision')
-        self.platformRoot = render.attachNewNode("GravityWalker-spawnTest-%s"%fakeId)
-        self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 0.0, 1.0))
-        self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
-        self.platform.reparentTo(self.platformRoot)
-
-        self.platform2 = MovingPlatform.MovingPlatform()
-        self.platform2.setupCopyModel(1+fakeId, model, 'platformcollision')
-        self.platform2Root = render.attachNewNode("GravityWalker-spawnTest2-%s"%fakeId)
-        self.platform2Root.setPos(base.localAvatar, Vec3(-16.0, 30.0, 1.0))
-        self.platform2Root.setHpr(base.localAvatar, Vec3.zero())
-        self.platform2.reparentTo(self.platform2Root)
-
-        duration = 5
-        self.moveIval = Parallel(
-                Sequence(
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 30.0, 0.0),
-                                    name='platformOut%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformBack%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 30.0),
-                                    name='platformUp%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformDown%s' % fakeId,
-                                    fluid = 1),
-                ),
-                Sequence(
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, -30.0, 0.0),
-                                    name='platform2Out%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, 30.0, 30.0),
-                                    name='platform2Back%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, -30.0, 0.0),
-                                    name='platform2Up%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformDown%s' % fakeId,
-                                    fluid = 1),
-                ),
-            name='platformIval%s' % fakeId,
-            )
-        self.moveIval.loop()
-    """
     def setWalkSpeed(self, forward, jump, reverse, rotate):
     def setWalkSpeed(self, forward, jump, reverse, rotate):
         assert self.notify.debugStateCall(self)
         assert self.notify.debugStateCall(self)
         self.avatarControlForwardSpeed=forward
         self.avatarControlForwardSpeed=forward

+ 0 - 45
direct/src/controls/PhysicsWalker.py

@@ -67,51 +67,6 @@ class PhysicsWalker(DirectObject.DirectObject):
         self.isAirborne = 0
         self.isAirborne = 0
         self.highMark = 0
         self.highMark = 0
 
 
-    """
-    def spawnTest(self):
-        assert self.debugPrint("\n\nspawnTest()\n")
-        if not self.wantDebugIndicator:
-            return
-        from pandac.PandaModules import *
-        from direct.interval.IntervalGlobal import *
-        from toontown.coghq import MovingPlatform
-
-        if hasattr(self, "platform"):
-            # Remove the prior instantiation:
-            self.moveIval.pause()
-            del self.moveIval
-            self.platform.destroy()
-            del self.platform
-
-        model = loader.loadModel('phase_9/models/cogHQ/platform1')
-        fakeId = id(self)
-        self.platform = MovingPlatform.MovingPlatform()
-        self.platform.setupCopyModel(fakeId, model, 'platformcollision')
-        self.platformRoot = render.attachNewNode("physicsWalker-spawnTest-%s"%fakeId)
-        self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 3.0, 1.0))
-        self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
-        self.platform.reparentTo(self.platformRoot)
-
-        startPos = Vec3(0.0, -15.0, 0.0)
-        endPos = Vec3(0.0, 15.0, 0.0)
-        distance = Vec3(startPos-endPos).length()
-        duration = distance/4
-        self.moveIval = Sequence(
-            WaitInterval(0.3),
-            LerpPosInterval(self.platform, duration,
-                            endPos, startPos=startPos,
-                            name='platformOut%s' % fakeId,
-                            fluid = 1),
-            WaitInterval(0.3),
-            LerpPosInterval(self.platform, duration,
-                            startPos, startPos=endPos,
-                            name='platformBack%s' % fakeId,
-                            fluid = 1),
-            name='platformIval%s' % fakeId,
-            )
-        self.moveIval.loop()
-    """
-
     def setWalkSpeed(self, forward, jump, reverse, rotate):
     def setWalkSpeed(self, forward, jump, reverse, rotate):
         assert self.debugPrint("setWalkSpeed()")
         assert self.debugPrint("setWalkSpeed()")
         self.avatarControlForwardSpeed=forward
         self.avatarControlForwardSpeed=forward

+ 5 - 689
direct/src/dcparser/dcClass.cxx

@@ -16,22 +16,13 @@
 #include "dcAtomicField.h"
 #include "dcAtomicField.h"
 #include "hashGenerator.h"
 #include "hashGenerator.h"
 #include "dcindent.h"
 #include "dcindent.h"
-#include "dcmsgtypes.h"
 
 
 #include "dcClassParameter.h"
 #include "dcClassParameter.h"
 #include <algorithm>
 #include <algorithm>
 
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
-using std::ostream;
-using std::ostringstream;
 using std::string;
 using std::string;
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
-#include "pStatTimer.h"
-
 #ifndef CPPPARSER
 #ifndef CPPPARSER
 PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
 PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
 PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
 PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
@@ -86,10 +77,7 @@ DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
   _number = -1;
   _number = -1;
   _constructor = nullptr;
   _constructor = nullptr;
 
 
-#ifdef HAVE_PYTHON
-  _class_def = nullptr;
-  _owner_class_def = nullptr;
-#endif
+  _python_class_defs = nullptr;
 }
 }
 
 
 /**
 /**
@@ -105,11 +93,6 @@ DCClass::
   for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
   for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
     delete (*fi);
     delete (*fi);
   }
   }
-
-#ifdef HAVE_PYTHON
-  Py_XDECREF(_class_def);
-  Py_XDECREF(_owner_class_def);
-#endif
 }
 }
 
 
 /**
 /**
@@ -335,7 +318,7 @@ inherits_from_bogus_class() const {
  * Write a string representation of this instance to <out>.
  * Write a string representation of this instance to <out>.
  */
  */
 void DCClass::
 void DCClass::
-output(ostream &out) const {
+output(std::ostream &out) const {
   if (_is_struct) {
   if (_is_struct) {
     out << "struct";
     out << "struct";
   } else {
   } else {
@@ -346,678 +329,11 @@ output(ostream &out) const {
   }
   }
 }
 }
 
 
-#ifdef HAVE_PYTHON
-/**
- * Returns true if the DCClass object has an associated Python class
- * definition, false otherwise.
- */
-bool DCClass::
-has_class_def() const {
-  return (_class_def != nullptr);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Sets the class object associated with this DistributedClass.  This object
- * will be used to construct new instances of the class.
- */
-void DCClass::
-set_class_def(PyObject *class_def) {
-  Py_XINCREF(class_def);
-  Py_XDECREF(_class_def);
-  _class_def = class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the class object that was previously associated with this
- * DistributedClass.  This will return a new reference to the object.
- */
-PyObject *DCClass::
-get_class_def() const {
-  if (_class_def == nullptr) {
-    Py_INCREF(Py_None);
-    return Py_None;
-  }
-
-  Py_INCREF(_class_def);
-  return _class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns true if the DCClass object has an associated Python owner class
- * definition, false otherwise.
- */
-bool DCClass::
-has_owner_class_def() const {
-  return (_owner_class_def != nullptr);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Sets the owner class object associated with this DistributedClass.  This
- * object will be used to construct new owner instances of the class.
- */
-void DCClass::
-set_owner_class_def(PyObject *owner_class_def) {
-  Py_XINCREF(owner_class_def);
-  Py_XDECREF(_owner_class_def);
-  _owner_class_def = owner_class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Returns the owner class object that was previously associated with this
- * DistributedClass.  This will return a new reference to the object.
- */
-PyObject *DCClass::
-get_owner_class_def() const {
-  if (_owner_class_def == nullptr) {
-    Py_INCREF(Py_None);
-    return Py_None;
-  }
-
-  Py_INCREF(_owner_class_def);
-  return _owner_class_def;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Extracts the update message out of the packer and applies it to the
- * indicated object by calling the appropriate method.
- */
-void DCClass::
-receive_update(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-    DCPacker packer;
-    const char *data = (const char *)di.get_datagram().get_data();
-    packer.set_unpack_data(data + di.get_current_index(),
-                           di.get_remaining_size(), false);
-
-    int field_id = packer.raw_unpack_uint16();
-    DCField *field = get_field_by_index(field_id);
-    if (field == nullptr) {
-            ostringstream strm;
-            strm
-                << "Received update for field " << field_id << ", not in class "
-                << get_name();
-            nassert_raise(strm.str());
-            return;
-    }
-
-    packer.begin_unpack(field);
-    field->receive_update(packer, distobj);
-    packer.end_unpack();
-
-    di.skip_bytes(packer.get_num_unpacked_bytes());
-
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent along with a normal "generate with required" message.  This is all
- * of the atomic fields that are marked "broadcast required".
- */
-void DCClass::
-receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required() && field->is_broadcast()) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent along with a normal "generate with required" message.  This is all
- * of the atomic fields that are marked "broadcast ownrecv". Should be used
- * for 'owner-view' objects.
- */
-void DCClass::
-receive_update_broadcast_required_owner(PyObject *distobj,
-                                        DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a big datagram that includes all of the "required" fields that
- * are sent when an avatar is created.  This is all of the atomic fields that
- * are marked "required", whether they are broadcast or not.
- */
-void DCClass::
-receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  DCPacker packer;
-  const char *data = (const char *)di.get_datagram().get_data();
-  packer.set_unpack_data(data + di.get_current_index(),
-                         di.get_remaining_size(), false);
-
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->as_molecular_field() == nullptr &&
-        field->is_required()) {
-      packer.begin_unpack(field);
-      field->receive_update(packer, distobj);
-      if (!packer.end_unpack()) {
-        break;
-      }
-    }
-  }
-
-  di.skip_bytes(packer.get_num_unpacked_bytes());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes a datagram that lists some additional fields that are broadcast
- * in one chunk.
- */
-void DCClass::
-receive_update_other(PyObject *distobj, DatagramIterator &di) const {
-#ifdef WITHIN_PANDA
-  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
-#endif
-  int num_fields = di.get_uint16();
-  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
-    receive_update(distobj, di);
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes an update for a named field from a packed value blob.
- */
-void DCClass::
-direct_update(PyObject *distobj, const string &field_name,
-              const vector_uchar &value_blob) {
-  DCField *field = get_field_by_name(field_name);
-  nassertv_always(field != nullptr);
-
-  DCPacker packer;
-  packer.set_unpack_data(value_blob);
-  packer.begin_unpack(field);
-  field->receive_update(packer, distobj);
-  packer.end_unpack();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Processes an update for a named field from a packed datagram.
- */
-void DCClass::
-direct_update(PyObject *distobj, const string &field_name,
-              const Datagram &datagram) {
-  DCField *field = get_field_by_name(field_name);
-  nassertv_always(field != nullptr);
-
-  DCPacker packer;
-  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
-  packer.begin_unpack(field);
-  field->receive_update(packer, distobj);
-  packer.end_unpack();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Looks up the current value of the indicated field by calling the
- * appropriate get*() function, then packs that value into the datagram.  This
- * field is presumably either a required field or a specified optional field,
- * and we are building up a datagram for the generate-with-required message.
- *
- * Returns true on success, false on failure.
- */
-bool DCClass::
-pack_required_field(Datagram &datagram, PyObject *distobj,
-                    const DCField *field) const {
-  DCPacker packer;
-  packer.begin_pack(field);
-  if (!pack_required_field(packer, distobj, field)) {
-    return false;
-  }
-  if (!packer.end_pack()) {
-    return false;
-  }
-
-  datagram.append_data(packer.get_data(), packer.get_length());
-  return true;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Looks up the current value of the indicated field by calling the
- * appropriate get*() function, then packs that value into the packer.  This
- * field is presumably either a required field or a specified optional field,
- * and we are building up a datagram for the generate-with-required message.
- *
- * Returns true on success, false on failure.
- */
-bool DCClass::
-pack_required_field(DCPacker &packer, PyObject *distobj,
-                    const DCField *field) const {
-  const DCParameter *parameter = field->as_parameter();
-  if (parameter != nullptr) {
-    // This is the easy case: to pack a parameter, we just look on the class
-    // object for the data element.
-    string field_name = field->get_name();
-
-    if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
-      // If the attribute is not defined, but the field has a default value
-      // specified, quietly pack the default value.
-      if (field->has_default_value()) {
-        packer.pack_default_value();
-        return true;
-      }
-
-      // If there is no default value specified, it's an error.
-      ostringstream strm;
-      strm << "Data element " << field_name
-           << ", required by dc file for dclass " << get_name()
-           << ", not defined on object";
-      nassert_raise(strm.str());
-      return false;
-    }
-    PyObject *result =
-      PyObject_GetAttrString(distobj, (char *)field_name.c_str());
-    nassertr(result != nullptr, false);
-
-    // Now pack the value into the datagram.
-    bool pack_ok = parameter->pack_args(packer, result);
-    Py_DECREF(result);
-
-    return pack_ok;
-  }
-
-  if (field->as_molecular_field() != nullptr) {
-    ostringstream strm;
-    strm << "Cannot pack molecular field " << field->get_name()
-         << " for generate";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  const DCAtomicField *atom = field->as_atomic_field();
-  nassertr(atom != nullptr, false);
-
-  // We need to get the initial value of this field.  There isn't a good,
-  // robust way to get this; presently, we just mangle the "setFoo()" name of
-  // the required field into "getFoo()" and call that.
-  string setter_name = atom->get_name();
-
-  if (setter_name.empty()) {
-    ostringstream strm;
-    strm << "Required field is unnamed!";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  if (atom->get_num_elements() == 0) {
-    // It sure doesn't make sense to have a required field with no parameters.
-    // What data, exactly, is required?
-    ostringstream strm;
-    strm << "Required field " << setter_name << " has no parameters!";
-    nassert_raise(strm.str());
-    return false;
-  }
-
-  string getter_name = setter_name;
-  if (setter_name.substr(0, 3) == "set") {
-    // If the original method started with "set", we mangle this directly to
-    // "get".
-    getter_name[0] = 'g';
-
-  } else {
-    // Otherwise, we add a "get" prefix, and capitalize the next letter.
-    getter_name = "get" + setter_name;
-    getter_name[3] = toupper(getter_name[3]);
-  }
-
-  // Now we have to look up the getter on the distributed object and call it.
-  if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
-    // As above, if there's no getter but the field has a default value
-    // specified, quietly pack the default value.
-    if (field->has_default_value()) {
-      packer.pack_default_value();
-      return true;
-    }
-
-    // Otherwise, with no default value it's an error.
-    ostringstream strm;
-    strm << "Distributed class " << get_name()
-         << " doesn't have getter named " << getter_name
-         << " to match required field " << setter_name;
-    nassert_raise(strm.str());
-    return false;
-  }
-  PyObject *func =
-    PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
-  nassertr(func != nullptr, false);
-
-  PyObject *empty_args = PyTuple_New(0);
-  PyObject *result = PyObject_CallObject(func, empty_args);
-  Py_DECREF(empty_args);
-  Py_DECREF(func);
-  if (result == nullptr) {
-    // We don't set this as an exception, since presumably the Python method
-    // itself has already triggered a Python exception.
-    std::cerr << "Error when calling " << getter_name << "\n";
-    return false;
-  }
-
-  if (atom->get_num_elements() == 1) {
-    // In this case, we expect the getter to return one object, which we wrap
-    // up in a tuple.
-    PyObject *tuple = PyTuple_New(1);
-    PyTuple_SET_ITEM(tuple, 0, result);
-    result = tuple;
-
-  } else {
-    // Otherwise, it had better already be a sequence or tuple of some sort.
-    if (!PySequence_Check(result)) {
-      ostringstream strm;
-      strm << "Since dclass " << get_name() << " method " << setter_name
-           << " is declared to have multiple parameters, Python function "
-           << getter_name << " must return a list or tuple.\n";
-      nassert_raise(strm.str());
-      return false;
-    }
-  }
-
-  // Now pack the arguments into the datagram.
-  bool pack_ok = atom->pack_args(packer, result);
-  Py_DECREF(result);
-
-  return pack_ok;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the client.
- */
-Datagram DCClass::
-client_format_update(const string &field_name, DOID_TYPE do_id,
-                     PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->client_format_update(do_id, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the AI.
- */
-Datagram DCClass::
-ai_format_update(const string &field_name, DOID_TYPE do_id,
-                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->ai_format_update(do_id, to_id, from_id, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update,
- * using the indicated msg type for the indicated distributed object from the
- * AI.
- */
-Datagram DCClass::
-ai_format_update_msg_type(const string &field_name, DOID_TYPE do_id,
-                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
-  DCField *field = get_field_by_name(field_name);
-  if (field == nullptr) {
-    ostringstream strm;
-    strm << "No field named " << field_name << " in class " << get_name()
-         << "\n";
-    nassert_raise(strm.str());
-    return Datagram();
-  }
-
-  return field->ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to generate a new
- * distributed object from the client.  This requires querying the object for
- * the initial value of its required fields.
- *
- * optional_fields is a list of fieldNames to generate in addition to the
- * normal required fields.
- *
- * This method is only called by the CMU implementation.
- */
-Datagram DCClass::
-client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
-                           ZONEID_TYPE zone_id,
-                           PyObject *optional_fields) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
-
-  packer.raw_pack_uint32(zone_id);
-  packer.raw_pack_uint16(_number);
-  packer.raw_pack_uint32(do_id);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == nullptr) {
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  // Also specify the optional fields.
-  int num_optional_fields = 0;
-  if (PyObject_IsTrue(optional_fields)) {
-    num_optional_fields = PySequence_Size(optional_fields);
-  }
-  packer.raw_pack_uint16(num_optional_fields);
-
-  for (int i = 0; i < num_optional_fields; i++) {
-    PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
-    string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-    string field_name = PyString_AsString(py_field_name);
-#endif
-    Py_XDECREF(py_field_name);
-
-    DCField *field = get_field_by_name(field_name);
-    if (field == nullptr) {
-      ostringstream strm;
-      strm << "No field named " << field_name << " in class " << get_name()
-           << "\n";
-      nassert_raise(strm.str());
-      return Datagram();
-    }
-    packer.raw_pack_uint16(field->get_number());
-    packer.begin_pack(field);
-    if (!pack_required_field(packer, distobj, field)) {
-      return Datagram();
-    }
-    packer.end_pack();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to generate a new
- * distributed object from the AI. This requires querying the object for the
- * initial value of its required fields.
- *
- * optional_fields is a list of fieldNames to generate in addition to the
- * normal required fields.
- */
-Datagram DCClass::
-ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
-                   DOID_TYPE parent_id, ZONEID_TYPE zone_id,
-                   CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
-                   PyObject *optional_fields) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(district_channel_id);
-  packer.RAW_PACK_CHANNEL(from_channel_id);
-    // packer.raw_pack_uint8('A');
-
-  bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
-
-  if (has_optional_fields) {
-    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
-  } else {
-    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
-  }
-
-  packer.raw_pack_uint32(do_id);
-  // Parent is a bit overloaded; this parent is not about inheritance, this
-  // one is about the visibility container parent, i.e.  the zone parent:
-  packer.raw_pack_uint32(parent_id);
-  packer.raw_pack_uint32(zone_id);
-  packer.raw_pack_uint16(_number);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == nullptr) {
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  // Also specify the optional fields.
-  if (has_optional_fields) {
-    int num_optional_fields = PySequence_Size(optional_fields);
-    packer.raw_pack_uint16(num_optional_fields);
-
-    for (int i = 0; i < num_optional_fields; ++i) {
-      PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
-      string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-      string field_name = PyString_AsString(py_field_name);
-#endif
-      Py_XDECREF(py_field_name);
-
-      DCField *field = get_field_by_name(field_name);
-      if (field == nullptr) {
-        ostringstream strm;
-        strm << "No field named " << field_name << " in class " << get_name()
-             << "\n";
-        nassert_raise(strm.str());
-        return Datagram();
-      }
-
-      packer.raw_pack_uint16(field->get_number());
-
-      packer.begin_pack(field);
-      if (!pack_required_field(packer, distobj, field)) {
-        return Datagram();
-      }
-      packer.end_pack();
-    }
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
 /**
 /**
  * Write a string representation of this instance to <out>.
  * Write a string representation of this instance to <out>.
  */
  */
 void DCClass::
 void DCClass::
-output(ostream &out, bool brief) const {
+output(std::ostream &out, bool brief) const {
   output_instance(out, brief, "", "", "");
   output_instance(out, brief, "", "", "");
 }
 }
 
 
@@ -1026,7 +342,7 @@ output(ostream &out, bool brief) const {
  * stream.
  * stream.
  */
  */
 void DCClass::
 void DCClass::
-write(ostream &out, bool brief, int indent_level) const {
+write(std::ostream &out, bool brief, int indent_level) const {
   indent(out, indent_level);
   indent(out, indent_level);
   if (_is_struct) {
   if (_is_struct) {
     out << "struct";
     out << "struct";
@@ -1086,7 +402,7 @@ write(ostream &out, bool brief, int indent_level) const {
  * stream.
  * stream.
  */
  */
 void DCClass::
 void DCClass::
-output_instance(ostream &out, bool brief, const string &prename,
+output_instance(std::ostream &out, bool brief, const string &prename,
                 const string &name, const string &postname) const {
                 const string &name, const string &postname) const {
   if (_is_struct) {
   if (_is_struct) {
     out << "struct";
     out << "struct";

+ 58 - 44
direct/src/dcparser/dcClass.h

@@ -17,11 +17,12 @@
 #include "dcbase.h"
 #include "dcbase.h"
 #include "dcField.h"
 #include "dcField.h"
 #include "dcDeclaration.h"
 #include "dcDeclaration.h"
-#include "dcPython.h"
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
 #include "pStatCollector.h"
 #include "pStatCollector.h"
 #include "configVariableBool.h"
 #include "configVariableBool.h"
+#include "extension.h"
+#include "datagramIterator.h"
 
 
 extern ConfigVariableBool dc_multiple_inheritance;
 extern ConfigVariableBool dc_multiple_inheritance;
 extern ConfigVariableBool dc_virtual_inheritance;
 extern ConfigVariableBool dc_virtual_inheritance;
@@ -80,44 +81,52 @@ PUBLISHED:
 
 
   virtual void output(std::ostream &out) const;
   virtual void output(std::ostream &out) const;
 
 
-#ifdef HAVE_PYTHON
-  bool has_class_def() const;
-  void set_class_def(PyObject *class_def);
-  PyObject *get_class_def() const;
-  bool has_owner_class_def() const;
-  void set_owner_class_def(PyObject *owner_class_def);
-  PyObject *get_owner_class_def() const;
-
-  void receive_update(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
-  void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
-
-  void direct_update(PyObject *distobj, const std::string &field_name,
-                     const vector_uchar &value_blob);
-  void direct_update(PyObject *distobj, const std::string &field_name,
-                     const Datagram &datagram);
-  bool pack_required_field(Datagram &datagram, PyObject *distobj,
-                           const DCField *field) const;
-  bool pack_required_field(DCPacker &packer, PyObject *distobj,
-                           const DCField *field) const;
-
-
-
-  Datagram client_format_update(const std::string &field_name,
-                                DOID_TYPE do_id, PyObject *args) const;
-  Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
-                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
-  Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
-                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
-  Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id, ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
-                              CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
-                              PyObject *optional_fields) const;
-  Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
-                                      ZONEID_TYPE zone_id,                                                           PyObject *optional_fields) const;
+  EXTENSION(bool has_class_def() const);
+  EXTENSION(void set_class_def(PyObject *class_def));
+  EXTENSION(PyObject *get_class_def() const);
+  EXTENSION(bool has_owner_class_def() const);
+  EXTENSION(void set_owner_class_def(PyObject *owner_class_def));
+  EXTENSION(PyObject *get_owner_class_def() const);
+
+  EXTENSION(void receive_update(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const);
+  EXTENSION(void receive_update_other(PyObject *distobj, DatagramIterator &di) const);
+
+  EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
+                               const vector_uchar &value_blob));
+  EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
+                               const Datagram &datagram));
+  EXTENSION(bool pack_required_field(Datagram &datagram, PyObject *distobj,
+                                     const DCField *field) const);
+  EXTENSION(bool pack_required_field(DCPacker &packer, PyObject *distobj,
+                                     const DCField *field) const);
+
+
+
+  EXTENSION(Datagram client_format_update(const std::string &field_name,
+                                          DOID_TYPE do_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update(const std::string &field_name,
+                                      DOID_TYPE do_id,
+                                      CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                                      PyObject *args) const);
+  EXTENSION(Datagram ai_format_update_msg_type(const std::string &field_name,
+                                               DOID_TYPE do_id,
+                                               CHANNEL_TYPE to_id,
+                                               CHANNEL_TYPE from_id,
+                                               int msg_type,
+                                               PyObject *args) const);
+  EXTENSION(Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                                        ZONEID_TYPE parent_id,
+                                        ZONEID_TYPE zone_id,
+                                        CHANNEL_TYPE district_channel_id,
+                                        CHANNEL_TYPE from_channel_id,
+                                        PyObject *optional_fields) const);
+  EXTENSION(Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                                                ZONEID_TYPE zone_id,
+                                                PyObject *optional_fields) const);
 
 
-#endif
 
 
 public:
 public:
   virtual void output(std::ostream &out, bool brief) const;
   virtual void output(std::ostream &out, bool brief) const;
@@ -136,8 +145,8 @@ private:
   void shadow_inherited_field(const std::string &name);
   void shadow_inherited_field(const std::string &name);
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
-  PStatCollector _class_update_pcollector;
-  PStatCollector _class_generate_pcollector;
+  mutable PStatCollector _class_update_pcollector;
+  mutable PStatCollector _class_generate_pcollector;
   static PStatCollector _update_pcollector;
   static PStatCollector _update_pcollector;
   static PStatCollector _generate_pcollector;
   static PStatCollector _generate_pcollector;
 #endif
 #endif
@@ -163,12 +172,17 @@ private:
   typedef pmap<int, DCField *> FieldsByIndex;
   typedef pmap<int, DCField *> FieldsByIndex;
   FieldsByIndex _fields_by_index;
   FieldsByIndex _fields_by_index;
 
 
-#ifdef HAVE_PYTHON
-  PyObject *_class_def;
-  PyObject *_owner_class_def;
-#endif
+  // See pandaNode.h for an explanation of this trick
+  class PythonClassDefs : public ReferenceCount {
+  public:
+    virtual ~PythonClassDefs() {};
+  };
+  PT(PythonClassDefs) _python_class_defs;
 
 
   friend class DCField;
   friend class DCField;
+#ifdef WITHIN_PANDA
+  friend class Extension<DCClass>;
+#endif
 };
 };
 
 
 #include "dcClass.I"
 #include "dcClass.I"

+ 665 - 0
direct/src/dcparser/dcClass_ext.cxx

@@ -0,0 +1,665 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcClass_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcClass_ext.h"
+#include "dcField_ext.h"
+#include "dcAtomicField.h"
+#include "dcPacker.h"
+#include "dcmsgtypes.h"
+
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "pStatTimer.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Returns true if the DCClass object has an associated Python class
+ * definition, false otherwise.
+ */
+bool Extension<DCClass>::
+has_class_def() const {
+  return _this->_python_class_defs != nullptr
+      && ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_class_def != nullptr;
+}
+
+/**
+ * Sets the class object associated with this DistributedClass.  This object
+ * will be used to construct new instances of the class.
+ */
+void Extension<DCClass>::
+set_class_def(PyObject *class_def) {
+  PythonClassDefsImpl *defs = do_get_defs();
+
+  Py_XINCREF(class_def);
+  Py_XDECREF(defs->_class_def);
+  defs->_class_def = class_def;
+}
+
+/**
+ * Returns the class object that was previously associated with this
+ * DistributedClass.  This will return a new reference to the object.
+ */
+PyObject *Extension<DCClass>::
+get_class_def() const {
+  if (!has_class_def()) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  PythonClassDefsImpl *defs = do_get_defs();
+  Py_INCREF(defs->_class_def);
+  return defs->_class_def;
+}
+
+/**
+ * Returns true if the DCClass object has an associated Python owner class
+ * definition, false otherwise.
+ */
+bool Extension<DCClass>::
+has_owner_class_def() const {
+  return _this->_python_class_defs != nullptr
+      && ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_owner_class_def != nullptr;
+}
+
+/**
+ * Sets the owner class object associated with this DistributedClass.  This
+ * object will be used to construct new owner instances of the class.
+ */
+void Extension<DCClass>::
+set_owner_class_def(PyObject *owner_class_def) {
+  PythonClassDefsImpl *defs = do_get_defs();
+
+  Py_XINCREF(owner_class_def);
+  Py_XDECREF(defs->_owner_class_def);
+  defs->_owner_class_def = owner_class_def;
+}
+
+/**
+ * Returns the owner class object that was previously associated with this
+ * DistributedClass.  This will return a new reference to the object.
+ */
+PyObject *Extension<DCClass>::
+get_owner_class_def() const {
+  if (!has_owner_class_def()) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  PythonClassDefsImpl *defs = do_get_defs();
+  Py_INCREF(defs->_owner_class_def);
+  return defs->_owner_class_def;
+}
+
+/**
+ * Extracts the update message out of the packer and applies it to the
+ * indicated object by calling the appropriate method.
+ */
+void Extension<DCClass>::
+receive_update(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int field_id = packer.raw_unpack_uint16();
+  DCField *field = _this->get_field_by_index(field_id);
+  if (field == nullptr) {
+    ostringstream strm;
+    strm
+        << "Received update for field " << field_id << ", not in class "
+        << _this->get_name();
+    nassert_raise(strm.str());
+    return;
+  }
+
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent along with a normal "generate with required" message.  This is all
+ * of the atomic fields that are marked "broadcast required".
+ */
+void Extension<DCClass>::
+receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required() && field->is_broadcast()) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent along with a normal "generate with required" message.  This is all
+ * of the atomic fields that are marked "broadcast ownrecv". Should be used
+ * for 'owner-view' objects.
+ */
+void Extension<DCClass>::
+receive_update_broadcast_required_owner(PyObject *distobj,
+                                        DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a big datagram that includes all of the "required" fields that
+ * are sent when an avatar is created.  This is all of the atomic fields that
+ * are marked "required", whether they are broadcast or not.
+ */
+void Extension<DCClass>::
+receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  DCPacker packer;
+  const char *data = (const char *)di.get_datagram().get_data();
+  packer.set_unpack_data(data + di.get_current_index(),
+                         di.get_remaining_size(), false);
+
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->as_molecular_field() == nullptr &&
+        field->is_required()) {
+      packer.begin_unpack(field);
+      invoke_extension(field).receive_update(packer, distobj);
+      if (!packer.end_unpack()) {
+        break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+
+/**
+ * Processes a datagram that lists some additional fields that are broadcast
+ * in one chunk.
+ */
+void Extension<DCClass>::
+receive_update_other(PyObject *distobj, DatagramIterator &di) const {
+  PStatTimer timer(_this->_class_update_pcollector);
+  int num_fields = di.get_uint16();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    receive_update(distobj, di);
+  }
+}
+
+/**
+ * Processes an update for a named field from a packed value blob.
+ */
+void Extension<DCClass>::
+direct_update(PyObject *distobj, const std::string &field_name,
+              const vector_uchar &value_blob) {
+  DCField *field = _this->get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data(value_blob);
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+}
+
+/**
+ * Processes an update for a named field from a packed datagram.
+ */
+void Extension<DCClass>::
+direct_update(PyObject *distobj, const std::string &field_name,
+              const Datagram &datagram) {
+  DCField *field = _this->get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
+  packer.begin_unpack(field);
+  invoke_extension(field).receive_update(packer, distobj);
+  packer.end_unpack();
+}
+
+/**
+ * Looks up the current value of the indicated field by calling the
+ * appropriate get*() function, then packs that value into the datagram.  This
+ * field is presumably either a required field or a specified optional field,
+ * and we are building up a datagram for the generate-with-required message.
+ *
+ * Returns true on success, false on failure.
+ */
+bool Extension<DCClass>::
+pack_required_field(Datagram &datagram, PyObject *distobj,
+                    const DCField *field) const {
+  DCPacker packer;
+  packer.begin_pack(field);
+  if (!pack_required_field(packer, distobj, field)) {
+    return false;
+  }
+  if (!packer.end_pack()) {
+    return false;
+  }
+
+  datagram.append_data(packer.get_data(), packer.get_length());
+  return true;
+}
+
+/**
+ * Looks up the current value of the indicated field by calling the
+ * appropriate get*() function, then packs that value into the packer.  This
+ * field is presumably either a required field or a specified optional field,
+ * and we are building up a datagram for the generate-with-required message.
+ *
+ * Returns true on success, false on failure.
+ */
+bool Extension<DCClass>::
+pack_required_field(DCPacker &packer, PyObject *distobj,
+                    const DCField *field) const {
+  using std::ostringstream;
+
+  const DCParameter *parameter = field->as_parameter();
+  if (parameter != nullptr) {
+    // This is the easy case: to pack a parameter, we just look on the class
+    // object for the data element.
+    std::string field_name = field->get_name();
+
+    if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
+      // If the attribute is not defined, but the field has a default value
+      // specified, quietly pack the default value.
+      if (field->has_default_value()) {
+        packer.pack_default_value();
+        return true;
+      }
+
+      // If there is no default value specified, it's an error.
+      ostringstream strm;
+      strm << "Data element " << field_name
+           << ", required by dc file for dclass " << _this->get_name()
+           << ", not defined on object";
+      nassert_raise(strm.str());
+      return false;
+    }
+    PyObject *result =
+      PyObject_GetAttrString(distobj, (char *)field_name.c_str());
+    nassertr(result != nullptr, false);
+
+    // Now pack the value into the datagram.
+    bool pack_ok = invoke_extension((DCField *)parameter).pack_args(packer, result);
+    Py_DECREF(result);
+
+    return pack_ok;
+  }
+
+  if (field->as_molecular_field() != nullptr) {
+    ostringstream strm;
+    strm << "Cannot pack molecular field " << field->get_name()
+         << " for generate";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  const DCAtomicField *atom = field->as_atomic_field();
+  nassertr(atom != nullptr, false);
+
+  // We need to get the initial value of this field.  There isn't a good,
+  // robust way to get this; presently, we just mangle the "setFoo()" name of
+  // the required field into "getFoo()" and call that.
+  std::string setter_name = atom->get_name();
+
+  if (setter_name.empty()) {
+    ostringstream strm;
+    strm << "Required field is unnamed!";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  if (atom->get_num_elements() == 0) {
+    // It sure doesn't make sense to have a required field with no parameters.
+    // What data, exactly, is required?
+    ostringstream strm;
+    strm << "Required field " << setter_name << " has no parameters!";
+    nassert_raise(strm.str());
+    return false;
+  }
+
+  std::string getter_name = setter_name;
+  if (setter_name.substr(0, 3) == "set") {
+    // If the original method started with "set", we mangle this directly to
+    // "get".
+    getter_name[0] = 'g';
+
+  } else {
+    // Otherwise, we add a "get" prefix, and capitalize the next letter.
+    getter_name = "get" + setter_name;
+    getter_name[3] = toupper(getter_name[3]);
+  }
+
+  // Now we have to look up the getter on the distributed object and call it.
+  if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
+    // As above, if there's no getter but the field has a default value
+    // specified, quietly pack the default value.
+    if (field->has_default_value()) {
+      packer.pack_default_value();
+      return true;
+    }
+
+    // Otherwise, with no default value it's an error.
+    ostringstream strm;
+    strm << "Distributed class " << _this->get_name()
+         << " doesn't have getter named " << getter_name
+         << " to match required field " << setter_name;
+    nassert_raise(strm.str());
+    return false;
+  }
+  PyObject *func =
+    PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
+  nassertr(func != nullptr, false);
+
+  PyObject *empty_args = PyTuple_New(0);
+  PyObject *result = PyObject_CallObject(func, empty_args);
+  Py_DECREF(empty_args);
+  Py_DECREF(func);
+  if (result == nullptr) {
+    // We don't set this as an exception, since presumably the Python method
+    // itself has already triggered a Python exception.
+    std::cerr << "Error when calling " << getter_name << "\n";
+    return false;
+  }
+
+  if (atom->get_num_elements() == 1) {
+    // In this case, we expect the getter to return one object, which we wrap
+    // up in a tuple.
+    PyObject *tuple = PyTuple_New(1);
+    PyTuple_SET_ITEM(tuple, 0, result);
+    result = tuple;
+
+  } else {
+    // Otherwise, it had better already be a sequence or tuple of some sort.
+    if (!PySequence_Check(result)) {
+      ostringstream strm;
+      strm << "Since dclass " << _this->get_name() << " method " << setter_name
+           << " is declared to have multiple parameters, Python function "
+           << getter_name << " must return a list or tuple.\n";
+      nassert_raise(strm.str());
+      return false;
+    }
+  }
+
+  // Now pack the arguments into the datagram.
+  bool pack_ok = invoke_extension((DCField *)atom).pack_args(packer, result);
+  Py_DECREF(result);
+
+  return pack_ok;
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the client.
+ */
+Datagram Extension<DCClass>::
+client_format_update(const std::string &field_name, DOID_TYPE do_id,
+                     PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).client_format_update(do_id, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the AI.
+ */
+Datagram Extension<DCClass>::
+ai_format_update(const std::string &field_name, DOID_TYPE do_id,
+                 CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).ai_format_update(do_id, to_id, from_id, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update,
+ * using the indicated msg type for the indicated distributed object from the
+ * AI.
+ */
+Datagram Extension<DCClass>::
+ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
+                          CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                          int msg_type, PyObject *args) const {
+  DCField *field = _this->get_field_by_name(field_name);
+  if (field == nullptr) {
+    std::ostringstream strm;
+    strm << "No field named " << field_name << " in class " << _this->get_name()
+         << "\n";
+    nassert_raise(strm.str());
+    return Datagram();
+  }
+
+  return invoke_extension(field).ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
+}
+
+/**
+ * Generates a datagram containing the message necessary to generate a new
+ * distributed object from the client.  This requires querying the object for
+ * the initial value of its required fields.
+ *
+ * optional_fields is a list of fieldNames to generate in addition to the
+ * normal required fields.
+ *
+ * This method is only called by the CMU implementation.
+ */
+Datagram Extension<DCClass>::
+client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                           ZONEID_TYPE zone_id,
+                           PyObject *optional_fields) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
+
+  packer.raw_pack_uint32(zone_id);
+  packer.raw_pack_uint16(_this->_number);
+  packer.raw_pack_uint32(do_id);
+
+  // Specify all of the required fields.
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields; ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->is_required() && field->as_molecular_field() == nullptr) {
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  // Also specify the optional fields.
+  int num_optional_fields = 0;
+  if (PyObject_IsTrue(optional_fields)) {
+    num_optional_fields = PySequence_Size(optional_fields);
+  }
+  packer.raw_pack_uint16(num_optional_fields);
+
+  for (int i = 0; i < num_optional_fields; i++) {
+    PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
+#if PY_MAJOR_VERSION >= 3
+    std::string field_name = PyUnicode_AsUTF8(py_field_name);
+#else
+    std::string field_name = PyString_AsString(py_field_name);
+#endif
+    Py_XDECREF(py_field_name);
+
+    DCField *field = _this->get_field_by_name(field_name);
+    if (field == nullptr) {
+      std::ostringstream strm;
+      strm << "No field named " << field_name << " in class " << _this->get_name()
+           << "\n";
+      nassert_raise(strm.str());
+      return Datagram();
+    }
+    packer.raw_pack_uint16(field->get_number());
+    packer.begin_pack(field);
+    if (!pack_required_field(packer, distobj, field)) {
+      return Datagram();
+    }
+    packer.end_pack();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to generate a new
+ * distributed object from the AI. This requires querying the object for the
+ * initial value of its required fields.
+ *
+ * optional_fields is a list of fieldNames to generate in addition to the
+ * normal required fields.
+ */
+Datagram Extension<DCClass>::
+ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                   DOID_TYPE parent_id, ZONEID_TYPE zone_id,
+                   CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
+                   PyObject *optional_fields) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(district_channel_id);
+  packer.RAW_PACK_CHANNEL(from_channel_id);
+    // packer.raw_pack_uint8('A');
+
+  bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
+
+  if (has_optional_fields) {
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
+  } else {
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
+  }
+
+  packer.raw_pack_uint32(do_id);
+  // Parent is a bit overloaded; this parent is not about inheritance, this
+  // one is about the visibility container parent, i.e.  the zone parent:
+  packer.raw_pack_uint32(parent_id);
+  packer.raw_pack_uint32(zone_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  // Specify all of the required fields.
+  int num_fields = _this->get_num_inherited_fields();
+  for (int i = 0; i < num_fields; ++i) {
+    DCField *field = _this->get_inherited_field(i);
+    if (field->is_required() && field->as_molecular_field() == nullptr) {
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  // Also specify the optional fields.
+  if (has_optional_fields) {
+    int num_optional_fields = PySequence_Size(optional_fields);
+    packer.raw_pack_uint16(num_optional_fields);
+
+    for (int i = 0; i < num_optional_fields; ++i) {
+      PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
+#if PY_MAJOR_VERSION >= 3
+      std::string field_name = PyUnicode_AsUTF8(py_field_name);
+#else
+      std::string field_name = PyString_AsString(py_field_name);
+#endif
+      Py_XDECREF(py_field_name);
+
+      DCField *field = _this->get_field_by_name(field_name);
+      if (field == nullptr) {
+        std::ostringstream strm;
+        strm << "No field named " << field_name << " in class "
+             << _this->get_name() << "\n";
+        nassert_raise(strm.str());
+        return Datagram();
+      }
+
+      packer.raw_pack_uint16(field->get_number());
+
+      packer.begin_pack(field);
+      if (!pack_required_field(packer, distobj, field)) {
+        return Datagram();
+      }
+      packer.end_pack();
+    }
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Returns the PythonClassDefsImpl object stored on the DCClass object,
+ * creating it if it didn't yet exist.
+ */
+Extension<DCClass>::PythonClassDefsImpl *Extension<DCClass>::
+do_get_defs() const {
+  if (!_this->_python_class_defs) {
+    _this->_python_class_defs = new PythonClassDefsImpl();
+  }
+  return (PythonClassDefsImpl *)_this->_python_class_defs.p();
+}
+
+#endif  // HAVE_PYTHON

+ 93 - 0
direct/src/dcparser/dcClass_ext.h

@@ -0,0 +1,93 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcClass_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCCLASS_EXT_H
+#define DCCLASS_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcClass.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCClass, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCClass> : public ExtensionBase<DCClass> {
+public:
+  bool has_class_def() const;
+  void set_class_def(PyObject *class_def);
+  PyObject *get_class_def() const;
+  bool has_owner_class_def() const;
+  void set_owner_class_def(PyObject *owner_class_def);
+  PyObject *get_owner_class_def() const;
+
+  void receive_update(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
+
+  void direct_update(PyObject *distobj, const std::string &field_name,
+                     const vector_uchar &value_blob);
+  void direct_update(PyObject *distobj, const std::string &field_name,
+                     const Datagram &datagram);
+  bool pack_required_field(Datagram &datagram, PyObject *distobj,
+                           const DCField *field) const;
+  bool pack_required_field(DCPacker &packer, PyObject *distobj,
+                           const DCField *field) const;
+
+
+
+  Datagram client_format_update(const std::string &field_name,
+                                DOID_TYPE do_id, PyObject *args) const;
+  Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
+                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
+  Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
+                            CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
+  Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
+                              ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
+                              CHANNEL_TYPE district_channel_id,
+                              CHANNEL_TYPE from_channel_id,
+                              PyObject *optional_fields) const;
+  Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
+                                      ZONEID_TYPE zone_id,
+                                      PyObject *optional_fields) const;
+
+private:
+  /**
+   * Implementation of DCClass::PythonClassDefs which actually stores the
+   * Python pointers.  This needs to be defined here rather than on DCClass
+   * itself, since DCClass cannot include Python.h or call Python functions.
+   */
+  class PythonClassDefsImpl : public DCClass::PythonClassDefs {
+  public:
+    virtual ~PythonClassDefsImpl() {
+      Py_XDECREF(_class_def);
+      Py_XDECREF(_owner_class_def);
+    }
+
+    PyObject *_class_def = nullptr;
+    PyObject *_owner_class_def = nullptr;
+  };
+
+  PythonClassDefsImpl *do_get_defs() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCCLASS_EXT_H

+ 6 - 313
direct/src/dcparser/dcField.cxx

@@ -18,16 +18,6 @@
 #include "hashGenerator.h"
 #include "hashGenerator.h"
 #include "dcmsgtypes.h"
 #include "dcmsgtypes.h"
 
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
-#ifdef WITHIN_PANDA
-#include "pStatTimer.h"
-#endif
-
-using std::string;
-
 /**
 /**
  *
  *
  */
  */
@@ -58,7 +48,7 @@ DCField() :
  *
  *
  */
  */
 DCField::
 DCField::
-DCField(const string &name, DCClass *dclass) :
+DCField(const std::string &name, DCClass *dclass) :
   DCPackerInterface(name),
   DCPackerInterface(name),
   _dclass(dclass)
   _dclass(dclass)
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
@@ -161,14 +151,14 @@ as_parameter() const {
  * string formatting it for human consumption.  Returns empty string if there
  * string formatting it for human consumption.  Returns empty string if there
  * is an error.
  * is an error.
  */
  */
-string DCField::
+std::string DCField::
 format_data(const vector_uchar &packed_data, bool show_field_names) {
 format_data(const vector_uchar &packed_data, bool show_field_names) {
   DCPacker packer;
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
   packer.begin_unpack(this);
-  string result = packer.unpack_and_format(show_field_names);
+  std::string result = packer.unpack_and_format(show_field_names);
   if (!packer.end_unpack()) {
   if (!packer.end_unpack()) {
-    return string();
+    return std::string();
   }
   }
   return result;
   return result;
 }
 }
@@ -179,7 +169,7 @@ format_data(const vector_uchar &packed_data, bool show_field_names) {
  * the corresponding packed data.  Returns empty string if there is an error.
  * the corresponding packed data.  Returns empty string if there is an error.
  */
  */
 vector_uchar DCField::
 vector_uchar DCField::
-parse_string(const string &formatted_string) {
+parse_string(const std::string &formatted_string) {
   DCPacker packer;
   DCPacker packer;
   packer.begin_pack(this);
   packer.begin_pack(this);
   if (!packer.parse_and_pack(formatted_string)) {
   if (!packer.parse_and_pack(formatted_string)) {
@@ -212,254 +202,6 @@ validate_ranges(const vector_uchar &packed_data) const {
   return (packer.get_num_unpacked_bytes() == packed_data.size());
   return (packer.get_num_unpacked_bytes() == packed_data.size());
 }
 }
 
 
-#ifdef HAVE_PYTHON
-/**
- * Packs the Python arguments from the indicated tuple into the packer.
- * Returns true on success, false on failure.
- *
- * It is assumed that the packer is currently positioned on this field.
- */
-bool DCField::
-pack_args(DCPacker &packer, PyObject *sequence) const {
-  nassertr(!packer.had_error(), false);
-  nassertr(packer.get_current_field() == this, false);
-
-  packer.pack_object(sequence);
-  if (!packer.had_error()) {
-    /*
-    cerr << "pack " << get_name() << get_pystr(sequence) << "\n";
-    */
-
-    return true;
-  }
-
-  if (!Notify::ptr()->has_assert_failed()) {
-    std::ostringstream strm;
-    PyObject *exc_type = PyExc_Exception;
-
-    if (as_parameter() != nullptr) {
-      // If it's a parameter-type field, the value may or may not be a
-      // sequence.
-      if (packer.had_pack_error()) {
-        strm << "Incorrect arguments to field: " << get_name()
-             << " = " << get_pystr(sequence);
-        exc_type = PyExc_TypeError;
-      } else {
-        strm << "Value out of range on field: " << get_name()
-             << " = " << get_pystr(sequence);
-        exc_type = PyExc_ValueError;
-      }
-
-    } else {
-      // If it's a molecular or atomic field, the value should be a sequence.
-      PyObject *tuple = PySequence_Tuple(sequence);
-      if (tuple == nullptr) {
-        strm << "Value for " << get_name() << " not a sequence: " \
-             << get_pystr(sequence);
-        exc_type = PyExc_TypeError;
-
-      } else {
-        if (packer.had_pack_error()) {
-          strm << "Incorrect arguments to field: " << get_name()
-               << get_pystr(sequence);
-          exc_type = PyExc_TypeError;
-        } else {
-          strm << "Value out of range on field: " << get_name()
-               << get_pystr(sequence);
-          exc_type = PyExc_ValueError;
-        }
-
-        Py_DECREF(tuple);
-      }
-    }
-
-    string message = strm.str();
-    PyErr_SetString(exc_type, message.c_str());
-  }
-  return false;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks the values from the packer, beginning at the current point in the
- * unpack_buffer, into a Python tuple and returns the tuple.
- *
- * It is assumed that the packer is currently positioned on this field.
- */
-PyObject *DCField::
-unpack_args(DCPacker &packer) const {
-  nassertr(!packer.had_error(), nullptr);
-  nassertr(packer.get_current_field() == this, nullptr);
-
-  size_t start_byte = packer.get_num_unpacked_bytes();
-  PyObject *object = packer.unpack_object();
-
-  if (!packer.had_error()) {
-    // Successfully unpacked.
-    /*
-    cerr << "recv " << get_name() << get_pystr(object) << "\n";
-    */
-
-    return object;
-  }
-
-  if (!Notify::ptr()->has_assert_failed()) {
-    std::ostringstream strm;
-    PyObject *exc_type = PyExc_Exception;
-
-    if (packer.had_pack_error()) {
-      strm << "Data error unpacking field ";
-      output(strm, true);
-      size_t length = packer.get_unpack_length() - start_byte;
-      strm << "\nGot data (" << (int)length << " bytes):\n";
-      Datagram dg(packer.get_unpack_data() + start_byte, length);
-      dg.dump_hex(strm);
-      size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
-      strm << "Error detected on byte " << error_byte
-           << " (" << std::hex << error_byte << std::dec << " hex)";
-
-      exc_type = PyExc_RuntimeError;
-    } else {
-      strm << "Value outside specified range when unpacking field "
-           << get_name() << ": " << get_pystr(object);
-      exc_type = PyExc_ValueError;
-    }
-
-    string message = strm.str();
-    PyErr_SetString(exc_type, message.c_str());
-  }
-
-  Py_XDECREF(object);
-  return nullptr;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Extracts the update message out of the datagram and applies it to the
- * indicated object by calling the appropriate method.
- */
-void DCField::
-receive_update(DCPacker &packer, PyObject *distobj) const {
-  if (as_parameter() != nullptr) {
-    // If it's a parameter-type field, just store a new value on the object.
-    PyObject *value = unpack_args(packer);
-    if (value != nullptr) {
-      PyObject_SetAttrString(distobj, (char *)_name.c_str(), value);
-    }
-    Py_DECREF(value);
-
-  } else {
-    // Otherwise, it must be an atomic or molecular field, so call the
-    // corresponding method.
-
-    if (!PyObject_HasAttrString(distobj, (char *)_name.c_str())) {
-      // If there's no Python method to receive this message, don't bother
-      // unpacking it to a Python tuple--just skip past the message.
-      packer.unpack_skip();
-
-    } else {
-      // Otherwise, get a Python tuple from the args and call the Python
-      // method.
-      PyObject *args = unpack_args(packer);
-
-      if (args != nullptr) {
-        PyObject *func = PyObject_GetAttrString(distobj, (char *)_name.c_str());
-        nassertv(func != nullptr);
-
-        PyObject *result;
-        {
-#ifdef WITHIN_PANDA
-          PStatTimer timer(((DCField *)this)->_field_update_pcollector);
-#endif
-          result = PyObject_CallObject(func, args);
-        }
-        Py_XDECREF(result);
-        Py_DECREF(func);
-        Py_DECREF(args);
-      }
-    }
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the client.
- */
-Datagram DCField::
-client_format_update(DOID_TYPE do_id, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update for
- * the indicated distributed object from the AI.
- */
-Datagram DCField::
-ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(to_id);
-  packer.RAW_PACK_CHANNEL(from_id);
-  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to send an update,
- * with the msg type, for the indicated distributed object from the AI.
- */
-Datagram DCField::
-ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
-  DCPacker packer;
-
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(to_id);
-  packer.RAW_PACK_CHANNEL(from_id);
-  packer.raw_pack_uint16(msg_type);
-  packer.raw_pack_uint32(do_id);
-  packer.raw_pack_uint16(_number);
-
-  packer.begin_pack(this);
-  pack_args(packer, args);
-  if (!packer.end_pack()) {
-    return Datagram();
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-
 /**
 /**
  * Accumulates the properties of this field into the hash.
  * Accumulates the properties of this field into the hash.
  */
  */
@@ -499,62 +241,13 @@ pack_default_value(DCPackData &pack_data, bool &) const {
  * Sets the name of this field.
  * Sets the name of this field.
  */
  */
 void DCField::
 void DCField::
-set_name(const string &name) {
+set_name(const std::string &name) {
   DCPackerInterface::set_name(name);
   DCPackerInterface::set_name(name);
   if (_dclass != nullptr) {
   if (_dclass != nullptr) {
     _dclass->_dc_file->mark_inherited_fields_stale();
     _dclass->_dc_file->mark_inherited_fields_stale();
   }
   }
 }
 }
 
 
-#ifdef HAVE_PYTHON
-/**
- * Returns the string representation of the indicated Python object.
- */
-string DCField::
-get_pystr(PyObject *value) {
-  if (value == nullptr) {
-    return "(null)";
-  }
-
-  PyObject *str = PyObject_Str(value);
-  if (str != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-    string result = PyUnicode_AsUTF8(str);
-#else
-    string result = PyString_AsString(str);
-#endif
-    Py_DECREF(str);
-    return result;
-  }
-
-  PyObject *repr = PyObject_Repr(value);
-  if (repr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-    string result = PyUnicode_AsUTF8(repr);
-#else
-    string result = PyString_AsString(repr);
-#endif
-    Py_DECREF(repr);
-    return result;
-  }
-
-  if (value->ob_type != nullptr) {
-    PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
-    if (typestr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
-      string result = PyUnicode_AsUTF8(typestr);
-#else
-      string result = PyString_AsString(typestr);
-#endif
-      Py_DECREF(typestr);
-      return result;
-    }
-  }
-
-  return "(invalid object)";
-}
-#endif  // HAVE_PYTHON
-
 /**
 /**
  * Recomputes the default value of the field by repacking it.
  * Recomputes the default value of the field by repacking it.
  */
  */

+ 13 - 15
direct/src/dcparser/dcField.h

@@ -17,10 +17,11 @@
 #include "dcbase.h"
 #include "dcbase.h"
 #include "dcPackerInterface.h"
 #include "dcPackerInterface.h"
 #include "dcKeywordList.h"
 #include "dcKeywordList.h"
-#include "dcPython.h"
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
 #include "pStatCollector.h"
 #include "pStatCollector.h"
+#include "extension.h"
+#include "datagram.h"
 #endif
 #endif
 
 
 class DCPacker;
 class DCPacker;
@@ -76,18 +77,17 @@ PUBLISHED:
   INLINE void output(std::ostream &out) const;
   INLINE void output(std::ostream &out) const;
   INLINE void write(std::ostream &out, int indent_level) const;
   INLINE void write(std::ostream &out, int indent_level) const;
 
 
-#ifdef HAVE_PYTHON
-  bool pack_args(DCPacker &packer, PyObject *sequence) const;
-  PyObject *unpack_args(DCPacker &packer) const;
+  EXTENSION(bool pack_args(DCPacker &packer, PyObject *sequence) const);
+  EXTENSION(PyObject *unpack_args(DCPacker &packer) const);
 
 
-  void receive_update(DCPacker &packer, PyObject *distobj) const;
+  EXTENSION(void receive_update(DCPacker &packer, PyObject *distobj) const);
 
 
-  Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
-  Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
-                            PyObject *args) const;
-  Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
-                            int msg_type, PyObject *args) const;
-#endif
+  EXTENSION(Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id,
+                                      CHANNEL_TYPE from_id, PyObject *args) const);
+  EXTENSION(Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id,
+                                               CHANNEL_TYPE from_id, int msg_type,
+                                               PyObject *args) const);
 
 
 public:
 public:
   virtual void output(std::ostream &out, bool brief) const=0;
   virtual void output(std::ostream &out, bool brief) const=0;
@@ -100,10 +100,6 @@ public:
   INLINE void set_class(DCClass *dclass);
   INLINE void set_class(DCClass *dclass);
   INLINE void set_default_value(vector_uchar default_value);
   INLINE void set_default_value(vector_uchar default_value);
 
 
-#ifdef HAVE_PYTHON
-  static std::string get_pystr(PyObject *value);
-#endif
-
 protected:
 protected:
   void refresh_default_value();
   void refresh_default_value();
 
 
@@ -119,6 +115,8 @@ private:
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
   PStatCollector _field_update_pcollector;
   PStatCollector _field_update_pcollector;
+
+  friend class Extension<DCField>;
 #endif
 #endif
 };
 };
 
 

+ 305 - 0
direct/src/dcparser/dcField_ext.cxx

@@ -0,0 +1,305 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcField_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcField_ext.h"
+#include "dcPacker_ext.h"
+#include "dcmsgtypes.h"
+
+#include "datagram.h"
+#include "pStatTimer.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Packs the Python arguments from the indicated tuple into the packer.
+ * Returns true on success, false on failure.
+ *
+ * It is assumed that the packer is currently positioned on this field.
+ */
+bool Extension<DCField>::
+pack_args(DCPacker &packer, PyObject *sequence) const {
+  nassertr(!packer.had_error(), false);
+  nassertr(packer.get_current_field() == _this, false);
+
+  invoke_extension(&packer).pack_object(sequence);
+  if (!packer.had_error()) {
+    /*
+    cerr << "pack " << _this->get_name() << get_pystr(sequence) << "\n";
+    */
+
+    return true;
+  }
+
+  if (!Notify::ptr()->has_assert_failed()) {
+    std::ostringstream strm;
+    PyObject *exc_type = PyExc_Exception;
+
+    if (_this->as_parameter() != nullptr) {
+      // If it's a parameter-type field, the value may or may not be a
+      // sequence.
+      if (packer.had_pack_error()) {
+        strm << "Incorrect arguments to field: " << _this->get_name()
+             << " = " << get_pystr(sequence);
+        exc_type = PyExc_TypeError;
+      } else {
+        strm << "Value out of range on field: " << _this->get_name()
+             << " = " << get_pystr(sequence);
+        exc_type = PyExc_ValueError;
+      }
+
+    } else {
+      // If it's a molecular or atomic field, the value should be a sequence.
+      PyObject *tuple = PySequence_Tuple(sequence);
+      if (tuple == nullptr) {
+        strm << "Value for " << _this->get_name() << " not a sequence: " \
+             << get_pystr(sequence);
+        exc_type = PyExc_TypeError;
+
+      } else {
+        if (packer.had_pack_error()) {
+          strm << "Incorrect arguments to field: " << _this->get_name()
+               << get_pystr(sequence);
+          exc_type = PyExc_TypeError;
+        } else {
+          strm << "Value out of range on field: " << _this->get_name()
+               << get_pystr(sequence);
+          exc_type = PyExc_ValueError;
+        }
+
+        Py_DECREF(tuple);
+      }
+    }
+
+    std::string message = strm.str();
+    PyErr_SetString(exc_type, message.c_str());
+  }
+  return false;
+}
+
+/**
+ * Unpacks the values from the packer, beginning at the current point in the
+ * unpack_buffer, into a Python tuple and returns the tuple.
+ *
+ * It is assumed that the packer is currently positioned on this field.
+ */
+PyObject *Extension<DCField>::
+unpack_args(DCPacker &packer) const {
+  nassertr(!packer.had_error(), nullptr);
+  nassertr(packer.get_current_field() == _this, nullptr);
+
+  size_t start_byte = packer.get_num_unpacked_bytes();
+  PyObject *object = invoke_extension(&packer).unpack_object();
+
+  if (!packer.had_error()) {
+    // Successfully unpacked.
+    /*
+    cerr << "recv " << _this->get_name() << get_pystr(object) << "\n";
+    */
+
+    return object;
+  }
+
+  if (!Notify::ptr()->has_assert_failed()) {
+    std::ostringstream strm;
+    PyObject *exc_type = PyExc_Exception;
+
+    if (packer.had_pack_error()) {
+      strm << "Data error unpacking field ";
+      _this->output(strm, true);
+      size_t length = packer.get_unpack_length() - start_byte;
+      strm << "\nGot data (" << (int)length << " bytes):\n";
+      Datagram dg(packer.get_unpack_data() + start_byte, length);
+      dg.dump_hex(strm);
+      size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
+      strm << "Error detected on byte " << error_byte
+           << " (" << std::hex << error_byte << std::dec << " hex)";
+
+      exc_type = PyExc_RuntimeError;
+    } else {
+      strm << "Value outside specified range when unpacking field "
+           << _this->get_name() << ": " << get_pystr(object);
+      exc_type = PyExc_ValueError;
+    }
+
+    std::string message = strm.str();
+    PyErr_SetString(exc_type, message.c_str());
+  }
+
+  Py_XDECREF(object);
+  return nullptr;
+}
+
+/**
+ * Extracts the update message out of the datagram and applies it to the
+ * indicated object by calling the appropriate method.
+ */
+void Extension<DCField>::
+receive_update(DCPacker &packer, PyObject *distobj) const {
+  if (_this->as_parameter() != nullptr) {
+    // If it's a parameter-type field, just store a new value on the object.
+    PyObject *value = unpack_args(packer);
+    if (value != nullptr) {
+      PyObject_SetAttrString(distobj, (char *)_this->_name.c_str(), value);
+    }
+    Py_DECREF(value);
+
+  } else {
+    // Otherwise, it must be an atomic or molecular field, so call the
+    // corresponding method.
+
+    if (!PyObject_HasAttrString(distobj, (char *)_this->_name.c_str())) {
+      // If there's no Python method to receive this message, don't bother
+      // unpacking it to a Python tuple--just skip past the message.
+      packer.unpack_skip();
+
+    } else {
+      // Otherwise, get a Python tuple from the args and call the Python
+      // method.
+      PyObject *args = unpack_args(packer);
+
+      if (args != nullptr) {
+        PyObject *func = PyObject_GetAttrString(distobj, (char *)_this->_name.c_str());
+        nassertv(func != nullptr);
+
+        PyObject *result;
+        {
+#ifdef WITHIN_PANDA
+          PStatTimer timer(_this->_field_update_pcollector);
+#endif
+          result = PyObject_CallObject(func, args);
+        }
+        Py_XDECREF(result);
+        Py_DECREF(func);
+        Py_DECREF(args);
+      }
+    }
+  }
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the client.
+ */
+Datagram Extension<DCField>::
+client_format_update(DOID_TYPE do_id, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update for
+ * the indicated distributed object from the AI.
+ */
+Datagram Extension<DCField>::
+ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(to_id);
+  packer.RAW_PACK_CHANNEL(from_id);
+  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Generates a datagram containing the message necessary to send an update,
+ * with the msg type, for the indicated distributed object from the AI.
+ */
+Datagram Extension<DCField>::
+ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
+  DCPacker packer;
+
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(to_id);
+  packer.RAW_PACK_CHANNEL(from_id);
+  packer.raw_pack_uint16(msg_type);
+  packer.raw_pack_uint32(do_id);
+  packer.raw_pack_uint16(_this->_number);
+
+  packer.begin_pack(_this);
+  pack_args(packer, args);
+  if (!packer.end_pack()) {
+    return Datagram();
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+
+/**
+ * Returns the string representation of the indicated Python object.
+ */
+std::string Extension<DCField>::
+get_pystr(PyObject *value) {
+  if (value == nullptr) {
+    return "(null)";
+  }
+
+  PyObject *str = PyObject_Str(value);
+  if (str != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+    std::string result = PyUnicode_AsUTF8(str);
+#else
+    std::string result = PyString_AsString(str);
+#endif
+    Py_DECREF(str);
+    return result;
+  }
+
+  PyObject *repr = PyObject_Repr(value);
+  if (repr != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+    std::string result = PyUnicode_AsUTF8(repr);
+#else
+    std::string result = PyString_AsString(repr);
+#endif
+    Py_DECREF(repr);
+    return result;
+  }
+
+  if (value->ob_type != nullptr) {
+    PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
+    if (typestr != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+      std::string result = PyUnicode_AsUTF8(typestr);
+#else
+      std::string result = PyString_AsString(typestr);
+#endif
+      Py_DECREF(typestr);
+      return result;
+    }
+  }
+
+  return "(invalid object)";
+}
+
+#endif  // HAVE_PYTHON

+ 48 - 0
direct/src/dcparser/dcField_ext.h

@@ -0,0 +1,48 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcField_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCFIELD_EXT_H
+#define DCFIELD_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcField.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCField, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCField> : public ExtensionBase<DCField> {
+public:
+  bool pack_args(DCPacker &packer, PyObject *sequence) const;
+  PyObject *unpack_args(DCPacker &packer) const;
+
+  void receive_update(DCPacker &packer, PyObject *distobj) const;
+
+  Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
+  Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                            PyObject *args) const;
+  Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
+                            int msg_type, PyObject *args) const;
+
+  static std::string get_pystr(PyObject *value);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCFIELD_EXT_H

+ 0 - 508
direct/src/dcparser/dcPacker.cxx

@@ -19,10 +19,6 @@
 #include "dcSwitchParameter.h"
 #include "dcSwitchParameter.h"
 #include "dcClass.h"
 #include "dcClass.h"
 
 
-#ifdef HAVE_PYTHON
-#include "py_panda.h"
-#endif
-
 using std::istream;
 using std::istream;
 using std::istringstream;
 using std::istringstream;
 using std::ostream;
 using std::ostream;
@@ -622,335 +618,6 @@ unpack_skip() {
   }
   }
 }
 }
 
 
-#ifdef HAVE_PYTHON
-/**
- * Packs the Python object of whatever type into the packer.  Each numeric
- * object and string object maps to the corresponding pack_value() call; a
- * tuple or sequence maps to a push() followed by all of the tuple's contents
- * followed by a pop().
- */
-void DCPacker::
-pack_object(PyObject *object) {
-  nassertv(_mode == M_pack || _mode == M_repack);
-  DCPackType pack_type = get_pack_type();
-
-  // had to add this for basic 64 and unsigned data to get packed right .. Not
-  // sure if we can just do the rest this way..
-
- switch(pack_type)
-  {
-  case PT_int64:
-      if(PyLong_Check(object))
-      {
-            pack_int64(PyLong_AsLongLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            pack_int64(PyInt_AsLong(object));
-            return;
-      }
-#endif
-      break;
-  case PT_uint64:
-      if(PyLong_Check(object))
-      {
-            pack_uint64(PyLong_AsUnsignedLongLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if(PyInt_Check(object))
-      {
-            PyObject  *obj1 = PyNumber_Long(object);
-            pack_int(PyLong_AsUnsignedLongLong(obj1));
-            Py_DECREF(obj1);
-            return;
-      }
-#endif
-      break;
-  case PT_int:
-      if(PyLong_Check(object))
-      {
-            pack_int(PyLong_AsLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            pack_int(PyInt_AsLong(object));
-            return;
-      }
-#endif
-      break;
-  case PT_uint:
-      if(PyLong_Check(object))
-      {
-            pack_uint(PyLong_AsUnsignedLong(object));
-            return;
-      }
-#if PY_MAJOR_VERSION < 3
-      else if (PyInt_Check(object))
-      {
-            PyObject *obj1 = PyNumber_Long(object);
-            pack_uint(PyLong_AsUnsignedLong(obj1));
-            Py_DECREF(obj1);
-            return;
-      }
-#endif
-      break;
-  default:
-      break;
-  }
-  if (PyLong_Check(object)) {
-    pack_int(PyLong_AsLong(object));
-#if PY_MAJOR_VERSION < 3
-  } else if (PyInt_Check(object)) {
-    pack_int(PyInt_AS_LONG(object));
-#endif
-  } else if (PyFloat_Check(object)) {
-    pack_double(PyFloat_AS_DOUBLE(object));
-  } else if (PyLong_Check(object)) {
-    pack_int64(PyLong_AsLongLong(object));
-#if PY_MAJOR_VERSION >= 3
-  } else if (PyUnicode_Check(object)) {
-    const char *buffer;
-    Py_ssize_t length;
-    buffer = PyUnicode_AsUTF8AndSize(object, &length);
-    if (buffer) {
-      pack_string(string(buffer, length));
-    }
-  } else if (PyBytes_Check(object)) {
-    const unsigned char *buffer;
-    Py_ssize_t length;
-    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
-    if (buffer) {
-      pack_blob(vector_uchar(buffer, buffer + length));
-    }
-#else
-  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
-    char *buffer;
-    Py_ssize_t length;
-    PyString_AsStringAndSize(object, &buffer, &length);
-    if (buffer) {
-      pack_string(string(buffer, length));
-    }
-#endif
-  } else {
-    // For some reason, PySequence_Check() is incorrectly reporting that a
-    // class instance is a sequence, even if it doesn't provide __len__, so we
-    // double-check by testing for __len__ explicitly.
-    bool is_sequence =
-      (PySequence_Check(object) != 0) &&
-      (PyObject_HasAttrString(object, "__len__") != 0);
-    bool is_instance = false;
-
-    const DCClass *dclass = nullptr;
-    const DCPackerInterface *current_field = get_current_field();
-    if (current_field != nullptr) {
-      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
-      if (class_param != nullptr) {
-        dclass = class_param->get_class();
-
-        if (dclass->has_class_def()) {
-          PyObject *class_def = dclass->get_class_def();
-          is_instance = (PyObject_IsInstance(object, dclass->get_class_def()) != 0);
-          Py_DECREF(class_def);
-        }
-      }
-    }
-
-    // If dclass is not NULL, the packer is expecting a class object.  There
-    // are then two cases: (1) the user has supplied a matching class object,
-    // or (2) the user has supplied a sequence object.  Unfortunately, it may
-    // be difficult to differentiate these two cases, since a class object may
-    // also be a sequence object.
-
-    // The rule to differentiate them is:
-
-    // (1) If the supplied class object is an instance of the expected class
-    // object, it is considered to be a class object.
-
-    // (2) Otherwise, if the supplied class object has a __len__() method
-    // (i.e.  PySequence_Check() returns true), then it is considered to be a
-    // sequence.
-
-    // (3) Otherwise, it is considered to be a class object.
-
-    if (dclass != nullptr && (is_instance || !is_sequence)) {
-      // The supplied object is either an instance of the expected class
-      // object, or it is not a sequence--this is case (1) or (3).
-      pack_class_object(dclass, object);
-    } else if (is_sequence) {
-      // The supplied object is not an instance of the expected class object,
-      // but it is a sequence.  This is case (2).
-      push();
-      int size = PySequence_Size(object);
-      for (int i = 0; i < size; ++i) {
-        PyObject *element = PySequence_GetItem(object, i);
-        if (element != nullptr) {
-          pack_object(element);
-          Py_DECREF(element);
-        } else {
-          std::cerr << "Unable to extract item " << i << " from sequence.\n";
-        }
-      }
-      pop();
-    } else {
-      // The supplied object is not a sequence, and we weren't expecting a
-      // class parameter.  This is none of the above, an error.
-      ostringstream strm;
-      strm << "Don't know how to pack object: "
-           << DCField::get_pystr(object);
-      nassert_raise(strm.str());
-      _pack_error = true;
-    }
-  }
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks a Python object of the appropriate type from the stream for the
- * current field.  This may be an integer or a string for a simple field
- * object; if the current field represents a list of fields it will be a
- * tuple.
- */
-PyObject *DCPacker::
-unpack_object() {
-  PyObject *object = nullptr;
-
-  DCPackType pack_type = get_pack_type();
-
-  switch (pack_type) {
-  case PT_invalid:
-    object = Py_None;
-    Py_INCREF(object);
-    unpack_skip();
-    break;
-
-  case PT_double:
-    {
-      double value = unpack_double();
-      object = PyFloat_FromDouble(value);
-    }
-    break;
-
-  case PT_int:
-    {
-      int value = unpack_int();
-#if PY_MAJOR_VERSION >= 3
-      object = PyLong_FromLong(value);
-#else
-      object = PyInt_FromLong(value);
-#endif
-    }
-    break;
-
-  case PT_uint:
-    {
-      unsigned int value = unpack_uint();
-#if PY_MAJOR_VERSION >= 3
-      object = PyLong_FromLong(value);
-#else
-      if (value & 0x80000000) {
-        object = PyLong_FromUnsignedLong(value);
-      } else {
-        object = PyInt_FromLong(value);
-      }
-#endif
-    }
-    break;
-
-  case PT_int64:
-    {
-      int64_t value = unpack_int64();
-      object = PyLong_FromLongLong(value);
-    }
-    break;
-
-  case PT_uint64:
-    {
-      uint64_t value = unpack_uint64();
-      object = PyLong_FromUnsignedLongLong(value);
-    }
-    break;
-
-  case PT_blob:
-#if PY_MAJOR_VERSION >= 3
-    {
-      string str;
-      unpack_string(str);
-      object = PyBytes_FromStringAndSize(str.data(), str.size());
-    }
-    break;
-#endif
-    // On Python 2, fall through to below.
-
-  case PT_string:
-    {
-      string str;
-      unpack_string(str);
-#if PY_MAJOR_VERSION >= 3
-      object = PyUnicode_FromStringAndSize(str.data(), str.size());
-#else
-      object = PyString_FromStringAndSize(str.data(), str.size());
-#endif
-    }
-    break;
-
-  case PT_class:
-    {
-      const DCClassParameter *class_param = get_current_field()->as_class_parameter();
-      if (class_param != nullptr) {
-        const DCClass *dclass = class_param->get_class();
-        if (dclass->has_class_def()) {
-          // If we know what kind of class object this is and it has a valid
-          // constructor, create the class object instead of just a tuple.
-          object = unpack_class_object(dclass);
-          if (object == nullptr) {
-            std::cerr << "Unable to construct object of class "
-                 << dclass->get_name() << "\n";
-          } else {
-            break;
-          }
-        }
-      }
-    }
-    // Fall through (if no constructor)
-
-    // If we don't know what kind of class object it is, or it doesn't have a
-    // constructor, fall through and make a tuple.
-  default:
-    {
-      // First, build up a list from the nested objects.
-      object = PyList_New(0);
-
-      push();
-      while (more_nested_fields()) {
-        PyObject *element = unpack_object();
-        PyList_Append(object, element);
-        Py_DECREF(element);
-      }
-      pop();
-
-      if (pack_type != PT_array) {
-        // For these other kinds of objects, we'll convert the list into a
-        // tuple.
-        PyObject *tuple = PyList_AsTuple(object);
-        Py_DECREF(object);
-        object = tuple;
-      }
-    }
-    break;
-  }
-
-  nassertr(object != nullptr, nullptr);
-  return object;
-}
-#endif  // HAVE_PYTHON
-
-
 /**
 /**
  * Parses an object's value according to the DC file syntax (e.g.  as a
  * Parses an object's value according to the DC file syntax (e.g.  as a
  * default value string) and packs it.  Returns true on success, false on a
  * default value string) and packs it.  Returns true on success, false on a
@@ -1206,178 +873,3 @@ clear_stack() {
     _stack = next;
     _stack = next;
   }
   }
 }
 }
-
-#ifdef HAVE_PYTHON
-/**
- * Given that the current element is a ClassParameter for a Python class
- * object, try to extract the appropriate values from the class object and
- * pack in.
- */
-void DCPacker::
-pack_class_object(const DCClass *dclass, PyObject *object) {
-  push();
-  while (more_nested_fields() && !_pack_error) {
-    const DCField *field = get_current_field()->as_field();
-    nassertv(field != nullptr);
-    get_class_element(dclass, object, field);
-  }
-  pop();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-/**
- * Given that the current element is a ClassParameter for a Python class for
- * which we have a valid constructor, unpack it and fill in its values.
- */
-PyObject *DCPacker::
-unpack_class_object(const DCClass *dclass) {
-  PyObject *class_def = dclass->get_class_def();
-  nassertr(class_def != nullptr, nullptr);
-
-  PyObject *object = nullptr;
-
-  if (!dclass->has_constructor()) {
-    // If the class uses a default constructor, go ahead and create the Python
-    // object for it now.
-    object = PyObject_CallObject(class_def, nullptr);
-    if (object == nullptr) {
-      return nullptr;
-    }
-  }
-
-  push();
-  if (object == nullptr && more_nested_fields()) {
-    // The first nested field will be the constructor.
-    const DCField *field = get_current_field()->as_field();
-    nassertr(field != nullptr, object);
-    nassertr(field == dclass->get_constructor(), object);
-
-    set_class_element(class_def, object, field);
-
-    // By now, the object should have been constructed.
-    if (object == nullptr) {
-      return nullptr;
-    }
-  }
-  while (more_nested_fields()) {
-    const DCField *field = get_current_field()->as_field();
-    nassertr(field != nullptr, object);
-
-    set_class_element(class_def, object, field);
-  }
-  pop();
-
-  return object;
-}
-#endif  // HAVE_PYTHON
-
-
-#ifdef HAVE_PYTHON
-/**
- * Unpacks the current element and stuffs it on the Python class object in
- * whatever way is appropriate.
- */
-void DCPacker::
-set_class_element(PyObject *class_def, PyObject *&object,
-                  const DCField *field) {
-  string field_name = field->get_name();
-  DCPackType pack_type = get_pack_type();
-
-  if (field_name.empty()) {
-    switch (pack_type) {
-    case PT_class:
-    case PT_switch:
-      // If the field has no name, but it is one of these container objects,
-      // we want to unpack its nested objects directly into the class.
-      push();
-      while (more_nested_fields()) {
-        const DCField *field = get_current_field()->as_field();
-        nassertv(field != nullptr);
-        nassertv(object != nullptr);
-        set_class_element(class_def, object, field);
-      }
-      pop();
-      break;
-
-    default:
-      // Otherwise, we just skip over the field.
-      unpack_skip();
-    }
-
-  } else {
-    // If the field does have a name, we will want to store it on the class,
-    // either by calling a method (for a PT_field pack_type) or by setting a
-    // value (for any other kind of pack_type).
-
-    PyObject *element = unpack_object();
-
-    if (pack_type == PT_field) {
-      if (object == nullptr) {
-        // If the object hasn't been constructed yet, assume this is the
-        // constructor.
-        object = PyObject_CallObject(class_def, element);
-
-      } else {
-        if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
-          PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
-          if (func != nullptr) {
-            PyObject *result = PyObject_CallObject(func, element);
-            Py_XDECREF(result);
-            Py_DECREF(func);
-          }
-        }
-      }
-
-    } else {
-      nassertv(object != nullptr);
-      PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
-    }
-
-    Py_DECREF(element);
-  }
-}
-#endif  // HAVE_PYTHON
-
-
-#ifdef HAVE_PYTHON
-/**
- * Gets the current element from the Python object and packs it.
- */
-void DCPacker::
-get_class_element(const DCClass *dclass, PyObject *object,
-                  const DCField *field) {
-  string field_name = field->get_name();
-  DCPackType pack_type = get_pack_type();
-
-  if (field_name.empty()) {
-    switch (pack_type) {
-    case PT_class:
-    case PT_switch:
-      // If the field has no name, but it is one of these container objects,
-      // we want to get its nested objects directly from the class.
-      push();
-      while (more_nested_fields() && !_pack_error) {
-        const DCField *field = get_current_field()->as_field();
-        nassertv(field != nullptr);
-        get_class_element(dclass, object, field);
-      }
-      pop();
-      break;
-
-    default:
-      // Otherwise, we just pack the default value.
-      pack_default_value();
-    }
-
-  } else {
-    // If the field does have a name, we will want to get it from the class
-    // and pack it.  It just so happens that there's already a method that
-    // does this on DCClass.
-
-    if (!dclass->pack_required_field(*this, object, field)) {
-      _pack_error = true;
-    }
-  }
-}
-#endif  // HAVE_PYTHON

+ 17 - 14
direct/src/dcparser/dcPacker.h

@@ -19,7 +19,10 @@
 #include "dcSubatomicType.h"
 #include "dcSubatomicType.h"
 #include "dcPackData.h"
 #include "dcPackData.h"
 #include "dcPackerCatalog.h"
 #include "dcPackerCatalog.h"
-#include "dcPython.h"
+
+#ifdef WITHIN_PANDA
+#include "extension.h"
+#endif
 
 
 class DCClass;
 class DCClass;
 class DCSwitchParameter;
 class DCSwitchParameter;
@@ -104,10 +107,8 @@ public:
 
 
 PUBLISHED:
 PUBLISHED:
 
 
-#ifdef HAVE_PYTHON
-  void pack_object(PyObject *object);
-  PyObject *unpack_object();
-#endif
+  EXTENSION(void pack_object(PyObject *object));
+  EXTENSION(PyObject *unpack_object());
 
 
   bool parse_and_pack(const std::string &formatted_object);
   bool parse_and_pack(const std::string &formatted_object);
   bool parse_and_pack(std::istream &in);
   bool parse_and_pack(std::istream &in);
@@ -195,14 +196,12 @@ private:
   void clear();
   void clear();
   void clear_stack();
   void clear_stack();
 
 
-#ifdef HAVE_PYTHON
-  void pack_class_object(const DCClass *dclass, PyObject *object);
-  PyObject *unpack_class_object(const DCClass *dclass);
-  void set_class_element(PyObject *class_def, PyObject *&object,
-                         const DCField *field);
-  void get_class_element(const DCClass *dclass, PyObject *object,
-                         const DCField *field);
-#endif
+  EXTENSION(void pack_class_object(const DCClass *dclass, PyObject *object));
+  EXTENSION(PyObject *unpack_class_object(const DCClass *dclass));
+  EXTENSION(void set_class_element(PyObject *class_def, PyObject *&object,
+                                   const DCField *field));
+  EXTENSION(void get_class_element(const DCClass *dclass, PyObject *object,
+                                   const DCField *field));
 
 
 private:
 private:
   enum Mode {
   enum Mode {
@@ -223,7 +222,7 @@ private:
   const DCPackerCatalog *_catalog;
   const DCPackerCatalog *_catalog;
   const DCPackerCatalog::LiveCatalog *_live_catalog;
   const DCPackerCatalog::LiveCatalog *_live_catalog;
 
 
-  class StackElement {
+  class EXPCL_DIRECT_DCPARSER StackElement {
   public:
   public:
     // As an optimization, we implement operator new and delete here to
     // As an optimization, we implement operator new and delete here to
     // minimize allocation overhead during push() and pop().
     // minimize allocation overhead during push() and pop().
@@ -258,6 +257,10 @@ private:
   bool _parse_error;
   bool _parse_error;
   bool _pack_error;
   bool _pack_error;
   bool _range_error;
   bool _range_error;
+
+#ifdef WITHIN_PANDA
+  friend class Extension<DCPacker>;
+#endif
 };
 };
 
 
 #include "dcPacker.I"
 #include "dcPacker.I"

+ 508 - 0
direct/src/dcparser/dcPacker_ext.cxx

@@ -0,0 +1,508 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcPacker_ext.cxx
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#include "dcPacker_ext.h"
+#include "dcClass_ext.h"
+#include "dcField_ext.h"
+
+#include "dcClassParameter.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Packs the Python object of whatever type into the packer.  Each numeric
+ * object and string object maps to the corresponding pack_value() call; a
+ * tuple or sequence maps to a push() followed by all of the tuple's contents
+ * followed by a pop().
+ */
+void Extension<DCPacker>::
+pack_object(PyObject *object) {
+  nassertv(_this->_mode == DCPacker::Mode::M_pack ||
+           _this->_mode == DCPacker::Mode::M_repack);
+  DCPackType pack_type = _this->get_pack_type();
+
+  // had to add this for basic 64 and unsigned data to get packed right .. Not
+  // sure if we can just do the rest this way..
+
+  switch(pack_type) {
+  case PT_int64:
+    if (PyLong_Check(object)) {
+      _this->pack_int64(PyLong_AsLongLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      _this->pack_int64(PyInt_AsLong(object));
+      return;
+    }
+#endif
+    break;
+
+  case PT_uint64:
+    if (PyLong_Check(object)) {
+      _this->pack_uint64(PyLong_AsUnsignedLongLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      PyObject *obj1 = PyNumber_Long(object);
+      _this->pack_int(PyLong_AsUnsignedLongLong(obj1));
+      Py_DECREF(obj1);
+      return;
+    }
+#endif
+    break;
+
+  case PT_int:
+    if (PyLong_Check(object)) {
+      _this->pack_int(PyLong_AsLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      _this->pack_int(PyInt_AsLong(object));
+      return;
+    }
+#endif
+    break;
+
+  case PT_uint:
+    if (PyLong_Check(object)) {
+      _this->pack_uint(PyLong_AsUnsignedLong(object));
+      return;
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyInt_Check(object)) {
+      PyObject *obj1 = PyNumber_Long(object);
+      _this->pack_uint(PyLong_AsUnsignedLong(obj1));
+      Py_DECREF(obj1);
+      return;
+    }
+#endif
+    break;
+
+  default:
+    break;
+  }
+
+  if (PyLong_Check(object)) {
+    _this->pack_int(PyLong_AsLong(object));
+#if PY_MAJOR_VERSION < 3
+  } else if (PyInt_Check(object)) {
+    _this->pack_int(PyInt_AS_LONG(object));
+#endif
+  } else if (PyFloat_Check(object)) {
+    _this->pack_double(PyFloat_AS_DOUBLE(object));
+  } else if (PyLong_Check(object)) {
+    _this->pack_int64(PyLong_AsLongLong(object));
+#if PY_MAJOR_VERSION >= 3
+  } else if (PyUnicode_Check(object)) {
+    const char *buffer;
+    Py_ssize_t length;
+    buffer = PyUnicode_AsUTF8AndSize(object, &length);
+    if (buffer) {
+      _this->pack_string(std::string(buffer, length));
+    }
+  } else if (PyBytes_Check(object)) {
+    const unsigned char *buffer;
+    Py_ssize_t length;
+    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
+    if (buffer) {
+      _this->pack_blob(vector_uchar(buffer, buffer + length));
+    }
+#else
+  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
+    char *buffer;
+    Py_ssize_t length;
+    PyString_AsStringAndSize(object, &buffer, &length);
+    if (buffer) {
+      _this->pack_string(std::string(buffer, length));
+    }
+#endif
+  } else {
+    // For some reason, PySequence_Check() is incorrectly reporting that a
+    // class instance is a sequence, even if it doesn't provide __len__, so we
+    // double-check by testing for __len__ explicitly.
+    bool is_sequence =
+      (PySequence_Check(object) != 0) &&
+      (PyObject_HasAttrString(object, "__len__") != 0);
+    bool is_instance = false;
+
+    const DCClass *dclass = nullptr;
+    const DCPackerInterface *current_field = _this->get_current_field();
+    if (current_field != nullptr) {
+      const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
+      if (class_param != nullptr) {
+        dclass = class_param->get_class();
+
+        if (invoke_extension(dclass).has_class_def()) {
+          PyObject *class_def = invoke_extension(dclass).get_class_def();
+          is_instance = (PyObject_IsInstance(object, invoke_extension(dclass).get_class_def()) != 0);
+          Py_DECREF(class_def);
+        }
+      }
+    }
+
+    // If dclass is not NULL, the packer is expecting a class object.  There
+    // are then two cases: (1) the user has supplied a matching class object,
+    // or (2) the user has supplied a sequence object.  Unfortunately, it may
+    // be difficult to differentiate these two cases, since a class object may
+    // also be a sequence object.
+
+    // The rule to differentiate them is:
+
+    // (1) If the supplied class object is an instance of the expected class
+    // object, it is considered to be a class object.
+
+    // (2) Otherwise, if the supplied class object has a __len__() method
+    // (i.e.  PySequence_Check() returns true), then it is considered to be a
+    // sequence.
+
+    // (3) Otherwise, it is considered to be a class object.
+
+    if (dclass != nullptr && (is_instance || !is_sequence)) {
+      // The supplied object is either an instance of the expected class
+      // object, or it is not a sequence--this is case (1) or (3).
+      pack_class_object(dclass, object);
+    } else if (is_sequence) {
+      // The supplied object is not an instance of the expected class object,
+      // but it is a sequence.  This is case (2).
+      _this->push();
+      int size = PySequence_Size(object);
+      for (int i = 0; i < size; ++i) {
+        PyObject *element = PySequence_GetItem(object, i);
+        if (element != nullptr) {
+          pack_object(element);
+          Py_DECREF(element);
+        } else {
+          std::cerr << "Unable to extract item " << i << " from sequence.\n";
+        }
+      }
+      _this->pop();
+    } else {
+      // The supplied object is not a sequence, and we weren't expecting a
+      // class parameter.  This is none of the above, an error.
+      std::ostringstream strm;
+      strm << "Don't know how to pack object: "
+           << Extension<DCField>::get_pystr(object);
+      nassert_raise(strm.str());
+      _this->_pack_error = true;
+    }
+  }
+}
+
+/**
+ * Unpacks a Python object of the appropriate type from the stream for the
+ * current field.  This may be an integer or a string for a simple field
+ * object; if the current field represents a list of fields it will be a
+ * tuple.
+ */
+PyObject *Extension<DCPacker>::
+unpack_object() {
+  PyObject *object = nullptr;
+
+  DCPackType pack_type = _this->get_pack_type();
+
+  switch (pack_type) {
+  case PT_invalid:
+    object = Py_None;
+    Py_INCREF(object);
+    _this->unpack_skip();
+    break;
+
+  case PT_double:
+    {
+      double value = _this->unpack_double();
+      object = PyFloat_FromDouble(value);
+    }
+    break;
+
+  case PT_int:
+    {
+      int value = _this->unpack_int();
+#if PY_MAJOR_VERSION >= 3
+      object = PyLong_FromLong(value);
+#else
+      object = PyInt_FromLong(value);
+#endif
+    }
+    break;
+
+  case PT_uint:
+    {
+      unsigned int value = _this->unpack_uint();
+#if PY_MAJOR_VERSION >= 3
+      object = PyLong_FromLong(value);
+#else
+      if (value & 0x80000000) {
+        object = PyLong_FromUnsignedLong(value);
+      } else {
+        object = PyInt_FromLong(value);
+      }
+#endif
+    }
+    break;
+
+  case PT_int64:
+    {
+      int64_t value = _this->unpack_int64();
+      object = PyLong_FromLongLong(value);
+    }
+    break;
+
+  case PT_uint64:
+    {
+      uint64_t value = _this->unpack_uint64();
+      object = PyLong_FromUnsignedLongLong(value);
+    }
+    break;
+
+  case PT_blob:
+#if PY_MAJOR_VERSION >= 3
+    {
+      std::string str;
+      _this->unpack_string(str);
+      object = PyBytes_FromStringAndSize(str.data(), str.size());
+    }
+    break;
+#endif
+    // On Python 2, fall through to below.
+
+  case PT_string:
+    {
+      std::string str;
+      _this->unpack_string(str);
+#if PY_MAJOR_VERSION >= 3
+      object = PyUnicode_FromStringAndSize(str.data(), str.size());
+#else
+      object = PyString_FromStringAndSize(str.data(), str.size());
+#endif
+    }
+    break;
+
+  case PT_class:
+    {
+      const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
+      if (class_param != nullptr) {
+        const DCClass *dclass = class_param->get_class();
+        if (invoke_extension(dclass).has_class_def()) {
+          // If we know what kind of class object this is and it has a valid
+          // constructor, create the class object instead of just a tuple.
+          object = unpack_class_object(dclass);
+          if (object == nullptr) {
+            std::cerr << "Unable to construct object of class "
+                 << dclass->get_name() << "\n";
+          } else {
+            break;
+          }
+        }
+      }
+    }
+    // Fall through (if no constructor)
+
+    // If we don't know what kind of class object it is, or it doesn't have a
+    // constructor, fall through and make a tuple.
+  default:
+    {
+      // First, build up a list from the nested objects.
+      object = PyList_New(0);
+
+      _this->push();
+      while (_this->more_nested_fields()) {
+        PyObject *element = unpack_object();
+        PyList_Append(object, element);
+        Py_DECREF(element);
+      }
+      _this->pop();
+
+      if (pack_type != PT_array) {
+        // For these other kinds of objects, we'll convert the list into a
+        // tuple.
+        PyObject *tuple = PyList_AsTuple(object);
+        Py_DECREF(object);
+        object = tuple;
+      }
+    }
+    break;
+  }
+
+  nassertr(object != nullptr, nullptr);
+  return object;
+}
+
+/**
+ * Given that the current element is a ClassParameter for a Python class
+ * object, try to extract the appropriate values from the class object and
+ * pack in.
+ */
+void Extension<DCPacker>::
+pack_class_object(const DCClass *dclass, PyObject *object) {
+  _this->push();
+  while (_this->more_nested_fields() && !_this->_pack_error) {
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertv(field != nullptr);
+    get_class_element(dclass, object, field);
+  }
+  _this->pop();
+}
+
+/**
+ * Given that the current element is a ClassParameter for a Python class for
+ * which we have a valid constructor, unpack it and fill in its values.
+ */
+PyObject *Extension<DCPacker>::
+unpack_class_object(const DCClass *dclass) {
+  PyObject *class_def = invoke_extension(dclass).get_class_def();
+  nassertr(class_def != nullptr, nullptr);
+
+  PyObject *object = nullptr;
+
+  if (!dclass->has_constructor()) {
+    // If the class uses a default constructor, go ahead and create the Python
+    // object for it now.
+    object = PyObject_CallObject(class_def, nullptr);
+    if (object == nullptr) {
+      return nullptr;
+    }
+  }
+
+  _this->push();
+  if (object == nullptr && _this->more_nested_fields()) {
+    // The first nested field will be the constructor.
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertr(field != nullptr, object);
+    nassertr(field == dclass->get_constructor(), object);
+
+    set_class_element(class_def, object, field);
+
+    // By now, the object should have been constructed.
+    if (object == nullptr) {
+      return nullptr;
+    }
+  }
+  while (_this->more_nested_fields()) {
+    const DCField *field = _this->get_current_field()->as_field();
+    nassertr(field != nullptr, object);
+
+    set_class_element(class_def, object, field);
+  }
+  _this->pop();
+
+  return object;
+}
+
+/**
+ * Unpacks the current element and stuffs it on the Python class object in
+ * whatever way is appropriate.
+ */
+void Extension<DCPacker>::
+set_class_element(PyObject *class_def, PyObject *&object,
+                  const DCField *field) {
+  std::string field_name = field->get_name();
+  DCPackType pack_type = _this->get_pack_type();
+
+  if (field_name.empty()) {
+    switch (pack_type) {
+    case PT_class:
+    case PT_switch:
+      // If the field has no name, but it is one of these container objects,
+      // we want to unpack its nested objects directly into the class.
+      _this->push();
+      while (_this->more_nested_fields()) {
+        const DCField *field = _this->get_current_field()->as_field();
+        nassertv(field != nullptr);
+        nassertv(object != nullptr);
+        set_class_element(class_def, object, field);
+      }
+      _this->pop();
+      break;
+
+    default:
+      // Otherwise, we just skip over the field.
+      _this->unpack_skip();
+    }
+
+  } else {
+    // If the field does have a name, we will want to store it on the class,
+    // either by calling a method (for a PT_field pack_type) or by setting a
+    // value (for any other kind of pack_type).
+
+    PyObject *element = unpack_object();
+
+    if (pack_type == PT_field) {
+      if (object == nullptr) {
+        // If the object hasn't been constructed yet, assume this is the
+        // constructor.
+        object = PyObject_CallObject(class_def, element);
+
+      } else {
+        if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
+          PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
+          if (func != nullptr) {
+            PyObject *result = PyObject_CallObject(func, element);
+            Py_XDECREF(result);
+            Py_DECREF(func);
+          }
+        }
+      }
+
+    } else {
+      nassertv(object != nullptr);
+      PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
+    }
+
+    Py_DECREF(element);
+  }
+}
+
+/**
+ * Gets the current element from the Python object and packs it.
+ */
+void Extension<DCPacker>::
+get_class_element(const DCClass *dclass, PyObject *object,
+                  const DCField *field) {
+  std::string field_name = field->get_name();
+  DCPackType pack_type = _this->get_pack_type();
+
+  if (field_name.empty()) {
+    switch (pack_type) {
+    case PT_class:
+    case PT_switch:
+      // If the field has no name, but it is one of these container objects,
+      // we want to get its nested objects directly from the class.
+      _this->push();
+      while (_this->more_nested_fields() && !_this->_pack_error) {
+        const DCField *field = _this->get_current_field()->as_field();
+        nassertv(field != nullptr);
+        get_class_element(dclass, object, field);
+      }
+      _this->pop();
+      break;
+
+    default:
+      // Otherwise, we just pack the default value.
+      _this->pack_default_value();
+    }
+
+  } else {
+    // If the field does have a name, we will want to get it from the class
+    // and pack it.  It just so happens that there's already a method that
+    // does this on DCClass.
+
+    if (!invoke_extension(dclass).pack_required_field(*_this, object, field)) {
+      _this->_pack_error = true;
+    }
+  }
+}
+
+#endif  // HAVE_PYTHON

+ 45 - 0
direct/src/dcparser/dcPacker_ext.h

@@ -0,0 +1,45 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file dcPacker_ext.h
+ * @author CFSworks
+ * @date 2019-07-03
+ */
+
+#ifndef DCPACKER_EXT_H
+#define DCPACKER_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "dcPacker.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for DCPacker, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<DCPacker> : public ExtensionBase<DCPacker> {
+public:
+  void pack_object(PyObject *object);
+  PyObject *unpack_object();
+
+  void pack_class_object(const DCClass *dclass, PyObject *object);
+  PyObject *unpack_class_object(const DCClass *dclass);
+  void set_class_element(PyObject *class_def, PyObject *&object,
+                         const DCField *field);
+  void get_class_element(const DCClass *dclass, PyObject *object,
+                         const DCField *field);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // DCPACKER_EXT_H

+ 0 - 42
direct/src/dcparser/dcPython.h

@@ -1,42 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file dcPython.h
- * @author drose
- * @date 2004-06-22
- */
-
-#ifndef DCPYTHON_H
-#define DCPYTHON_H
-
-// The only purpose of this file is to serve as a common place to put the
-// nonsense associated with #including <Python.h>.
-
-#ifdef HAVE_PYTHON
-
-#define PY_SSIZE_T_CLEAN 1
-
-#undef _POSIX_C_SOURCE
-#undef _XOPEN_SOURCE
-#include <Python.h>
-
-// Python 2.5 adds Py_ssize_t; earlier versions don't have it.
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-// Several interfaces in this module that use Python also require these header
-// files, so we might as well pick them up too.
-#include "datagram.h"
-#include "datagramIterator.h"
-
-#endif  // HAVE_PYTHON
-
-#endif

+ 3 - 0
direct/src/dcparser/p3dcparser_ext_composite.cxx

@@ -0,0 +1,3 @@
+#include "dcClass_ext.cxx"
+#include "dcField_ext.cxx"
+#include "dcPacker_ext.cxx"

+ 14 - 4
direct/src/directscripts/Doxyfile.cxx

@@ -432,7 +432,7 @@ EXTRACT_STATIC         = NO
 # for Java sources.
 # for Java sources.
 # The default value is: YES.
 # The default value is: YES.
 
 
-EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_CLASSES  = NO
 
 
 # This flag is only useful for Objective-C code. When set to YES local methods,
 # This flag is only useful for Objective-C code. When set to YES local methods,
 # which are defined in the implementation section but not in the interface are
 # which are defined in the implementation section but not in the interface are
@@ -802,6 +802,11 @@ EXCLUDE                = dtool/src/parser-inc \
                          panda/src/iphone \
                          panda/src/iphone \
                          panda/src/tinydisplay \
                          panda/src/tinydisplay \
                          panda/src/movies/dr_flac.h \
                          panda/src/movies/dr_flac.h \
+                         panda/src/windisplay/winDetectDx.h \
+                         panda/src/wgldisplay/wglext.h \
+                         panda/src/glxdisplay/panda_glxext.h \
+                         pandatool/src/gtk-stats \
+                         dtool/src/dtoolbase/fakestringstream.h \
                          dtool/src/dtoolbase/pdtoa.cxx \
                          dtool/src/dtoolbase/pdtoa.cxx \
                          dtool/src/dtoolutil/panda_getopt_long.h \
                          dtool/src/dtoolutil/panda_getopt_long.h \
                          dtool/src/dtoolutil/panda_getopt_impl.h \
                          dtool/src/dtoolutil/panda_getopt_impl.h \
@@ -840,7 +845,10 @@ EXCLUDE_PATTERNS       = */Opt*-*/* \
 # Note that the wildcards are matched against the file with absolute path, so to
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories use the pattern */test/*
 # exclude all test directories use the pattern */test/*
 
 
-EXCLUDE_SYMBOLS        =
+EXCLUDE_SYMBOLS        = InterrogateFunctionWrapper::Parameter \
+                         CollisionFloorMesh::TriangleIndices \
+                         tagTOUCHINPUT \
+                         WINDOW_METRICS
 
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or directories
 # The EXAMPLE_PATH tag can be used to specify one or more files or directories
 # that contain example code fragments that are included (see the \include
 # that contain example code fragments that are included (see the \include
@@ -1018,7 +1026,7 @@ ALPHABETICAL_INDEX     = YES
 # Minimum value: 1, maximum value: 20, default value: 5.
 # Minimum value: 1, maximum value: 20, default value: 5.
 # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
 # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
 
 
-COLS_IN_ALPHA_INDEX    = 5
+COLS_IN_ALPHA_INDEX    = 3
 
 
 # In case all classes in a project start with a common prefix, all classes will
 # In case all classes in a project start with a common prefix, all classes will
 # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
 # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
@@ -1972,6 +1980,7 @@ INCLUDE_FILE_PATTERNS  =
 
 
 PREDEFINED             = TVOLATILE= \
 PREDEFINED             = TVOLATILE= \
                          INLINE=inline \
                          INLINE=inline \
+                         ALWAYS_INLINE=inline \
                          PUBLISHED=public \
                          PUBLISHED=public \
                          protected=private \
                          protected=private \
                          INLINE_LINMATH=inline \
                          INLINE_LINMATH=inline \
@@ -1986,7 +1995,8 @@ PREDEFINED             = TVOLATILE= \
                          MAKE_SEQ(x)= \
                          MAKE_SEQ(x)= \
                          MAKE_SEQ_PROPERTY(x)= \
                          MAKE_SEQ_PROPERTY(x)= \
                          MAKE_MAP_PROPERTY(x)= \
                          MAKE_MAP_PROPERTY(x)= \
-                         MAKE_MAP_KEYS_SEQ(x)=
+                         MAKE_MAP_KEYS_SEQ(x)= \
+                         RETURNS_ALIGNED(x)=
 
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The
 # tag can be used to specify a list of macro names that should be expanded. The

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

@@ -805,7 +805,7 @@ ALPHABETICAL_INDEX     = YES
 # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
 # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
 # in which this list will be split (can be a number in the range [1..20])
 # in which this list will be split (can be a number in the range [1..20])
 
 
-COLS_IN_ALPHA_INDEX    = 5
+COLS_IN_ALPHA_INDEX    = 2
 
 
 # In case all classes in a project start with a common prefix, all 
 # In case all classes in a project start with a common prefix, all 
 # classes will be put under the same header in the alphabetical index. 
 # classes will be put under the same header in the alphabetical index. 

+ 3 - 3
direct/src/directscripts/gendocs.py

@@ -56,9 +56,9 @@ import os, sys, parser, symbol, token, re
 #
 #
 ########################################################################
 ########################################################################
 
 
-SECHEADER = re.compile("^[A-Z][a-z]+\s*:")
-JUNKHEADER = re.compile("^((Function)|(Access))\s*:")
-IMPORTSTAR = re.compile("^from\s+([a-zA-Z0-9_.]+)\s+import\s+[*]\s*$")
+SECHEADER = re.compile("^[A-Z][a-z]+\\s*:")
+JUNKHEADER = re.compile("^((Function)|(Access))\\s*:")
+IMPORTSTAR = re.compile("^from\\s+([a-zA-Z0-9_.]+)\\s+import\\s+[*]\\s*$")
 IDENTIFIER = re.compile("[a-zA-Z0-9_]+")
 IDENTIFIER = re.compile("[a-zA-Z0-9_]+")
 FILEHEADER = re.compile(
 FILEHEADER = re.compile(
 r"""^// Filename: [a-zA-Z.]+
 r"""^// Filename: [a-zA-Z.]+

+ 2 - 0
direct/src/directtools/DirectUtil.py

@@ -1,5 +1,7 @@
 
 
 from .DirectGlobals import *
 from .DirectGlobals import *
+from panda3d.core import VBase4
+from direct.task.Task import Task
 
 
 # Routines to adjust values
 # Routines to adjust values
 def ROUND_TO(value, divisor):
 def ROUND_TO(value, divisor):

+ 19 - 12
direct/src/dist/FreezeTool.py

@@ -210,15 +210,15 @@ class CompilationEnvironment:
             if ('MAKEPANDA' in os.environ):
             if ('MAKEPANDA' in os.environ):
                 self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
                 self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
                 self.compileObjDll = self.compileObjExe
                 self.compileObjDll = self.compileObjExe
-                self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
-                self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
+                self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs"  /out:%(basename)s.exe %(basename)s.obj'
+                self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
             else:
             else:
                 os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
                 os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
 
 
-                self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\include" /I"%(MSVC)s\include" %(filename)s'
+                self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\\include" /I"%(MSVC)s\\include" %(filename)s'
                 self.compileObjDll = self.compileObjExe
                 self.compileObjDll = self.compileObjExe
-                self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
-                self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
+                self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s.exe %(basename)s.obj'
+                self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
 
 
         elif self.platform.startswith('osx_'):
         elif self.platform.startswith('osx_'):
             # OSX
             # OSX
@@ -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),
                 ]
                 ]
 
 

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

@@ -13,12 +13,15 @@ import stat
 import struct
 import struct
 import imp
 import imp
 import string
 import string
+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
 
 
 
 
@@ -30,6 +33,15 @@ if sys.version_info < (3, 0):
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     FileNotFoundError = IOError
     FileNotFoundError = IOError
 
 
+    # Warn the user.  They might be using Python 2 by accident.
+    print("=================================================================")
+    print("WARNING: You are using Python 2, which will soon be discontinued.")
+    print("WARNING: Please use Python 3 for best results and continued")
+    print("WARNING: support after the EOL date of December 31st, 2019.")
+    print("=================================================================")
+    sys.stdout.flush()
+    time.sleep(4.0)
+
 
 
 def _parse_list(input):
 def _parse_list(input):
     if isinstance(input, basestring):
     if isinstance(input, basestring):
@@ -214,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',
@@ -229,7 +242,9 @@ class build_apps(setuptools.Command):
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.use_optimized_wheels = True
         self.use_optimized_wheels = True
         self.optimized_wheel_index = ''
         self.optimized_wheel_index = ''
-        self.pypi_extra_indexes = []
+        self.pypi_extra_indexes = [
+            'https://archive.panda3d.org/thirdparty',
+        ]
         self.file_handlers = {}
         self.file_handlers = {}
         self.exclude_dependencies = [
         self.exclude_dependencies = [
             # Windows
             # Windows
@@ -286,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)
@@ -345,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)
 
 
@@ -421,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"""
 
 
@@ -462,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)
@@ -606,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,
@@ -621,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:
@@ -1347,7 +1415,8 @@ class bdist_apps(setuptools.Command):
 
 
         platforms = build_cmd.platforms
         platforms = build_cmd.platforms
         build_base = os.path.abspath(build_cmd.build_base)
         build_base = os.path.abspath(build_cmd.build_base)
-        os.makedirs(self.dist_dir, exist_ok=True)
+        if not os.path.exists(self.dist_dir):
+            os.makedirs(self.dist_dir)
         os.chdir(self.dist_dir)
         os.chdir(self.dist_dir)
 
 
         for platform in platforms:
         for platform in platforms:

+ 4 - 3
direct/src/distributed/cConnectionRepository.cxx

@@ -26,6 +26,7 @@
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 #include "py_panda.h"
 #include "py_panda.h"
+#include "dcClass_ext.h"
 #endif
 #endif
 
 
 using std::endl;
 using std::endl;
@@ -736,7 +737,7 @@ handle_update_field() {
       // get into trouble if it tried to delete the object from the doId2do
       // get into trouble if it tried to delete the object from the doId2do
       // map.
       // map.
       Py_INCREF(distobj);
       Py_INCREF(distobj);
-      dclass->receive_update(distobj, _di);
+      invoke_extension(dclass).receive_update(distobj, _di);
       Py_DECREF(distobj);
       Py_DECREF(distobj);
 
 
       if (PyErr_Occurred()) {
       if (PyErr_Occurred()) {
@@ -820,7 +821,7 @@ handle_update_field_owner() {
         // make a copy of the datagram iterator so that we can use the main
         // make a copy of the datagram iterator so that we can use the main
         // iterator for the non-owner update
         // iterator for the non-owner update
         DatagramIterator _odi(_di);
         DatagramIterator _odi(_di);
-        dclass->receive_update(distobjOV, _odi);
+        invoke_extension(dclass).receive_update(distobjOV, _odi);
         Py_DECREF(distobjOV);
         Py_DECREF(distobjOV);
 
 
         if (PyErr_Occurred()) {
         if (PyErr_Occurred()) {
@@ -861,7 +862,7 @@ handle_update_field_owner() {
         // get into trouble if it tried to delete the object from the doId2do
         // get into trouble if it tried to delete the object from the doId2do
         // map.
         // map.
         Py_INCREF(distobj);
         Py_INCREF(distobj);
-        dclass->receive_update(distobj, _di);
+        invoke_extension(dclass).receive_update(distobj, _di);
         Py_DECREF(distobj);
         Py_DECREF(distobj);
 
 
         if (PyErr_Occurred()) {
         if (PyErr_Occurred()) {

+ 1 - 1
direct/src/distributed/cConnectionRepository.h

@@ -53,7 +53,7 @@ class SocketStream;
  * the C++ layer, while server messages that are not understood by the C++
  * the C++ layer, while server messages that are not understood by the C++
  * layer are returned up to the Python layer for processing.
  * layer are returned up to the Python layer for processing.
  */
  */
-class EXPCL_DIRECT_DISTRIBUTED CConnectionRepository {
+class CConnectionRepository {
 PUBLISHED:
 PUBLISHED:
   explicit CConnectionRepository(bool has_owner_view = false,
   explicit CConnectionRepository(bool has_owner_view = false,
                                  bool threaded_net = false);
                                  bool threaded_net = false);

+ 4 - 0
direct/src/distributed/cDistributedSmoothNodeBase.cxx

@@ -18,6 +18,10 @@
 #include "dcmsgtypes.h"
 #include "dcmsgtypes.h"
 #include "config_distributed.h"
 #include "config_distributed.h"
 
 
+#ifdef HAVE_PYTHON
+#include "py_panda.h"
+#endif
+
 static const PN_stdfloat smooth_node_epsilon = 0.01;
 static const PN_stdfloat smooth_node_epsilon = 0.01;
 static const double network_time_precision = 100.0;  // Matches ClockDelta.py
 static const double network_time_precision = 100.0;  // Matches ClockDelta.py
 
 

+ 1 - 2
direct/src/distributed/cDistributedSmoothNodeBase.h

@@ -18,7 +18,6 @@
 #include "nodePath.h"
 #include "nodePath.h"
 #include "dcbase.h"
 #include "dcbase.h"
 #include "dcPacker.h"
 #include "dcPacker.h"
-#include "dcPython.h"  // to pick up Python.h
 #include "clockObject.h"
 #include "clockObject.h"
 
 
 class DCClass;
 class DCClass;
@@ -28,7 +27,7 @@ class CConnectionRepository;
  * This class defines some basic methods of DistributedSmoothNodeBase which
  * This class defines some basic methods of DistributedSmoothNodeBase which
  * have been moved into C++ as a performance optimization.
  * have been moved into C++ as a performance optimization.
  */
  */
-class EXPCL_DIRECT_DISTRIBUTED CDistributedSmoothNodeBase {
+class CDistributedSmoothNodeBase {
 PUBLISHED:
 PUBLISHED:
   CDistributedSmoothNodeBase();
   CDistributedSmoothNodeBase();
   ~CDistributedSmoothNodeBase();
   ~CDistributedSmoothNodeBase();

+ 4 - 4
direct/src/distributed/config_distributed.h

@@ -23,10 +23,10 @@
 
 
 NotifyCategoryDecl(distributed, EXPCL_DIRECT_DISTRIBUTED, EXPTP_DIRECT_DISTRIBUTED);
 NotifyCategoryDecl(distributed, EXPCL_DIRECT_DISTRIBUTED, EXPTP_DIRECT_DISTRIBUTED);
 
 
-extern ConfigVariableInt game_server_timeout_ms;
-extern ConfigVariableDouble min_lag;
-extern ConfigVariableDouble max_lag;
-extern ConfigVariableBool handle_datagrams_internally;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableInt game_server_timeout_ms;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble min_lag;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble max_lag;
+extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableBool handle_datagrams_internally;
 
 
 extern EXPCL_DIRECT_DISTRIBUTED void init_libdistributed();
 extern EXPCL_DIRECT_DISTRIBUTED void init_libdistributed();
 
 

+ 14 - 5
direct/src/filter/FilterManager.py

@@ -124,7 +124,7 @@ class FilterManager(DirectObject):
 
 
         return winx,winy
         return winx,winy
 
 
-    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None):
+    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None):
 
 
         """ Causes the scene to be rendered into the supplied textures
         """ Causes the scene to be rendered into the supplied textures
         instead of into the original window.  Puts a fullscreen quad
         instead of into the original window.  Puts a fullscreen quad
@@ -185,7 +185,10 @@ class FilterManager(DirectObject):
         # Choose the size of the offscreen buffer.
         # Choose the size of the offscreen buffer.
 
 
         (winx, winy) = self.getScaledSize(1,1,1)
         (winx, winy) = self.getScaledSize(1,1,1)
-        buffer = self.createBuffer("filter-base", winx, winy, texgroup)
+        if fbprops is not None:
+            buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops)
+        else:
+            buffer = self.createBuffer("filter-base", winx, winy, texgroup)
 
 
         if (buffer == None):
         if (buffer == None):
             return None
             return None
@@ -236,7 +239,7 @@ class FilterManager(DirectObject):
 
 
         return quad
         return quad
 
 
-    def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
+    def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None):
 
 
         """ Creates an offscreen buffer for an intermediate
         """ Creates an offscreen buffer for an intermediate
         computation. Installs a quad into the buffer.  Returns
         computation. Installs a quad into the buffer.  Returns
@@ -250,7 +253,10 @@ class FilterManager(DirectObject):
 
 
         depthbits = bool(depthtex != None)
         depthbits = bool(depthtex != None)
 
 
-        buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
+        if fbprops is not None:
+            buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops)
+        else:
+            buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
 
 
         if (buffer == None):
         if (buffer == None):
             return None
             return None
@@ -287,7 +293,7 @@ class FilterManager(DirectObject):
 
 
         return quad
         return quad
 
 
-    def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1):
+    def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None):
         """ Low-level buffer creation.  Not intended for public use. """
         """ Low-level buffer creation.  Not intended for public use. """
 
 
         winprops = WindowProperties()
         winprops = WindowProperties()
@@ -297,6 +303,9 @@ class FilterManager(DirectObject):
         props.setRgbColor(1)
         props.setRgbColor(1)
         props.setDepthBits(depthbits)
         props.setDepthBits(depthbits)
         props.setStereo(self.win.isStereo())
         props.setStereo(self.win.isStereo())
+        if fbprops is not None:
+            props.addProperties(fbprops)
+
         depthtex, colortex, auxtex0, auxtex1 = texgroup
         depthtex, colortex, auxtex0, auxtex1 = texgroup
         if (auxtex0 != None):
         if (auxtex0 != None):
             props.setAuxRgba(1)
             props.setAuxRgba(1)

+ 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 - 3
direct/src/gui/DirectGuiBase.py

@@ -634,7 +634,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 +663,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 +729,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:

+ 19 - 3
direct/src/gui/DirectOptionMenu.py

@@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton):
             # List of items to display on the popup menu
             # List of items to display on the popup menu
             ('items',       [],             self.setItems),
             ('items',       [],             self.setItems),
             # Initial item to display on menu button
             # Initial item to display on menu button
-            # Can be an interger index or the same string as the button
+            # Can be an integer index or the same string as the button
             ('initialitem', None,           DGG.INITOPT),
             ('initialitem', None,           DGG.INITOPT),
             # Amount of padding to place around popup button indicator
             # Amount of padding to place around popup button indicator
             ('popupMarkerBorder', (.1, .1), None),
             ('popupMarkerBorder', (.1, .1), None),
+            # The initial position of the popup marker
+            ('popupMarker_pos', None, None),
             # Background color to use to highlight popup menu items
             # Background color to use to highlight popup menu items
             ('highlightColor', (.5, .5, .5, 1), None),
             ('highlightColor', (.5, .5, .5, 1), None),
             # Extra scale to use on highlight popup menu items
             # Extra scale to use on highlight popup menu items
@@ -50,6 +52,8 @@ class DirectOptionMenu(DirectButton):
             frameSize = (-0.5, 0.5, -0.2, 0.2),
             frameSize = (-0.5, 0.5, -0.2, 0.2),
             scale = 0.4,
             scale = 0.4,
             relief = DGG.RAISED)
             relief = DGG.RAISED)
+        # Record any user specified popup marker position
+        self.initPopupMarkerPos = self['popupMarker_pos']
         # This needs to popup the menu too
         # This needs to popup the menu too
         self.popupMarker.bind(DGG.B1PRESS, self.showPopupMenu)
         self.popupMarker.bind(DGG.B1PRESS, self.showPopupMenu)
         # Check if item is highlighted on release and select it if it is
         # Check if item is highlighted on release and select it if it is
@@ -73,6 +77,7 @@ class DirectOptionMenu(DirectButton):
             state = 'normal')
             state = 'normal')
         # Make sure this is on top of all the other widgets
         # Make sure this is on top of all the other widgets
         self.cancelFrame.setBin('gui-popup', 0)
         self.cancelFrame.setBin('gui-popup', 0)
+        self.cancelFrame.node().setBounds(OmniBoundingVolume())
         self.cancelFrame.bind(DGG.B1PRESS, self.hidePopupMenu)
         self.cancelFrame.bind(DGG.B1PRESS, self.hidePopupMenu)
         # Default action on press is to show popup menu
         # Default action on press is to show popup menu
         self.bind(DGG.B1PRESS, self.showPopupMenu)
         self.bind(DGG.B1PRESS, self.showPopupMenu)
@@ -168,8 +173,13 @@ class DirectOptionMenu(DirectButton):
         else:
         else:
             # Or base it upon largest item
             # Or base it upon largest item
             bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
             bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
-        pm.setPos(bounds[1] + pmw/2.0, 0,
-                  bounds[2] + (bounds[3] - bounds[2])/2.0)
+        if self.initPopupMarkerPos:
+            # Use specified position
+            pmPos = list(self.initPopupMarkerPos)
+        else:
+            # Or base the position on the frame size.
+            pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0]
+        pm.setPos(pmPos[0], pmPos[1], pmPos[2])
         # Adjust popup menu button to fit all items (or use user specified
         # Adjust popup menu button to fit all items (or use user specified
         # frame size
         # frame size
         bounds[1] += pmw
         bounds[1] += pmw
@@ -184,6 +194,12 @@ class DirectOptionMenu(DirectButton):
         Adjust popup position if default position puts it outside of
         Adjust popup position if default position puts it outside of
         visible screen region
         visible screen region
         """
         """
+
+        # Needed attributes (such as minZ) won't be set unless the user has specified
+        # items to display. Let's assert that we've given items to work with.
+        items = self['items']
+        assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!'
+
         # Show the menu
         # Show the menu
         self.popupMenu.show()
         self.popupMenu.show()
         # Make sure its at the right scale
         # Make sure its at the right scale

+ 12 - 5
direct/src/gui/DirectScrolledFrame.py

@@ -33,7 +33,7 @@ class DirectScrolledFrame(DirectFrame):
             ('canvasSize',     (-1, 1, -1, 1),        self.setCanvasSize),
             ('canvasSize',     (-1, 1, -1, 1),        self.setCanvasSize),
             ('manageScrollBars', 1,                self.setManageScrollBars),
             ('manageScrollBars', 1,                self.setManageScrollBars),
             ('autoHideScrollBars', 1,              self.setAutoHideScrollBars),
             ('autoHideScrollBars', 1,              self.setAutoHideScrollBars),
-            ('scrollBarWidth', 0.08,               None),
+            ('scrollBarWidth', 0.08,               self.setScrollBarWidth),
             ('borderWidth',    (0.01, 0.01),       self.setBorderWidth),
             ('borderWidth',    (0.01, 0.01),       self.setBorderWidth),
             )
             )
 
 
@@ -72,6 +72,11 @@ class DirectScrolledFrame(DirectFrame):
         # Call option initialization functions
         # Call option initialization functions
         self.initialiseoptions(DirectScrolledFrame)
         self.initialiseoptions(DirectScrolledFrame)
 
 
+    def setScrollBarWidth(self):
+        w = self['scrollBarWidth']
+        self.verticalScroll["frameSize"] = (-w / 2.0, w / 2.0, -1, 1)
+        self.horizontalScroll["frameSize"] = (-1, 1, -w / 2.0, w / 2.0)
+
     def setCanvasSize(self):
     def setCanvasSize(self):
         f = self['canvasSize']
         f = self['canvasSize']
         self.guiItem.setVirtualFrame(f[0], f[1], f[2], f[3])
         self.guiItem.setVirtualFrame(f[0], f[1], f[2], f[3])
@@ -100,8 +105,10 @@ class DirectScrolledFrame(DirectFrame):
                 simpleChildGui = self.guiDict.get(parts[-1])
                 simpleChildGui = self.guiDict.get(parts[-1])
                 if simpleChildGui:
                 if simpleChildGui:
                     simpleChildGui.destroy()
                     simpleChildGui.destroy()
-        self.verticalScroll.destroy()
-        self.horizontalScroll.destroy()
-        del self.verticalScroll
-        del self.horizontalScroll
+        if self.verticalScroll:
+            self.verticalScroll.destroy()
+        if self.horizontalScroll:
+            self.horizontalScroll.destroy()
+        self.verticalScroll = None
+        self.horizontalScroll = None
         DirectFrame.destroy(self)
         DirectFrame.destroy(self)

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

+ 1 - 19
direct/src/interval/SoundInterval.py

@@ -27,8 +27,7 @@ class SoundInterval(Interval.Interval):
     # than explicitly restarting the sound every time around. This
     # than explicitly restarting the sound every time around. This
     # prevents a skip in the sound at every repetition (the gap in
     # prevents a skip in the sound at every repetition (the gap in
     # the sound is caused by the delay between the end of the sound
     # the sound is caused by the delay between the end of the sound
-    # and the next taskMgr cycle). There still seems to be a skip
-    # in Miles when looping MP3s. =(
+    # and the next taskMgr cycle).
     # RAU 03/01/07 add listenerNode in case we don't want to
     # RAU 03/01/07 add listenerNode in case we don't want to
     # use base.camera as the listener, node must not be None
     # use base.camera as the listener, node must not be None
     def __init__(self, sound, loop = 0, duration = 0.0, name = None,
     def __init__(self, sound, loop = 0, duration = 0.0, name = None,
@@ -62,23 +61,6 @@ class SoundInterval(Interval.Interval):
             #if (duration == 0):
             #if (duration == 0):
             #    self.notify.warning('zero length duration!')
             #    self.notify.warning('zero length duration!')
 
 
-            # MPG - hack for Miles bug
-            #duration += 1.5
-
-            # DCR - hack for Miles bug - adding 1.5 seconds caused
-            # problems for MG_neg_buzzer.wav
-
-            # DCR - what this is all about: Miles is under-reporting the
-            # length of MP3 files, and they're getting cut off too early.
-            # This is a temporary hack. We could:
-            # - hack Miles to fix its MP3 length calculation
-            # - complain louder about this to RAD
-            # - precompute MP3 durations and store them in a table
-
-            # drose - ok, I've put in a lower-level workaround in the
-            # MilesAudioManager.  This is no longer necessary up here,
-            # where it pollutes SoundInterval for everyone.
-            #duration += min(duration * 2.4, 1.5)
 
 
         # Generate unique name if necessary
         # Generate unique name if necessary
         if (name == None):
         if (name == None):

+ 4 - 0
direct/src/interval/cLerpNodePathInterval.I

@@ -106,6 +106,8 @@ set_end_hpr(const LQuaternion &quat) {
  * if either set_end_quat() or set_end_hpr() is also called.  This parameter
  * if either set_end_quat() or set_end_hpr() is also called.  This parameter
  * is optional; if unspecified, the value will be taken from the node's actual
  * is optional; if unspecified, the value will be taken from the node's actual
  * rotation at the time the lerp is performed.
  * rotation at the time the lerp is performed.
+ *
+ * The given quaternion needs to be normalized.
  */
  */
 INLINE void CLerpNodePathInterval::
 INLINE void CLerpNodePathInterval::
 set_start_quat(const LQuaternion &quat) {
 set_start_quat(const LQuaternion &quat) {
@@ -143,6 +145,8 @@ set_end_quat(const LVecBase3 &hpr) {
  * This replaces a previous call to set_end_hpr().  If neither set_end_quat()
  * This replaces a previous call to set_end_hpr().  If neither set_end_quat()
  * nor set_end_hpr() is called, the node's rotation will not be affected by
  * nor set_end_hpr() is called, the node's rotation will not be affected by
  * the lerp.
  * the lerp.
+ *
+ * The given quaternion needs to be normalized.
  */
  */
 INLINE void CLerpNodePathInterval::
 INLINE void CLerpNodePathInterval::
 set_end_quat(const LQuaternion &quat) {
 set_end_quat(const LQuaternion &quat) {

+ 2 - 2
direct/src/interval/cLerpNodePathInterval.cxx

@@ -174,14 +174,14 @@ priv_step(double t) {
           setup_slerp();
           setup_slerp();
 
 
         } else if ((_flags & F_bake_in_start) != 0) {
         } else if ((_flags & F_bake_in_start) != 0) {
-          set_start_quat(transform->get_quat());
+          set_start_quat(transform->get_norm_quat());
           setup_slerp();
           setup_slerp();
 
 
         } else {
         } else {
           if (_prev_d == 1.0) {
           if (_prev_d == 1.0) {
             _start_quat = _end_quat;
             _start_quat = _end_quat;
           } else {
           } else {
-            LQuaternion prev_value = transform->get_quat();
+            LQuaternion prev_value = transform->get_norm_quat();
             _start_quat = (prev_value - _prev_d * _end_quat) / (1.0 - _prev_d);
             _start_quat = (prev_value - _prev_d * _end_quat) / (1.0 - _prev_d);
           }
           }
           setup_slerp();
           setup_slerp();

+ 1 - 1
direct/src/leveleditor/ActionMgr.py

@@ -1,4 +1,4 @@
-from pandac.PandaModules import *
+from panda3d.core import *
 from . import ObjectGlobals as OG
 from . import ObjectGlobals as OG
 
 
 class ActionMgr:
 class ActionMgr:

+ 1 - 1
direct/src/leveleditor/CurveEditor.py

@@ -2,7 +2,7 @@
 This is the module for curve edit
 This is the module for curve edit
 """
 """
 
 
-from pandac.PandaModules import *
+from panda3d.core import *
 from direct.wxwidgets.WxPandaShell import *
 from direct.wxwidgets.WxPandaShell import *
 from direct.showbase.DirectObject import *
 from direct.showbase.DirectObject import *
 from direct.directtools.DirectSelection import SelectionRay
 from direct.directtools.DirectSelection import SelectionRay

+ 1 - 1
direct/src/leveleditor/FileMgr.py

@@ -11,7 +11,7 @@ class FileMgr:
     def saveToFile(self, fileName):
     def saveToFile(self, fileName):
         try:
         try:
             f = open(fileName, 'w')
             f = open(fileName, 'w')
-            f.write("from pandac.PandaModules import *\n")
+            f.write("from panda3d.core import *\n")
             f.write("\nif hasattr(base, 'le'):\n")
             f.write("\nif hasattr(base, 'le'):\n")
             f.write("    objectMgr = base.le.objectMgr\n")
             f.write("    objectMgr = base.le.objectMgr\n")
             f.write("    animMgr = base.le.animMgr\n")
             f.write("    animMgr = base.le.animMgr\n")

+ 1 - 1
direct/src/leveleditor/LayerEditorUI.py

@@ -2,7 +2,7 @@
 Defines Layer UI
 Defines Layer UI
 """
 """
 import wx
 import wx
-from pandac.PandaModules import *
+from panda3d.core import *
 
 
 from . import ObjectGlobals as OG
 from . import ObjectGlobals as OG
 
 

+ 2 - 2
direct/src/leveleditor/LevelEditor.py

@@ -31,7 +31,7 @@ class LevelEditor(LevelEditorBase):
 
 
         # Populating uderlined data-structures
         # Populating uderlined data-structures
         self.ui = LevelEditorUI(self)
         self.ui = LevelEditorUI(self)
-        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+        self.ui.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
         self.objectPalette.populate()
         self.objectPalette.populate()
         self.protoPalette.populate()
         self.protoPalette.populate()
 
 
@@ -42,4 +42,4 @@ class LevelEditor(LevelEditorBase):
         # When you define your own LevelEditor class inheriting LevelEditorBase
         # When you define your own LevelEditor class inheriting LevelEditorBase
         # you should call self.initialize() at the end of __init__() function
         # you should call self.initialize() at the end of __init__() function
         self.initialize()
         self.initialize()
-        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+        self.ui.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

+ 2 - 2
direct/src/leveleditor/LevelEditorBase.py

@@ -305,7 +305,7 @@ class LevelEditorBase(DirectObject):
         if self.settingsFile is None:
         if self.settingsFile is None:
             return
             return
 
 
-        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+        self.ui.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
         try:
         try:
             f = open(self.settingsFile, 'r')
             f = open(self.settingsFile, 'r')
             configLines = f.readlines()
             configLines = f.readlines()
@@ -342,7 +342,7 @@ class LevelEditorBase(DirectObject):
             self.ui.updateMenu()
             self.ui.updateMenu()
         except:
         except:
             pass
             pass
-        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+        self.ui.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
 
 
     def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
     def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
         if obj and isAnim:
         if obj and isAnim:

+ 1 - 1
direct/src/leveleditor/LevelEditorStart.py

@@ -1,4 +1,4 @@
-from . import LevelEditor
+from direct.leveleditor import LevelEditor
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     base.le = LevelEditor.LevelEditor()
     base.le = LevelEditor.LevelEditor()

+ 4 - 4
direct/src/leveleditor/LevelEditorUIBase.py

@@ -2,7 +2,7 @@
 ## import os
 ## import os
 ## from wx.lib.agw import fourwaysplitter as FWS
 ## from wx.lib.agw import fourwaysplitter as FWS
 
 
-from pandac.PandaModules import *
+from panda3d.core import *
 from direct.wxwidgets.WxPandaShell import *
 from direct.wxwidgets.WxPandaShell import *
 from direct.directtools.DirectSelection import SelectionRay
 from direct.directtools.DirectSelection import SelectionRay
 
 
@@ -478,7 +478,7 @@ class LevelEditorUIBase(WxPandaShell):
         self.editor.reset()
         self.editor.reset()
 
 
     def onOpen(self, evt=None):
     def onOpen(self, evt=None):
-        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.OPEN)
+        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
         if dialog.ShowModal() == wx.ID_OK:
         if dialog.ShowModal() == wx.ID_OK:
             self.editor.load(dialog.GetPath())
             self.editor.load(dialog.GetPath())
             self.editor.setTitleWithFilename(dialog.GetPath())
             self.editor.setTitleWithFilename(dialog.GetPath())
@@ -492,7 +492,7 @@ class LevelEditorUIBase(WxPandaShell):
             self.editor.save()
             self.editor.save()
 
 
     def onSaveAs(self, evt):
     def onSaveAs(self, evt):
-        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.SAVE)
+        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
         result = True
         result = True
         if dialog.ShowModal() == wx.ID_OK:
         if dialog.ShowModal() == wx.ID_OK:
             self.editor.saveAs(dialog.GetPath())
             self.editor.saveAs(dialog.GetPath())
@@ -503,7 +503,7 @@ class LevelEditorUIBase(WxPandaShell):
         return result
         return result
 
 
     def onExportToMaya(self, evt):
     def onExportToMaya(self, evt):
-        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.mb", wx.SAVE)
+        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.mb", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
         if dialog.ShowModal() == wx.ID_OK:
         if dialog.ShowModal() == wx.ID_OK:
             self.editor.exportToMaya(dialog.GetPath())
             self.editor.exportToMaya(dialog.GetPath())
         dialog.Destroy()
         dialog.Destroy()

+ 1 - 1
direct/src/leveleditor/ObjectMgrBase.py

@@ -6,7 +6,7 @@ import os, time, copy
 
 
 from direct.task import Task
 from direct.task import Task
 from direct.actor.Actor import Actor
 from direct.actor.Actor import Actor
-from pandac.PandaModules import *
+from panda3d.core import *
 from .ActionMgr import *
 from .ActionMgr import *
 from . import ObjectGlobals as OG
 from . import ObjectGlobals as OG
 
 

+ 1 - 1
direct/src/leveleditor/ObjectPaletteUI.py

@@ -69,4 +69,4 @@ class ObjectPaletteUI(wx.Panel):
         return cmp(index1, index2)
         return cmp(index1, index2)
 
 
     def getSelected(self):
     def getSelected(self):
-        return self.tree.GetItemPyData(self.tree.GetSelection())
+        return self.tree.GetItemData(self.tree.GetSelection())

+ 1 - 1
direct/src/leveleditor/ObjectPropertyUI.py

@@ -9,7 +9,7 @@ from wx.lib.embeddedimage import PyEmbeddedImage
 from wx.lib.scrolledpanel import ScrolledPanel
 from wx.lib.scrolledpanel import ScrolledPanel
 from wx.lib.agw.cubecolourdialog import *
 from wx.lib.agw.cubecolourdialog import *
 from direct.wxwidgets.WxSlider import *
 from direct.wxwidgets.WxSlider import *
-from pandac.PandaModules import *
+from panda3d.core import *
 from . import ObjectGlobals as OG
 from . import ObjectGlobals as OG
 from . import AnimGlobals as AG
 from . import AnimGlobals as AG
 
 

+ 2 - 2
direct/src/leveleditor/PaletteTreeCtrl.py

@@ -41,7 +41,7 @@ class PaletteTreeCtrl(wx.TreeCtrl):
                roots.append(key)
                roots.append(key)
         for root in roots:
         for root in roots:
             newItem = self.AppendItem(parentItem, root)
             newItem = self.AppendItem(parentItem, root)
-            self.SetItemPyData(newItem, root)
+            self.SetItemData(newItem, root)
             rootItems.append(newItem)
             rootItems.append(newItem)
             itemKeys.remove(root)
             itemKeys.remove(root)
         for rootItem in rootItems:
         for rootItem in rootItems:
@@ -108,7 +108,7 @@ class PaletteTreeCtrl(wx.TreeCtrl):
         item, cookie = self.GetFirstChild(parent)
         item, cookie = self.GetFirstChild(parent)
         while item:
         while item:
            itemName = self.GetItemText(item)
            itemName = self.GetItemText(item)
-           itemData = self.GetItemPyData(item)
+           itemData = self.GetItemData(item)
 
 
            newItem = self.AppendItem(newParent, itemName)
            newItem = self.AppendItem(newParent, itemName)
            self.SetItemPyData(newItem, itemData)
            self.SetItemPyData(newItem, itemData)

+ 1 - 1
direct/src/leveleditor/ProtoObjsUI.py

@@ -4,7 +4,7 @@ Defines ProtoObjs List UI
 import wx
 import wx
 import os
 import os
 
 
-from pandac.PandaModules import *
+from panda3d.core import *
 from .ProtoObjs import *
 from .ProtoObjs import *
 
 
 class ProtoDropTarget(wx.PyDropTarget):
 class ProtoDropTarget(wx.PyDropTarget):

+ 3 - 3
direct/src/leveleditor/ProtoPaletteUI.py

@@ -3,13 +3,13 @@ Defines ProtoPalette tree UI
 """
 """
 import wx
 import wx
 import os
 import os
-from pandac.PandaModules import *
+from panda3d.core import *
 from .PaletteTreeCtrl import *
 from .PaletteTreeCtrl import *
 
 
-class UniversalDropTarget(wx.PyDropTarget):
+class UniversalDropTarget(wx.DropTarget):
    """Implements drop target functionality to receive files, bitmaps and text"""
    """Implements drop target functionality to receive files, bitmaps and text"""
    def __init__(self, editor):
    def __init__(self, editor):
-       wx.PyDropTarget.__init__(self)
+       wx.DropTarget.__init__(self)
        self.editor = editor
        self.editor = editor
        self.do = wx.DataObjectComposite()  # the dataobject that gets filled with the appropriate data
        self.do = wx.DataObjectComposite()  # the dataobject that gets filled with the appropriate data
        self.filedo = wx.FileDataObject()
        self.filedo = wx.FileDataObject()

+ 14 - 14
direct/src/leveleditor/SceneGraphUIBase.py

@@ -2,7 +2,7 @@
 Defines Scene Graph tree UI Base
 Defines Scene Graph tree UI Base
 """
 """
 import wx
 import wx
-from pandac.PandaModules import *
+from panda3d.core import *
 from .ActionMgr import *
 from .ActionMgr import *
 
 
 from . import ObjectGlobals as OG
 from . import ObjectGlobals as OG
@@ -26,7 +26,7 @@ class SceneGraphUIBase(wx.Panel):
                   size=wx.DefaultSize, style=wx.TR_MULTIPLE|wx.TR_DEFAULT_STYLE,
                   size=wx.DefaultSize, style=wx.TR_MULTIPLE|wx.TR_DEFAULT_STYLE,
                   validator=wx.DefaultValidator, name="treeCtrl")
                   validator=wx.DefaultValidator, name="treeCtrl")
         self.root = self.tree.AddRoot('render')
         self.root = self.tree.AddRoot('render')
-        self.tree.SetItemPyData(self.root, "render")
+        self.tree.SetItemData(self.root, "render")
 
 
         self.shouldShowPandaObjChildren = False
         self.shouldShowPandaObjChildren = False
 
 
@@ -61,7 +61,7 @@ class SceneGraphUIBase(wx.Panel):
             self.tree.Delete(item)
             self.tree.Delete(item)
 
 
     def traversePandaObjects(self, parent, objNodePath):
     def traversePandaObjects(self, parent, objNodePath):
-        itemId = self.tree.GetItemPyData(parent)
+        itemId = self.tree.GetItemData(parent)
         i = 0
         i = 0
         for child in objNodePath.getChildren():
         for child in objNodePath.getChildren():
             if child.hasTag('OBJRoot'):
             if child.hasTag('OBJRoot'):
@@ -78,7 +78,7 @@ class SceneGraphUIBase(wx.Panel):
 
 
     def addPandaObjectChildren(self, parent):
     def addPandaObjectChildren(self, parent):
         # first, find Panda Object's NodePath of the item
         # first, find Panda Object's NodePath of the item
-        itemId = self.tree.GetItemPyData(parent)
+        itemId = self.tree.GetItemData(parent)
         if itemId == "render":
         if itemId == "render":
            return
            return
         obj = self.editor.objectMgr.findObjectById(itemId)
         obj = self.editor.objectMgr.findObjectById(itemId)
@@ -96,7 +96,7 @@ class SceneGraphUIBase(wx.Panel):
 
 
     def removePandaObjectChildren(self, parent):
     def removePandaObjectChildren(self, parent):
         # first, find Panda Object's NodePath of the item
         # first, find Panda Object's NodePath of the item
-        itemId = self.tree.GetItemPyData(parent)
+        itemId = self.tree.GetItemData(parent)
         if itemId == "render":
         if itemId == "render":
            return
            return
         obj = self.editor.objectMgr.findObjectById(itemId)
         obj = self.editor.objectMgr.findObjectById(itemId)
@@ -142,14 +142,14 @@ class SceneGraphUIBase(wx.Panel):
 
 
     def traverse(self, parent, itemId):
     def traverse(self, parent, itemId):
         # prevent from traversing into self
         # prevent from traversing into self
-        if itemId == self.tree.GetItemPyData(parent):
+        if itemId == self.tree.GetItemData(parent):
            return None
            return None
 
 
         # main loop - serching for an item with an itemId
         # main loop - serching for an item with an itemId
         item, cookie = self.tree.GetFirstChild(parent)
         item, cookie = self.tree.GetFirstChild(parent)
         while item:
         while item:
               # if the item was found - return it
               # if the item was found - return it
-              if itemId == self.tree.GetItemPyData(item):
+              if itemId == self.tree.GetItemData(item):
                  return item
                  return item
 
 
               # the tem was not found - checking if it has children
               # the tem was not found - checking if it has children
@@ -168,7 +168,7 @@ class SceneGraphUIBase(wx.Panel):
         item, cookie = self.tree.GetFirstChild(parent)
         item, cookie = self.tree.GetFirstChild(parent)
         while item:
         while item:
            data = self.tree.GetItemText(item)
            data = self.tree.GetItemText(item)
-           itemId = self.tree.GetItemPyData(item)
+           itemId = self.tree.GetItemData(item)
            newItem = self.tree.AppendItem(newParent, data)
            newItem = self.tree.AppendItem(newParent, data)
            self.tree.SetItemPyData(newItem, itemId)
            self.tree.SetItemPyData(newItem, itemId)
 
 
@@ -186,13 +186,13 @@ class SceneGraphUIBase(wx.Panel):
     def reParent(self, oldParent, newParent, child):
     def reParent(self, oldParent, newParent, child):
         if newParent is None:
         if newParent is None:
            newParent = self.root
            newParent = self.root
-        itemId = self.tree.GetItemPyData(oldParent)
+        itemId = self.tree.GetItemData(oldParent)
         newItem = self.tree.AppendItem(newParent, child)
         newItem = self.tree.AppendItem(newParent, child)
         self.tree.SetItemPyData(newItem, itemId)
         self.tree.SetItemPyData(newItem, itemId)
         self.reParentTree(oldParent, newItem)
         self.reParentTree(oldParent, newItem)
 
 
         obj = self.editor.objectMgr.findObjectById(itemId)
         obj = self.editor.objectMgr.findObjectById(itemId)
-        itemId = self.tree.GetItemPyData(newParent)
+        itemId = self.tree.GetItemData(newParent)
         if itemId != "render":
         if itemId != "render":
           newParentObj = self.editor.objectMgr.findObjectById(itemId)
           newParentObj = self.editor.objectMgr.findObjectById(itemId)
           self.reParentData(newParentObj[OG.OBJ_NP], obj[OG.OBJ_NP])
           self.reParentData(newParentObj[OG.OBJ_NP], obj[OG.OBJ_NP])
@@ -207,7 +207,7 @@ class SceneGraphUIBase(wx.Panel):
            self.addPandaObjectChildren(newpParent)
            self.addPandaObjectChildren(newpParent)
 
 
     def isChildOrGrandChild(self, parent, child):
     def isChildOrGrandChild(self, parent, child):
-        childId = self.tree.GetItemPyData(child)
+        childId = self.tree.GetItemData(child)
         return self.traverse(parent, childId)
         return self.traverse(parent, childId)
 
 
     def changeHierarchy(self, data, x, y):
     def changeHierarchy(self, data, x, y):
@@ -226,7 +226,7 @@ class SceneGraphUIBase(wx.Panel):
               return
               return
 
 
            # undo function setup...
            # undo function setup...
-           action = ActionChangeHierarchy(self.editor, self.tree.GetItemPyData(self.tree.GetItemParent(item)), self.tree.GetItemPyData(item), self.tree.GetItemPyData(dragToItem), data)
+           action = ActionChangeHierarchy(self.editor, self.tree.GetItemData(self.tree.GetItemParent(item)), self.tree.GetItemData(item), self.tree.GetItemData(dragToItem), data)
            self.editor.actionMgr.push(action)
            self.editor.actionMgr.push(action)
            action()
            action()
 
 
@@ -283,7 +283,7 @@ class SceneGraphUIBase(wx.Panel):
     def onSelected(self, event):
     def onSelected(self, event):
         item = event.GetItem();
         item = event.GetItem();
         if item:
         if item:
-           itemId = self.tree.GetItemPyData(item)
+           itemId = self.tree.GetItemData(item)
            if itemId:
            if itemId:
               obj = self.editor.objectMgr.findObjectById(itemId);
               obj = self.editor.objectMgr.findObjectById(itemId);
               if obj:
               if obj:
@@ -313,7 +313,7 @@ class SceneGraphUIBase(wx.Panel):
         if not item.IsOk():
         if not item.IsOk():
             return
             return
         self.currItem = item
         self.currItem = item
-        itemId = self.tree.GetItemPyData(item)
+        itemId = self.tree.GetItemData(item)
         if not itemId:
         if not itemId:
             return
             return
         self.currObj = self.editor.objectMgr.findObjectById(itemId);
         self.currObj = self.editor.objectMgr.findObjectById(itemId);

+ 1 - 1
direct/src/leveleditor/testData.py

@@ -1,4 +1,4 @@
-from pandac.PandaModules import *
+from panda3d.core import *
 
 
 if hasattr(base, 'le'):
 if hasattr(base, 'le'):
     objectMgr = base.le.objectMgr
     objectMgr = base.le.objectMgr

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

+ 35 - 38
direct/src/particles/ParticleEffect.py

@@ -163,44 +163,41 @@ class ParticleEffect(NodePath):
 
 
     def saveConfig(self, filename):
     def saveConfig(self, filename):
         filename = Filename(filename)
         filename = Filename(filename)
-        f = open(filename.toOsSpecific(), 'wb')
-        # Add a blank line
-        f.write('\n')
-
-        # Make sure we start with a clean slate
-        f.write('self.reset()\n')
-
-        pos = self.getPos()
-        hpr = self.getHpr()
-        scale = self.getScale()
-        f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
-                (pos[0], pos[1], pos[2]))
-        f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
-                (hpr[0], hpr[1], hpr[2]))
-        f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
-                (scale[0], scale[1], scale[2]))
-
-        # Save all the particles to file
-        num = 0
-        for p in list(self.particlesDict.values()):
-            target = 'p%d' % num
-            num = num + 1
-            f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
-            p.printParams(f, target)
-            f.write('self.addParticles(%s)\n' % target)
-
-        # Save all the forces to file
-        num = 0
-        for fg in list(self.forceGroupDict.values()):
-            target = 'f%d' % num
-            num = num + 1
-            f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
-                                                fg.getName())
-            fg.printParams(f, target)
-            f.write('self.addForceGroup(%s)\n' % target)
-
-        # Close the file
-        f.close()
+        with open(filename.toOsSpecific(), 'w') as f:
+          # Add a blank line
+          f.write('\n')
+
+          # Make sure we start with a clean slate
+          f.write('self.reset()\n')
+
+          pos = self.getPos()
+          hpr = self.getHpr()
+          scale = self.getScale()
+          f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
+                  (pos[0], pos[1], pos[2]))
+          f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
+                  (hpr[0], hpr[1], hpr[2]))
+          f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
+                  (scale[0], scale[1], scale[2]))
+
+          # Save all the particles to file
+          num = 0
+          for p in list(self.particlesDict.values()):
+              target = 'p%d' % num
+              num = num + 1
+              f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
+              p.printParams(f, target)
+              f.write('self.addParticles(%s)\n' % target)
+
+          # Save all the forces to file
+          num = 0
+          for fg in list(self.forceGroupDict.values()):
+              target = 'f%d' % num
+              num = num + 1
+              f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
+                                                  fg.getName())
+              fg.printParams(f, target)
+              f.write('self.addForceGroup(%s)\n' % target)
 
 
     def loadConfig(self, filename):
     def loadConfig(self, filename):
         data = vfs.readFile(filename, 1)
         data = vfs.readFile(filename, 1)

+ 1 - 0
direct/src/particles/Particles.py

@@ -22,6 +22,7 @@ from panda3d.physics import RingEmitter
 from panda3d.physics import SphereSurfaceEmitter
 from panda3d.physics import SphereSurfaceEmitter
 from panda3d.physics import SphereVolumeEmitter
 from panda3d.physics import SphereVolumeEmitter
 from panda3d.physics import TangentRingEmitter
 from panda3d.physics import TangentRingEmitter
+from panda3d.physics import SpriteAnim
 
 
 from . import SpriteParticleRendererExt
 from . import SpriteParticleRendererExt
 
 

+ 18 - 0
direct/src/showbase/BufferViewer.py

@@ -442,3 +442,21 @@ class BufferViewer(DirectObject):
                     cards[index] = placer
                     cards[index] = placer
 
 
         return Task.cont
         return Task.cont
+
+    # Snake-case aliases, for people who prefer these.
+    advance_card = advanceCard
+    analyze_texture_set = analyzeTextureSet
+    is_enabled = isEnabled
+    is_valid_texture_set = isValidTextureSet
+    maintain_readout = maintainReadout
+    make_frame = makeFrame
+    refresh_readout = refreshReadout
+    select_card = selectCard
+    set_card_size = setCardSize
+    set_exclude = setExclude
+    set_include = setInclude
+    set_layout = setLayout
+    set_position = setPosition
+    set_render_parent = setRenderParent
+    set_sort = setSort
+    toggle_enable = toggleEnable

+ 36 - 44
direct/src/showbase/EventManager.py

@@ -19,7 +19,7 @@ class EventManager:
         Create a C++ event queue and handler
         Create a C++ event queue and handler
         """
         """
         # Make a notify category for this class (unless there already is one)
         # Make a notify category for this class (unless there already is one)
-        if (EventManager.notify == None):
+        if EventManager.notify is None:
             EventManager.notify = directNotify.newCategory("EventManager")
             EventManager.notify = directNotify.newCategory("EventManager")
 
 
         self.eventQueue = eventQueue
         self.eventQueue = eventQueue
@@ -37,8 +37,10 @@ class EventManager:
             processFunc = self.processEventPstats
             processFunc = self.processEventPstats
         else:
         else:
             processFunc = self.processEvent
             processFunc = self.processEvent
-        while (not self.eventQueue.isQueueEmpty()):
-            processFunc(self.eventQueue.dequeueEvent())
+        isEmptyFunc = self.eventQueue.isQueueEmpty
+        dequeueFunc = self.eventQueue.dequeueEvent
+        while not isEmptyFunc():
+            processFunc(dequeueFunc())
 
 
     def eventLoopTask(self, task):
     def eventLoopTask(self, task):
         """
         """
@@ -78,13 +80,13 @@ class EventManager:
         # ******** Duplicate any changes in processEventPstats *********
         # ******** Duplicate any changes in processEventPstats *********
         # **************************************************************
         # **************************************************************
         # Get the event name
         # Get the event name
-        eventName = event.getName()
+        eventName = event.name
         if eventName:
         if eventName:
             paramList = []
             paramList = []
-            for i in range(event.getNumParameters()):
-                eventParameter = event.getParameter(i)
+            for eventParameter in event.parameters:
                 eventParameterData = self.parseEventParameter(eventParameter)
                 eventParameterData = self.parseEventParameter(eventParameter)
                 paramList.append(eventParameterData)
                 paramList.append(eventParameterData)
+
             # Do not print the new frame debug, it is too noisy!
             # Do not print the new frame debug, it is too noisy!
             if (EventManager.notify.getDebug() and eventName != 'NewFrame'):
             if (EventManager.notify.getDebug() and eventName != 'NewFrame'):
                 EventManager.notify.debug('received C++ event named: ' + eventName +
                 EventManager.notify.debug('received C++ event named: ' + eventName +
@@ -94,13 +96,12 @@ class EventManager:
             # **************************************************************
             # **************************************************************
             # Send the event, we used to send it with the event
             # Send the event, we used to send it with the event
             # name as a parameter, but now you can use extraArgs for that
             # name as a parameter, but now you can use extraArgs for that
-            if paramList:
-                messenger.send(eventName, paramList)
-            else:
-                messenger.send(eventName)
+            messenger.send(eventName, paramList)
+
             # Also send the event down into C++ land
             # Also send the event down into C++ land
-            if self.eventHandler:
-                self.eventHandler.dispatchEvent(event)
+            handler = self.eventHandler
+            if handler:
+                handler.dispatchEvent(event)
 
 
         else:
         else:
             # An unnamed event from C++ is probably a bad thing
             # An unnamed event from C++ is probably a bad thing
@@ -115,13 +116,13 @@ class EventManager:
         # ******** Duplicate any changes in processEvent *********
         # ******** Duplicate any changes in processEvent *********
         # ********************************************************
         # ********************************************************
         # Get the event name
         # Get the event name
-        eventName = event.getName()
+        eventName = event.name
         if eventName:
         if eventName:
             paramList = []
             paramList = []
-            for i in range(event.getNumParameters()):
-                eventParameter = event.getParameter(i)
+            for eventParameter in event.parameters:
                 eventParameterData = self.parseEventParameter(eventParameter)
                 eventParameterData = self.parseEventParameter(eventParameter)
                 paramList.append(eventParameterData)
                 paramList.append(eventParameterData)
+
             # Do not print the new frame debug, it is too noisy!
             # Do not print the new frame debug, it is too noisy!
             if (EventManager.notify.getDebug() and eventName != 'NewFrame'):
             if (EventManager.notify.getDebug() and eventName != 'NewFrame'):
                 EventManager.notify.debug('received C++ event named: ' + eventName +
                 EventManager.notify.debug('received C++ event named: ' + eventName +
@@ -131,45 +132,36 @@ class EventManager:
             # ********************************************************
             # ********************************************************
             # ******** Duplicate any changes in processEvent *********
             # ******** Duplicate any changes in processEvent *********
             # ********************************************************
             # ********************************************************
-            if self._wantPstats:
-                name = eventName
-                hyphen = name.find('-')
-                if hyphen >= 0:
-                    name = name[0:hyphen]
-                pstatCollector = PStatCollector('App:Show code:eventManager:' + name)
-                pstatCollector.start()
-                if self.eventHandler:
-                    cppPstatCollector = PStatCollector(
-                        'App:Show code:eventManager:' + name + ':C++')
-
-            if paramList:
-                messenger.send(eventName, paramList)
-            else:
-                messenger.send(eventName)
-            # Also send the event down into C++ land
+            name = eventName
+            hyphen = name.find('-')
+            if hyphen >= 0:
+                name = name[0:hyphen]
+            pstatCollector = PStatCollector('App:Show code:eventManager:' + name)
+            pstatCollector.start()
             if self.eventHandler:
             if self.eventHandler:
-                if self._wantPstats:
-                    cppPstatCollector.start()
-                self.eventHandler.dispatchEvent(event)
-            # ********************************************************
-            # ******** Duplicate any changes in processEvent *********
-            # ********************************************************
+                cppPstatCollector = PStatCollector(
+                    'App:Show code:eventManager:' + name + ':C++')
 
 
-            if self._wantPstats:
-                if self.eventHandler:
-                    cppPstatCollector.stop()
-                pstatCollector.stop()
+            messenger.send(eventName, paramList)
+
+            # Also send the event down into C++ land
+            handler = self.eventHandler
+            if handler:
+                cppPstatCollector.start()
+                handler.dispatchEvent(event)
+                cppPstatCollector.stop()
+
+            pstatCollector.stop()
 
 
         else:
         else:
             # An unnamed event from C++ is probably a bad thing
             # An unnamed event from C++ is probably a bad thing
             EventManager.notify.warning('unnamed event in processEvent')
             EventManager.notify.warning('unnamed event in processEvent')
 
 
-
     def restart(self):
     def restart(self):
-        if self.eventQueue == None:
+        if self.eventQueue is None:
             self.eventQueue = EventQueue.getGlobalEventQueue()
             self.eventQueue = EventQueue.getGlobalEventQueue()
 
 
-        if self.eventHandler == None:
+        if self.eventHandler is None:
             if self.eventQueue == EventQueue.getGlobalEventQueue():
             if self.eventQueue == EventQueue.getGlobalEventQueue():
                 # If we are using the global event queue, then we also
                 # If we are using the global event queue, then we also
                 # want to use the global event handler.
                 # want to use the global event handler.

+ 0 - 100
direct/src/showbase/FindCtaPaths.py

@@ -1,100 +0,0 @@
-"""This module is used only by the VR Studio programmers who are using
-the ctattach tools.  It is imported before any other package, and its
-job is to figure out the correct paths to each of the packages.
-
-This module is not needed if you are not using ctattach; in this case
-all of the Panda packages will be collected under a common directory,
-which you will presumably have already on your PYTHONPATH. """
-
-__all__ = ['deCygwinify', 'getPaths']
-
-import os
-import sys
-
-def deCygwinify(path):
-    if os.name in ['nt'] and path[0] == '/':
-        # On Windows, we may need to convert from a Cygwin-style path
-        # to a native Windows path.
-
-        # Check for a case like /i/ or /p/: this converts
-        # to i:\ or p:\.
-
-        dirs = path.split('/')
-        if len(dirs) > 2 and len(dirs[1]) == 1:
-            path = '%s:\%s' % (dirs[1], '\\'.join(dirs[2:]))
-
-        else:
-            # Otherwise, prepend $PANDA_ROOT and flip the slashes.
-            pandaRoot = os.getenv('PANDA_ROOT')
-            if pandaRoot:
-                path = os.path.normpath(pandaRoot + path)
-
-    return path
-
-def getPaths():
-    """
-    Add to sys.path the appropriate director(ies) to search for the
-    various Panda projects.  Typically, these will all be in the same
-    directory (which is presumably already on sys.path), but if the VR
-    Studio ctattach tools are in use they could be scattered around in
-    several places.
-    """
-
-    ctprojs = os.getenv("CTPROJS")
-    if ctprojs:
-        # The CTPROJS environment variable is defined.  We must be
-        # using the ctattach tools.  In this case, we need to figure
-        # out the location of each of the separate trees, and put the
-        # parent directory of each one on sys.path.  In many cases,
-        # these will all be siblings, so we filter out duplicate
-        # parent directories.
-
-        print('Appending to sys.path based on $CTPROJS:')
-
-        # First, get the list of packages, then reverse the list to
-        # put it in ctattach order.  (The reversal may not matter too
-        # much these days, but let's be as correct as we can be.)
-        packages = []
-        for proj in ctprojs.split():
-            projName = proj.split(':')[0]
-            packages.append(projName)
-        packages.reverse()
-
-        # Now walk through the packages and figure out the parent of
-        # each referenced directory.
-
-        parents = []
-        for package in packages:
-            tree = os.getenv(package)
-            if not tree:
-                print("  CTPROJS contains %s, but $%s is not defined." % (package, package))
-                sys.exit(1)
-
-            tree = deCygwinify(tree)
-
-            parent, base = os.path.split(tree)
-            if base != package.lower():
-                print("  Warning: $%s refers to a directory named %s (instead of %s)" % (package, base, package.lower()))
-
-            if parent not in parents:
-                parents.append(parent)
-
-
-            # We also put tree/built/lib on sys.path by hand, because we
-            # will need to load up the generated C++ modules that got
-            # put there.  Also, we will find the output of genPyCode
-            # in $DIRECT/built/lib/pandac.
-            libdir = os.path.join(tree, 'built', 'lib')
-            if os.path.isdir(libdir):
-                if libdir not in sys.path:
-                    sys.path.append(libdir)
-
-
-        # Now the result goes onto sys.path.
-        for parent in parents:
-            print("  %s" % (parent))
-            if parent not in sys.path:
-                sys.path.append(parent)
-
-
-getPaths()

+ 59 - 21
direct/src/showbase/Loader.py

@@ -27,6 +27,42 @@ class Loader(DirectObject):
         # This indicates that this class behaves like a Future.
         # This indicates that this class behaves like a Future.
         _asyncio_future_blocking = False
         _asyncio_future_blocking = False
 
 
+        class ResultAwaiter(object):
+            """Reinvents generators because of PEP 479, sigh.  See #513."""
+
+            __slots__ = 'requestList', 'index'
+
+            def __init__(self, requestList):
+                self.requestList = requestList
+                self.index = 0
+
+            def __await__(self):
+                return self
+
+            def __anext__(self):
+                if self.index >= len(self.requestList):
+                    raise StopAsyncIteration
+                return self
+
+            def __iter__(self):
+                return self
+
+            def __next__(self):
+                i = self.index
+                request = self.requestList[i]
+                if not request.done():
+                    return request
+
+                self.index = i + 1
+
+                result = request.result()
+                if isinstance(result, PandaNode):
+                    result = NodePath(result)
+
+                exc = StopIteration(result)
+                exc.value = result
+                raise exc
+
         def __init__(self, loader, numObjects, gotList, callback, extraArgs):
         def __init__(self, loader, numObjects, gotList, callback, extraArgs):
             self._loader = loader
             self._loader = loader
             self.objects = [None] * numObjects
             self.objects = [None] * numObjects
@@ -81,16 +117,14 @@ class Loader(DirectObject):
         def __await__(self):
         def __await__(self):
             """ Returns a generator that raises StopIteration when the loading
             """ Returns a generator that raises StopIteration when the loading
             is complete.  This allows this class to be used with 'await'."""
             is complete.  This allows this class to be used with 'await'."""
+
             if self.requests:
             if self.requests:
                 self._asyncio_future_blocking = True
                 self._asyncio_future_blocking = True
-                yield self
 
 
-            # This should be a simple return, but older versions of Python
-            # don't allow return statements with arguments.
-            result = self.result()
-            exc = StopIteration(result)
-            exc.value = result
-            raise exc
+            if self.gotList:
+                return self.ResultAwaiter([self])
+            else:
+                return self.ResultAwaiter(self.requestList)
 
 
         def __aiter__(self):
         def __aiter__(self):
             """ This allows using `async for` to iterate asynchronously over
             """ This allows using `async for` to iterate asynchronously over
@@ -100,19 +134,7 @@ class Loader(DirectObject):
             requestList = self.requestList
             requestList = self.requestList
             assert requestList is not None, "Request was cancelled."
             assert requestList is not None, "Request was cancelled."
 
 
-            class AsyncIter:
-                index = 0
-                def __anext__(self):
-                    if self.index < len(requestList):
-                        i = self.index
-                        self.index = i + 1
-                        return requestList[i]
-                    else:
-                        raise StopAsyncIteration
-
-            iter = AsyncIter()
-            iter.objects = self.objects
-            return iter
+            return self.ResultAwaiter(requestList)
 
 
     # special methods
     # special methods
     def __init__(self, base):
     def __init__(self, base):
@@ -125,12 +147,28 @@ class Loader(DirectObject):
         Loader.loaderIndex += 1
         Loader.loaderIndex += 1
         self.accept(self.hook, self.__gotAsyncObject)
         self.accept(self.hook, self.__gotAsyncObject)
 
 
+        if ConfigVariableBool('loader-support-entry-points', True):
+            self._loadPythonFileTypes()
+
     def destroy(self):
     def destroy(self):
         self.ignore(self.hook)
         self.ignore(self.hook)
         self.loader.stopThreads()
         self.loader.stopThreads()
         del self.base
         del self.base
         del self.loader
         del self.loader
 
 
+    def _loadPythonFileTypes(self):
+        import importlib
+        try:
+            pkg_resources = importlib.import_module('pkg_resources')
+        except ImportError:
+            pkg_resources = None
+
+        if pkg_resources:
+            registry = LoaderFileTypeRegistry.getGlobalPtr()
+
+            for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'):
+                registry.register_deferred_type(entry_point)
+
     # model loading funcs
     # model loading funcs
     def loadModel(self, modelPath, loaderOptions = None, noCache = None,
     def loadModel(self, modelPath, loaderOptions = None, noCache = None,
                   allowInstance = False, okMissing = None,
                   allowInstance = False, okMissing = None,
@@ -192,7 +230,7 @@ class Loader(DirectObject):
 
 
         """
         """
 
 
-        assert Loader.notify.debug("Loading model: %s" % (modelPath))
+        assert Loader.notify.debug("Loading model: %s" % (modelPath,))
         if loaderOptions is None:
         if loaderOptions is None:
             loaderOptions = LoaderOptions()
             loaderOptions = LoaderOptions()
         else:
         else:

+ 3 - 2
direct/src/showbase/Messenger.py

@@ -624,8 +624,9 @@ class Messenger:
             for key in list(acceptorDict.keys()):
             for key in list(acceptorDict.keys()):
                 function, extraArgs, persistent = acceptorDict[key]
                 function, extraArgs, persistent = acceptorDict[key]
                 object = self._getObject(key)
                 object = self._getObject(key)
-                if (type(object) == types.InstanceType):
-                    className = object.__class__.__name__
+                objectClass = getattr(object, '__class__', None)
+                if objectClass:
+                    className = objectClass.__name__
                 else:
                 else:
                     className = "Not a class"
                     className = "Not a class"
                 functionName = function.__name__
                 functionName = function.__name__

+ 9 - 6
direct/src/showbase/PythonUtil.py

@@ -1130,6 +1130,10 @@ def weightedChoice(choiceList, rng=random.random, sum=None):
     """given a list of (weight, item) pairs, chooses an item based on the
     """given a list of (weight, item) pairs, chooses an item based on the
     weights. rng must return 0..1. if you happen to have the sum of the
     weights. rng must return 0..1. if you happen to have the sum of the
     weights, pass it in 'sum'."""
     weights, pass it in 'sum'."""
+    # Throw an IndexError if we got an empty list.
+    if not choiceList:
+        raise IndexError('Cannot choose from an empty sequence')
+
     # TODO: add support for dicts
     # TODO: add support for dicts
     if sum is None:
     if sum is None:
         sum = 0.
         sum = 0.
@@ -1138,6 +1142,7 @@ def weightedChoice(choiceList, rng=random.random, sum=None):
 
 
     rand = rng()
     rand = rng()
     accum = rand * sum
     accum = rand * sum
+    item = None
     for weight, item in choiceList:
     for weight, item in choiceList:
         accum -= weight
         accum -= weight
         if accum <= 0.:
         if accum <= 0.:
@@ -1652,17 +1657,15 @@ def itype(obj):
     # version of type that gives more complete information about instance types
     # version of type that gives more complete information about instance types
     global dtoolSuperBase
     global dtoolSuperBase
     t = type(obj)
     t = type(obj)
-    if t is types.InstanceType:
-        return '%s of <class %s>>' % (repr(types.InstanceType)[:-1],
-                                      str(obj.__class__))
+    if sys.version_info < (3, 0) and t is types.InstanceType:
+        return "<type 'instance' of <class %s>>" % (obj.__class__)
     else:
     else:
         # C++ object instances appear to be types via type()
         # C++ object instances appear to be types via type()
         # check if this is a C++ object
         # check if this is a C++ object
         if dtoolSuperBase is None:
         if dtoolSuperBase is None:
             _getDtoolSuperBase()
             _getDtoolSuperBase()
         if isinstance(obj, dtoolSuperBase):
         if isinstance(obj, dtoolSuperBase):
-            return '%s of %s>' % (repr(types.InstanceType)[:-1],
-                                  str(obj.__class__))
+            return "<type 'instance' of %s>" % (obj.__class__)
         return t
         return t
 
 
 def deeptype(obj, maxLen=100, _visitedIds=None):
 def deeptype(obj, maxLen=100, _visitedIds=None):
@@ -2513,7 +2516,7 @@ class AlphabetCounter:
         index = -1
         index = -1
         while True:
         while True:
             curChar = self._curCounter[index]
             curChar = self._curCounter[index]
-            if curChar is 'Z':
+            if curChar == 'Z':
                 nextChar = 'A'
                 nextChar = 'A'
                 carry = True
                 carry = True
             else:
             else:

+ 2 - 3
direct/src/showbase/SfxPlayer.py

@@ -53,6 +53,8 @@ class SfxPlayer:
                 d = node.getDistance(listenerNode)
                 d = node.getDistance(listenerNode)
             else:
             else:
                 d = node.getDistance(base.cam)
                 d = node.getDistance(base.cam)
+        if not cutoff:
+            cutoff = self.cutoffDistance
         if d == None or d > cutoff:
         if d == None or d > cutoff:
             volume = 0
             volume = 0
         else:
         else:
@@ -70,9 +72,6 @@ class SfxPlayer:
             self, sfx, looping = 0, interrupt = 1, volume = None,
             self, sfx, looping = 0, interrupt = 1, volume = None,
             time = 0.0, node=None, listenerNode = None, cutoff = None):
             time = 0.0, node=None, listenerNode = None, cutoff = None):
         if sfx:
         if sfx:
-            if not cutoff:
-                cutoff = self.cutoffDistance
-
             self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
             self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
 
 
             # don't start over if it's already playing, unless
             # don't start over if it's already playing, unless

+ 23 - 8
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()
@@ -829,9 +832,11 @@ class ShowBase(DirectObject.DirectObject):
             win.requestProperties(props)
             win.requestProperties(props)
 
 
         mainWindow = False
         mainWindow = False
-        if self.win == None:
+        if self.win is None:
             mainWindow = True
             mainWindow = True
             self.win = win
             self.win = win
+            if hasattr(self, 'bufferViewer'):
+                self.bufferViewer.win = win
 
 
         self.winList.append(win)
         self.winList.append(win)
 
 
@@ -1677,13 +1682,19 @@ class ShowBase(DirectObject.DirectObject):
         return self.mouseWatcherNode.getModifierButtons().isDown(
         return self.mouseWatcherNode.getModifierButtons().isDown(
             KeyboardButton.meta())
             KeyboardButton.meta())
 
 
-    def attachInputDevice(self, device, prefix=None):
+    def attachInputDevice(self, device, prefix=None, watch=False):
         """
         """
         This function attaches an input device to the data graph, which will
         This function attaches an input device to the data graph, which will
         cause the device to be polled and generate events.  If a prefix is
         cause the device to be polled and generate events.  If a prefix is
         given and not None, it is used to prefix events generated by this
         given and not None, it is used to prefix events generated by this
         device, separated by a hyphen.
         device, separated by a hyphen.
 
 
+        The watch argument can be set to True (as of Panda3D 1.10.3) to set up
+        the default MouseWatcher to receive inputs from this device, allowing
+        it to be polled via mouseWatcherNode and control user interfaces.
+        Setting this to True will also make it generate unprefixed events,
+        regardless of the specified prefix.
+
         If you call this, you should consider calling detachInputDevice when
         If you call this, you should consider calling detachInputDevice when
         you are done with the device or when it is disconnected.
         you are done with the device or when it is disconnected.
         """
         """
@@ -1694,13 +1705,17 @@ class ShowBase(DirectObject.DirectObject):
         idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
         idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
 
 
         # Setup the button thrower to generate events for the device.
         # Setup the button thrower to generate events for the device.
-        bt = idn.attachNewNode(ButtonThrower(device.name))
-        if prefix is not None:
-            bt.node().setPrefix(prefix + '-')
+        if prefix is not None or not watch:
+            bt = idn.attachNewNode(ButtonThrower(device.name))
+            if prefix is not None:
+                bt.node().setPrefix(prefix + '-')
+            self.deviceButtonThrowers.append(bt)
 
 
         assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
         assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
         self.__inputDeviceNodes[device] = idn
         self.__inputDeviceNodes[device] = idn
-        self.deviceButtonThrowers.append(bt)
+
+        if watch:
+            idn.node().addChild(self.mouseWatcherNode)
 
 
     def detachInputDevice(self, device):
     def detachInputDevice(self, device):
         """
         """
@@ -2926,7 +2941,7 @@ class ShowBase(DirectObject.DirectObject):
 
 
         if not self.wxApp:
         if not self.wxApp:
             # Create a new base.wxApp.
             # Create a new base.wxApp.
-            self.wxApp = wx.PySimpleApp(redirect = False)
+            self.wxApp = wx.App(redirect = False)
 
 
         if ConfigVariableBool('wx-main-loop', True):
         if ConfigVariableBool('wx-main-loop', True):
             # Put wxPython in charge of the main loop.  It really
             # Put wxPython in charge of the main loop.  It really

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

+ 9 - 7
direct/src/showutil/TexMemWatcher.py

@@ -786,8 +786,9 @@ class TexMemWatcher(DirectObject):
             # Look for a single rectangular hole to hold this piece.
             # Look for a single rectangular hole to hold this piece.
             tp = self.findHole(tr.area, tr.w, tr.h)
             tp = self.findHole(tr.area, tr.w, tr.h)
             if tp:
             if tp:
-                texCmp = cmp(tr.w, tr.h)
-                holeCmp = cmp(tp.p[1] - tp.p[0], tp.p[3] - tp.p[2])
+                texCmp = (tr.w > tr.h) - (tr.w < tr.h)
+                holeCmp = ((tp.p[1] - tp.p[0]) > (tp.p[3] - tp.p[2])) \
+                        - ((tp.p[1] - tp.p[0]) < (tp.p[3] - tp.p[2]))
                 if texCmp != 0 and holeCmp != 0 and texCmp != holeCmp:
                 if texCmp != 0 and holeCmp != 0 and texCmp != holeCmp:
                     tp.rotated = True
                     tp.rotated = True
                 tr.placements = [tp]
                 tr.placements = [tp]
@@ -803,10 +804,11 @@ class TexMemWatcher(DirectObject):
             # in.
             # in.
             tpList = self.findHolePieces(tr.area)
             tpList = self.findHolePieces(tr.area)
             if tpList:
             if tpList:
-                texCmp = cmp(tr.w, tr.h)
+                texCmp = (tr.w > tr.h) - (tr.w < tr.h)
                 tr.placements = tpList
                 tr.placements = tpList
                 for tp in tpList:
                 for tp in tpList:
-                    holeCmp = cmp(tp.p[1] - tp.p[0], tp.p[3] - tp.p[2])
+                    holeCmp = ((tp.p[1] - tp.p[0]) > (tp.p[3] - tp.p[2])) \
+                            - ((tp.p[1] - tp.p[0]) < (tp.p[3] - tp.p[2]))
                     if texCmp != 0 and holeCmp != 0 and texCmp != holeCmp:
                     if texCmp != 0 and holeCmp != 0 and texCmp != holeCmp:
                         tp.rotated = True
                         tp.rotated = True
                     tp.setBitmasks(self.bitmasks)
                     tp.setBitmasks(self.bitmasks)
@@ -858,11 +860,11 @@ class TexMemWatcher(DirectObject):
             # we have to squish it?
             # we have to squish it?
             if tw < w:
             if tw < w:
                 # We'd have to make it taller.
                 # We'd have to make it taller.
-                nh = min(area / tw, th)
+                nh = min(area // tw, th)
                 th = nh
                 th = nh
             elif th < h:
             elif th < h:
                 # We'd have to make it narrower.
                 # We'd have to make it narrower.
-                nw = min(area / th, tw)
+                nw = min(area // th, tw)
                 tw = nw
                 tw = nw
             else:
             else:
                 # Hey, we don't have to squish it after all!  Just
                 # Hey, we don't have to squish it after all!  Just
@@ -912,7 +914,7 @@ class TexMemWatcher(DirectObject):
             tpArea = (r - l) * (t - b)
             tpArea = (r - l) * (t - b)
             if tpArea >= area:
             if tpArea >= area:
                 # we're done.
                 # we're done.
-                shorten = (tpArea - area) / (r - l)
+                shorten = (tpArea - area) // (r - l)
                 t -= shorten
                 t -= shorten
                 tp.p = (l, r, b, t)
                 tp.p = (l, r, b, t)
                 tp.area = (r - l) * (t - b)
                 tp.area = (r - l) * (t - b)

+ 0 - 23
direct/src/stdpy/threading.py

@@ -201,17 +201,6 @@ class Lock(core.Mutex):
     def __init__(self, name = "PythonLock"):
     def __init__(self, name = "PythonLock"):
         core.Mutex.__init__(self, name)
         core.Mutex.__init__(self, name)
 
 
-    def acquire(self, blocking = True):
-        if blocking:
-            core.Mutex.acquire(self)
-            return True
-        else:
-            return core.Mutex.tryAcquire(self)
-
-    __enter__ = acquire
-
-    def __exit__(self, t, v, tb):
-        self.release()
 
 
 class RLock(core.ReMutex):
 class RLock(core.ReMutex):
     """ This class provides a wrapper around Panda's ReMutex object.
     """ This class provides a wrapper around Panda's ReMutex object.
@@ -221,18 +210,6 @@ class RLock(core.ReMutex):
     def __init__(self, name = "PythonRLock"):
     def __init__(self, name = "PythonRLock"):
         core.ReMutex.__init__(self, name)
         core.ReMutex.__init__(self, name)
 
 
-    def acquire(self, blocking = True):
-        if blocking:
-            core.ReMutex.acquire(self)
-            return True
-        else:
-            return core.ReMutex.tryAcquire(self)
-
-    __enter__ = acquire
-
-    def __exit__(self, t, v, tb):
-        self.release()
-
 
 
 class Condition(core.ConditionVarFull):
 class Condition(core.ConditionVarFull):
     """ This class provides a wrapper around Panda's ConditionVarFull
     """ This class provides a wrapper around Panda's ConditionVarFull

+ 4 - 7
direct/src/task/Task.py

@@ -388,13 +388,10 @@ class TaskManager:
     def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
     def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
         if isinstance(funcOrTask, AsyncTask):
         if isinstance(funcOrTask, AsyncTask):
             task = funcOrTask
             task = funcOrTask
-        elif hasattr(funcOrTask, '__call__'):
-            task = PythonTask(funcOrTask)
-            if name is None:
-                name = getattr(funcOrTask, '__qualname__', None) or \
-                       getattr(funcOrTask, '__name__', None)
-        elif hasattr(funcOrTask, 'cr_await') or type(funcOrTask) == types.GeneratorType:
-            # It's a coroutine, or something emulating one.
+        elif hasattr(funcOrTask, '__call__') or \
+                hasattr(funcOrTask, 'cr_await') or \
+                type(funcOrTask) == types.GeneratorType:
+            # It's a function, coroutine, or something emulating a coroutine.
             task = PythonTask(funcOrTask)
             task = PythonTask(funcOrTask)
             if name is None:
             if name is None:
                 name = getattr(funcOrTask, '__qualname__', None) or \
                 name = getattr(funcOrTask, '__qualname__', None) or \

+ 8 - 4
direct/src/tkpanels/AnimPanel.py

@@ -9,13 +9,16 @@ __all__ = ['AnimPanel', 'ActorControl']
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
-import Pmw, sys
+import Pmw, sys, os
 from direct.task import Task
 from direct.task import Task
+from panda3d.core import Filename, getModelPath
 
 
 if sys.version_info >= (3, 0):
 if sys.version_info >= (3, 0):
     from tkinter.simpledialog import askfloat
     from tkinter.simpledialog import askfloat
+    from tkinter.filedialog import askopenfilename
 else:
 else:
     from tkSimpleDialog import askfloat
     from tkSimpleDialog import askfloat
+    from tkFileDialog import askopenfilename
 
 
 
 
 FRAMES = 0
 FRAMES = 0
@@ -273,7 +276,7 @@ class AnimPanel(AppShell):
             title = 'Load Animation',
             title = 'Load Animation',
             parent = self.component('hull')
             parent = self.component('hull')
             )
             )
-        if (animFilename == ''):
+        if not animFilename:
             # no file selected, canceled
             # no file selected, canceled
             return
             return
 
 
@@ -369,8 +372,9 @@ class AnimPanel(AppShell):
     def destroy(self):
     def destroy(self):
         # First clean up
         # First clean up
         taskMgr.remove(self.id + '_UpdateTask')
         taskMgr.remove(self.id + '_UpdateTask')
-        self.destroyCallBack()
-        self.destroyCallBack = None
+        if self.destroyCallBack is not None:
+            self.destroyCallBack()
+            self.destroyCallBack = None
         AppShell.destroy(self)
         AppShell.destroy(self)
 
 
 class ActorControl(Pmw.MegaWidget):
 class ActorControl(Pmw.MegaWidget):

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

+ 40 - 31
direct/src/tkpanels/ParticlePanel.py

@@ -96,22 +96,21 @@ class ParticlePanel(AppShell):
 
 
         ## MENUBAR ENTRIES ##
         ## MENUBAR ENTRIES ##
         # FILE MENU
         # FILE MENU
-        # Get a handle on the file menu so commands can be inserted
-        # before quit item
-        fileMenu = self.menuBar.component('File-menu')
-        # MRM: Need to add load and save effects methods
-        fileMenu.insert_command(
-            fileMenu.index('Quit'),
-            label = 'Load Params',
-            command = self.loadParticleEffectFromFile)
-        fileMenu.insert_command(
-            fileMenu.index('Quit'),
-            label = 'Save Params',
-            command = self.saveParticleEffectToFile)
-        fileMenu.insert_command(
-            fileMenu.index('Quit'),
-            label = 'Print Params',
-            command = lambda s = self: s.particles.printParams())
+        # Get a handle on the file menu, and delete the Quit item that AppShell
+        # created so we can add it back after adding the other items.
+        self.menuBar.deletemenuitems('File', 0, 0)
+        self.menuBar.addmenuitem('File', 'command',
+                                 label='Load Params',
+                                 command=self.loadParticleEffectFromFile)
+        self.menuBar.addmenuitem('File', 'command',
+                                 label='Save Params',
+                                 command=self.saveParticleEffectToFile)
+        self.menuBar.addmenuitem('File', 'command',
+                                 label='Print Params',
+                                 command=lambda s=self:s.particles.printParams())
+        self.menuBar.addmenuitem('File', 'command', 'Quit this application',
+                                 label='Quit',
+                                 command=self.quit)
 
 
         # PARTICLE MANAGER MENU
         # PARTICLE MANAGER MENU
         self.menuBar.addmenu('ParticleMgr', 'ParticleMgr Operations')
         self.menuBar.addmenu('ParticleMgr', 'ParticleMgr Operations')
@@ -215,23 +214,23 @@ class ParticlePanel(AppShell):
             ('System', 'Pool Size',
             ('System', 'Pool Size',
              'Max number of simultaneous particles',
              'Max number of simultaneous particles',
              self.setSystemPoolSize,
              self.setSystemPoolSize,
-             1.0, 1.0),
+             1.0, 2000000, 1.0),
             ('System', 'Birth Rate',
             ('System', 'Birth Rate',
              'Seconds between particle births',
              'Seconds between particle births',
              self.setSystemBirthRate,
              self.setSystemBirthRate,
-             0.0, None),
+             0.0, None, None),
             ('System', 'Litter Size',
             ('System', 'Litter Size',
              'Number of particle created at each birth',
              'Number of particle created at each birth',
              self.setSystemLitterSize,
              self.setSystemLitterSize,
-             1.0, 1.0),
+             1.0, 0x7fffffff, 1.0),
             ('System', 'Litter Spread',
             ('System', 'Litter Spread',
              'Variation in litter size',
              'Variation in litter size',
              self.setSystemLitterSpread,
              self.setSystemLitterSpread,
-             0.0, 1.0),
+             0.0, 0x7fffffff, 1.0),
             ('System', 'Lifespan',
             ('System', 'Lifespan',
              'Age in seconds at which the system (vs. particles) should die',
              'Age in seconds at which the system (vs. particles) should die',
              self.setSystemLifespan,
              self.setSystemLifespan,
-             0.0, None)
+             0.0, None, None)
             )
             )
         self.createFloaters(systemPage, systemFloaterDefs)
         self.createFloaters(systemPage, systemFloaterDefs)
 
 
@@ -270,27 +269,27 @@ class ParticlePanel(AppShell):
             ('Factory', 'Life Span',
             ('Factory', 'Life Span',
              'Average particle lifespan in seconds',
              'Average particle lifespan in seconds',
              self.setFactoryLifeSpan,
              self.setFactoryLifeSpan,
-             0.0, None),
+             0.0, None, None),
             ('Factory', 'Life Span Spread',
             ('Factory', 'Life Span Spread',
              'Variation in lifespan',
              'Variation in lifespan',
              self.setFactoryLifeSpanSpread,
              self.setFactoryLifeSpanSpread,
-             0.0, None),
+             0.0, None, None),
             ('Factory', 'Mass',
             ('Factory', 'Mass',
              'Average particle mass',
              'Average particle mass',
              self.setFactoryParticleMass,
              self.setFactoryParticleMass,
-             0.001, None),
+             0.001, None, None),
             ('Factory', 'Mass Spread',
             ('Factory', 'Mass Spread',
              'Variation in particle mass',
              'Variation in particle mass',
              self.setFactoryParticleMassSpread,
              self.setFactoryParticleMassSpread,
-             0.0, None),
+             0.0, None, None),
             ('Factory', 'Terminal Velocity',
             ('Factory', 'Terminal Velocity',
              'Cap on average particle velocity',
              'Cap on average particle velocity',
              self.setFactoryTerminalVelocity,
              self.setFactoryTerminalVelocity,
-             0.0, None),
+             0.0, None, None),
             ('Factory', 'Terminal Vel. Spread',
             ('Factory', 'Terminal Vel. Spread',
              'Variation in terminal velocity',
              'Variation in terminal velocity',
              self.setFactoryTerminalVelocitySpread,
              self.setFactoryTerminalVelocitySpread,
-             0.0, None),
+             0.0, None, None),
         )
         )
         self.createFloaters(factoryPage, factoryWidgets)
         self.createFloaters(factoryPage, factoryWidgets)
 
 
@@ -967,19 +966,29 @@ class ParticlePanel(AppShell):
 
 
     def createFloaters(self, parent, widgetDefinitions):
     def createFloaters(self, parent, widgetDefinitions):
         widgets = []
         widgets = []
-        for category, label, balloonHelp, command, min, resolution in widgetDefinitions:
+        for category, label, balloonHelp, command, min, max, resolution in widgetDefinitions:
             widgets.append(
             widgets.append(
                 self.createFloater(parent, category, label, balloonHelp,
                 self.createFloater(parent, category, label, balloonHelp,
-                                   command, min, resolution)
+                                   command, min, max, resolution)
                 )
                 )
         return widgets
         return widgets
 
 
     def createFloater(self, parent, category, text, balloonHelp,
     def createFloater(self, parent, category, text, balloonHelp,
-                      command = None, min = 0.0, resolution = None,
-                      numDigits = 3, **kw):
+                      command = None, min = 0.0, max = None, resolution = None,
+                      numDigits = None, **kw):
         kw['text'] = text
         kw['text'] = text
         kw['min'] = min
         kw['min'] = min
+        if max is not None:
+            kw['max'] = max
         kw['resolution'] = resolution
         kw['resolution'] = resolution
+        if numDigits is None:
+            # If this is apparently an integer setting, show no decimals.
+            if resolution is not None and int(resolution) == resolution and \
+                (min is None or int(min) == min) and \
+                (max is None or int(max) == max):
+                numDigits = 0
+            else:
+                numDigits = 3
         kw['numDigits'] = numDigits
         kw['numDigits'] = numDigits
         widget = Floater.Floater(parent, **kw)
         widget = Floater.Floater(parent, **kw)
         # Do this after the widget so command isn't called on creation
         # Do this after the widget so command isn't called on creation

+ 33 - 17
direct/src/tkwidgets/Valuator.py

@@ -7,6 +7,7 @@ from direct.showbase.TkGlobal import *
 from . import WidgetPropertiesDialog
 from . import WidgetPropertiesDialog
 import Pmw
 import Pmw
 from direct.directtools.DirectUtil import getTkColorString
 from direct.directtools.DirectUtil import getTkColorString
+from panda3d.core import Vec4
 
 
 if sys.version_info >= (3, 0):
 if sys.version_info >= (3, 0):
     from tkinter.colorchooser import askcolor
     from tkinter.colorchooser import askcolor
@@ -655,25 +656,35 @@ def rgbPanel(nodePath, callback = None, style = 'mini'):
     pButton.pack(expand = 1, fill = BOTH)
     pButton.pack(expand = 1, fill = BOTH)
 
 
     # Update menu
     # Update menu
-    menu = vgp.component('menubar').component('Valuator Group-menu')
+    menubar = vgp.component('menubar')
+    menubar.deletemenuitems('Valuator Group', 1, 1)
+
     # Some helper functions
     # Some helper functions
     # Clear color
     # Clear color
-    menu.insert_command(index = 1, label = 'Clear Color',
-                        command = lambda: nodePath.clearColor())
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Clear Color', command=lambda: nodePath.clearColor())
     # Set Clear Transparency
     # Set Clear Transparency
-    menu.insert_command(index = 2, label = 'Set Transparency',
-                        command = lambda: nodePath.setTransparency(1))
-    menu.insert_command(
-        index = 3, label = 'Clear Transparency',
-        command = lambda: nodePath.clearTransparency())
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Set Transparency', command=lambda: nodePath.setTransparency(1))
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Clear Transparency', command=lambda: nodePath.clearTransparency())
 
 
 
 
     # System color picker
     # System color picker
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Print to log', command=printToLog)
 
 
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+    menubar.addmenuitem(
+        'Valuator Group', 'command', 'Dismiss Valuator Group panel',
+        label='Dismiss', command=vgp.destroy)
 
 
     def setNodePathColor(color):
     def setNodePathColor(color):
         nodePath.setColor(color[0]/255.0, color[1]/255.0,
         nodePath.setColor(color[0]/255.0, color[1]/255.0,
@@ -723,18 +734,23 @@ def lightRGBPanel(light, style = 'mini'):
     # Update menu button
     # Update menu button
     vgp.component('menubar').component('Valuator Group-button')['text'] = (
     vgp.component('menubar').component('Valuator Group-button')['text'] = (
         'Light Control Panel')
         'Light Control Panel')
+
     # Add a print button which will also serve as a color tile
     # Add a print button which will also serve as a color tile
     pButton = Button(vgp.interior(), text = 'Print to Log',
     pButton = Button(vgp.interior(), text = 'Print to Log',
                      bg = getTkColorString(initColor),
                      bg = getTkColorString(initColor),
                      command = printToLog)
                      command = printToLog)
     pButton.pack(expand = 1, fill = BOTH)
     pButton.pack(expand = 1, fill = BOTH)
+
     # Update menu
     # Update menu
-    menu = vgp.component('menubar').component('Valuator Group-menu')
+    menubar = vgp.component('menubar')
     # System color picker
     # System color picker
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Print to log', command=printToLog)
+
     def setLightColor(color):
     def setLightColor(color):
         light.setColor(Vec4(color[0]/255.0, color[1]/255.0,
         light.setColor(Vec4(color[0]/255.0, color[1]/255.0,
                             color[2]/255.0, color[3]/255.0))
                             color[2]/255.0, color[3]/255.0))

+ 4 - 4
direct/src/wxwidgets/WxPandaShell.py

@@ -202,15 +202,15 @@ class WxPandaShell(WxAppShell):
         """A step in the WX event loop. You can either call this yourself or use as task."""
         """A step in the WX event loop. You can either call this yourself or use as task."""
         while self.evtLoop.Pending():
         while self.evtLoop.Pending():
           self.evtLoop.Dispatch()
           self.evtLoop.Dispatch()
-        self.wxApp.ProcessIdle()
+        self.evtLoop.ProcessIdle()
         if task != None: return task.cont
         if task != None: return task.cont
 
 
     def appInit(self):
     def appInit(self):
         """Overridden from WxAppShell.py."""
         """Overridden from WxAppShell.py."""
         # Create a new event loop (to overide default wxEventLoop)
         # Create a new event loop (to overide default wxEventLoop)
-        self.evtLoop = wx.EventLoop()
-        self.oldLoop = wx.EventLoop.GetActive()
-        wx.EventLoop.SetActive(self.evtLoop)
+        self.evtLoop = wx.GUIEventLoop()
+        self.oldLoop = wx.GUIEventLoop.GetActive()
+        wx.GUIEventLoop.SetActive(self.evtLoop)
         taskMgr.add(self.wxStep, "evtLoopTask")
         taskMgr.add(self.wxStep, "evtLoopTask")
 
 
     def onViewChange(self, evt, viewIdx):
     def onViewChange(self, evt, viewIdx):

+ 0 - 108
dmodels/src/level_editor/donaldsDockColors.txt

@@ -1,108 +0,0 @@
-corniceColor: Vec4(0.11, 0.80, 0.22, 1.0)
-corniceColor: Vec4(0.21, 0.73, 0.31, 1.0)
-corniceColor: Vec4(0.22, 0.96, 0.27, 1.0)
-corniceColor: Vec4(0.57, 0.39, 0.34, 1.0)
-corniceColor: Vec4(0.59, 1.00, 0.92, 1.0)
-corniceColor: Vec4(0.66, 1.00, 0.86, 1.0)
-corniceColor: Vec4(0.73, 0.51, 0.51, 1.0)
-corniceColor: Vec4(0.75, 0.45, 0.45, 1.0)
-corniceColor: Vec4(0.76, 0.57, 0.29, 1.0)
-corniceColor: Vec4(0.76, 0.73, 0.64, 1.0)
-corniceColor: Vec4(0.82, 0.91, 0.44, 1.0)
-corniceColor: Vec4(0.90, 0.56, 0.34, 1.0)
-corniceColor: Vec4(0.91, 0.54, 0.44, 1.0)
-corniceColor: Vec4(0.91, 0.63, 0.44, 1.0)
-corniceColor: Vec4(0.94, 0.35, 0.35, 1.0)
-corniceColor: Vec4(0.94, 0.72, 0.66, 1.0)
-corniceColor: Vec4(1.00, 0.42, 0.27, 1.0)
-corniceColor: Vec4(1.00, 0.50, 0.38, 1.0)
-corniceColor: Vec4(1.00, 0.69, 0.49, 1.0)
-corniceColor: Vec4(1.00, 0.71, 0.27, 1.0)
-corniceColor: Vec4(1.00, 0.84, 0.59, 1.0)
-corniceColor: Vec4(1.00, 0.88, 0.80, 1.0)
-corniceColor: Vec4(1.00, 0.92, 0.59, 1.0)
-corniceColor: Vec4(1.00, 1.00, 0.59, 1.0)
-
-doorColor: Vec4(0.45, 0.53, 0.45, 1.0)
-doorColor: Vec4(0.52, 0.61, 0.52, 1.0)
-doorColor: Vec4(0.60, 0.67, 0.54, 1.0)
-doorColor: Vec4(0.63, 0.48, 0.34, 1.0)
-doorColor: Vec4(0.79, 0.47, 0.47, 1.0)
-doorColor: Vec4(0.86, 0.48, 0.23, 1.0)
-doorColor: Vec4(0.88, 0.43, 0.43, 1.0)
-doorColor: Vec4(0.91, 0.34, 0.34, 1.0)
-doorColor: Vec4(0.94, 0.70, 0.49, 1.0)
-doorColor: Vec4(0.97, 0.55, 0.40, 1.0)
-doorColor: Vec4(1.00, 0.59, 0.59, 1.0)
-
-wallColor: Vec4(0.17, 0.44, 0.28, 1.0)
-wallColor: Vec4(0.17, 0.45, 0.23, 1.0)
-wallColor: Vec4(0.36, 0.45, 0.22, 1.0)
-wallColor: Vec4(0.38, 0.31, 0.19, 1.0)
-wallColor: Vec4(0.38, 0.68, 0.46, 1.0)
-wallColor: Vec4(0.42, 0.16, 0.16, 1.0)
-wallColor: Vec4(0.42, 0.25, 0.25, 1.0)
-wallColor: Vec4(0.44, 0.67, 0.45, 1.0)
-wallColor: Vec4(0.45, 0.31, 0.17, 1.0)
-wallColor: Vec4(0.48, 0.93, 0.74, 1.0)
-wallColor: Vec4(0.52, 0.96, 0.79, 1.0)
-wallColor: Vec4(0.57, 0.70, 0.35, 1.0)
-wallColor: Vec4(0.63, 0.47, 0.24, 1.0)
-wallColor: Vec4(0.63, 0.60, 0.40, 1.0)
-wallColor: Vec4(0.64, 0.24, 0.32, 1.0)
-wallColor: Vec4(0.71, 0.49, 0.35, 1.0)
-wallColor: Vec4(0.75, 0.45, 0.45, 1.0)
-wallColor: Vec4(0.81, 0.48, 0.48, 1.0)
-wallColor: Vec4(0.87, 0.61, 0.61, 1.0)
-wallColor: Vec4(0.87, 0.69, 0.42, 1.0)
-wallColor: Vec4(0.91, 0.54, 0.54, 1.0)
-wallColor: Vec4(0.92, 0.63, 0.42, 1.0)
-wallColor: Vec4(0.93, 0.15, 0.15, 1.0)
-wallColor: Vec4(1.00, 0.00, 0.00, 1.0)
-
-windowColor: Vec4(0.00, 0.53, 0.32, 1.0)
-windowColor: Vec4(0.00, 0.61, 0.36, 1.0)
-windowColor: Vec4(0.07, 0.47, 0.31, 1.0)
-windowColor: Vec4(0.08, 0.47, 0.31, 1.0)
-windowColor: Vec4(0.17, 0.64, 0.45, 1.0)
-windowColor: Vec4(0.50, 0.38, 0.22, 1.0)
-windowColor: Vec4(0.64, 0.93, 0.45, 1.0)
-windowColor: Vec4(0.69, 0.44, 0.15, 1.0)
-windowColor: Vec4(0.73, 0.46, 0.37, 1.0)
-windowColor: Vec4(0.73, 0.63, 0.37, 1.0)
-windowColor: Vec4(0.73, 0.63, 0.45, 1.0)
-windowColor: Vec4(0.74, 0.55, 0.32, 1.0)
-windowColor: Vec4(0.78, 0.49, 0.29, 1.0)
-windowColor: Vec4(0.79, 0.47, 0.47, 1.0)
-windowColor: Vec4(0.82, 0.82, 0.40, 1.0)
-windowColor: Vec4(0.87, 0.55, 0.33, 1.0)
-windowColor: Vec4(0.87, 0.65, 0.33, 1.0)
-windowColor: Vec4(0.88, 0.43, 0.43, 1.0)
-windowColor: Vec4(0.91, 0.34, 0.45, 1.0)
-windowColor: Vec4(1.00, 0.50, 0.38, 1.0)
-windowColor: Vec4(1.00, 0.63, 0.38, 1.0)
-
-propColor: Vec4(0.17, 0.44, 0.28, 1.0)
-propColor: Vec4(0.17, 0.45, 0.23, 1.0)
-propColor: Vec4(0.36, 0.45, 0.22, 1.0)
-propColor: Vec4(0.38, 0.31, 0.19, 1.0)
-propColor: Vec4(0.38, 0.68, 0.46, 1.0)
-propColor: Vec4(0.42, 0.16, 0.16, 1.0)
-propColor: Vec4(0.42, 0.25, 0.25, 1.0)
-propColor: Vec4(0.44, 0.67, 0.45, 1.0)
-propColor: Vec4(0.45, 0.31, 0.17, 1.0)
-propColor: Vec4(0.48, 0.93, 0.74, 1.0)
-propColor: Vec4(0.52, 0.96, 0.79, 1.0)
-propColor: Vec4(0.57, 0.70, 0.35, 1.0)
-propColor: Vec4(0.63, 0.47, 0.24, 1.0)
-propColor: Vec4(0.63, 0.60, 0.40, 1.0)
-propColor: Vec4(0.64, 0.24, 0.32, 1.0)
-propColor: Vec4(0.71, 0.49, 0.35, 1.0)
-propColor: Vec4(0.75, 0.45, 0.45, 1.0)
-propColor: Vec4(0.81, 0.48, 0.48, 1.0)
-propColor: Vec4(0.87, 0.61, 0.61, 1.0)
-propColor: Vec4(0.87, 0.69, 0.42, 1.0)
-propColor: Vec4(0.91, 0.54, 0.54, 1.0)
-propColor: Vec4(0.92, 0.63, 0.42, 1.0)
-propColor: Vec4(0.93, 0.15, 0.15, 1.0)
-propColor: Vec4(1.00, 0.00, 0.00, 1.0)

+ 0 - 64
dmodels/src/level_editor/donaldsDockStyles.txt

@@ -1,64 +0,0 @@
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.417323, 0.15711, 0.15711, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.874016, 0.654655, 0.329041, 1.0)
-corniceTexture: cornice_marble_ur
-corniceColor: Vec4(0.76378, 0.572086, 0.287541, 1.0)
-
-wallTexture: wall_sm_wood_ur
-wallColor: Vec4(0.874016, 0.610097, 0.610097, 1.0)
-windowTexture: window_sm_shuttered_ur
-windowColor: Vec4(0.874016, 0.548402, 0.329041, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_sm_wood_ur
-wallColor: Vec4(0.913386, 0.540868, 0.540868, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.0778138, 0.472441, 0.314961, 1.0)
-corniceTexture: cornice_horizontal_ur
-corniceColor: Vec4(1.0, 0.501961, 0.376471, 1.0)
-
-wallTexture: wall_sm_wood_ur
-wallColor: Vec4(0.913386, 0.540868, 0.540868, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.0778138, 0.472441, 0.314961, 1.0)
-corniceTexture: cornice_shingles_ur
-corniceColor: Vec4(0.732283, 0.511163, 0.511163, 1.0)
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.384314, 0.305635, 0.187618, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.779528, 0.489115, 0.293469, 1.0)
-corniceTexture: cornice_dental_ur
-corniceColor: Vec4(0.574803, 0.38771, 0.340374, 1.0)
-
-wallTexture: wall_bricks_dr
-wallColor: Vec4(0.629921, 0.471823, 0.237147, 1.0)
-windowTexture: window_sm_shuttered_ur
-windowColor: Vec4(1.0, 0.627451, 0.376471, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_board_ur
-wallColor: Vec4(0.929134, 0.153034, 0.153034, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.0, 0.532747, 0.317894, 1.0)
-corniceTexture: cornice_shingles_ur
-corniceColor: Vec4(0.944882, 0.715146, 0.659565, 1.0)
-
-wallTexture: wall_lg_brick_ur
-wallColor: Vec4(0.166003, 0.440945, 0.276671, 1.0)
-windowTexture: window_md_curtains_ur
-windowColor: Vec4(0.17258, 0.637795, 0.450208, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_board_ur
-wallColor: Vec4(0.929134, 0.153034, 0.153034, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.0, 0.532747, 0.317894, 1.0)
-corniceTexture: None
-corniceColor: None
-
-

BIN
dmodels/src/level_editor/donalds_dock_layout.flt


+ 0 - 61
dmodels/src/level_editor/minniesMelodyLandColors.txt

@@ -1,61 +0,0 @@
-corniceColor: Vec4(0.43, 0.32, 0.85, 1.0)
-corniceColor: Vec4(0.67, 0.40, 0.99, 1.0)
-corniceColor: Vec4(0.68, 0.22, 0.80, 1.0)
-corniceColor: Vec4(0.80, 0.22, 0.68, 1.0)
-corniceColor: Vec4(0.81, 0.30, 0.51, 1.0)
-corniceColor: Vec4(1.00, 1.00, 0.58, 1.0)
-
-doorColor: Vec4(0.42, 0.42, 0.85, 1.0)
-doorColor: Vec4(0.45, 0.22, 0.60, 1.0)
-doorColor: Vec4(0.49, 0.79, 1.00, 1.0)
-doorColor: Vec4(0.56, 0.80, 0.65, 1.0)
-doorColor: Vec4(0.59, 0.49, 1.00, 1.0)
-doorColor: Vec4(0.64, 0.45, 0.92, 1.0)
-doorColor: Vec4(0.65, 0.40, 0.82, 1.0)
-doorColor: Vec4(0.69, 0.33, 0.69, 1.0)
-doorColor: Vec4(0.80, 0.56, 0.65, 1.0)
-doorColor: Vec4(0.87, 0.80, 0.47, 1.0)
-doorColor: Vec4(0.97, 0.87, 0.47, 1.0)
-doorColor: Vec4(1.00, 0.42, 0.85, 1.0)
-doorColor: Vec4(1.00, 0.79, 0.49, 1.0)
-
-wallColor: Vec4(0.49, 0.69, 1.00, 1.0)
-wallColor: Vec4(0.59, 0.75, 1.00, 1.0)
-wallColor: Vec4(0.70, 0.76, 1.00, 1.0)
-wallColor: Vec4(0.80, 0.47, 0.80, 1.0)
-wallColor: Vec4(0.80, 0.80, 0.47, 1.0)
-wallColor: Vec4(0.91, 0.91, 0.54, 1.0)
-wallColor: Vec4(0.96, 0.96, 0.47, 1.0)
-wallColor: Vec4(0.96, 0.96, 0.73, 1.0)
-wallColor: Vec4(0.97, 0.36, 0.36, 1.0)
-wallColor: Vec4(0.97, 0.47, 0.67, 1.0)
-wallColor: Vec4(1.00, 0.70, 0.70, 1.0)
-wallColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-windowColor: Vec4(0.42, 0.42, 0.85, 1.0)
-windowColor: Vec4(0.45, 0.22, 0.60, 1.0)
-windowColor: Vec4(0.49, 0.79, 1.00, 1.0)
-windowColor: Vec4(0.56, 0.80, 0.65, 1.0)
-windowColor: Vec4(0.59, 0.49, 1.00, 1.0)
-windowColor: Vec4(0.64, 0.45, 0.92, 1.0)
-windowColor: Vec4(0.65, 0.40, 0.82, 1.0)
-windowColor: Vec4(0.69, 0.33, 0.69, 1.0)
-windowColor: Vec4(0.80, 0.45, 0.71, 1.0)
-windowColor: Vec4(0.80, 0.56, 0.65, 1.0)
-windowColor: Vec4(0.97, 0.87, 0.47, 1.0)
-windowColor: Vec4(1.00, 0.79, 0.49, 1.0)
-
-propColor: Vec4(0.49, 0.69, 1.00, 1.0)
-propColor: Vec4(0.59, 0.75, 1.00, 1.0)
-propColor: Vec4(0.70, 0.76, 1.00, 1.0)
-propColor: Vec4(0.80, 0.47, 0.80, 1.0)
-propColor: Vec4(0.80, 0.80, 0.47, 1.0)
-propColor: Vec4(0.91, 0.91, 0.54, 1.0)
-propColor: Vec4(0.96, 0.96, 0.47, 1.0)
-propColor: Vec4(0.96, 0.96, 0.73, 1.0)
-propColor: Vec4(0.97, 0.36, 0.36, 1.0)
-propColor: Vec4(0.97, 0.47, 0.67, 1.0)
-propColor: Vec4(1.00, 0.70, 0.70, 1.0)
-propColor: Vec4(1.00, 1.00, 1.00, 1.0)
-propColor: Vec4(0.92, 0.92, 0.92, 1.0)
-wallColor: Vec4(0.97, 0.58, 0.55, 1.0)

+ 0 - 79
dmodels/src/level_editor/minniesMelodyLandStyles.txt

@@ -1,79 +0,0 @@
-wallTexture: wall_md_bricks_ur
-wallColor: Vec4(0.59, 0.75, 1.00, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.45, 0.22, 0.60, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.91, 0.91, 0.54, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.65, 0.40, 0.82, 1.0)
-corniceTexture: cornice_curved_ur
-corniceColor: Vec4(0.68, 0.22, 0.80, 1.0)
-
-wallTexture: wall_sm_brick_pink_ur
-wallColor: Vec4(1.00, 1.00, 1.00, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.49, 0.79, 1.00, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_lg_rock_ur
-wallColor: Vec4(0.80, 0.47, 0.80, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.80, 0.56, 0.65, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_lg_rock_ur
-wallColor: Vec4(0.80, 0.80, 0.47, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.56, 0.80, 0.65, 1.0)
-corniceTexture: cornice_marble_ur
-corniceColor: Vec4(0.80, 0.22, 0.68, 1.0)
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.49, 0.69, 1.00, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.59, 0.49, 1.00, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_bricks_ur
-wallColor: Vec4(1.00, 0.70, 0.70, 1.0)
-windowTexture: window_md_curved_ur
-windowColor: Vec4(1.00, 0.79, 0.49, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.96, 0.96, 0.47, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.69, 0.33, 0.69, 1.0)
-corniceTexture: cornice_brick_ur
-corniceColor: Vec4(0.81, 0.30, 0.51, 1.0)
-
-wallTexture: wall_md_bricks_ur
-wallColor: Vec4(0.70, 0.76, 1.00, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.42, 0.42, 0.85, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_lg_brick_ur
-wallColor: Vec4(0.97, 0.47, 0.67, 1.0)
-windowTexture: window_md_curtains_ur
-windowColor: Vec4(0.64, 0.45, 0.92, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.97, 0.36, 0.36, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.97, 0.87, 0.47, 1.0)
-corniceTexture: cornice_curved_ur
-corniceColor: Vec4(0.43, 0.32, 0.85, 1.0)
-
-
-

BIN
dmodels/src/level_editor/minnies_melody_land_layout.flt


+ 0 - 65
dmodels/src/level_editor/theBurrrghColors.txt

@@ -1,65 +0,0 @@
-corniceColor: Vec4(0.00, 0.34, 0.58, 1.0)
-corniceColor: Vec4(0.34, 0.90, 0.87, 1.0)
-corniceColor: Vec4(0.41, 0.58, 0.85, 1.0)
-corniceColor: Vec4(0.42, 0.98, 0.74, 1.0)
-corniceColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-doorColor: Vec4(0.00, 0.87, 0.69, 1.0)
-doorColor: Vec4(0.22, 0.83, 0.83, 1.0)
-doorColor: Vec4(0.23, 0.71, 0.84, 1.0)
-doorColor: Vec4(0.23, 0.85, 0.60, 1.0)
-doorColor: Vec4(0.24, 0.76, 0.89, 1.0)
-doorColor: Vec4(0.27, 1.00, 0.85, 1.0)
-doorColor: Vec4(0.38, 0.63, 1.00, 1.0)
-doorColor: Vec4(0.49, 0.69, 1.00, 1.0)
-doorColor: Vec4(0.49, 0.89, 1.00, 1.0)
-doorColor: Vec4(0.50, 0.64, 0.85, 1.0)
-doorColor: Vec4(0.59, 1.00, 0.92, 1.0)
-doorColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-wallColor: Vec4(0.00, 0.87, 0.69, 1.0)
-wallColor: Vec4(0.22, 0.83, 0.83, 1.0)
-wallColor: Vec4(0.23, 0.85, 0.60, 1.0)
-wallColor: Vec4(0.24, 0.76, 0.89, 1.0)
-wallColor: Vec4(0.25, 0.64, 0.91, 1.0)
-wallColor: Vec4(0.27, 1.00, 0.85, 1.0)
-wallColor: Vec4(0.38, 0.63, 1.00, 1.0)
-wallColor: Vec4(0.49, 0.69, 1.00, 1.0)
-wallColor: Vec4(0.49, 0.89, 1.00, 1.0)
-wallColor: Vec4(0.50, 0.64, 0.85, 1.0)
-wallColor: Vec4(0.59, 1.00, 0.92, 1.0)
-wallColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-windowColor: Vec4(0.00, 0.69, 0.69, 1.0)
-windowColor: Vec4(0.00, 0.87, 0.69, 1.0)
-windowColor: Vec4(0.14, 0.42, 0.85, 1.0)
-windowColor: Vec4(0.16, 0.67, 1.00, 1.0)
-windowColor: Vec4(0.20, 0.53, 0.46, 1.0)
-windowColor: Vec4(0.22, 0.83, 0.83, 1.0)
-windowColor: Vec4(0.23, 0.71, 0.84, 1.0)
-windowColor: Vec4(0.23, 0.85, 0.60, 1.0)
-windowColor: Vec4(0.24, 0.76, 0.89, 1.0)
-windowColor: Vec4(0.27, 1.00, 0.85, 1.0)
-windowColor: Vec4(0.38, 0.63, 1.00, 1.0)
-windowColor: Vec4(0.38, 0.87, 1.00, 1.0)
-windowColor: Vec4(0.49, 0.69, 1.00, 1.0)
-windowColor: Vec4(0.49, 0.89, 1.00, 1.0)
-windowColor: Vec4(0.49, 1.00, 1.00, 1.0)
-windowColor: Vec4(0.50, 0.64, 0.85, 1.0)
-windowColor: Vec4(0.59, 0.85, 0.69, 1.0)
-windowColor: Vec4(0.59, 0.92, 0.61, 1.0)
-windowColor: Vec4(0.59, 1.00, 0.92, 1.0)
-windowColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-propColor: Vec4(0.00, 0.87, 0.69, 1.0)
-propColor: Vec4(0.22, 0.83, 0.83, 1.0)
-propColor: Vec4(0.23, 0.85, 0.60, 1.0)
-propColor: Vec4(0.24, 0.76, 0.89, 1.0)
-propColor: Vec4(0.25, 0.64, 0.91, 1.0)
-propColor: Vec4(0.27, 1.00, 0.85, 1.0)
-propColor: Vec4(0.38, 0.63, 1.00, 1.0)
-propColor: Vec4(0.49, 0.69, 1.00, 1.0)
-propColor: Vec4(0.49, 0.89, 1.00, 1.0)
-propColor: Vec4(0.50, 0.64, 0.85, 1.0)
-propColor: Vec4(0.59, 1.00, 0.92, 1.0)
-propColor: Vec4(1.00, 1.00, 1.00, 1.0)

+ 0 - 90
dmodels/src/level_editor/theBurrrghStyles.txt

@@ -1,90 +0,0 @@
-wallTexture: wall_lg_brick_ur
-wallColor: Vec4(0.25, 0.64, 0.91, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.23, 0.71, 0.84, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.00, 0.87, 0.69, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.23, 0.71, 0.84, 1.0)
-corniceTexture: cornice_curved_ur
-corniceColor: Vec4(0.00, 0.34, 0.58, 1.0)
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.49, 0.69, 1.00, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.20, 0.53, 0.46, 1.0)
-corniceTexture: cornice_ice_ur
-corniceColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-wallTexture: wall_sm_brick_blue_ur
-wallColor: Vec4(1.00, 1.00, 1.00, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.49, 1.00, 1.00, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.27, 1.00, 0.85, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.49, 1.00, 1.00, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.49, 0.89, 1.00, 1.0)
-windowTexture: window_sm_pointed_ur
-windowColor: Vec4(0.00, 0.69, 0.69, 1.0)
-corniceTexture: cornice_ice_ur
-corniceColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-wallTexture: wall_md_bricks_ur
-wallColor: Vec4(0.50, 0.64, 0.85, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.14, 0.42, 0.85, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.38, 0.63, 1.00, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.14, 0.42, 0.85, 1.0)
-corniceTexture: cornice_dental_ur
-corniceColor: Vec4(0.41, 0.58, 0.85, 1.0)
-
-wallTexture: wall_bricks_ur
-wallColor: Vec4(0.23, 0.85, 0.60, 1.0)
-windowTexture: window_sm_square_ur
-windowColor: Vec4(0.59, 0.85, 0.69, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_bricks_ur
-wallColor: Vec4(0.59, 1.00, 0.92, 1.0)
-windowTexture: window_md_curved_ur
-windowColor: Vec4(0.16, 0.67, 1.00, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(0.22, 0.83, 0.83, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.38, 0.87, 1.00, 1.0)
-corniceTexture: cornice_ice_ur
-corniceColor: Vec4(1.00, 1.00, 1.00, 1.0)
-
-wallTexture: wall_sm_cement_blue_ur
-wallColor: Vec4(1.00, 1.00, 1.00, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.59, 0.92, 0.61, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_bricks_ur
-wallColor: Vec4(0.24, 0.76, 0.89, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(0.59, 0.92, 0.61, 1.0)
-corniceTexture: None
-corniceColor: None

BIN
dmodels/src/level_editor/the_burrrgh_layout.flt


+ 0 - 106
dmodels/src/level_editor/toontownCentralColors.txt

@@ -1,106 +0,0 @@
-corniceColor: Vec4(0.11, 0.80, 0.22, 1.0)
-corniceColor: Vec4(0.21, 0.73, 0.31, 1.0)
-corniceColor: Vec4(0.22, 0.96, 0.27, 1.0)
-corniceColor: Vec4(0.27, 0.77, 0.42, 1.0)
-corniceColor: Vec4(0.32, 0.54, 0.36, 1.0)
-corniceColor: Vec4(0.44, 0.91, 0.63, 1.0)
-corniceColor: Vec4(0.76, 0.73, 0.64, 1.0)
-corniceColor: Vec4(0.79, 0.36, 0.19, 1.0)
-corniceColor: Vec4(0.82, 0.91, 0.44, 1.0)
-corniceColor: Vec4(0.90, 0.56, 0.34, 1.0)
-corniceColor: Vec4(0.91, 0.54, 0.44, 1.0)
-corniceColor: Vec4(0.91, 0.63, 0.44, 1.0)
-corniceColor: Vec4(0.94, 0.35, 0.35, 1.0)
-corniceColor: Vec4(0.94, 0.71, 0.66, 1.0)
-corniceColor: Vec4(0.98, 0.58, 0.42, 1.0)
-corniceColor: Vec4(1.00, 0.42, 0.27, 1.0)
-corniceColor: Vec4(1.00, 0.56, 0.21, 1.0)
-corniceColor: Vec4(1.00, 0.69, 0.49, 1.0)
-corniceColor: Vec4(1.00, 0.71, 0.27, 1.0)
-corniceColor: Vec4(1.00, 0.75, 0.38, 1.0)
-corniceColor: Vec4(1.00, 0.84, 0.59, 1.0)
-corniceColor: Vec4(1.00, 0.88, 0.80, 1.0)
-corniceColor: Vec4(1.00, 0.92, 0.59, 1.0)
-corniceColor: Vec4(1.00, 1.00, 0.59, 1.0)
-
-doorColor: Vec4(0.75, 0.79, 0.55, 1.0)
-doorColor: Vec4(0.81, 0.61, 0.48, 1.0)
-doorColor: Vec4(1.00, 0.59, 0.59, 1.0)
-doorColor: Vec4(1.00, 0.63, 0.38, 1.0)
-doorColor: Vec4(1.00, 0.87, 0.38, 1.0)
-doorColor: Vec4(1.00, 0.88, 0.56, 1.0)
-doorColor: Vec4(1.00, 0.88, 0.56, 1.0)
-
-wallColor: Vec4(0.11, 0.80, 0.22, 1.0)
-wallColor: Vec4(0.21, 0.73, 0.31, 1.0)
-wallColor: Vec4(0.22, 0.96, 0.27, 1.0)
-wallColor: Vec4(0.24, 0.79, 0.32, 1.0)
-wallColor: Vec4(0.25, 0.65, 0.29, 1.0)
-wallColor: Vec4(0.49, 0.96, 0.42, 1.0)
-wallColor: Vec4(0.76, 0.73, 0.64, 1.0)
-wallColor: Vec4(0.82, 0.91, 0.44, 1.0)
-wallColor: Vec4(0.90, 0.56, 0.34, 1.0)
-wallColor: Vec4(0.91, 0.54, 0.44, 1.0)
-wallColor: Vec4(0.91, 0.63, 0.44, 1.0)
-wallColor: Vec4(0.94, 0.35, 0.35, 1.0)
-wallColor: Vec4(0.95, 0.89, 0.17, 1.0)
-wallColor: Vec4(0.99, 0.67, 0.25, 1.0)
-wallColor: Vec4(0.99, 0.69, 0.49, 1.0)
-wallColor: Vec4(0.99, 0.89, 0.49, 1.0)
-wallColor: Vec4(1.00, 0.42, 0.27, 1.0)
-wallColor: Vec4(1.00, 0.67, 0.59, 1.0)
-wallColor: Vec4(1.00, 0.69, 0.49, 1.0)
-wallColor: Vec4(1.00, 0.71, 0.27, 1.0)
-wallColor: Vec4(1.00, 0.84, 0.59, 1.0)
-wallColor: Vec4(1.00, 0.88, 0.80, 1.0)
-wallColor: Vec4(1.00, 0.90, 0.33, 1.0)
-wallColor: Vec4(1.00, 0.92, 0.59, 1.0)
-wallColor: Vec4(1.00, 1.00, 0.59, 1.0)
-
-windowColor: Vec4(0.10, 0.59, 0.39, 1.0)
-windowColor: Vec4(0.11, 0.67, 0.33, 1.0)
-windowColor: Vec4(0.13, 0.78, 0.52, 1.0)
-windowColor: Vec4(0.14, 0.53, 0.30, 1.0)
-windowColor: Vec4(0.27, 1.00, 0.42, 1.0)
-windowColor: Vec4(0.31, 0.63, 0.37, 1.0)
-windowColor: Vec4(0.32, 0.54, 0.36, 1.0)
-windowColor: Vec4(0.32, 0.67, 0.60, 1.0)
-windowColor: Vec4(0.40, 0.61, 0.67, 1.0)
-windowColor: Vec4(0.59, 1.00, 0.49, 1.0)
-windowColor: Vec4(0.88, 0.44, 0.15, 1.0)
-windowColor: Vec4(0.91, 0.63, 0.44, 1.0)
-windowColor: Vec4(0.95, 0.89, 0.17, 1.0)
-windowColor: Vec4(0.99, 0.99, 0.49, 1.0)
-windowColor: Vec4(1.00, 0.27, 0.27, 1.0)
-windowColor: Vec4(1.00, 0.50, 0.38, 1.0)
-windowColor: Vec4(1.00, 0.59, 0.59, 1.0)
-windowColor: Vec4(1.00, 0.63, 0.30, 1.0)
-windowColor: Vec4(1.00, 0.63, 0.38, 1.0)
-windowColor: Vec4(1.00, 0.87, 0.38, 1.0)
-windowColor: Vec4(1.00, 0.92, 0.59, 1.0)
-
-propColor: Vec4(0.11, 0.80, 0.22, 1.0)
-propColor: Vec4(0.21, 0.73, 0.31, 1.0)
-propColor: Vec4(0.22, 0.96, 0.27, 1.0)
-propColor: Vec4(0.24, 0.79, 0.32, 1.0)
-propColor: Vec4(0.25, 0.65, 0.29, 1.0)
-propColor: Vec4(0.49, 0.96, 0.42, 1.0)
-propColor: Vec4(0.76, 0.73, 0.64, 1.0)
-propColor: Vec4(0.82, 0.91, 0.44, 1.0)
-propColor: Vec4(0.90, 0.56, 0.34, 1.0)
-propColor: Vec4(0.91, 0.54, 0.44, 1.0)
-propColor: Vec4(0.91, 0.63, 0.44, 1.0)
-propColor: Vec4(0.94, 0.35, 0.35, 1.0)
-propColor: Vec4(0.95, 0.89, 0.17, 1.0)
-propColor: Vec4(0.99, 0.67, 0.25, 1.0)
-propColor: Vec4(0.99, 0.69, 0.49, 1.0)
-propColor: Vec4(0.99, 0.89, 0.49, 1.0)
-propColor: Vec4(1.00, 0.42, 0.27, 1.0)
-propColor: Vec4(1.00, 0.67, 0.59, 1.0)
-propColor: Vec4(1.00, 0.69, 0.49, 1.0)
-propColor: Vec4(1.00, 0.71, 0.27, 1.0)
-propColor: Vec4(1.00, 0.84, 0.59, 1.0)
-propColor: Vec4(1.00, 0.88, 0.80, 1.0)
-propColor: Vec4(1.00, 0.90, 0.33, 1.0)
-propColor: Vec4(1.00, 0.92, 0.59, 1.0)
-propColor: Vec4(1.00, 1.00, 0.59, 1.0)

+ 0 - 84
dmodels/src/level_editor/toontownCentralStyles.txt

@@ -1,84 +0,0 @@
-wallTexture: wall_md_pillars_ur
-wallColor: Vec4(1.0, 0.917, 0.592, 1.0)
-windowTexture: window_sm_pointed_ur
-windowColor: Vec4(0.396,  0.611,  0.666,  1.0)
-corniceTexture: cornice_stone_ur
-corniceColor: Vec4(1.0, 1.0, 0.592,  1.0)
-
-wallTexture: wall_md_pillars_ur
-wallColor: Vec4(1.0, 1.0, 0.592157, 1.0)
-windowTexture: window_sm_pointed_ur
-windowColor: Vec4(0.142751, 0.527559, 0.295847, 1.0)
-corniceTexture: cornice_stone_ur
-corniceColor: Vec4(1.0, 1.0, 0.592157, 1.0)
-        
-wallTexture: wall_lg_brick_ur
-wallColor: Vec4(1.0, 0.415686, 0.270588, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.306315, 0.627451, 0.370542, 1.0)
-corniceTexture: None
-corniceColor: None
-        
-wallTexture: wall_sm_cement_ur
-wallColor: Vec4(1.0, 0.882353, 0.803922, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.0972673, 0.590551, 0.393701, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_dental_ur
-wallColor: Vec4(0.996078, 0.894118, 0.486275, 1.0)
-windowTexture: window_md_curved_ur
-windowColor: Vec4(1.0, 0.87451, 0.376471, 1.0)
-corniceTexture: None
-corniceColor: None
-        
-wallTexture: wall_md_pillars_ur
-wallColor: Vec4(0.996078, 0.690196, 0.486275, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.31706, 0.535433, 0.361155, 1.0)
-corniceTexture: None
-corniceColor: None
-        
-wallTexture: wall_md_pillars_ur
-wallColor: Vec4(0.996078, 0.690196, 0.486275, 1.0)
-windowTexture: window_porthole_ur
-windowColor: Vec4(0.31706, 0.535433, 0.361155, 1.0)
-corniceTexture: cornice_brick_ur
-corniceColor: Vec4(0.31706, 0.535433, 0.361155, 1.0)
-
-wallTexture: wall_lg_brick_ur
-wallColor: Vec4(0.996078, 0.690196, 0.486275, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(0.996078, 0.996078, 0.486275, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(1.0, 0.415686, 0.270588, 1.0)
-windowTexture: window_sm_curved_ur
-windowColor: Vec4(1.0, 0.87451, 0.376471, 1.0)
-corniceTexture: cornice_marble_ur
-corniceColor: Vec4(1.0, 0.74902, 0.376471, 1.0)
-
-wallTexture: wall_sm_brick_ur
-wallColor: Vec4(1.0, 0.67451, 0.592157, 1.0)
-windowTexture: window_sm_pointed_ur
-windowColor: Vec4(0.88189, 0.439216, 0.145252, 1.0)
-corniceTexture: None
-corniceColor: None
-
-wallTexture: wall_md_blank_ur
-wallColor: Vec4(1.0, 0.705882, 0.270588, 1.0)
-windowTexture: window_sm_pointed_ur
-windowColor: Vec4(0.110236, 0.669291, 0.333333, 1.0)
-corniceTexture: cornice_stone_ur
-corniceColor: Vec4(0.944882, 0.711441, 0.559518, 1.0)
-
-wallTexture: wall_md_dental_ur
-wallColor: Vec4(0.909804, 0.630415, 0.444156, 1.0)
-windowTexture: window_sm_round_ur
-windowColor: Vec4(1.0, 0.270588, 0.270588, 1.0)
-corniceTexture: None
-corniceColor: None
-

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä