Browse Source

Merge branch 'master' into shaderpipeline

rdb 4 years ago
parent
commit
fd4c68e9f5
100 changed files with 1865 additions and 2309 deletions
  1. 15 18
      .github/workflows/ci.yml
  2. 534 0
      .pylintrc
  3. 7 0
      .travis.yml
  4. 5 1
      BACKERS.md
  5. 45 20
      CMakeLists.txt
  6. 4 4
      README.md
  7. 4 0
      cmake/macros/AddFlexTarget.cmake
  8. 12 2
      cmake/macros/PackageConfig.cmake
  9. 24 0
      cmake/modules/FindFFMPEG.cmake
  10. 1 1
      contrib/src/ai/aiBehaviors.h
  11. 3 8
      contrib/src/sceneeditor/SideWindow.py
  12. 0 1
      contrib/src/sceneeditor/collisionWindow.py
  13. 3 8
      contrib/src/sceneeditor/controllerWindow.py
  14. 1 7
      contrib/src/sceneeditor/dataHolder.py
  15. 3 7
      contrib/src/sceneeditor/lightingPanel.py
  16. 1 1
      contrib/src/sceneeditor/quad.py
  17. 2 12
      contrib/src/sceneeditor/sceneEditor.py
  18. 2 8
      contrib/src/sceneeditor/seAnimPanel.py
  19. 2 9
      contrib/src/sceneeditor/seBlendAnimPanel.py
  20. 1 0
      contrib/src/sceneeditor/seCameraControl.py
  21. 2 6
      contrib/src/sceneeditor/seColorEntry.py
  22. 0 1
      contrib/src/sceneeditor/seFileSaver.py
  23. 1 0
      contrib/src/sceneeditor/seGrid.py
  24. 0 1
      contrib/src/sceneeditor/seLights.py
  25. 3 2
      contrib/src/sceneeditor/seManipulation.py
  26. 6 10
      contrib/src/sceneeditor/seMopathRecorder.py
  27. 3 8
      contrib/src/sceneeditor/seParticlePanel.py
  28. 0 1
      contrib/src/sceneeditor/seParticles.py
  29. 3 8
      contrib/src/sceneeditor/sePlacer.py
  30. 3 8
      contrib/src/sceneeditor/seSceneGraphExplorer.py
  31. 1 1
      contrib/src/sceneeditor/seSelection.py
  32. 2 2
      contrib/src/sceneeditor/seSession.py
  33. 4 8
      contrib/src/sceneeditor/seTree.py
  34. 75 76
      direct/src/actor/Actor.py
  35. 3 7
      direct/src/actor/DistributedActor.py
  36. 35 40
      direct/src/cluster/ClusterClient.py
  37. 0 1
      direct/src/cluster/ClusterConfig.py
  38. 10 19
      direct/src/cluster/ClusterMsgs.py
  39. 28 30
      direct/src/cluster/ClusterServer.py
  40. 2 148
      direct/src/controls/BattleWalker.py
  41. 9 9
      direct/src/controls/ControlManager.py
  42. 2 0
      direct/src/controls/DevWalker.py
  43. 10 8
      direct/src/controls/GravityWalker.py
  44. 2 0
      direct/src/controls/InputState.py
  45. 7 5
      direct/src/controls/NonPhysicsWalker.py
  46. 0 5
      direct/src/controls/ObserverWalker.py
  47. 98 135
      direct/src/controls/PhysicsWalker.py
  48. 1 0
      direct/src/controls/TwoDWalker.py
  49. 1 3
      direct/src/dcparser/dcClass_ext.cxx
  50. 1 1
      direct/src/dcparser/dcPacker_ext.cxx
  51. 2 15
      direct/src/directbase/TestStart.py
  52. 5 17
      direct/src/directbase/ThreeUpStart.py
  53. 17 16
      direct/src/directdevices/DirectDeviceManager.py
  54. 3 6
      direct/src/directdevices/DirectFastrak.py
  55. 13 16
      direct/src/directdevices/DirectJoybox.py
  56. 12 13
      direct/src/directdevices/DirectRadamec.py
  57. 3 3
      direct/src/directnotify/DirectNotify.py
  58. 11 26
      direct/src/directnotify/Logger.py
  59. 11 12
      direct/src/directnotify/Notifier.py
  60. 1 1
      direct/src/directnotify/RotatingLog.py
  61. 22 17
      direct/src/directscripts/eggcacher.py
  62. 2 2
      direct/src/directscripts/extract_docs.py
  63. 0 986
      direct/src/directscripts/gendocs.py
  64. 8 8
      direct/src/directtools/DirectCameraControl.py
  65. 3 4
      direct/src/directtools/DirectGeometry.py
  66. 0 1
      direct/src/directtools/DirectGlobals.py
  67. 4 3
      direct/src/directtools/DirectGrid.py
  68. 8 9
      direct/src/directtools/DirectLights.py
  69. 58 57
      direct/src/directtools/DirectManipulation.py
  70. 12 12
      direct/src/directtools/DirectSelection.py
  71. 15 16
      direct/src/directtools/DirectSession.py
  72. 2 2
      direct/src/directtools/DirectUtil.py
  73. 3 1
      direct/src/directutil/DistributedLargeBlobSender.py
  74. 2 2
      direct/src/directutil/DistributedLargeBlobSenderAI.py
  75. 23 24
      direct/src/directutil/Mopath.py
  76. 138 24
      direct/src/dist/FreezeTool.py
  77. 154 197
      direct/src/dist/commands.py
  78. 1 1
      direct/src/dist/icon.py
  79. 198 0
      direct/src/dist/installers.py
  80. 3 4
      direct/src/dist/pefile.py
  81. 1 1
      direct/src/dist/pfreeze.py
  82. 3 1
      direct/src/distributed/AsyncRequest.py
  83. 7 4
      direct/src/distributed/CRCache.py
  84. 0 1
      direct/src/distributed/CRDataCache.py
  85. 4 4
      direct/src/distributed/CartesianGridBase.py
  86. 9 8
      direct/src/distributed/ClientRepository.py
  87. 18 16
      direct/src/distributed/ClientRepositoryBase.py
  88. 4 4
      direct/src/distributed/ClockDelta.py
  89. 9 7
      direct/src/distributed/ConnectionRepository.py
  90. 13 10
      direct/src/distributed/DistributedCamera.py
  91. 2 2
      direct/src/distributed/DistributedCameraOV.py
  92. 9 9
      direct/src/distributed/DistributedCartesianGrid.py
  93. 9 9
      direct/src/distributed/DistributedCartesianGridAI.py
  94. 4 8
      direct/src/distributed/DistributedNode.py
  95. 3 5
      direct/src/distributed/DistributedNodeAI.py
  96. 4 6
      direct/src/distributed/DistributedNodeUD.py
  97. 23 28
      direct/src/distributed/DistributedObject.py
  98. 27 32
      direct/src/distributed/DistributedObjectAI.py
  99. 1 8
      direct/src/distributed/DistributedObjectBase.py
  100. 0 1
      direct/src/distributed/DistributedObjectGlobal.py

+ 15 - 18
.github/workflows/ci.yml

@@ -92,16 +92,13 @@ jobs:
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-mac.tar.gz
-        tar -xf panda3d-1.10.7-tools-mac.tar.gz
-        mv panda3d-1.10.7/thirdparty thirdparty
-        rmdir panda3d-1.10.7
+        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
+        tar -xf panda3d-1.10.8-tools-mac.tar.gz
+        mv panda3d-1.10.8/thirdparty thirdparty
+        rmdir panda3d-1.10.8
 
-        mkdir -p build/Frameworks
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework build/Frameworks/Cg.framework
-
-        mkdir -p "build/${{ matrix.config }}/Frameworks"
-        cp -R thirdparty/darwin-libs-a/nvidiacg/Cg.framework "build/${{ matrix.config }}/Frameworks/Cg.framework"
+        # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
+        install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
 
         brew install ccache
 
@@ -127,16 +124,16 @@ jobs:
       uses: actions/cache@v1
       with:
         path: thirdparty
-        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.7-r1
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.9-r1
     - name: Install dependencies (Windows)
       if: runner.os == 'Windows'
       shell: powershell
       run: |
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
           $wc = New-Object System.Net.WebClient
-          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip", "thirdparty-tools.zip")
           Expand-Archive -Path thirdparty-tools.zip
-          Move-Item -Path thirdparty-tools/panda3d-1.10.7/thirdparty -Destination .
+          Move-Item -Path thirdparty-tools/panda3d-1.10.9/thirdparty -Destination .
         }
 
     - name: ccache (non-Windows)
@@ -350,16 +347,16 @@ jobs:
       shell: powershell
       run: |
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.7/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-mac.tar.gz
-        tar -xf panda3d-1.10.7-tools-mac.tar.gz
-        mv panda3d-1.10.7/thirdparty thirdparty
-        rmdir panda3d-1.10.7
+        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
+        tar -xf panda3d-1.10.8-tools-mac.tar.gz
+        mv panda3d-1.10.8/thirdparty thirdparty
+        rmdir panda3d-1.10.8
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
     - name: Set up Python 3.9
       uses: actions/setup-python@v1

+ 534 - 0
.pylintrc

@@ -0,0 +1,534 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-whitelist=panda3d
+
+# Specify a score threshold to be exceeded before program exits with error.
+fail-under=10.0
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=thirdparty
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=1
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=attribute-defined-outside-init,
+        broad-except,
+        comparison-with-callable,
+        dangerous-default-value,
+        global-statement,
+        import-outside-toplevel,
+        invalid-name,
+        line-too-long,
+        misplaced-comparison-constant,
+        missing-class-docstring,
+        missing-function-docstring,
+        missing-module-docstring,
+        protected-access,
+        r,
+        raise-missing-from,
+        redefined-builtin,
+        redefined-outer-name,
+        too-many-lines,
+        unused-argument,
+        unused-variable,
+        unused-wildcard-import,
+        using-constant-test,
+        wildcard-import,
+        wrong-import-order,
+        wrong-import-position
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'error', 'warning', 'refactor', and 'convention'
+# which contain the number of messages in each category, as well as 'statement'
+# which is the total number of statements analyzed. This score is used by the
+# global evaluation report (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=LF
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it work,
+# install the python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local,direct.showbase.PythonUtil.ScratchPad
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=base,simbase,__dev__,onScreenDebug,globalClock,render,hidden,cluster
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+          _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+      XXX,
+      TODO
+
+# Regular expression of note tags to take in consideration.
+#notes-rgx=
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+          bar,
+          baz,
+          toto,
+          tutu,
+          tata
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+           j,
+           k,
+           ex,
+           Run,
+           _
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
+
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method.
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[IMPORTS]
+
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled).
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled).
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+                      __new__,
+                      setUp,
+                      __post_init__
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+                  _fields,
+                  _replace,
+                  _source,
+                  _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=BaseException,
+                       Exception

+ 7 - 0
.travis.yml

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

+ 5 - 1
BACKERS.md

@@ -10,11 +10,12 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 ## Bronze Sponsors
 
-![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
+[<img src="https://www.panda3d.org/wp-content/uploads/2021/02/changecrab_logo.png" alt="ChangeCrab" height="48">](https://changecrab.com/) ![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)
+* [ChangeCrab](https://changecrab.com)
 
 ## Benefactors
 
@@ -22,6 +23,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 * Sam Edwards
 * Max Voss
+* Hawkheart
+* Dan Mlodecki
 
 ## Enthusiasts
 
@@ -30,6 +33,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Eric Thomson
 * Kyle Roach
 * Brian Lach
+* C0MPU73R
 
 ## Backers
 

+ 45 - 20
CMakeLists.txt

@@ -32,23 +32,32 @@ a CMake < 3.9. Making a guess if this is a multi-config generator.")
   endif()
 endif()
 
-# Define the type of build we are setting up.
-set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
-if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
-  list(APPEND _configs Coverage)
-endif()
-
+# Set the default CMAKE_BUILD_TYPE before calling project().
 if(IS_MULTICONFIG)
   message(STATUS "Using multi-configuration generator")
 else()
-  # Set the default CMAKE_BUILD_TYPE before calling project().
   if(NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Standard CACHE STRING "Choose the type of build." FORCE)
     message(STATUS "Using default build type ${CMAKE_BUILD_TYPE}")
   else()
     message(STATUS "Using build type ${CMAKE_BUILD_TYPE}")
   endif()
-  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
+# Set defaults for macOS, must be before project().
+if(APPLE)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS version to target")
+  set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
+
+  if(CMAKE_VERSION VERSION_LESS "3.19" AND NOT CMAKE_OSX_SYSROOT)
+    # Older CMake chose SDK based on deployment target, against Apple's recommendations.
+    # However, we need to use the latest to be able to target arm64.
+    if(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk" CACHE STRING "")
+    elseif(IS_DIRECTORY "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk")
+      set(CMAKE_OSX_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk" CACHE STRING "")
+    endif()
+  endif()
 endif()
 
 # Figure out the version
@@ -59,6 +68,18 @@ project(Panda3D VERSION ${_version})
 unset(_version)
 unset(_s)
 
+# Determine the possible build types.  Must be *after* calling project().
+set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
+if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
+  list(APPEND _configs Coverage)
+endif()
+
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "${_configs}" CACHE STRING "" FORCE)
+else()
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
 enable_testing()
 
 string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")
@@ -131,26 +152,30 @@ if(BUILD_MODELS)
     "${CMAKE_CURRENT_SOURCE_DIR}/models/"
     "${PROJECT_BINARY_DIR}/${PANDA_CFG_INTDIR}/models"
     *.egg)
-  run_pzip(dmodels
-    "${CMAKE_CURRENT_SOURCE_DIR}/dmodels/src/"
-    "${PROJECT_BINARY_DIR}/${PANDA_CFG_INTDIR}/models"
-    *.egg)
 
   add_custom_command(TARGET models
                      POST_BUILD
                      COMMAND ${CMAKE_COMMAND}
-                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/models/maps/"
-                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models/maps"
+                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/models/audio/"
+                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models/audio"
                              -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
-                     COMMENT "Copying models/maps")
-  add_custom_command(TARGET dmodels
+                     COMMENT "Copying models/audio")
+
+  add_custom_command(TARGET models
                      POST_BUILD
                      COMMAND ${CMAKE_COMMAND}
-                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/dmodels/src/"
-                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models"
-                             -DFILES_MATCHING="PATTERN;*.rgb;PATTERN;*.png;PATTERN;*.jpg;PATTERN;*.wav"
+                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/models/icons/"
+                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models/icons"
                              -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
-                     COMMENT "Copying dmodels' assets")
+                     COMMENT "Copying models/icons")
+
+  add_custom_command(TARGET models
+                     POST_BUILD
+                     COMMAND ${CMAKE_COMMAND}
+                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/models/maps/"
+                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models/maps"
+                             -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
+                     COMMENT "Copying models/maps")
 
   install(DIRECTORY "${PANDA_OUTPUT_DIR}/models"
     COMPONENT Models DESTINATION ${CMAKE_INSTALL_DATADIR}/panda3d)

+ 4 - 4
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-7/).
+[this page](https://www.panda3d.org/download/sdk-1-10-9/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 
-- https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win32.zip
 
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
@@ -136,7 +136,7 @@ macOS
 -----
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.7/panda3d-1.10.7-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz).
 
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:

+ 4 - 0
cmake/macros/AddFlexTarget.cmake

@@ -74,4 +74,8 @@ function(add_flex_target output_cxx input_lxx)
       ${commands}
       DEPENDS ${depends})
   endif()
+
+  if(MSVC)
+    set_source_files_properties(${outputs} PROPERTIES COMPILE_DEFINITIONS YY_NO_UNISTD_H=1)
+  endif()
 endfunction(add_flex_target)

+ 12 - 2
cmake/macros/PackageConfig.cmake

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

+ 24 - 0
cmake/modules/FindFFMPEG.cmake

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

+ 1 - 1
contrib/src/ai/aiBehaviors.h

@@ -151,7 +151,7 @@ PUBLISHED:
 
   void obstacle_avoidance(float feeler_length = 1.0);
 
-  void path_follow(float follow_wt);
+  void path_follow(float follow_wt = 1.0f);
   void add_to_path(LVecBase3 pos);
   void start_follow(std::string type = "normal");
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -548,7 +548,7 @@ class SelectionRay(SelectionQueue):
         if xy:
             mx = xy[0]
             my = xy[1]
-        elif direct:
+        elif base.direct:
             mx = SEditor.dr.mouseX
             my = SEditor.dr.mouseY
         else:

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

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

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

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

+ 75 - 76
direct/src/actor/Actor.py

@@ -11,7 +11,7 @@ from panda3d.core import Loader as PandaLoader
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.Loader import Loader
 from direct.directnotify import DirectNotifyGlobal
-
+import warnings
 
 class Actor(DirectObject, NodePath):
     """
@@ -163,11 +163,10 @@ class Actor(DirectObject, NodePath):
             #the actor for a few frames, otherwise it has no effect
             a.fixBounds()
         """
-        try:
-            self.Actor_initialized
+        if hasattr(self, 'Actor_initialized'):
             return
-        except:
-            self.Actor_initialized = 1
+
+        self.Actor_initialized = 1
 
         # initialize our NodePath essence
         NodePath.__init__(self)
@@ -252,9 +251,9 @@ class Actor(DirectObject, NodePath):
             # make sure we have models
             if models:
                 # do we have a dictionary of models?
-                if type(models) == dict:
+                if isinstance(models, dict):
                     # if this is a dictionary of dictionaries
-                    if type(models[next(iter(models))]) == dict:
+                    if isinstance(models[next(iter(models))], dict):
                         # then it must be a multipart actor w/LOD
                         self.setLODNode(node = lodNode)
                         # preserve numerical order for lod's
@@ -271,7 +270,7 @@ class Actor(DirectObject, NodePath):
                                                modelName, lodName, copy = copy,
                                                okMissing = okMissing)
                     # then if there is a dictionary of dictionaries of anims
-                    elif type(anims[next(iter(anims))]) == dict:
+                    elif isinstance(anims[next(iter(anims))], dict):
                         # then this is a multipart actor w/o LOD
                         for partName in models:
                             # pass in each part
@@ -297,10 +296,10 @@ class Actor(DirectObject, NodePath):
             if anims:
                 if len(anims) >= 1:
                     # if so, does it have a dictionary of dictionaries?
-                    if type(anims[next(iter(anims))]) == dict:
+                    if isinstance(anims[next(iter(anims))], dict):
                         # are the models a dict of dicts too?
-                        if type(models) == dict:
-                            if type(models[next(iter(models))]) == dict:
+                        if isinstance(models, dict):
+                            if isinstance(models[next(iter(models))], dict):
                                 # then we have a multi-part w/ LOD
                                 sortedKeys = list(models.keys())
                                 sortedKeys.sort()
@@ -313,7 +312,7 @@ class Actor(DirectObject, NodePath):
                                 # then it must be multi-part w/o LOD
                                 for partName in anims:
                                     self.loadAnims(anims[partName], partName)
-                    elif type(models) == dict:
+                    elif isinstance(models, dict):
                         # then we have single-part w/ LOD
                         sortedKeys = list(models.keys())
                         sortedKeys.sort()
@@ -344,50 +343,46 @@ class Actor(DirectObject, NodePath):
             self.__geomNode.node().setFinal(1)
 
     def delete(self):
-        try:
-            self.Actor_deleted
-            return
-        except:
+        if not hasattr(self, 'Actor_deleted'):
             self.Actor_deleted = 1
             self.cleanup()
 
     def copyActor(self, other, overwrite=False):
-            # act like a copy constructor
-            self.gotName = other.gotName
-
-            # copy the scene graph elements of other
-            if overwrite:
-                otherCopy = other.copyTo(NodePath())
-                otherCopy.detachNode()
-                # assign these elements to ourselve (overwrite)
-                self.assign(otherCopy)
-            else:
-                # just copy these to ourselves
-                otherCopy = other.copyTo(self)
-            # masad: check if otherCopy has a geomNode as its first child
-            # if actor is initialized with flattenable, then otherCopy, not
-            # its first child, is the geom node; check __init__, for reference
-            if other.getGeomNode().getName() == other.getName():
-                self.setGeomNode(otherCopy)
-            else:
-                self.setGeomNode(otherCopy.getChild(0))
-
-            # copy the switches for lods
-            self.switches = other.switches
-            self.__LODNode = self.find('**/+LODNode')
-            self.__hasLOD = 0
-            if not self.__LODNode.isEmpty():
-                self.__hasLOD = 1
+        # act like a copy constructor
+        self.gotName = other.gotName
+
+        # copy the scene graph elements of other
+        if overwrite:
+            otherCopy = other.copyTo(NodePath())
+            otherCopy.detachNode()
+            # assign these elements to ourselve (overwrite)
+            self.assign(otherCopy)
+        else:
+            # just copy these to ourselves
+            otherCopy = other.copyTo(self)
+        # masad: check if otherCopy has a geomNode as its first child
+        # if actor is initialized with flattenable, then otherCopy, not
+        # its first child, is the geom node; check __init__, for reference
+        if other.getGeomNode().getName() == other.getName():
+            self.setGeomNode(otherCopy)
+        else:
+            self.setGeomNode(otherCopy.getChild(0))
 
+        # copy the switches for lods
+        self.switches = other.switches
+        self.__LODNode = self.find('**/+LODNode')
+        self.__hasLOD = 0
+        if not self.__LODNode.isEmpty():
+            self.__hasLOD = 1
 
-            # copy the part dictionary from other
-            self.__copyPartBundles(other)
-            self.__copySubpartDict(other)
-            self.__subpartsComplete = other.__subpartsComplete
 
-            # copy the anim dictionary from other
-            self.__copyAnimControls(other)
+        # copy the part dictionary from other
+        self.__copyPartBundles(other)
+        self.__copySubpartDict(other)
+        self.__subpartsComplete = other.__subpartsComplete
 
+        # copy the anim dictionary from other
+        self.__copyAnimControls(other)
 
     def __cmp__(self, other):
         # Actor inherits from NodePath, which inherits a definition of
@@ -514,7 +509,7 @@ class Actor(DirectObject, NodePath):
         self.stop(None)
         self.clearPythonData()
         self.flush()
-        if(self.__geomNode):
+        if self.__geomNode:
             self.__geomNode.removeNode()
             self.__geomNode = None
         if not self.isEmpty():
@@ -597,12 +592,10 @@ class Actor(DirectObject, NodePath):
                         'l':1,
                         'f':0}
 
-                """
-                sx = smap.get(x[0], None)
-
-                if sx is None:
-                    self.notify.error('Invalid lodName: %s' % x)
-                """
+                #sx = smap.get(x[0], None)
+                #
+                #if sx is None:
+                #    self.notify.error('Invalid lodName: %s' % x)
                 return smap[x[0]]
             else:
                 return int(x)
@@ -1471,7 +1464,7 @@ class Actor(DirectObject, NodePath):
             #iterate through for a specific part
             for lodData in self.__partBundleDict.values():
                 partData = lodData.get(partName)
-                if(partData):
+                if partData:
                     char = partData.partBundleNP
                     char.node().update()
                     geomNodes = char.findAllMatches("**/+GeomNode")
@@ -1659,6 +1652,8 @@ class Actor(DirectObject, NodePath):
 
         This method is deprecated.  You should use setBlend() instead.
         """
+        if __debug__:
+            warnings.warn("This method is deprecated.  You should use setBlend() instead.", DeprecationWarning, stacklevel=2)
         self.setBlend(animBlend = True, blendType = blendType, partName = partName)
 
     def disableBlend(self, partName = None):
@@ -1668,6 +1663,8 @@ class Actor(DirectObject, NodePath):
 
         This method is deprecated.  You should use setBlend() instead.
         """
+        if __debug__:
+            warnings.warn("This method is deprecated.  You should use setBlend() instead.", DeprecationWarning, stacklevel=2)
         self.setBlend(animBlend = False, partName = partName)
 
     def setControlEffect(self, animName, effect,
@@ -1693,11 +1690,20 @@ class Actor(DirectObject, NodePath):
         else:
             lodName = 'lodRoot'
 
-        try:
-            return self.__animControlDict[lodName][partName][animName].filename
-        except:
+        partDict = self.__animControlDict.get(lodName)
+        if partDict is None:
+            return None
+
+        animDict = partDict.get(partName)
+        if animDict is None:
+            return None
+
+        anim = animDict.get(animName)
+        if anim is None:
             return None
 
+        return anim.filename
+
     def getAnimControl(self, animName, partName=None, lodName=None,
                        allowAsyncBind = True):
         """
@@ -1731,7 +1737,6 @@ class Actor(DirectObject, NodePath):
             if anim is None:
                 # anim was not present
                 assert Actor.notify.debug("couldn't find anim: %s" % (animName))
-                pass
             else:
                 # bind the animation first if we need to
                 if not anim.animControl:
@@ -1852,7 +1857,6 @@ class Actor(DirectObject, NodePath):
                         if anim is None:
                             # anim was not present
                             assert Actor.notify.debug("couldn't find anim: %s" % (animName))
-                            pass
                         else:
                             # bind the animation first if we need to
                             animControl = anim.animControl
@@ -1987,7 +1991,7 @@ class Actor(DirectObject, NodePath):
 
         node = bundleNP.node()
         # A model loaded from disk will always have just one bundle.
-        assert(node.getNumBundles() == 1)
+        assert node.getNumBundles() == 1
         bundleHandle = node.getBundleHandle(0)
 
         if self.mergeLODBundles:
@@ -2003,7 +2007,7 @@ class Actor(DirectObject, NodePath):
         bundleDict[partName] = Actor.PartDef(bundleNP, bundleHandle, partModel)
 
 
-    def makeSubpart(self, partName, includeJoints, excludeJoints = [],
+    def makeSubpart(self, partName, includeJoints, excludeJoints = (),
                     parent="modelRoot", overlapping = False):
 
         """Defines a new "part" of the Actor that corresponds to the
@@ -2128,10 +2132,7 @@ class Actor(DirectObject, NodePath):
             lodNames = ['common']
         elif lodName == 'all':
             reload = False
-            lodNames = list(self.switches.keys())
-            lodNames.sort()
-            for i in range(0, len(lodNames)):
-                lodNames[i] = str(lodNames[i])
+            lodNames = list(map(str, sorted(self.switches.keys())))
         else:
             lodNames = [lodName]
 
@@ -2140,11 +2141,10 @@ class Actor(DirectObject, NodePath):
 
         firstLoad = True
         if not reload:
-            try:
-                self.__animControlDict[lodNames[0]][partName]
+            if lodNames[0] in self.__animControlDict and \
+               partName in self.__animControlDict[lodNames[0]]:
                 firstLoad = False
-            except:
-                pass
+
         for lName in lodNames:
             if firstLoad:
                 self.__animControlDict.setdefault(lName, {})
@@ -2438,7 +2438,7 @@ class Actor(DirectObject, NodePath):
         bundles in our part bundle dict that have matching names, and
         store the resulting anim controls in our own part bundle dict"""
 
-        assert(other.mergeLODBundles == self.mergeLODBundles)
+        assert other.mergeLODBundles == self.mergeLODBundles
 
         for lodName in other.__animControlDict:
             self.__animControlDict[lodName] = {}
@@ -2509,11 +2509,10 @@ class Actor(DirectObject, NodePath):
         for lodName, animList in self.getAnimBlends(animName, partName, lodName):
             print('LOD %s:' % (lodName))
             for animName, blendList in animList:
-
-                list = []
+                strings = []
                 for partName, effect in blendList:
-                    list.append('%s:%.3f' % (partName, effect))
-                print('  %s: %s' % (animName, ', '.join(list)))
+                    strings.append('%s:%.3f' % (partName, effect))
+                print('  %s: %s' % (animName, ', '.join(strings)))
 
     def osdAnimBlends(self, animName=None, partName=None, lodName=None):
         if not onScreenDebug.enabled:

+ 3 - 7
direct/src/actor/DistributedActor.py

@@ -8,9 +8,7 @@ from . import Actor
 
 class DistributedActor(DistributedNode.DistributedNode, Actor.Actor):
     def __init__(self, cr):
-        try:
-            self.DistributedActor_initialized
-        except:
+        if not hasattr(self, 'DistributedActor_initialized'):
             self.DistributedActor_initialized = 1
             Actor.Actor.__init__(self)
             DistributedNode.DistributedNode.__init__(self, cr)
@@ -20,14 +18,12 @@ class DistributedActor(DistributedNode.DistributedNode, Actor.Actor):
 
     def disable(self):
         # remove all anims, on all parts and all lods
-        if (not self.isEmpty()):
+        if not self.isEmpty():
             Actor.Actor.unloadAnims(self, None, None, None)
         DistributedNode.DistributedNode.disable(self)
 
     def delete(self):
-        try:
-            self.DistributedActor_deleted
-        except:
+        if not hasattr(self, 'DistributedActor_deleted'):
             self.DistributedActor_deleted = 1
             DistributedNode.DistributedNode.delete(self)
             Actor.Actor.delete(self)

+ 35 - 40
direct/src/cluster/ClusterClient.py

@@ -6,6 +6,7 @@ from .ClusterConfig import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 import os
 
 
@@ -70,7 +71,7 @@ class ClusterClient(DirectObject.DirectObject):
             server = DisplayConnection(
                 self.qcm, serverConfig.serverName,
                 serverConfig.serverMsgPort, self.msgHandler)
-            if server == None:
+            if server is None:
                 self.notify.error('Could not open %s on %s port %d' %
                                   (serverConfig.serverConfigName,
                                    serverConfig.serverName,
@@ -138,15 +139,14 @@ class ClusterClient(DirectObject.DirectObject):
             object     = pair[1]
             name       = self.controlMappings[object][0]
             serverList = self.controlMappings[object][1]
-            if (object in self.objectMappings):
+            if object in self.objectMappings:
                 self.moveObject(self.objectMappings[object],name,serverList,
                                 self.controlOffsets[object], self.objectHasColor[object])
         self.sendNamedMovementDone()
         return Task.cont
 
     def sendNamedMovementDone(self, serverList = None):
-
-        if (serverList == None):
+        if serverList is None:
             serverList = range(len(self.serverList))
 
         for server in serverList:
@@ -155,7 +155,6 @@ class ClusterClient(DirectObject.DirectObject):
 
 
     def redoSortedPriorities(self):
-
         self.sortedControlMappings = []
         for key in self.controlMappings:
             self.sortedControlMappings.append([self.controlPriorities[key],
@@ -169,7 +168,7 @@ class ClusterClient(DirectObject.DirectObject):
         hpr = nodePath.getHpr(render)
         scale = nodePath.getScale(render)
         hidden = nodePath.isHidden()
-        if (hasColor):
+        if hasColor:
             color = nodePath.getColor()
         else:
             color = [1,1,1,1]
@@ -193,7 +192,7 @@ class ClusterClient(DirectObject.DirectObject):
 
     def moveSelectedTask(self, state):
         # Update cluster if current display is a cluster client
-        if (last is not None):
+        if last is not None:
             self.notify.debug('moving selected node path')
             xyz = Point3(0)
             hpr = VBase3(0)
@@ -204,24 +203,24 @@ class ClusterClient(DirectObject.DirectObject):
         return Task.cont
 
 
-    def addNamedObjectMapping(self,object,name,hasColor = True):
-        if (name not in self.objectMappings):
+    def addNamedObjectMapping(self, object, name, hasColor = True):
+        if name not in self.objectMappings:
             self.objectMappings[name] = object
             self.objectHasColor[name] = hasColor
         else:
             self.notify.debug('attempt to add duplicate named object: '+name)
 
     def removeObjectMapping(self,name):
-        if (name in self.objectMappings):
+        if name in self.objectMappings:
             self.objectMappings.pop(name)
 
 
-    def addControlMapping(self,objectName,controlledName, serverList = None,
+    def addControlMapping(self, objectName, controlledName, serverList = None,
                           offset = None, priority = 0):
-        if (objectName not in self.controlMappings):
-            if (serverList == None):
+        if objectName not in self.controlMappings:
+            if serverList is None:
                 serverList = range(len(self.serverList))
-            if (offset == None):
+            if offset is None:
                 offset = Vec3(0,0,0)
 
             self.controlMappings[objectName] = [controlledName,serverList]
@@ -233,30 +232,29 @@ class ClusterClient(DirectObject.DirectObject):
             for item in oldList:
                 mergedList.append(item)
             for item in serverList:
-                if (item not in mergedList):
+                if item not in mergedList:
                     mergedList.append(item)
 
         self.redoSortedPriorities()
             #self.notify.debug('attempt to add duplicate controlled object: '+name)
 
-    def setControlMappingOffset(self,objectName,offset):
-        if (objectName in self.controlMappings):
+    def setControlMappingOffset(self, objectName, offset):
+        if objectName in self.controlMappings:
             self.controlOffsets[objectName] = offset
 
-    def removeControlMapping(self,name, serverList = None):
-        if (name in self.controlMappings):
-
-            if (serverList == None):
+    def removeControlMapping(self, name, serverList = None):
+        if name in self.controlMappings:
+            if serverList is None:
                 self.controlMappings.pop(name)
                 self.controlPriorities.pop(name)
             else:
-                list = self.controlMappings[key][1]
+                oldList = self.controlMappings[key][1]
                 newList = []
-                for server in list:
-                    if (server not in serverList):
+                for server in oldList:
+                    if server not in serverList:
                         newList.append(server)
                 self.controlMappings[key][1] = newList
-                if (len(newList) == 0):
+                if len(newList) == 0:
                     self.controlMappings.pop(name)
                     self.controlPriorities.pop(name)
         self.redoSortedPriorities()
@@ -303,7 +301,7 @@ class ClusterClient(DirectObject.DirectObject):
             tag = self.taggedObjects[name]
             function = tag["selectFunction"]
             args     = tag["selectArgs"]
-            if (function != None):
+            if function is not None:
                 function(*args)
         else:
             self(self.getNodePathFindCmd(nodePath) + '.select()', 0)
@@ -315,7 +313,7 @@ class ClusterClient(DirectObject.DirectObject):
             tag = self.taggedObjects[name]
             function = tag["deselectFunction"]
             args     = tag["deselectArgs"]
-            if (function != None):
+            if function is not None:
                 function(*args)
             self.startMoveSelectedTask()
         self(self.getNodePathFindCmd(nodePath) + '.deselect()', 0)
@@ -348,24 +346,23 @@ class ClusterClient(DirectObject.DirectObject):
 
 
     def handleDatagram(self,dgi,type,server):
-        if (type == CLUSTER_NONE):
+        if type == CLUSTER_NONE:
             pass
-        elif (type == CLUSTER_NAMED_OBJECT_MOVEMENT):
+        elif type == CLUSTER_NAMED_OBJECT_MOVEMENT:
             self.serverQueues[server].append(self.msgHandler.parseNamedMovementDatagram(dgi))
             #self.handleNamedMovement(dgi)
         # when we recieve a 'named movement done' packet from a server we handle
         # all of its messages
-        elif (type == CLUSTER_NAMED_MOVEMENT_DONE):
+        elif type == CLUSTER_NAMED_MOVEMENT_DONE:
             self.handleMessageQueue(server)
         else:
             self.notify.warning("Received unsupported packet type:" % type)
         return type
 
     def handleMessageQueue(self,server):
-
-        list = self.serverQueues[server]
+        queue = self.serverQueues[server]
         # handle all messages in the queue
-        for data in list:
+        for data in queue:
             #print dgi
             self.handleNamedMovement(data)
 
@@ -378,14 +375,14 @@ class ClusterClient(DirectObject.DirectObject):
 
         (name,x, y, z, h, p, r, sx, sy, sz,red,g,b,a, hidden) = data
         #print "name"
-        #if (name == "camNode"):
+        #if name == "camNode":
         #    print x,y,z,h,p,r, sx, sy, sz,red,g,b,a, hidden
-        if (name in self.objectMappings):
+        if name in self.objectMappings:
             self.objectMappings[name].setPosHpr(render, x, y, z, h, p, r)
             self.objectMappings[name].setScale(render,sx,sy,sz)
-            if (self.objectHasColor[name]):
+            if self.objectHasColor[name]:
                 self.objectMappings[name].setColor(red,g,b,a)
-            if (hidden):
+            if hidden:
                 self.objectMappings[name].hide()
             else:
                 self.objectMappings[name].show()
@@ -451,7 +448,7 @@ class DisplayConnection:
         self.tcpConn = qcm.openTCPClientConnection(
             serverName, port, gameServerTimeoutMs)
         # Test for bad connection
-        if self.tcpConn == None:
+        if self.tcpConn is None:
             return None
         else:
             self.tcpConn.setNoDelay(1)
@@ -680,5 +677,3 @@ class DummyClusterClient(DirectObject.DirectObject):
         if fLocally:
             # Execute locally
             exec(commandString, __builtins__)
-
-

+ 0 - 1
direct/src/cluster/ClusterConfig.py

@@ -165,4 +165,3 @@ ClientConfigs = {
                               }
                              ],
     }
-

+ 10 - 19
direct/src/cluster/ClusterMsgs.py

@@ -59,17 +59,17 @@ class ClusterMsgHandler:
         if qcr.dataAvailable():
             datagram = NetDatagram()
             if qcr.getData(datagram):
-                (dgi, type) = self.readHeader(datagram)
+                (dgi, dtype) = self.readHeader(datagram)
             else:
                 dgi = None
-                type = CLUSTER_NONE
+                dtype = CLUSTER_NONE
                 self.notify.warning("getData returned false")
         else:
             datagram = None
             dgi = None
-            type = CLUSTER_NONE
+            dtype = CLUSTER_NONE
         # Note, return datagram to keep a handle on the data
-        return (datagram, dgi, type)
+        return (datagram, dgi, dtype)
 
     def blockingRead(self, qcr):
         """
@@ -85,19 +85,19 @@ class ClusterMsgHandler:
         # Data is available, create a datagram iterator
         datagram = NetDatagram()
         if qcr.getData(datagram):
-            (dgi, type) = self.readHeader(datagram)
+            (dgi, dtype) = self.readHeader(datagram)
         else:
-            (dgi, type) = (None, CLUSTER_NONE)
+            (dgi, dtype) = (None, CLUSTER_NONE)
             self.notify.warning("getData returned false")
         # Note, return datagram to keep a handle on the data
-        return (datagram, dgi, type)
+        return (datagram, dgi, dtype)
 
     def readHeader(self, datagram):
         dgi = PyDatagramIterator(datagram)
         number = dgi.getUint32()
-        type = dgi.getUint8()
-        self.notify.debug("Packet %d type %d received" % (number, type))
-        return (dgi, type)
+        dtype = dgi.getUint8()
+        self.notify.debug("Packet %d type %d received" % (number, dtype))
+        return (dgi, dtype)
 
     def makeCamOffsetDatagram(self, xyz, hpr):
         datagram = PyDatagram()
@@ -298,12 +298,3 @@ class ClusterMsgHandler:
         dt=dgi.getFloat32()
         self.notify.debug('time data=%f %f' % (frameTime, dt))
         return (frameCount, frameTime, dt)
-
-
-
-
-
-
-
-
-

+ 28 - 30
direct/src/cluster/ClusterServer.py

@@ -4,6 +4,7 @@ from direct.distributed.MsgTypes import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 
 # NOTE: This assumes the following variables are set via bootstrap command line
 # arguments on server startup:
@@ -100,16 +101,16 @@ class ClusterServer(DirectObject.DirectObject):
         return Task.cont
 
 
-    def addNamedObjectMapping(self,object,name,hasColor = True,
+    def addNamedObjectMapping(self, object, name, hasColor = True,
                               priority = 0):
-        if (name not in self.objectMappings):
+        if name not in self.objectMappings:
             self.objectMappings[name] = object
             self.objectHasColor[name] = hasColor
         else:
             self.notify.debug('attempt to add duplicate named object: '+name)
 
-    def removeObjectMapping(self,name):
-        if (name in self.objectMappings):
+    def removeObjectMapping(self, name):
+        if name in self.objectMappings:
             self.objectMappings.pop(name)
 
 
@@ -123,11 +124,11 @@ class ClusterServer(DirectObject.DirectObject):
         self.sortedControlMappings.sort()
 
 
-    def addControlMapping(self,objectName,controlledName, offset = None,
+    def addControlMapping(self, objectName, controlledName, offset = None,
                           priority = 0):
-        if (objectName not in self.controlMappings):
+        if objectName not in self.controlMappings:
             self.controlMappings[objectName] = controlledName
-            if (offset == None):
+            if offset is None:
                 offset = Vec3(0,0,0)
             self.controlOffsets[objectName]  = offset
             self.controlPriorities[objectName] = priority
@@ -135,13 +136,13 @@ class ClusterServer(DirectObject.DirectObject):
         else:
             self.notify.debug('attempt to add duplicate controlled object: '+name)
 
-    def setControlMappingOffset(self,objectName,offset):
-        if (objectName in self.controlMappings):
+    def setControlMappingOffset(self, objectName, offset):
+        if objectName in self.controlMappings:
             self.controlOffsets[objectName] = offset
 
 
-    def removeControlMapping(self,name):
-        if (name in self.controlMappings):
+    def removeControlMapping(self, name):
+        if name in self.controlMappings:
             self.controlMappings.pop(name)
             self.controlPriorities.pop(name)
         self.redoSortedPriorities()
@@ -156,7 +157,7 @@ class ClusterServer(DirectObject.DirectObject):
         for pair in self.sortedControlPriorities:
             object = pair[1]
             name   = self.controlMappings[object]
-            if (object in self.objectMappings):
+            if object in self.objectMappings:
                 self.moveObject(self.objectMappings[object],name,self.controlOffsets[object],
                                 self.objectHasColor[object])
 
@@ -165,7 +166,6 @@ class ClusterServer(DirectObject.DirectObject):
 
 
     def sendNamedMovementDone(self):
-
         self.notify.debug("named movement done")
         datagram = self.msgHandler.makeNamedMovementDone()
         self.cw.send(datagram,self.lastConnection)
@@ -176,7 +176,7 @@ class ClusterServer(DirectObject.DirectObject):
         xyz = nodePath.getPos(render) + offset
         hpr = nodePath.getHpr(render)
         scale = nodePath.getScale(render)
-        if (hasColor):
+        if hasColor:
             color = nodePath.getColor()
         else:
             color = [1,1,1,1]
@@ -243,34 +243,34 @@ class ClusterServer(DirectObject.DirectObject):
 
     def handleDatagram(self, dgi, type):
         """ Process a datagram depending upon type flag """
-        if (type == CLUSTER_NONE):
+        if type == CLUSTER_NONE:
             pass
-        elif (type == CLUSTER_EXIT):
+        elif type == CLUSTER_EXIT:
             print('GOT EXIT')
             import sys
             sys.exit()
-        elif (type == CLUSTER_CAM_OFFSET):
+        elif type == CLUSTER_CAM_OFFSET:
             self.handleCamOffset(dgi)
-        elif (type == CLUSTER_CAM_FRUSTUM):
+        elif type == CLUSTER_CAM_FRUSTUM:
             self.handleCamFrustum(dgi)
-        elif (type == CLUSTER_CAM_MOVEMENT):
+        elif type == CLUSTER_CAM_MOVEMENT:
             self.handleCamMovement(dgi)
-        elif (type == CLUSTER_SELECTED_MOVEMENT):
+        elif type == CLUSTER_SELECTED_MOVEMENT:
             self.handleSelectedMovement(dgi)
-        elif (type == CLUSTER_COMMAND_STRING):
+        elif type == CLUSTER_COMMAND_STRING:
             self.handleCommandString(dgi)
-        elif (type == CLUSTER_SWAP_READY):
+        elif type == CLUSTER_SWAP_READY:
             pass
-        elif (type == CLUSTER_SWAP_NOW):
+        elif type == CLUSTER_SWAP_NOW:
             self.notify.debug('swapping')
             base.graphicsEngine.flipFrame()
-        elif (type == CLUSTER_TIME_DATA):
+        elif type == CLUSTER_TIME_DATA:
             self.notify.debug('time data')
             self.handleTimeData(dgi)
-        elif (type == CLUSTER_NAMED_OBJECT_MOVEMENT):
+        elif type == CLUSTER_NAMED_OBJECT_MOVEMENT:
             self.messageQueue.append(self.msgHandler.parseNamedMovementDatagram(dgi))
             #self.handleNamedMovement(dgi)
-        elif (type == CLUSTER_NAMED_MOVEMENT_DONE):
+        elif type == CLUSTER_NAMED_MOVEMENT_DONE:
             #print "got done",self.messageQueue
             #if (len(self.messageQueue) > 0):
             #    print self.messageQueue[0]
@@ -297,11 +297,11 @@ class ClusterServer(DirectObject.DirectObject):
     def handleNamedMovement(self, data):
         """ Update cameraJig position to reflect latest position """
         (name,x, y, z, h, p, r,sx,sy,sz, red, g, b, a, hidden) = data
-        if (name in self.objectMappings):
+        if name in self.objectMappings:
             self.objectMappings[name].setPosHpr(render, x, y, z, h, p, r)
             self.objectMappings[name].setScale(render,sx,sy,sz)
             self.objectMappings[name].setColor(red,g,b,a)
-            if (hidden):
+            if hidden:
                 self.objectMappings[name].hide()
             else:
                 self.objectMappings[name].show()
@@ -346,5 +346,3 @@ class ClusterServer(DirectObject.DirectObject):
             exec(command, __builtins__)
         except:
             pass
-
-

+ 2 - 148
direct/src/controls/BattleWalker.py

@@ -1,5 +1,6 @@
 
 from direct.showbase.InputStateGlobal import inputState
+from direct.showbase.MessengerGlobal import messenger
 from direct.task.Task import Task
 from panda3d.core import *
 from . import GravityWalker
@@ -54,7 +55,7 @@ class BattleWalker(GravityWalker.GravityWalker):
 
         debugRunning = inputState.isSet("debugRunning")
 
-        if(debugRunning):
+        if debugRunning:
             self.speed*=base.debugRunningMultiplier
             self.slideSpeed*=base.debugRunningMultiplier
             self.rotationSpeed*=1.25
@@ -139,150 +140,3 @@ class BattleWalker(GravityWalker.GravityWalker):
         if self.moving or jump:
             messenger.send("avatarMoving")
         return Task.cont
-
-    if 0:
-        def handleAvatarControls(self, task):
-            # If targetNp is not available, revert back to GravityWalker.handleAvatarControls.
-            # This situation occurs when the target dies, but we aren't switched out of
-            # battle walker control mode.
-
-            targetNp = self.avatarNodePath.currentTarget
-            if not BattleStrafe or targetNp == None or targetNp.isEmpty():
-                return GravityWalker.GravityWalker.handleAvatarControls(self, task)
-
-            # get the button states:
-            run = inputState.isSet("run")
-            forward = inputState.isSet("forward")
-            reverse = inputState.isSet("reverse")
-            turnLeft = inputState.isSet("turnLeft")
-            turnRight = inputState.isSet("turnRight")
-            slide = inputState.isSet("slide")
-            jump = inputState.isSet("jump")
-            # Determine what the speeds are based on the buttons:
-            self.advanceSpeed=(forward and self.avatarControlForwardSpeed or
-                               reverse and -self.avatarControlReverseSpeed)
-            if run and self.advanceSpeed>0.0:
-                self.advanceSpeed*=2.0 #*#
-            # Should fSlide be renamed slideButton?
-            self.slideSpeed=.15*(turnLeft and -self.avatarControlForwardSpeed or
-                                 turnRight and self.avatarControlForwardSpeed)
-            print('slideSpeed: %s' % self.slideSpeed)
-            self.rotationSpeed=0
-            self.speed=0
-
-            debugRunning = inputState.isSet("debugRunning")
-            if debugRunning:
-                self.advanceSpeed*=4.0
-                self.slideSpeed*=4.0
-                self.rotationSpeed*=1.25
-
-            if self.needToDeltaPos:
-                self.setPriorParentVector()
-                self.needToDeltaPos = 0
-            if self.wantDebugIndicator:
-                self.displayDebugInfo()
-            if self.lifter.isOnGround():
-                if self.isAirborne:
-                    self.isAirborne = 0
-                    assert self.debugPrint("isAirborne 0 due to isOnGround() true")
-                    impact = self.lifter.getImpactVelocity()
-                    if impact < -30.0:
-                        messenger.send("jumpHardLand")
-                        self.startJumpDelay(0.3)
-                    else:
-                        messenger.send("jumpLand")
-                        if impact < -5.0:
-                            self.startJumpDelay(0.2)
-                        # else, ignore the little potholes.
-                assert self.isAirborne == 0
-                self.priorParent = Vec3.zero()
-                if jump and self.mayJump:
-                    # The jump button is down and we're close
-                    # enough to the ground to jump.
-                    self.lifter.addVelocity(self.avatarControlJumpForce)
-                    messenger.send("jumpStart")
-                    self.isAirborne = 1
-                    assert self.debugPrint("isAirborne 1 due to jump")
-            else:
-                if self.isAirborne == 0:
-                    assert self.debugPrint("isAirborne 1 due to isOnGround() false")
-                self.isAirborne = 1
-
-            self.__oldPosDelta = self.avatarNodePath.getPosDelta(render)
-            # How far did we move based on the amount of time elapsed?
-            self.__oldDt = ClockObject.getGlobalClock().getDt()
-            dt=self.__oldDt
-
-            # Before we do anything with position or orientation, make the avatar
-            # face it's target.  Only allow rMax degrees rotation per frame, so
-            # we don't get an unnatural spinning effect
-            curH = self.avatarNodePath.getH()
-            self.avatarNodePath.headsUp(targetNp)
-            newH = self.avatarNodePath.getH()
-            delH = reduceAngle(newH-curH)
-            rMax = 10
-            if delH < -rMax:
-                self.avatarNodePath.setH(curH-rMax)
-                self.rotationSpeed=-self.avatarControlRotateSpeed
-            elif delH > rMax:
-                self.avatarNodePath.setH(curH+rMax)
-                self.rotationSpeed=self.avatarControlRotateSpeed
-
-            # Check to see if we're moving at all:
-            self.moving = self.speed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
-            if self.moving:
-                distance = dt * self.speed
-                slideDistance = dt * self.slideSpeed
-                print('slideDistance: %s' % slideDistance)
-                rotation = dt * self.rotationSpeed
-
-                # Take a step in the direction of our previous heading.
-                self.vel=Vec3(Vec3.forward() * distance +
-                              Vec3.right() * slideDistance)
-                if self.vel != Vec3.zero() or self.priorParent != Vec3.zero():
-                    if 1:
-                        # rotMat is the rotation matrix corresponding to
-                        # our previous heading.
-                        rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
-                        step=(self.priorParent * dt) + rotMat.xform(self.vel)
-                        self.avatarNodePath.setFluidPos(Point3(
-                                self.avatarNodePath.getPos()+step))
-                self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
-            else:
-                self.vel.set(0.0, 0.0, 0.0)
-
-            """
-            # Check to see if we're moving at all:
-            self.moving = self.advanceSpeed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
-            if self.moving:
-                distance = dt * self.advanceSpeed
-                slideDistance = dt * self.slideSpeed
-                rotation = dt * self.rotationSpeed
-
-                # Prevent avatar from getting too close to target
-                d = self.avatarNodePath.getPos(targetNp)
-                # TODO:  make min distance adjust for current weapon
-                if (d[0]*d[0]+d[1]*d[1] < 6.0 and distance > 0):
-                    # move the avatar sideways instead of forward
-                    slideDistance += .2
-                    distance = 0
-
-                # Take a step in the direction of our previous heading.
-                self.vel=Vec3(Vec3.forward() * distance +
-                              Vec3.right() * slideDistance)
-                if self.vel != Vec3.zero() or self.priorParent != Vec3.zero():
-                    # rotMat is the rotation matrix corresponding to
-                    # our previous heading.
-                    rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
-                    step=rotMat.xform(self.vel) + (self.priorParent * dt)
-                    self.avatarNodePath.setFluidPos(Point3(
-                        self.avatarNodePath.getPos()+step))
-                self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
-            else:
-                self.vel.set(0.0, 0.0, 0.0)
-            """
-            if self.moving or jump:
-                messenger.send("avatarMoving")
-            return Task.cont
-
-

+ 9 - 9
direct/src/controls/ControlManager.py

@@ -1,5 +1,6 @@
 
 from direct.showbase.InputStateGlobal import inputState
+from direct.showbase.MessengerGlobal import messenger
 #from DirectGui import *
 #from PythonUtil import *
 #from IntervalGlobal import *
@@ -292,18 +293,18 @@ class ControlManager:
         self.forceAvJumpToken.release()
         self.forceAvJumpToken = None
 
-    def monitor(self, foo):
+    def monitor(self, _):
         #assert self.debugPrint("monitor()")
         #if 1:
         #    airborneHeight=self.avatar.getAirborneHeight()
         #    onScreenDebug.add("airborneHeight", "% 10.4f"%(airborneHeight,))
-        if 0:
-            onScreenDebug.add("InputState forward", "%d"%(inputState.isSet("forward")))
-            onScreenDebug.add("InputState reverse", "%d"%(inputState.isSet("reverse")))
-            onScreenDebug.add("InputState turnLeft", "%d"%(inputState.isSet("turnLeft")))
-            onScreenDebug.add("InputState turnRight", "%d"%(inputState.isSet("turnRight")))
-            onScreenDebug.add("InputState slideLeft", "%d"%(inputState.isSet("slideLeft")))
-            onScreenDebug.add("InputState slideRight", "%d"%(inputState.isSet("slideRight")))
+        #if 0:
+        #    onScreenDebug.add("InputState forward", "%d"%(inputState.isSet("forward")))
+        #    onScreenDebug.add("InputState reverse", "%d"%(inputState.isSet("reverse")))
+        #    onScreenDebug.add("InputState turnLeft", "%d"%(inputState.isSet("turnLeft")))
+        #    onScreenDebug.add("InputState turnRight", "%d"%(inputState.isSet("turnRight")))
+        #    onScreenDebug.add("InputState slideLeft", "%d"%(inputState.isSet("slideLeft")))
+        #    onScreenDebug.add("InputState slideRight", "%d"%(inputState.isSet("slideRight")))
         return Task.cont
 
     def setWASDTurn(self, turn):
@@ -343,4 +344,3 @@ class ControlManager:
 
             inputState.set("turnLeft", False, inputSource=inputState.WASD)
             inputState.set("turnRight", False, inputSource=inputState.WASD)
-

+ 2 - 0
direct/src/controls/DevWalker.py

@@ -19,7 +19,9 @@ animations based on walker events.
 from direct.showbase.InputStateGlobal import inputState
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
+from direct.showbase.MessengerGlobal import messenger
 from direct.task.Task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from panda3d.core import *
 
 

+ 10 - 8
direct/src/controls/GravityWalker.py

@@ -19,10 +19,12 @@ from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase import DirectObject
 from direct.controls.ControlManager import CollisionHandlerRayStart
 from direct.showbase.InputStateGlobal import inputState
+from direct.showbase.MessengerGlobal import messenger
 from direct.task.Task import Task
-from panda3d.core import *
+from direct.task.TaskManagerGlobal import taskMgr
 from direct.extensions_native import VBase3_extensions
 from direct.extensions_native import VBase4_extensions
+from panda3d.core import *
 import math
 
 
@@ -142,7 +144,7 @@ class GravityWalker(DirectObject.DirectObject):
         cSphereNode.setIntoCollideMask(BitMask32.allOff())
 
         # set up collision mechanism
-        if config.GetBool('want-fluid-pusher', 0):
+        if ConfigVariableBool('want-fluid-pusher', 0):
             self.pusher = CollisionHandlerFluidPusher()
         else:
             self.pusher = CollisionHandlerPusher()
@@ -284,11 +286,11 @@ class GravityWalker(DirectObject.DirectObject):
             # make sure we have a shadow traverser
             base.initShadowTrav()
             if active:
-                if 1:
-                    # Please let skyler or drose know if this is causing a problem
-                    # This is a bit of a hack fix:
-                    self.avatarNodePath.setP(0.0)
-                    self.avatarNodePath.setR(0.0)
+                # Please let skyler or drose know if this is causing a problem
+                # This is a bit of a hack fix:
+                self.avatarNodePath.setP(0.0)
+                self.avatarNodePath.setR(0.0)
+
                 self.cTrav.addCollider(self.cWallSphereNodePath, self.pusher)
                 if self.wantFloorSphere:
                     self.cTrav.addCollider(self.cFloorSphereNodePath, self.pusherFloor)
@@ -440,7 +442,7 @@ class GravityWalker(DirectObject.DirectObject):
             self.slideSpeed *= GravityWalker.DiagonalFactor
 
         debugRunning = inputState.isSet("debugRunning")
-        if(debugRunning):
+        if debugRunning:
             self.speed*=base.debugRunningMultiplier
             self.slideSpeed*=base.debugRunningMultiplier
             self.rotationSpeed*=1.25

+ 2 - 0
direct/src/controls/InputState.py

@@ -1,6 +1,8 @@
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.showbase.PythonUtil import SerialNumGen
+from direct.showbase.MessengerGlobal import messenger
+
 
 # internal class, don't create these on your own
 class InputStateToken:

+ 7 - 5
direct/src/controls/NonPhysicsWalker.py

@@ -20,7 +20,9 @@ from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.controls.ControlManager import CollisionHandlerRayStart
 from direct.showbase.InputStateGlobal import inputState
+from direct.showbase.MessengerGlobal import messenger
 from direct.task.Task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from panda3d.core import *
 
 class NonPhysicsWalker(DirectObject.DirectObject):
@@ -184,7 +186,7 @@ class NonPhysicsWalker(DirectObject.DirectObject):
         tempCTrav = CollisionTraverser("oneTimeCollide")
         tempCTrav.addCollider(self.cSphereNodePath, self.pusher)
         tempCTrav.addCollider(self.cRayNodePath, self.lifter)
-        tempCTrav.traverse(render)
+        tempCTrav.traverse(base.render)
 
     def addBlastForce(self, vector):
         pass
@@ -268,12 +270,12 @@ class NonPhysicsWalker(DirectObject.DirectObject):
         else:
             self.vel.set(0.0, 0.0, 0.0)
 
-        self.__oldPosDelta = self.avatarNodePath.getPosDelta(render)
+        self.__oldPosDelta = self.avatarNodePath.getPosDelta(base.render)
         self.__oldDt = dt
 
-        try:
-            self.worldVelocity = self.__oldPosDelta*(1/self.__oldDt)
-        except:
+        if self.__oldDt != 0:
+            self.worldVelocity = self.__oldPosDelta * (1 / self.__oldDt)
+        else:
             # divide by zero
             self.worldVelocity = 0
 

+ 0 - 5
direct/src/controls/ObserverWalker.py

@@ -31,9 +31,6 @@ class ObserverWalker(NonPhysicsWalker.NonPhysicsWalker):
         """
         Set up the avatar for collisions
         """
-        """
-        Set up the avatar for collisions
-        """
         assert not avatarNodePath.isEmpty()
 
         self.cTrav = collisionTraverser
@@ -102,11 +99,9 @@ class ObserverWalker(NonPhysicsWalker.NonPhysicsWalker):
         Activate the arrow keys, etc.
         """
         assert self.debugPrint("enableAvatarControls")
-        pass
 
     def disableAvatarControls(self):
         """
         Ignore the arrow keys, etc.
         """
         assert self.debugPrint("disableAvatarControls")
-        pass

+ 98 - 135
direct/src/controls/PhysicsWalker.py

@@ -20,12 +20,14 @@ from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.controls.ControlManager import CollisionHandlerRayStart
 from direct.showbase.InputStateGlobal import inputState
+from direct.showbase.MessengerGlobal import messenger
 from direct.task.Task import Task
-from panda3d.core import *
-from panda3d.physics import *
+from direct.task.TaskManagerGlobal import taskMgr
 from direct.extensions_native import Mat3_extensions
 from direct.extensions_native import VBase3_extensions
 from direct.extensions_native import VBase4_extensions
+from panda3d.core import *
+from panda3d.physics import *
 import math
 
 #import LineStream
@@ -131,11 +133,6 @@ class PhysicsWalker(DirectObject.DirectObject):
             assert onScreenDebug.add("height", height.getZ())
             return height.getZ() - self.floorOffset
         else: # useCollisionHandlerQueue
-            """
-            returns the height of the avatar above the ground.
-            If there is no floor below the avatar, 0.0 is returned.
-            aka get airborne height.
-            """
             height = 0.0
             #*#self.cRayTrav.traverse(render)
             if self.cRayQueue.getNumEntries() != 0:
@@ -239,7 +236,7 @@ class PhysicsWalker(DirectObject.DirectObject):
         self.floorOffset = floorOffset = 7.0
 
         self.avatarNodePath = self.setupPhysics(avatarNodePath)
-        if 0 or self.useHeightRay:
+        if self.useHeightRay:
             #self.setupRay(floorBitmask, avatarRadius)
             self.setupRay(floorBitmask, 0.0)
         self.setupSphere(wallBitmask|floorBitmask, avatarRadius)
@@ -257,14 +254,14 @@ class PhysicsWalker(DirectObject.DirectObject):
         self.cSphereNodePath.show()
         if indicator:
             # Indicator Node:
-            change=render.attachNewNode("change")
+            change = render.attachNewNode("change")
             #change.setPos(Vec3(1.0, 1.0, 1.0))
             #change.setHpr(0.0, 0.0, 0.0)
             change.setScale(0.1)
             #change.setColor(Vec4(1.0, 1.0, 1.0, 1.0))
             indicator.reparentTo(change)
 
-            indicatorNode=render.attachNewNode("physVelocityIndicator")
+            indicatorNode = render.attachNewNode("physVelocityIndicator")
             #indicatorNode.setScale(0.1)
             #indicatorNode.setP(90.0)
             indicatorNode.setPos(self.avatarNodePath, 0.0, 0.0, 6.0)
@@ -273,7 +270,7 @@ class PhysicsWalker(DirectObject.DirectObject):
 
             self.physVelocityIndicator=indicatorNode
             # Contact Node:
-            contactIndicatorNode=render.attachNewNode("physContactIndicator")
+            contactIndicatorNode = render.attachNewNode("physContactIndicator")
             contactIndicatorNode.setScale(0.25)
             contactIndicatorNode.setP(90.0)
             contactIndicatorNode.setPos(self.avatarNodePath, 0.0, 0.0, 5.0)
@@ -472,137 +469,103 @@ class PhysicsWalker(DirectObject.DirectObject):
                 onScreenDebug.add("posDelta1",
                     self.avatarNodePath.getPosDelta(render).pPrintValues())
 
-                if 0:
-                    onScreenDebug.add("posDelta3",
-                        render.getRelativeVector(
-                            self.avatarNodePath,
-                            self.avatarNodePath.getPosDelta(render)).pPrintValues())
-
-                if 0:
-                    onScreenDebug.add("gravity",
-                        self.gravity.getLocalVector().pPrintValues())
-                    onScreenDebug.add("priorParent",
-                        self.priorParent.getLocalVector().pPrintValues())
-                    onScreenDebug.add("avatarViscosity",
-                        "% 10.4f"%(self.avatarViscosity.getCoef(),))
-
-                    onScreenDebug.add("physObject pos",
-                        physObject.getPosition().pPrintValues())
-                    onScreenDebug.add("physObject hpr",
-                        physObject.getOrientation().getHpr().pPrintValues())
-                    onScreenDebug.add("physObject orien",
-                        physObject.getOrientation().pPrintValues())
-
-                if 1:
-                    onScreenDebug.add("physObject vel",
-                        physObject.getVelocity().pPrintValues())
-                    onScreenDebug.add("physObject len",
-                        "% 10.4f"%physObject.getVelocity().length())
-
-                if 0:
-                    onScreenDebug.add("posDelta4",
-                        self.priorParentNp.getRelativeVector(
-                            render,
-                            self.avatarNodePath.getPosDelta(render)).pPrintValues())
-
-                if 1:
-                    onScreenDebug.add("priorParent",
-                        self.priorParent.getLocalVector().pPrintValues())
-
-                if 0:
-                    onScreenDebug.add("priorParent po",
-                        self.priorParent.getVector(physObject).pPrintValues())
-
-                if 0:
-                    onScreenDebug.add("__posDelta",
-                        self.__oldPosDelta.pPrintValues())
-
-                if 1:
-                    onScreenDebug.add("contact",
-                        contact.pPrintValues())
-                    #onScreenDebug.add("airborneHeight", "% 10.4f"%(
-                    #    self.getAirborneHeight(),))
-
-                if 0:
-                    onScreenDebug.add("__oldContact",
-                        contact.pPrintValues())
-                    onScreenDebug.add("__oldAirborneHeight", "% 10.4f"%(
-                        self.getAirborneHeight(),))
-        airborneHeight=self.getAirborneHeight()
+                #onScreenDebug.add("posDelta3",
+                #    render.getRelativeVector(
+                #        self.avatarNodePath,
+                #        self.avatarNodePath.getPosDelta(render)).pPrintValues())
+
+                #onScreenDebug.add("gravity",
+                #    self.gravity.getLocalVector().pPrintValues())
+                #onScreenDebug.add("priorParent",
+                #    self.priorParent.getLocalVector().pPrintValues())
+                #onScreenDebug.add("avatarViscosity",
+                #    "% 10.4f"%(self.avatarViscosity.getCoef(),))
+                #
+                #onScreenDebug.add("physObject pos",
+                #    physObject.getPosition().pPrintValues())
+                #onScreenDebug.add("physObject hpr",
+                #    physObject.getOrientation().getHpr().pPrintValues())
+                #onScreenDebug.add("physObject orien",
+                #    physObject.getOrientation().pPrintValues())
+
+                onScreenDebug.add("physObject vel",
+                    physObject.getVelocity().pPrintValues())
+                onScreenDebug.add("physObject len",
+                    "% 10.4f"%physObject.getVelocity().length())
+
+                #onScreenDebug.add("posDelta4",
+                #    self.priorParentNp.getRelativeVector(
+                #        render,
+                #        self.avatarNodePath.getPosDelta(render)).pPrintValues())
+
+                onScreenDebug.add("priorParent",
+                    self.priorParent.getLocalVector().pPrintValues())
+
+                #onScreenDebug.add("priorParent po",
+                #    self.priorParent.getVector(physObject).pPrintValues())
+
+                #onScreenDebug.add("__posDelta",
+                #    self.__oldPosDelta.pPrintValues())
+
+                onScreenDebug.add("contact",
+                    contact.pPrintValues())
+                #onScreenDebug.add("airborneHeight", "% 10.4f"%(
+                #    self.getAirborneHeight(),))
+
+                #onScreenDebug.add("__oldContact",
+                #    contact.pPrintValues())
+                #onScreenDebug.add("__oldAirborneHeight", "% 10.4f"%(
+                #    self.getAirborneHeight(),))
+        airborneHeight = self.getAirborneHeight()
         if airborneHeight > self.highMark:
             self.highMark = airborneHeight
             if __debug__:
                 onScreenDebug.add("highMark", "% 10.4f"%(self.highMark,))
         #if airborneHeight < 0.1: #contact!=Vec3.zero():
-        if 1:
-            if (airborneHeight > self.avatarRadius*0.5
-                    or physObject.getVelocity().getZ() > 0.0
-                    ): # Check stair angles before changing this.
-                # ...the avatar is airborne (maybe a lot or a tiny amount).
-                self.isAirborne = 1
-            else:
-                # ...the avatar is very close to the ground (close enough to be
-                # considered on the ground).
-                if self.isAirborne and physObject.getVelocity().getZ() <= 0.0:
-                    # ...the avatar has landed.
-                    contactLength = contact.length()
-                    if contactLength>self.__hardLandingForce:
-                        #print "jumpHardLand"
-                        messenger.send("jumpHardLand")
-                    else:
-                        #print "jumpLand"
-                        messenger.send("jumpLand")
-                    self.priorParent.setVector(Vec3.zero())
-                    self.isAirborne = 0
-                elif jump:
-                    #print "jump"
-                    #self.__jumpButton=0
-                    messenger.send("jumpStart")
-                    if 0:
-                        # ...jump away from walls and with with the slope normal.
-                        jumpVec=Vec3(contact+Vec3.up())
-                        #jumpVec=Vec3(rotAvatarToPhys.xform(jumpVec))
-                        jumpVec.normalize()
-                    else:
-                        # ...jump straight up, even if next to a wall.
-                        jumpVec=Vec3.up()
-                    jumpVec*=self.avatarControlJumpForce
-                    physObject.addImpulse(Vec3(jumpVec))
-                    self.isAirborne = 1 # Avoid double impulse before fully airborne.
-                else:
-                    self.isAirborne = 0
-            if __debug__:
-                onScreenDebug.add("isAirborne", "%d"%(self.isAirborne,))
+        if (airborneHeight > self.avatarRadius*0.5
+                or physObject.getVelocity().getZ() > 0.0
+                ): # Check stair angles before changing this.
+            # ...the avatar is airborne (maybe a lot or a tiny amount).
+            self.isAirborne = 1
         else:
-            if contact!=Vec3.zero():
-                # ...the avatar has touched something (but might not be on the ground).
+            # ...the avatar is very close to the ground (close enough to be
+            # considered on the ground).
+            if self.isAirborne and physObject.getVelocity().getZ() <= 0.0:
+                # ...the avatar has landed.
                 contactLength = contact.length()
-                contact.normalize()
-                angle=contact.dot(Vec3.up())
-                if angle>self.__standableGround:
-                    # ...avatar is on standable ground.
-                    if self.__oldContact==Vec3.zero():
-                    #if self.__oldAirborneHeight > 0.1: #self.__oldContact==Vec3.zero():
-                        # ...avatar was airborne.
-                        self.jumpCount-=1
-                        if contactLength>self.__hardLandingForce:
-                            messenger.send("jumpHardLand")
-                        else:
-                            messenger.send("jumpLand")
-                    elif jump:
-                        self.jumpCount+=1
-                        #self.__jumpButton=0
-                        messenger.send("jumpStart")
-                        jump=Vec3(contact+Vec3.up())
-                        #jump=Vec3(rotAvatarToPhys.xform(jump))
-                        jump.normalize()
-                        jump*=self.avatarControlJumpForce
-                        physObject.addImpulse(Vec3(jump))
-
-        if contact!=self.__oldContact:
+                if contactLength>self.__hardLandingForce:
+                    #print "jumpHardLand"
+                    messenger.send("jumpHardLand")
+                else:
+                    #print "jumpLand"
+                    messenger.send("jumpLand")
+                self.priorParent.setVector(Vec3.zero())
+                self.isAirborne = 0
+            elif jump:
+                #print "jump"
+                #self.__jumpButton = 0
+                messenger.send("jumpStart")
+
+                ## ...jump away from walls and with with the slope normal.
+                #jumpVec=Vec3(contact+Vec3.up())
+                ##jumpVec=Vec3(rotAvatarToPhys.xform(jumpVec))
+                #jumpVec.normalize()
+
+                # ...jump straight up, even if next to a wall.
+                jumpVec=Vec3.up()
+
+                jumpVec *= self.avatarControlJumpForce
+                physObject.addImpulse(Vec3(jumpVec))
+                self.isAirborne = 1 # Avoid double impulse before fully airborne.
+            else:
+                self.isAirborne = 0
+        if __debug__:
+            onScreenDebug.add("isAirborne", "%d"%(self.isAirborne,))
+
+        if contact != self.__oldContact:
             # We must copy the vector to preserve it:
-            self.__oldContact=Vec3(contact)
-        self.__oldAirborneHeight=airborneHeight
+            self.__oldContact = Vec3(contact)
+        self.__oldAirborneHeight = airborneHeight
 
         moveToGround = Vec3.zero()
         if not self.useHeightRay or self.isAirborne:
@@ -741,7 +704,7 @@ class PhysicsWalker(DirectObject.DirectObject):
     if __debug__:
         def setupAvatarPhysicsIndicator(self):
             if self.wantDebugIndicator:
-                indicator=loader.loadModel('phase_5/models/props/dagger')
+                indicator = base.loader.loadModel('phase_5/models/props/dagger')
                 #self.walkControls.setAvatarPhysicsIndicator(indicator)
 
         def debugPrint(self, message):

+ 1 - 0
direct/src/controls/TwoDWalker.py

@@ -3,6 +3,7 @@ TwoDWalker.py is for controlling the avatars in a 2D scroller game environment.
 """
 
 from .GravityWalker import *
+from direct.showbase.MessengerGlobal import messenger
 from panda3d.core import ConfigVariableBool
 
 

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

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

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

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

+ 2 - 15
direct/src/directbase/TestStart.py

@@ -7,22 +7,9 @@ from direct.showbase import ShowBase
 base = ShowBase.ShowBase()
 
 # Put an axis in the world:
-loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
 
-if 0:
-    # Hack:
-    # Enable drive mode but turn it off, and reset the camera
-    # This is here because ShowBase sets up a drive interface, this
-    # can be removed if ShowBase is changed to not set that up.
-    base.useDrive()
-    base.disableMouse()
-    if base.mouseInterface:
-        base.mouseInterface.reparentTo(base.dataUnused)
-    if base.mouse2cam:
-        base.mouse2cam.reparentTo(base.dataUnused)
-    # end of hack.
-
-camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
+base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)
 base.camLens.setNearFar(1.0, 10000.0)
 

+ 5 - 17
direct/src/directbase/ThreeUpStart.py

@@ -5,25 +5,13 @@ from panda3d.core import *
 
 from direct.showbase.PythonUtil import *
 from direct.showbase import ThreeUpShow
-ThreeUpShow.ThreeUpShow()
+
+base = ThreeUpShow.ThreeUpShow()
 
 # Put an axis in the world:
-loader.loadModel("models/misc/xyzAxis").reparentTo(render)
-
-if 0:
-    # Hack:
-    # Enable drive mode but turn it off, and reset the camera
-    # This is here because ShowBase sets up a drive interface, this
-    # can be removed if ShowBase is changed to not set that up.
-    base.useDrive()
-    base.disableMouse()
-    if base.mouseInterface:
-        base.mouseInterface.reparentTo(base.dataUnused)
-    if base.mouse2cam:
-        base.mouse2cam.reparentTo(base.dataUnused)
-    # end of hack.
-
-camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+
+base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)
 base.camLens.setNearFar(1.0, 10000.0)
 

+ 17 - 16
direct/src/directdevices/DirectDeviceManager.py

@@ -14,7 +14,7 @@ class DirectDeviceManager(VrpnClient, DirectObject):
     def __init__(self, server = None):
 
         # Determine which server to use
-        if server != None:
+        if server is not None:
             # One given as constructor argument
             self.server = server
         else:
@@ -77,10 +77,10 @@ class DirectButtons(ButtonNode, DirectObject):
         return self.nodePath
 
     def __repr__(self):
-        str = self.name + ': '
+        string = self.name + ': '
         for val in self:
-            str = str + '%d' % val + ' '
-        return str
+            string = string + '%d' % val + ' '
+        return string
 
 class DirectAnalogs(AnalogNode, DirectObject):
     analogCount = 0
@@ -149,12 +149,12 @@ class DirectAnalogs(AnalogNode, DirectObject):
         aMin = self.analogMin
         center = self.analogCenter
         deadband = self.analogDeadband
-        range = self.analogRange
+
         # Zero out values in deadband
-        if (abs(rawValue-center) <= deadband):
+        if abs(rawValue - center) <= deadband:
             return 0.0
         # Clamp value between aMin and aMax and scale around center
-        if (rawValue >= center):
+        if rawValue >= center:
             # Convert positive values to range 0 to 1
             val = min(rawValue * sf, aMax)
             percentVal = ((val - (center + deadband))/
@@ -165,7 +165,7 @@ class DirectAnalogs(AnalogNode, DirectObject):
             percentVal = -((val - (center - deadband))/
                            float(aMin - (center - deadband)))
         # Normalize values to given minVal and maxVal range
-        return (((maxVal - minVal) * ((percentVal + 1)/2.0)) + minVal)
+        return ((maxVal - minVal) * ((percentVal + 1)/2.0)) + minVal
 
     def normalizeChannel(self, chan, minVal = -1, maxVal = 1, sf = 1.0):
         try:
@@ -180,13 +180,14 @@ class DirectAnalogs(AnalogNode, DirectObject):
         return self.nodePath
 
     def __repr__(self):
-        str = self.name + ': '
+        string = self.name + ': '
         for val in self:
-            str = str + '%.3f' % val + ' '
-        return str
+            string = string + '%.3f' % val + ' '
+        return string
 
 class DirectTracker(TrackerNode, DirectObject):
     trackerCount = 0
+
     def __init__(self, vrpnClient, device):
         # Keep track of number of trackers created
         DirectTracker.trackerCount += 1
@@ -257,10 +258,10 @@ class DirectDials(DialNode, DirectObject):
         return self.nodePath
 
     def __repr__(self):
-        str = self.name + ': '
+        string = self.name + ': '
         for i in range(self.getNumDials()):
-            str = str + '%.3f' % self[i] + ' '
-        return str
+            string = string + '%.3f' % self[i] + ' '
+        return string
 
 class DirectTimecodeReader(AnalogNode, DirectObject):
     timecodeReaderCount = 0
@@ -316,5 +317,5 @@ class DirectTimecodeReader(AnalogNode, DirectObject):
                 self.totalSeconds)
 
     def __repr__(self):
-        str = ('%s: %d:%d:%d:%d' % ((self.name,) + self.getTime()[:-1]))
-        return str
+        string = ('%s: %d:%d:%d:%d' % ((self.name,) + self.getTime()[:-1]))
+        return string

+ 3 - 6
direct/src/directdevices/DirectFastrak.py

@@ -1,14 +1,12 @@
 """ Class used to create and control radamec device """
-from math import *
 from direct.showbase.DirectObject import DirectObject
+from direct.task.Task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from .DirectDeviceManager import *
 
 from direct.directnotify import DirectNotifyGlobal
 
-"""
-TODO:
-Handle interaction between widget, followSelectedTask and updateTask
-"""
+#TODO: Handle interaction between widget, followSelectedTask and updateTask
 
 # ANALOGS
 NULL_AXIS = -1
@@ -65,4 +63,3 @@ class DirectFastrak(DirectObject):
                                3.280839895013123 * pos[1],
                                3.280839895013123 * pos[0])
         self.notify.debug("Tracker(%d) Pos = %s" % (self.deviceNo, repr(self.trackerPos)))
-

+ 13 - 16
direct/src/directdevices/DirectJoybox.py

@@ -4,12 +4,10 @@ from .DirectDeviceManager import *
 from direct.directtools.DirectUtil import *
 from direct.gui import OnscreenText
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 import math
 
-"""
-TODO:
-Handle interaction between widget, followSelectedTask and updateTask
-"""
+#TODO: Handle interaction between widget, followSelectedTask and updateTask
 
 # BUTTONS
 L_STICK = 0
@@ -39,10 +37,11 @@ class DirectJoybox(DirectObject):
     joyboxCount = 0
     xyzMultiplier = 1.0
     hprMultiplier = 1.0
+
     def __init__(self, device = 'CerealBox', nodePath = base.direct.camera,
                  headingNP = base.direct.camera):
         # See if device manager has been initialized
-        if base.direct.deviceManager == None:
+        if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()
         # Set name
         DirectJoybox.joyboxCount += 1
@@ -172,12 +171,12 @@ class DirectJoybox(DirectObject):
         for chan in range(len(self.analogs)):
             val = self.analogs.getControlState(chan)
             # Zero out values in deadband
-            if (val < 0):
+            if val < 0:
                 val = min(val + ANALOG_DEADBAND, 0.0)
             else:
                 val = max(val - ANALOG_DEADBAND, 0.0)
             # Scale up rotating knob values
-            if (chan == L_TWIST) or (chan == R_TWIST):
+            if chan == L_TWIST or chan == R_TWIST:
                 val *= 3.0
             # Now clamp value between minVal and maxVal
             val = CLAMP(val, JOYBOX_MIN, JOYBOX_MAX)
@@ -240,7 +239,7 @@ class DirectJoybox(DirectObject):
 
     def joyboxFly(self):
         # Do nothing if no nodePath selected
-        if self.nodePath == None:
+        if self.nodePath is None:
             return
         hprScale = ((self.aList[L_SLIDE] + 1.0) *
                     50.0 * DirectJoybox.hprMultiplier)
@@ -264,14 +263,14 @@ class DirectJoybox(DirectObject):
         # if we are using a heading nodepath, we want
         # to drive in the direction we are facing,
         # however, we don't want the z component to change
-        if (self.useHeadingNP and self.headingNP != None):
+        if self.useHeadingNP and self.headingNP is not None:
             oldZ = pos.getZ()
             pos = self.nodePath.getRelativeVector(self.headingNP,
                                                   pos)
             pos.setZ(oldZ)
             # if we are using a heading NP we might want to rotate
             # in place around that NP
-            if (self.rotateInPlace):
+            if self.rotateInPlace:
                 parent = self.nodePath.getParent()
                 self.floatingNP.reparentTo(parent)
                 self.floatingNP.setPos(self.headingNP,0,0,0)
@@ -389,7 +388,7 @@ class DirectJoybox(DirectObject):
 
     def spaceFly(self):
         # Do nothing if no nodePath selected
-        if self.nodePath == None:
+        if self.nodePath is None:
             return
         hprScale = (self.normalizeChannel(L_SLIDE, 0.1, 100) *
                     DirectJoybox.hprMultiplier)
@@ -408,7 +407,7 @@ class DirectJoybox(DirectObject):
 
     def planetFly(self):
         # Do nothing if no nodePath selected
-        if self.nodePath == None:
+        if self.nodePath is None:
             return
         hprScale = (self.normalizeChannel(L_SLIDE, 0.1, 100) *
                     DirectJoybox.hprMultiplier)
@@ -459,7 +458,7 @@ class DirectJoybox(DirectObject):
 
     def orbitFly(self):
         # Do nothing if no nodePath selected
-        if self.nodePath == None:
+        if self.nodePath is None:
             return
         hprScale = (self.normalizeChannel(L_SLIDE, 0.1, 100) *
                     DirectJoybox.hprMultiplier)
@@ -502,7 +501,7 @@ class DirectJoybox(DirectObject):
     # correct the ranges of the two twist axes of the joybox.
     def normalizeChannel(self, chan, minVal = -1, maxVal = 1):
         try:
-            if (chan == L_TWIST) or (chan == R_TWIST):
+            if chan == L_TWIST or chan == R_TWIST:
                 # These channels have reduced range
                 return self.analogs.normalize(
                     self.analogs.getControlState(chan), minVal, maxVal, 3.0)
@@ -511,5 +510,3 @@ class DirectJoybox(DirectObject):
                     self.analogs.getControlState(chan), minVal, maxVal)
         except IndexError:
             return 0.0
-
-

+ 12 - 13
direct/src/directdevices/DirectRadamec.py

@@ -1,15 +1,13 @@
 """ Class used to create and control radamec device """
 from math import *
 from direct.showbase.DirectObject import DirectObject
+from direct.task.Task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from .DirectDeviceManager import *
 
 from direct.directnotify import DirectNotifyGlobal
 
-
-"""
-TODO:
-Handle interaction between widget, followSelectedTask and updateTask
-"""
+#TODO: Handle interaction between widget, followSelectedTask and updateTask
 
 # ANALOGS
 RAD_PAN = 0
@@ -23,7 +21,7 @@ class DirectRadamec(DirectObject):
 
     def __init__(self, device = 'Analog0', nodePath = None):
         # See if device manager has been initialized
-        if base.direct.deviceManager == None:
+        if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()
         # Set name
         self.name = 'Radamec-' + repr(DirectRadamec.radamecCount)
@@ -73,11 +71,12 @@ class DirectRadamec(DirectObject):
     # Normalize to the range [-minVal, maxVal] based on some hard-coded
     # max/min numbers of the Radamec device
     def normalizeChannel(self, chan, minVal = -1, maxVal = 1):
-        try:
-            maxRange = self.maxRange[chan]
-            minRange = self.minRange[chan]
-        except IndexError:
+        if chan < 0 or chan >= min(len(self.maxRange), len(self.minRange)):
             raise RuntimeError("can't normalize this channel (channel %d)" % chan)
-        range = maxRange - minRange
-        clampedVal = CLAMP(self.aList[chan], minRange, maxRange)
-        return ((maxVal - minVal) * (clampedVal - minRange) / range) + minVal
+
+        maxRange = self.maxRange[chan]
+        minRange = self.minRange[chan]
+
+        diff = maxRange - minRange
+        clampedVal = max(min(self.aList[chan], maxRange), maxRange)
+        return ((maxVal - minVal) * (clampedVal - minRange) / diff) + minVal

+ 3 - 3
direct/src/directnotify/DirectNotify.py

@@ -41,17 +41,17 @@ class DirectNotify:
         """getCategory(self, string)
         Return the category with given name if present, None otherwise
         """
-        return (self.__categories.get(categoryName, None))
+        return self.__categories.get(categoryName, None)
 
     def newCategory(self, categoryName, logger=None):
         """newCategory(self, string)
         Make a new notify category named categoryName. Return new category
         if no such category exists, else return existing category
         """
-        if (categoryName not in self.__categories):
+        if categoryName not in self.__categories:
             self.__categories[categoryName] = Notifier.Notifier(categoryName, logger)
             self.setDconfigLevel(categoryName)
-        return (self.getCategory(categoryName))
+        return self.getCategory(categoryName)
 
     def setDconfigLevel(self, categoryName):
         """

+ 11 - 26
direct/src/directnotify/Logger.py

@@ -4,6 +4,7 @@
 import time
 import math
 
+
 class Logger:
     def __init__(self, fileName="log"):
         """
@@ -14,18 +15,17 @@ class Logger:
         self.__logFile = None
         self.__logFileName = fileName
 
-    def setTimeStamp(self, bool):
+    def setTimeStamp(self, enable):
         """
         Toggle time stamp printing with log entries on and off
         """
-        self.__timeStamp = bool
+        self.__timeStamp = enable
 
     def getTimeStamp(self):
         """
         Return whether or not we are printing time stamps with log entries
         """
-        return(self.__timeStamp)
-
+        return self.__timeStamp
 
     # logging control
 
@@ -38,13 +38,12 @@ class Logger:
     def log(self, entryString):
         """log(self, string)
         Print the given string to the log file"""
-        if (self.__logFile == None):
+        if self.__logFile is None:
             self.__openLogFile()
-        if (self.__timeStamp):
+        if self.__timeStamp:
             self.__logFile.write(self.__getTimeStamp())
         self.__logFile.write(entryString + '\n')
 
-
     # logging functions
 
     def __openLogFile(self):
@@ -61,7 +60,7 @@ class Logger:
         """
         Close the error/warning output file
         """
-        if (self.__logFile != None):
+        if self.__logFile is not None:
             self.__logFile.close()
 
     def __getTimeStamp(self):
@@ -70,22 +69,8 @@ class Logger:
         """
         t = time.time()
         dt = t - self.__startTime
-        if (dt >= 86400):
-            days = int(math.floor(dt/86400))
-            dt = dt%86400
-        else:
-            days = 0
-        if (dt >= 3600):
-            hours = int(math.floor(dt/3600))
-            dt = dt%3600
-        else:
-            hours = 0
-        if (dt >= 60):
-            minutes = int(math.floor(dt/60))
-            dt = dt%60
-        else:
-            minutes = 0
+        days, dt = divmod(dt, 86400)
+        hours, dt = divmod(dt, 3600)
+        minutes, dt = divmod(dt, 60)
         seconds = int(math.ceil(dt))
-        return("%02d:%02d:%02d:%02d: " % (days, hours, minutes, seconds))
-
-
+        return "%02d:%02d:%02d:%02d: " % (days, hours, minutes, seconds)

+ 11 - 12
direct/src/directnotify/Notifier.py

@@ -32,7 +32,7 @@ class Notifier:
         """
         self.__name = name
 
-        if (logger==None):
+        if logger is None:
             self.__logger = defaultLogger
         else:
             self.__logger = logger
@@ -144,17 +144,17 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert myNotify.warning("blah")
 
-    def setWarning(self, bool):
+    def setWarning(self, enable):
         """
         Enable/Disable the printing of warning messages
         """
-        self.__warning = bool
+        self.__warning = enable
 
     def getWarning(self):
         """
         Return whether the printing of warning messages is on or off
         """
-        return(self.__warning)
+        return self.__warning
 
     # debug funcs
     def debug(self, debugString):
@@ -171,11 +171,11 @@ class Notifier:
             self.__print(string)
         return 1 # to allow assert myNotify.debug("blah")
 
-    def setDebug(self, bool):
+    def setDebug(self, enable):
         """
         Enable/Disable the printing of debug messages
         """
-        self.__debug = bool
+        self.__debug = enable
 
     def getDebug(self):
         """
@@ -204,11 +204,11 @@ class Notifier:
         """
         return self.__info
 
-    def setInfo(self, bool):
+    def setInfo(self, enable):
         """
         Enable/Disable informational message  printing
         """
-        self.__info = bool
+        self.__info = enable
 
     # log funcs
     def __log(self, logEntry):
@@ -222,13 +222,13 @@ class Notifier:
         """
         Return 1 if logging enabled, 0 otherwise
         """
-        return (self.__logging)
+        return self.__logging
 
-    def setLogging(self, bool):
+    def setLogging(self, enable):
         """
         Set the logging flag to int (1=on, 0=off)
         """
-        self.__logging = bool
+        self.__logging = enable
 
     def __print(self, string):
         """
@@ -297,4 +297,3 @@ class Notifier:
             self.__log(string)
             self.__print(string)
         return 1 # to allow assert self.notify.debugCall("blah")
-

+ 1 - 1
direct/src/directnotify/RotatingLog.py

@@ -4,7 +4,7 @@ import time
 
 class RotatingLog:
     """
-    A file() (or open()) replacement that will automatically open and write
+    An `open()` replacement that will automatically open and write
     to a new file if the prior file is too large or after a time interval.
     """
 

+ 22 - 17
direct/src/directscripts/eggcacher.py

@@ -8,7 +8,9 @@
 #
 ##############################################################################
 
-import os,sys,gc
+import os
+import sys
+import gc
 from panda3d.core import *
 
 class EggCacher:
@@ -18,7 +20,7 @@ class EggCacher:
         self.bamcache = BamCache.getGlobalPtr()
         self.pandaloader = Loader()
         self.loaderopts = LoaderOptions(LoaderOptions.LF_no_ram_cache)
-        if (self.bamcache.getActive() == 0):
+        if not self.bamcache.getActive():
             print("The model cache is not currently active.")
             print("You must set a model-cache-dir in your config file.")
             sys.exit(1)
@@ -29,42 +31,44 @@ class EggCacher:
     def parseArgs(self, args):
         self.concise = 0
         self.pzkeep = 0
-        while len(args):
-            if (args[0]=="--concise"):
+        while len(args) > 0:
+            if args[0] == "--concise":
                 self.concise = 1
                 args = args[1:]
-            elif (args[0]=="--pzkeep"):
+            elif args[0] == "--pzkeep":
                 self.pzkeep = 1
                 args = args[1:]
             else:
                 break
-        if (len(args) < 1):
+        if len(args) < 1:
             print("Usage: eggcacher options file-or-directory")
             sys.exit(1)
         self.paths = args
 
     def scanPath(self, eggs, path):
-        if (os.path.exists(path)==0):
+        if not os.path.exists(path):
             print("No such file or directory: " + path)
             return
-        if (os.path.isdir(path)):
+        if os.path.isdir(path):
             for f in os.listdir(path):
                 self.scanPath(eggs, os.path.join(path,f))
             return
-        if (path.endswith(".egg")):
+        if path.endswith(".egg"):
             size = os.path.getsize(path)
             eggs.append((path,size))
             return
-        if (path.endswith(".egg.pz") or path.endswith(".egg.gz")):
+        if path.endswith(".egg.pz") or path.endswith(".egg.gz"):
             size = os.path.getsize(path)
-            if (self.pzkeep): eggs.append((path,size))
-            else: eggs.append((path[:-3],size))
+            if self.pzkeep:
+                eggs.append((path, size))
+            else:
+                eggs.append((path[:-3], size))
 
     def scanPaths(self, paths):
         eggs = []
         for path in paths:
-            abs = os.path.abspath(path)
-            self.scanPath(eggs,path)
+            #abs = os.path.abspath(path)
+            self.scanPath(eggs, path)
         return eggs
 
     def processFiles(self, files):
@@ -72,15 +76,16 @@ class EggCacher:
         for (path, size) in files:
             total += size
         progress = 0
-        for (path,size) in files:
+        for (path, size) in files:
             fn = Filename.fromOsSpecific(path)
             cached = self.bamcache.lookup(fn, "bam")
             percent = (progress * 100) / total
             report = path
-            if (self.concise): report = os.path.basename(report)
+            if self.concise:
+                report = os.path.basename(report)
             print("Preprocessing Models %2d%% %s" % (percent, report))
             sys.stdout.flush()
-            if (cached) and (cached.hasData()==0):
+            if cached and not cached.hasData():
                 self.pandaloader.loadSync(fn, self.loaderopts)
             gc.collect()
             ModelPool.releaseAllModels()

+ 2 - 2
direct/src/directscripts/extract_docs.py

@@ -10,8 +10,8 @@ from __future__ import print_function
 __all__ = []
 
 import os
-from distutils import sysconfig
-import panda3d, pandac
+import panda3d
+import pandac
 from panda3d.interrogatedb import *
 
 

+ 0 - 986
direct/src/directscripts/gendocs.py

@@ -1,986 +0,0 @@
-########################################################################
-#
-# Documentation generator for panda.
-#
-# How to use this module:
-#
-#   from direct.directscripts import gendocs
-#   gendocs.generate(version, indirlist, directdirlist, docdir, header, footer, urlprefix, urlsuffix)
-#
-#   - version is the panda version number
-#
-#   - indirlist is the name of a directory, or a list of directories,
-#     containing the "xxx.in" files that interrogate generates.  No
-#     slash at end.
-#
-#   - directdirlist is the name of a directory, or a list of
-#     directories, containing the source code for "direct," as well as
-#     for other Python-based trees that should be included in the
-#     documentation pages.  No slash at end.
-#
-#   - docdir is the name of a directory into which HTML files
-#     will be emitted.  No slash at end.
-#
-#   - header is a string that will be placed at the front of
-#     every HTML page.
-#
-#   - footer is a string that will be placed at the end of
-#     every HTML page.
-#
-#   - urlprefix is a string that will be appended to the front of
-#     every URL.
-#
-#   - urlsuffix is a string that will be appended to the end of
-#     every URL.
-#
-########################################################################
-#
-# The major subsystems are:
-#
-# * The module that loads interrogate databases.
-#
-# * The module that loads python parse-trees.
-#
-# * The "code database", which provides a single access point
-#   for both interrogate databases and python parse trees.
-#
-# * The HTML generator.
-#
-########################################################################
-
-import os, sys, parser, symbol, token, re
-
-########################################################################
-#
-# assorted utility functions
-#
-########################################################################
-
-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_]+")
-FILEHEADER = re.compile(
-r"""^// Filename: [a-zA-Z.]+
-// Created by:  [a-zA-Z. ()0-9]+(
-//)?
-////////////////////////////////////////////////////////////////////
-//
-// 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."
-//
-////////////////////////////////////////////////////////////////////""")
-
-def readFile(fn):
-    try:
-        srchandle = open(fn, "r")
-        data = srchandle.read()
-        srchandle.close()
-        return data
-    except:
-        sys.exit("Cannot read "+fn)
-
-def writeFile(wfile, data):
-    try:
-        dsthandle = open(wfile, "wb")
-        dsthandle.write(data)
-        dsthandle.close()
-    except:
-        sys.exit("Cannot write "+wfile)
-
-def writeFileLines(wfile, lines):
-    try:
-        dsthandle = open(wfile, "wb")
-        for x in lines:
-            dsthandle.write(x)
-            dsthandle.write("\n")
-        dsthandle.close()
-    except:
-        sys.exit("Cannot write "+wfile)
-
-def findFiles(dirlist, ext, ign, list):
-    if isinstance(dirlist, str):
-        dirlist = [dirlist]
-    for dir in dirlist:
-        for file in os.listdir(dir):
-            full = dir + "/" + file
-            if full not in ign and file not in ign:
-                if (os.path.isfile(full)):
-                    if (file.endswith(ext)):
-                        list.append(full)
-                elif (os.path.isdir(full)):
-                    findFiles(full, ext, ign, list)
-
-def pathToModule(result):
-    if (result[-3:]==".py"): result=result[:-3]
-    result = result.replace("/src/","/")
-    result = result.replace("/",".")
-    return result
-
-def textToHTML(comment, sep, delsection=None):
-    sections = [""]
-    included = {}
-    for line in comment.split("\n"):
-        line = line.lstrip(" ").lstrip(sep).lstrip(" ").rstrip("\r").rstrip(" ")
-        if (line == ""):
-            sections.append("")
-        elif (line[0]=="*") or (line[0]=="-"):
-            sections.append(line)
-            sections.append("")
-        elif (SECHEADER.match(line)):
-            sections.append(line)
-        else:
-            sections[-1] = sections[-1] + " " + line
-    total = ""
-    for sec in sections:
-        if (sec != ""):
-            sec = sec.replace("&","&amp;")
-            sec = sec.replace("<","&lt;")
-            sec = sec.replace(">","&gt;")
-            sec = sec.replace("  "," ")
-            sec = sec.replace("  "," ")
-            if (delsection != None) and (delsection.match(sec)):
-                included[sec] = 1
-            if sec not in included:
-                included[sec] = 1
-                total = total + sec + "<br>\n"
-    return total
-
-def linkTo(link, text):
-    return '<a href="' + link + '">' + text + '</a>'
-
-def convertToPythonFn(fn):
-    result = ""
-    lastc = 0
-    for c in fn:
-        if (c!="_"):
-            if (lastc=="_"):
-                result = result + c.upper()
-            else:
-                result = result + c
-        lastc = c
-    return result
-
-def removeFileLicense(content):
-    # Removes the license part at the top of a file.
-    return re.sub(FILEHEADER, "", content).strip()
-
-########################################################################
-#
-# Interrogate Database Tokenizer
-#
-########################################################################
-
-class InterrogateTokenizer:
-    """
-    A big string, with a "parse pointer", and routines to
-    extract integers and strings.  The token syntax is that
-    used by interrogate databases.
-    """
-
-    def __init__(self, fn):
-        self.fn = fn
-        self.pos = 0
-        self.data = readFile(fn)
-
-    def readint(self):
-        neg = 0
-        while (self.data[self.pos].isspace()):
-            self.pos += 1
-        if (self.data[self.pos] == "-"):
-            neg = 1
-            self.pos += 1
-        if (self.data[self.pos].isdigit()==0):
-            print("File position " + str(self.pos))
-            print("Text: " + self.data[self.pos:self.pos+50])
-            sys.exit("Syntax error in interrogate file format 0")
-        value = 0
-        while (self.data[self.pos].isdigit()):
-            value = value*10 + int(self.data[self.pos])
-            self.pos += 1
-        if (neg): value = -value
-        return value
-
-    def readstring(self):
-        length = self.readint()
-        if (self.data[self.pos].isspace()==0):
-            sys.exit("Syntax error in interrogate file format 1")
-        self.pos += 1
-        body = self.data[self.pos:self.pos+length]
-        if (len(body) != length):
-            sys.exit("Syntax error in interrogate file format 2")
-        self.pos += length
-        return body
-
-########################################################################
-#
-# Interrogate Database Storage/Parsing
-#
-########################################################################
-
-def parseInterrogateIntVec(tokzr):
-    length = tokzr.readint()
-    result = []
-    for i in range(length):
-        result.append(tokzr.readint())
-    return result
-
-class InterrogateFunction:
-    def __init__(self, tokzr, db):
-        self.db = db
-        self.index = tokzr.readint()
-        self.componentname = tokzr.readstring()
-        self.flags = tokzr.readint()
-        self.classindex = tokzr.readint()
-        self.scopedname = tokzr.readstring()
-        self.cwrappers = parseInterrogateIntVec(tokzr)
-        self.pythonwrappers = parseInterrogateIntVec(tokzr)
-        self.comment = tokzr.readstring()
-        self.prototype = tokzr.readstring()
-
-class InterrogateEnumValue:
-    def __init__(self, tokzr):
-        self.name = tokzr.readstring()
-        self.scopedname = tokzr.readstring()
-        self.value = tokzr.readint()
-
-class InterrogateDerivation:
-    def __init__(self, tokzr):
-        self.flags = tokzr.readint()
-        self.base = tokzr.readint()
-        self.upcast = tokzr.readint()
-        self.downcast = tokzr.readint()
-
-class InterrogateType:
-    def __init__(self, tokzr, db):
-        self.db = db
-        self.index = tokzr.readint()
-        self.componentname = tokzr.readstring()
-        self.flags = tokzr.readint()
-        self.scopedname = tokzr.readstring()
-        self.truename = tokzr.readstring()
-        self.outerclass = tokzr.readint()
-        self.atomictype = tokzr.readint()
-        self.wrappedtype = tokzr.readint()
-        self.constructors = parseInterrogateIntVec(tokzr)
-        self.destructor = tokzr.readint()
-        self.elements = parseInterrogateIntVec(tokzr)
-        self.methods = parseInterrogateIntVec(tokzr)
-        self.casts = parseInterrogateIntVec(tokzr)
-        self.derivations = []
-        nderivations = tokzr.readint()
-        for i in range(nderivations):
-            self.derivations.append(InterrogateDerivation(tokzr))
-        self.enumvalues = []
-        nenumvalues = tokzr.readint()
-        for i in range(nenumvalues):
-            self.enumvalues.append(InterrogateEnumValue(tokzr))
-        self.nested = parseInterrogateIntVec(tokzr)
-        self.comment = tokzr.readstring()
-
-class InterrogateParameter:
-    def __init__(self, tokzr):
-        self.name = tokzr.readstring()
-        self.parameterflags = tokzr.readint()
-        self.type = tokzr.readint()
-
-class InterrogateWrapper:
-    def __init__(self, tokzr, db):
-        self.db = db
-        self.index = tokzr.readint()
-        self.componentname = tokzr.readstring()
-        self.flags = tokzr.readint()
-        self.function = tokzr.readint()
-        self.returntype = tokzr.readint()
-        self.returnvaluedestructor = tokzr.readint()
-        self.uniquename = tokzr.readstring()
-        self.parameters = []
-        nparameters = tokzr.readint()
-        for i in range(nparameters):
-            self.parameters.append(InterrogateParameter(tokzr))
-
-class InterrogateDatabase:
-    def __init__(self, tokzr):
-        self.fn = tokzr.fn
-        self.magic = tokzr.readint()
-        version1 = tokzr.readint()
-        version2 = tokzr.readint()
-        if (version1 != 2) or (version2 != 2):
-            sys.exit("This program only understands interrogate file format 2.2")
-        self.library = tokzr.readstring()
-        self.libhash = tokzr.readstring()
-        self.module = tokzr.readstring()
-        self.functions = {}
-        self.wrappers = {}
-        self.types = {}
-        self.namedtypes = {}
-        count_functions = tokzr.readint()
-        for i in range(count_functions):
-            fn = InterrogateFunction(tokzr, self)
-            self.functions[fn.index] = fn
-        count_wrappers = tokzr.readint()
-        for i in range(count_wrappers):
-            wr = InterrogateWrapper(tokzr, self)
-            self.wrappers[wr.index] = wr
-        count_types = tokzr.readint()
-        for i in range(count_types):
-            tp = InterrogateType(tokzr, self)
-            self.types[tp.index] = tp
-            self.namedtypes[tp.scopedname] = tp
-
-########################################################################
-#
-# Pattern Matching for Python Parse Trees
-#
-########################################################################
-
-def printTree(tree, indent):
-    spacing = "                                                        "[:indent]
-    if isinstance(tree, tuple) and isinstance(tree[0], int):
-        if tree[0] in symbol.sym_name:
-            for i in range(len(tree)):
-                if (i==0):
-                    print(spacing + "(symbol." + symbol.sym_name[tree[0]] + ",")
-                else:
-                    printTree(tree[i], indent+1)
-            print(spacing + "),")
-        elif tree[0] in token.tok_name:
-            print(spacing + "(token." + token.tok_name[tree[0]] + ", '" + tree[1] + "'),")
-        else:
-            print(spacing + str(tree))
-    else:
-        print(spacing + str(tree))
-
-
-COMPOUND_STMT_PATTERN = (
-    symbol.stmt,
-    (symbol.compound_stmt, ['compound'])
-    )
-
-
-DOCSTRING_STMT_PATTERN = (
-    symbol.stmt,
-    (symbol.simple_stmt,
-     (symbol.small_stmt,
-      (symbol.expr_stmt,
-       (symbol.testlist,
-        (symbol.test,
-         (symbol.or_test,
-           (symbol.and_test,
-            (symbol.not_test,
-             (symbol.comparison,
-              (symbol.expr,
-               (symbol.xor_expr,
-                (symbol.and_expr,
-                 (symbol.shift_expr,
-                  (symbol.arith_expr,
-                   (symbol.term,
-                    (symbol.factor,
-                     (symbol.power,
-                      (symbol.atom,
-                       (token.STRING, ['docstring'])
-                       ))))))))))))))))),
-     (token.NEWLINE, '')
-     ))
-
-DERIVATION_PATTERN = (
-    symbol.test,
-    (symbol.or_test,
-     (symbol.and_test,
-      (symbol.not_test,
-       (symbol.comparison,
-        (symbol.expr,
-         (symbol.xor_expr,
-          (symbol.and_expr,
-           (symbol.shift_expr,
-            (symbol.arith_expr,
-             (symbol.term,
-              (symbol.factor,
-               (symbol.power,
-                (symbol.atom,
-                 (token.NAME, ['classname'])
-   ))))))))))))))
-
-ASSIGNMENT_STMT_PATTERN = (
-    symbol.stmt,
-    (symbol.simple_stmt,
-     (symbol.small_stmt,
-      (symbol.expr_stmt,
-       (symbol.testlist,
-        (symbol.test,
-         (symbol.or_test,
-           (symbol.and_test,
-            (symbol.not_test,
-             (symbol.comparison,
-              (symbol.expr,
-               (symbol.xor_expr,
-                (symbol.and_expr,
-                 (symbol.shift_expr,
-                  (symbol.arith_expr,
-                   (symbol.term,
-                    (symbol.factor,
-                     (symbol.power,
-                      (symbol.atom,
-                       (token.NAME, ['varname']),
-       ))))))))))))))),
-       (token.EQUAL, '='),
-       (symbol.testlist, ['rhs']))),
-     (token.NEWLINE, ''),
-   ))
-
-class ParseTreeInfo:
-    docstring = ''
-    name = ''
-
-    def __init__(self, tree, name, file):
-        """
-        The code can be a string (in which case it is parsed), or it
-        can be in parse tree form already.
-        """
-        self.name = name
-        self.file = file
-        self.class_info = {}
-        self.function_info = {}
-        self.assign_info = {}
-        self.derivs = {}
-        if isinstance(tree, str):
-            try:
-                tree = parser.suite(tree+"\n").totuple()
-                if (tree):
-                    found, vars = self.match(DOCSTRING_STMT_PATTERN, tree[1])
-                    if found:
-                        self.docstring = vars["docstring"]
-            except:
-                print("CAUTION --- Parse failed: " + name)
-        if isinstance(tree, tuple):
-            self.extract_info(tree)
-
-    def match(self, pattern, data, vars=None):
-        """
-        pattern
-            Pattern to match against, possibly containing variables.
-        data
-            Data to be checked and against which variables are extracted.
-        vars
-            Dictionary of variables which have already been found.  If not
-            provided, an empty dictionary is created.
-
-        The `pattern' value may contain variables of the form ['varname']
-        which are allowed to parseTreeMatch anything.  The value that is
-        parseTreeMatched is returned as part of a dictionary which maps
-        'varname' to the parseTreeMatched value.  'varname' is not required
-        to be a string object, but using strings makes patterns and the code
-        which uses them more readable.  This function returns two values: a
-        boolean indicating whether a parseTreeMatch was found and a
-        dictionary mapping variable names to their associated values.
-        """
-        if vars is None:
-            vars = {}
-        if type(pattern) is list:       # 'variables' are ['varname']
-            vars[pattern[0]] = data
-            return 1, vars
-        if type(pattern) is not tuple:
-            return (pattern == data), vars
-        if len(data) != len(pattern):
-            return 0, vars
-        for pattern, data in map(None, pattern, data):
-            same, vars = self.match(pattern, data, vars)
-            if not same:
-                break
-        return same, vars
-
-    def extract_info(self, tree):
-        # extract docstring
-        found = 0
-        if len(tree) == 2:
-            found, vars = self.match(DOCSTRING_STMT_PATTERN[1], tree[1])
-        elif len(tree) >= 4:
-            found, vars = self.match(DOCSTRING_STMT_PATTERN, tree[3])
-        if found:
-            self.docstring = eval(vars['docstring'])
-        # discover inner definitions
-        for node in tree[1:]:
-            found, vars = self.match(ASSIGNMENT_STMT_PATTERN, node)
-            if found:
-                self.assign_info[vars['varname']] = 1
-            found, vars = self.match(COMPOUND_STMT_PATTERN, node)
-            if found:
-                cstmt = vars['compound']
-                if cstmt[0] == symbol.funcdef:
-                    name = cstmt[2][1]
-                    # Workaround for a weird issue with static and classmethods
-                    if name == "def":
-                        name = cstmt[3][1]
-                        self.function_info[name] = ParseTreeInfo(cstmt and cstmt[-1] or None, name, self.file)
-                        self.function_info[name].prototype = self.extract_tokens("", cstmt[4])
-                    else:
-                        self.function_info[name] = ParseTreeInfo(cstmt and cstmt[-1] or None, name, self.file)
-                        self.function_info[name].prototype = self.extract_tokens("", cstmt[3])
-                elif cstmt[0] == symbol.classdef:
-                    name = cstmt[2][1]
-                    self.class_info[name] = ParseTreeInfo(cstmt and cstmt[-1] or None, name, self.file)
-                    self.extract_derivs(self.class_info[name], cstmt)
-
-    def extract_derivs(self, classinfo, tree):
-        if (len(tree)==8):
-            derivs = tree[4]
-            for deriv in derivs[1:]:
-                found, vars = self.match(DERIVATION_PATTERN, deriv)
-                if (found):
-                    classinfo.derivs[vars["classname"]] = 1
-
-    def extract_tokens(self, str, tree):
-        if (isinstance(tree, tuple)):
-            if tree[0] in token.tok_name:
-                str = str + tree[1]
-                if (tree[1]==","): str=str+" "
-            elif tree[0] in symbol.sym_name:
-                for sub in tree[1:]:
-                    str = self.extract_tokens(str, sub)
-        return str
-
-########################################################################
-#
-# The code database contains:
-#
-#  - a list of InterrogateDatabase objects representing C++ modules.
-#  - a list of ParseTreeInfo objects representing python modules.
-#
-# Collectively, these make up all the data about all the code.
-#
-########################################################################
-
-class CodeDatabase:
-    def __init__(self, cxxlist, pylist):
-        self.types = {}
-        self.funcs = {}
-        self.goodtypes = {}
-        self.funcExports = {}
-        self.typeExports = {}
-        self.varExports = {}
-        self.globalfn = []
-        self.formattedprotos = {}
-        print("Reading C++ source files")
-        for cxx in cxxlist:
-            tokzr = InterrogateTokenizer(cxx)
-            idb = InterrogateDatabase(tokzr)
-            for type in idb.types.values():
-                if (type.flags & 8192) or type.scopedname not in self.types:
-                    self.types[type.scopedname] = type
-                if (type.flags & 8192) and (type.atomictype == 0) and (type.scopedname.count(" ")==0) and (type.scopedname.count(":")==0):
-                    self.goodtypes[type.scopedname] = type
-                    self.typeExports.setdefault("pandac.PandaModules", []).append(type.scopedname)
-            for func in idb.functions.values():
-                type = idb.types.get(func.classindex)
-                func.pyname = convertToPythonFn(func.componentname)
-                if (type == None):
-                    self.funcs["GLOBAL."+func.pyname] = func
-                    self.globalfn.append("GLOBAL."+func.pyname)
-                    self.funcExports.setdefault("pandac.PandaModules", []).append(func.pyname)
-                else:
-                    self.funcs[type.scopedname+"."+func.pyname] = func
-        print("Reading Python sources files")
-        for py in pylist:
-            pyinf = ParseTreeInfo(readFile(py), py, py)
-            mod = pathToModule(py)
-            for type in pyinf.class_info.keys():
-                typinf = pyinf.class_info[type]
-                self.types[type] = typinf
-                self.goodtypes[type] = typinf
-                self.typeExports.setdefault(mod, []).append(type)
-                for func in typinf.function_info.keys():
-                    self.funcs[type+"."+func] = typinf.function_info[func]
-            for func in pyinf.function_info.keys():
-                self.funcs["GLOBAL."+func] = pyinf.function_info[func]
-                self.globalfn.append("GLOBAL."+func)
-                self.funcExports.setdefault(mod, []).append(func)
-            for var in pyinf.assign_info.keys():
-                self.varExports.setdefault(mod, []).append(var)
-
-    def getClassList(self):
-        return list(self.goodtypes.keys())
-
-    def getGlobalFunctionList(self):
-        return self.globalfn
-
-    def getClassComment(self, cn):
-        type = self.types.get(cn)
-        if (isinstance(type, InterrogateType)):
-            return textToHTML(type.comment,"/")
-        elif (isinstance(type, ParseTreeInfo)):
-            return textToHTML(type.docstring,"#")
-        else:
-            return ""
-
-    def getClassParents(self, cn):
-        type = self.types.get(cn)
-        if (isinstance(type, InterrogateType)):
-            parents = []
-            for deriv in type.derivations:
-                basetype = type.db.types[deriv.base]
-                parents.append(basetype.scopedname)
-            return parents
-        elif (isinstance(type, ParseTreeInfo)):
-            return list(type.derivs.keys())
-        else:
-            return []
-
-    def getClassConstants(self, cn):
-        type = self.types.get(cn)
-        if (isinstance(type, InterrogateType)):
-            result = []
-            for subtype in type.nested:
-                enumtype = type.db.types[subtype]
-                if (len(enumtype.enumvalues)):
-                    for enumvalue in enumtype.enumvalues:
-                        name = convertToPythonFn(enumvalue.name)
-                        result.append((name, "("+enumtype.componentname+")"))
-                    result.append(("",""))
-            return result
-        else:
-            return []
-
-    def buildInheritance(self, inheritance, cn):
-        if (inheritance.count(cn) == 0):
-            inheritance.append(cn)
-            for parent in self.getClassParents(cn):
-                self.buildInheritance(inheritance, parent)
-
-    def getInheritance(self, cn):
-        inheritance = []
-        self.buildInheritance(inheritance, cn)
-        return inheritance
-
-    def getClassImport(self, cn):
-        type = self.types.get(cn)
-        if (isinstance(type, InterrogateType)):
-            return "pandac.PandaModules"
-        else:
-            return pathToModule(type.file)
-
-    def getClassConstructors(self, cn):
-        # Only detects C++ constructors, not Python constructors, since
-        # those are treated as ordinary methods.
-        type = self.types.get(cn)
-        result = []
-        if (isinstance(type, InterrogateType)):
-            for constructor in type.constructors:
-                func = type.db.functions[constructor]
-                if (func.classindex == type.index):
-                    result.append(type.scopedname+"."+func.pyname)
-        return result
-
-    def getClassMethods(self, cn):
-        type = self.types.get(cn)
-        result = []
-        if (isinstance(type, InterrogateType)):
-            for method in type.methods:
-                func = type.db.functions[method]
-                if (func.classindex == type.index):
-                    result.append(type.scopedname+"."+func.pyname)
-        elif (isinstance(type, ParseTreeInfo)):
-            for method in type.function_info.keys():
-                result.append(type.name + "." + method)
-        return result
-
-    def getFunctionName(self, fn):
-        func = self.funcs.get(fn)
-        if (isinstance(func, InterrogateFunction)):
-            return func.pyname
-        elif (isinstance(func, ParseTreeInfo)):
-            return func.name
-        else:
-            return fn
-
-    def getFunctionImport(self, fn):
-        func = self.funcs.get(fn)
-        if (isinstance(func, InterrogateFunction)):
-            return "pandac.PandaModules"
-        else:
-            return pathToModule(func.file)
-
-    def getFunctionPrototype(self, fn, urlprefix, urlsuffix):
-        func = self.funcs.get(fn)
-        if (isinstance(func, InterrogateFunction)):
-            if fn in self.formattedprotos:
-                proto = self.formattedprotos[fn]
-            else:
-                proto = func.prototype
-                proto = proto.replace(" inline "," ")
-                if (proto.startswith("inline ")): proto = proto[7:]
-                proto = proto.replace("basic_string< char >", "string")
-                proto = textToHTML(proto,"")
-                if "." in fn:
-                    for c in self.goodtypes.keys():
-                        if c != fn.split(".")[0] and (c in proto):
-                            proto = re.sub("\\b%s\\b" % c, linkTo(urlprefix+c+urlsuffix, c), proto)
-                self.formattedprotos[fn] = proto
-            return proto
-        elif (isinstance(func, ParseTreeInfo)):
-            return textToHTML("def "+func.name+func.prototype,"")
-        return fn
-
-    def getFunctionComment(self, fn):
-        func = self.funcs.get(fn)
-        if (isinstance(func, InterrogateFunction)):
-            return textToHTML(removeFileLicense(func.comment), "/", JUNKHEADER)
-        elif (isinstance(func, ParseTreeInfo)):
-            return textToHTML(func.docstring, "#")
-        return fn
-
-    def isFunctionPython(self, fn):
-        func = self.funcs.get(fn)
-        if (isinstance(func, InterrogateFunction)):
-            return False
-        elif (isinstance(func, ParseTreeInfo)):
-            return True
-        return False
-
-    def getFuncExports(self, mod):
-        return self.funcExports.get(mod, [])
-
-    def getTypeExports(self, mod):
-        return self.typeExports.get(mod, [])
-
-    def getVarExports(self, mod):
-        return self.varExports.get(mod, [])
-
-########################################################################
-#
-# The "Class Rename Dictionary" - Yech.
-#
-########################################################################
-
-CLASS_RENAME_DICT = {
-    # No longer used, now empty.
-}
-
-
-########################################################################
-#
-# HTML generation
-#
-########################################################################
-
-def makeCodeDatabase(indirlist, directdirlist):
-    if isinstance(directdirlist, str):
-        directdirlist = [directdirlist]
-    ignore = {}
-    ignore["__init__.py"] = 1
-    for directdir in directdirlist:
-        ignore[directdir + "/src/directscripts"] = 1
-        ignore[directdir + "/src/extensions"] = 1
-        ignore[directdir + "/src/extensions_native"] = 1
-        ignore[directdir + "/src/ffi"] = 1
-        ignore[directdir + "/built"] = 1
-    cxxfiles = []
-    pyfiles = []
-    findFiles(indirlist,     ".in", ignore, cxxfiles)
-    findFiles(directdirlist, ".py", ignore, pyfiles)
-    return CodeDatabase(cxxfiles, pyfiles)
-
-def generateFunctionDocs(code, method, urlprefix, urlsuffix):
-    name = code.getFunctionName(method)
-    proto = code.getFunctionPrototype(method, urlprefix, urlsuffix)
-    comment = code.getFunctionComment(method)
-    if (comment == ""): comment = "Undocumented function.<br>\n"
-    chunk = '<table bgcolor="e8e8e8" border=0 cellspacing=0 cellpadding=5 width="100%"><tr><td>' + "\n"
-    chunk = chunk + '<a name="' + name + '"><b>' + name + "</b></a><br>\n"
-    chunk = chunk + proto + "<br>\n"
-    chunk = chunk + comment
-    chunk = chunk + "</td></tr></table><br>\n"
-    return chunk
-
-def generateLinkTable(link, text, cols, urlprefix, urlsuffix):
-    column = (len(link)+cols-1)/cols
-    percent = 100 / cols
-    result = '<table width="100%">\n'
-    for i in range(column):
-        line = ""
-        for j in range(cols):
-            slot = i + column*j
-            linkval = ""
-            textval = ""
-            if (slot < len(link)): linkval = link[slot]
-            if (slot < len(text)): textval = text[slot]
-            if (i==0):
-                line = line + '<td width="' + str(percent) + '%">' + linkTo(urlprefix+linkval+urlsuffix, textval) + "</td>"
-            else:
-                line = line + '<td>' + linkTo(urlprefix+linkval+urlsuffix, textval) + "</td>"
-        result = result + "<tr>" + line + "</tr>\n"
-    result = result + "</table>\n"
-    return result
-
-def generate(pversion, indirlist, directdirlist, docdir, header, footer, urlprefix, urlsuffix):
-    code = makeCodeDatabase(indirlist, directdirlist)
-    classes = code.getClassList()[:]
-    classes.sort(None, str.lower)
-    xclasses = classes[:]
-    print("Generating HTML pages")
-    for type in classes:
-        body = "<h1>" + type + "</h1>\n"
-        comment = code.getClassComment(type)
-        body = body + "<ul>\nfrom " + code.getClassImport(type) + " import " + type + "</ul>\n\n"
-        body = body + "<ul>\n" + comment + "</ul>\n\n"
-        inheritance = code.getInheritance(type)
-        body = body + "<h2>Inheritance:</h2>\n<ul>\n"
-        for inh in inheritance:
-            line = "  " + linkTo(urlprefix+inh+urlsuffix, inh) + ": "
-            for parent in code.getClassParents(inh):
-                line = line + linkTo(urlprefix+parent+urlsuffix, parent) + " "
-            body = body + line + "<br>\n"
-        body = body + "</ul>\n"
-        for sclass in inheritance:
-            methods = code.getClassMethods(sclass)[:]
-            methods.sort(None, str.lower)
-            constructors = code.getClassConstructors(sclass)
-            if (len(methods) > 0 or len(constructors) > 0):
-                body = body + "<h2>Methods of "+sclass+":</h2>\n<ul>\n"
-                if len(constructors) > 0:
-                    fn = code.getFunctionName(constructors[0])
-                    body = body + '<a href="#' + fn + '">' + fn + " (Constructor)</a><br>\n"
-                for method in methods:
-                    fn = code.getFunctionName(method)
-                    body = body + '<a href="#' + fn + '">' + fn + "</a><br>\n"
-                body = body + "</ul>\n"
-        for sclass in inheritance:
-            enums = code.getClassConstants(sclass)[:]
-            if (len(enums) > 0):
-                body = body + "<h2>Constants in "+sclass+":</h2>\n<ul><table>\n"
-                for (value, comment) in enums:
-                    body = body + "<tr><td>" + value + "</td><td>" + comment + "</td></tr>\n"
-                body = body + "</table></ul>"
-        for sclass in inheritance:
-            constructors = code.getClassConstructors(sclass)
-            for constructor in constructors:
-                body = body + generateFunctionDocs(code, constructor, urlprefix, urlsuffix)
-            methods = code.getClassMethods(sclass)[:]
-            methods.sort(None, str.lower)
-            for method in methods:
-                body = body + generateFunctionDocs(code, method, urlprefix, urlsuffix)
-        body = header + body + footer
-        writeFile(docdir + "/" + type + ".html", body)
-        if type in CLASS_RENAME_DICT:
-            modtype = CLASS_RENAME_DICT[type]
-            writeFile(docdir + "/" + modtype + ".html", body)
-            xclasses.append(modtype)
-    xclasses.sort(None, str.lower)
-
-    index = "<h1>List of Classes - Panda " + pversion + "</h1>\n"
-    index = index + generateLinkTable(xclasses, xclasses, 3, urlprefix, urlsuffix)
-    fnlist = code.getGlobalFunctionList()[:]
-    fnlist.sort(None, str.lower)
-    fnnames = []
-    for i in range(len(fnlist)):
-        fnnames.append(code.getFunctionName(fnlist[i]))
-    index = header + index + footer
-    writeFile(docdir + "/classes.html", index)
-
-    index = "<h1>List of Global Functions - Panda " + pversion + "</h1>\n"
-    index = index + generateLinkTable(fnnames, fnnames, 3,"#","")
-    for func in fnlist:
-        index = index + generateFunctionDocs(code, func, urlprefix, urlsuffix)
-    index = header + index + footer
-    writeFile(docdir + "/functions.html", index)
-
-    table = {}
-    for type in classes:
-        for method in code.getClassMethods(type)[:]:
-            name = code.getFunctionName(method)
-            prefix = name[0].upper()
-            if prefix not in table:
-                table[prefix] = {}
-            if name not in table[prefix]:
-                table[prefix][name] = []
-            table[prefix][name].append(type)
-
-    index = "<h1>List of Methods - Panda " + pversion + "</h1>\n"
-
-    prefixes = list(table.keys())
-    prefixes.sort(None, str.lower)
-    for prefix in prefixes:
-        index = index + linkTo("#"+prefix, prefix) + " "
-    index = index + "<br><br>"
-    for prefix in prefixes:
-        index = index + '<a name="' + prefix + '">' + "\n"
-        names = list(table[prefix].keys())
-        names.sort(None, str.lower)
-        for name in names:
-            line = '<b>' + name + ":</b><ul>\n"
-            ctypes = table[prefix][name]
-            ctypes.sort(None, str.lower)
-            for type in ctypes:
-                line = line + "<li>" + linkTo(urlprefix+type+urlsuffix+"#"+name, type) + "\n"
-            line = line + "</ul>\n"
-            index = index + line + "\n"
-    index = header + index + footer
-    writeFile(docdir + "/methods.html", index)
-
-    index = "<h1>Panda " + pversion + "</h1>\n"
-    index = index + "<ul>\n"
-    index = index + "<li>" + linkTo(urlprefix+"classes"+urlsuffix, "List of all Classes") + "\n"
-    index = index + "</ul>\n"
-    index = index + "<ul>\n"
-    index = index + "<li>" + linkTo(urlprefix+"functions"+urlsuffix, "List of all Global Functions") + "\n"
-    index = index + "</ul>\n"
-    index = index + "<ul>\n"
-    index = index + "<li>" + linkTo(urlprefix+"methods"+urlsuffix, "List of all Methods (very long)") + "\n"
-    index = index + "</ul>\n"
-    writeFile(docdir + "/index.html", index)
-
-
-########################################################################
-#
-# IMPORT repair
-#
-########################################################################
-
-def expandImports(indirlist, directdirlist, fixdirlist):
-    code = makeCodeDatabase(indirlist, directdirlist)
-    fixfiles = []
-    findFiles(fixdirlist, ".py", {}, fixfiles)
-    for fixfile in fixfiles:
-        if (os.path.isfile(fixfile+".orig")):
-            text = readFile(fixfile+".orig")
-        else:
-            text = readFile(fixfile)
-            writeFile(fixfile+".orig", text)
-        text = text.replace("\r","")
-        lines = text.split("\n")
-        used = {}
-        for id in IDENTIFIER.findall(text):
-            used[id] = 1
-        result = []
-        for line in lines:
-            mat = IMPORTSTAR.match(line)
-            if (mat):
-                module = mat.group(1)
-                if (fixfile.count("/")!=0) and (module.count(".")==0):
-                    modfile = os.path.dirname(fixfile)+"/"+module+".py"
-                    if (os.path.isfile(modfile)):
-                        module = pathToModule(modfile)
-                typeExports = code.getTypeExports(module)
-                funcExports = code.getFuncExports(module)
-                varExports = code.getVarExports(module)
-                if (len(typeExports)+len(funcExports)+len(varExports)==0):
-                    result.append(line)
-                    print(fixfile + " : " + module + " : no exports")
-                else:
-                    print(fixfile + " : " + module + " : repairing")
-                    for x in funcExports:
-                        fn = code.getFunctionName(x)
-                        if fn in used:
-                            result.append("from "+module+" import "+fn)
-                    for x in typeExports:
-                        if x in used:
-                            result.append("from "+module+" import "+x)
-                    for x in varExports:
-                        if x in used:
-                            result.append("from "+module+" import "+x)
-            else:
-                result.append(line)
-        writeFileLines(fixfile, result)

+ 8 - 8
direct/src/directtools/DirectCameraControl.py

@@ -6,6 +6,7 @@ from .DirectSelection import SelectionRay
 from direct.interval.IntervalGlobal import Sequence, Func
 from direct.directnotify import DirectNotifyGlobal
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 
 CAM_MOVE_DURATION = 1.2
 COA_MARKER_SF = 0.0075
@@ -22,7 +23,7 @@ class DirectCameraControl(DirectObject):
         self.orthoViewRoll = 0.0
         self.lastView = 0
         self.coa = Point3(0, 100, 0)
-        self.coaMarker = loader.loadModel('models/misc/sphere')
+        self.coaMarker = base.loader.loadModel('models/misc/sphere')
         self.coaMarker.setName('DirectCameraCOAMarker')
         self.coaMarker.setTransparency(1)
         self.coaMarker.setColor(1, 0, 0, 0)
@@ -150,7 +151,7 @@ class DirectCameraControl(DirectObject):
     def __startManipulateCamera(self, func = None, task = None, ival = None):
         self.__stopManipulateCamera()
         if func:
-            assert(task is None)
+            assert task is None
             task = Task.Task(func)
         if task:
             self.manipulateCameraTask = taskMgr.add(task, 'manipulateCamera')
@@ -366,7 +367,7 @@ class DirectCameraControl(DirectObject):
                                 moveDir[2],
                                 hVal,
                                 0.0, 0.0)
-        if (self.lockRoll == True):
+        if self.lockRoll:
             # flatten roll
             base.direct.camera.setR(0)
 
@@ -449,7 +450,7 @@ class DirectCameraControl(DirectObject):
                                  (deltaX * base.direct.dr.fovH),
                                  (-deltaY * base.direct.dr.fovV),
                                  0.0)
-            if (self.lockRoll == True):
+            if self.lockRoll:
                 # flatten roll
                 base.direct.camera.setR(0)
             self.camManipRef.setPos(self.coaMarkerPos)
@@ -466,7 +467,7 @@ class DirectCameraControl(DirectObject):
                                     (deltaY * 180.0),
                                     0.0)
 
-            if (self.lockRoll == True):
+            if self.lockRoll:
                 # flatten roll
                 self.camManipRef.setR(0)
             base.direct.camera.setTransform(self.camManipRef, wrt)
@@ -491,7 +492,7 @@ class DirectCameraControl(DirectObject):
         deltaAngle = angle - state.lastAngle
         state.lastAngle = angle
         self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
-        if (self.lockRoll == True):
+        if self.lockRoll:
             # flatten roll
             self.camManipRef.setR(0)
         base.direct.camera.setTransform(self.camManipRef, wrt)
@@ -576,7 +577,7 @@ class DirectCameraControl(DirectObject):
         if not coaDist:
             coaDist = Vec3(self.coa - ZERO_POINT).length()
         # Place the marker in render space
-        if ref == None:
+        if ref is None:
             # KEH: use the current display region
             # ref = base.cam
             ref = base.direct.drList.getCurrentDr().cam
@@ -892,4 +893,3 @@ class DirectCameraControl(DirectObject):
 
     def removeManipulateCameraTask(self):
         self.__stopManipulateCamera()
-

+ 3 - 4
direct/src/directtools/DirectGeometry.py

@@ -163,7 +163,7 @@ def getCrankAngle(center):
     # origin) in screen space
     x = base.direct.dr.mouseX - center[0]
     y = base.direct.dr.mouseY - center[2]
-    return (180 + rad2Deg(math.atan2(y, x)))
+    return 180 + rad2Deg(math.atan2(y, x))
 
 def relHpr(nodePath, base, h, p, r):
     # Compute nodePath2newNodePath relative to base coordinate system
@@ -205,9 +205,9 @@ def qSlerp(startQuat, endQuat, t):
         startQ.setJ(-1 * startQ.getJ())
         startQ.setK(-1 * startQ.getK())
         startQ.setR(-1 * startQ.getR())
-    if ((1.0 + cosOmega) > Q_EPSILON):
+    if (1.0 + cosOmega) > Q_EPSILON:
         # usual case
-        if ((1.0 - cosOmega) > Q_EPSILON):
+        if (1.0 - cosOmega) > Q_EPSILON:
             # usual case
             omega = math.acos(cosOmega)
             sinOmega = math.sin(omega)
@@ -240,4 +240,3 @@ def qSlerp(startQuat, endQuat, t):
         destQuat.setK(startScale * startQ.getK() +
                       endScale * endQuat.getK())
     return destQuat
-

+ 0 - 1
direct/src/directtools/DirectGlobals.py

@@ -59,4 +59,3 @@ def LE_showInOneCam(nodePath, thisCamName):
     for camName in LE_CAM_MASKS:
         if camName != thisCamName:
             nodePath.hide(LE_CAM_MASKS[camName])
-

+ 4 - 3
direct/src/directtools/DirectGrid.py

@@ -4,6 +4,7 @@ from direct.showbase.DirectObject import DirectObject
 from .DirectUtil import *
 from .DirectGeometry import *
 
+
 class DirectGrid(NodePath, DirectObject):
     def __init__(self,gridSize=100.0,gridSpacing=5.0,planeColor=(0.5,0.5,0.5,0.5),parent = None):
         # Initialize superclass
@@ -13,7 +14,7 @@ class DirectGrid(NodePath, DirectObject):
 
         # Load up grid parts to initialize grid object
         # Polygon used to mark grid plane
-        self.gridBack = loader.loadModel('models/misc/gridBack')
+        self.gridBack = base.loader.loadModel('models/misc/gridBack')
         self.gridBack.reparentTo(self)
         self.gridBack.setColor(*planeColor)
 
@@ -35,7 +36,7 @@ class DirectGrid(NodePath, DirectObject):
         self.centerLines.setThickness(3)
 
         # Small marker to hilight snap-to-grid point
-        self.snapMarker = loader.loadModel('models/misc/sphere')
+        self.snapMarker = base.loader.loadModel('models/misc/sphere')
         self.snapMarker.node().setName('gridSnapMarker')
         self.snapMarker.reparentTo(self)
         self.snapMarker.setColor(1, 0, 0, 1)
@@ -107,7 +108,7 @@ class DirectGrid(NodePath, DirectObject):
         center.create()
         minor.create()
         major.create()
-        if (self.gridBack):
+        if self.gridBack:
             self.gridBack.setScale(scaledSize)
 
     def setXyzSnap(self, fSnap):

+ 8 - 9
direct/src/directtools/DirectLights.py

@@ -1,5 +1,7 @@
 
 from panda3d.core import *
+from direct.showbase.MessengerGlobal import messenger
+
 
 class DirectLight(NodePath):
     def __init__(self, light, parent):
@@ -58,21 +60,21 @@ class DirectLights(NodePath):
         nameList.sort()
         return nameList
 
-    def create(self, type):
-        type = type.lower()
-        if type == 'ambient':
+    def create(self, ltype):
+        ltype = ltype.lower()
+        if ltype == 'ambient':
             self.ambientCount += 1
             light = AmbientLight('ambient-' + repr(self.ambientCount))
             light.setColor(VBase4(.3, .3, .3, 1))
-        elif type == 'directional':
+        elif ltype == 'directional':
             self.directionalCount += 1
             light = DirectionalLight('directional-' + repr(self.directionalCount))
             light.setColor(VBase4(1))
-        elif type == 'point':
+        elif ltype == 'point':
             self.pointCount += 1
             light = PointLight('point-' + repr(self.pointCount))
             light.setColor(VBase4(1))
-        elif type == 'spot':
+        elif ltype == 'spot':
             self.spotCount += 1
             light = Spotlight('spot-' + repr(self.spotCount))
             light.setColor(VBase4(1))
@@ -130,6 +132,3 @@ class DirectLights(NodePath):
         Turn off the given directLight
         """
         render.clearLight(directLight)
-
-
-

+ 58 - 57
direct/src/directtools/DirectManipulation.py

@@ -1,4 +1,5 @@
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.MessengerGlobal import messenger
 from .DirectGlobals import *
 from .DirectUtil import *
 from .DirectGeometry import *
@@ -6,6 +7,7 @@ from .DirectSelection import SelectionRay
 from direct.task import Task
 from copy import deepcopy
 
+
 class DirectManipulationControl(DirectObject):
     def __init__(self):
         # Create the grid
@@ -201,7 +203,7 @@ class DirectManipulationControl(DirectObject):
             ((abs (endY - startY)) < 0.01)):
             return
 
-        self.marquee = LineNodePath(render2d, 'marquee', 0.5, VBase4(.8, .6, .6, 1))
+        self.marquee = LineNodePath(base.render2d, 'marquee', 0.5, VBase4(.8, .6, .6, 1))
         self.marqueeInfo = (startX, startY, endX, endY)
         self.marquee.drawLines([
             [(startX, 0, startY), (startX, 0, endY)],
@@ -252,7 +254,7 @@ class DirectManipulationControl(DirectObject):
                 lens.extrude((endX, endY), nlr, flr)
                 lens.extrude((startX, endY), nll, fll)
 
-                marqueeFrustum = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul);
+                marqueeFrustum = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul)
                 marqueeFrustum.xform(base.direct.cam.getNetTransform().getMat())
 
                 base.marqueeFrustum = marqueeFrustum
@@ -273,13 +275,13 @@ class DirectManipulationControl(DirectObject):
 ##                     elif (skipFlags & SKIP_BACKFACE) and base.direct.iRay.isEntryBackfacing():
 ##                         # Skip, if backfacing poly
 ##                         pass
-                    elif ((skipFlags & SKIP_CAMERA) and
-                          (camera in geom.getAncestors())):
+                    elif (skipFlags & SKIP_CAMERA) and \
+                         (base.camera in geom.getAncestors()):
                         # Skip if parented to a camera.
                         continue
                     # Can pick unpickable, use the first visible node
-                    elif ((skipFlags & SKIP_UNPICKABLE) and
-                          (geom.getName() in base.direct.iRay.unpickable)):
+                    elif (skipFlags & SKIP_UNPICKABLE) and \
+                         (geom.getName() in base.direct.iRay.unpickable):
                         # Skip if in unpickable list
                         continue
 
@@ -486,7 +488,7 @@ class DirectManipulationControl(DirectObject):
         for tag in self.unmovableTagList:
             for selected in objects:
                 unmovableTag = selected.getTag(tag)
-                if (unmovableTag):
+                if unmovableTag:
                     # check value of unmovableTag to see if it is
                     # completely uneditable or if it allows only certain
                     # types of editing
@@ -498,7 +500,7 @@ class DirectManipulationControl(DirectObject):
         selectedList = base.direct.selected.getSelectedAsList()
         # See if any of the selected are completely uneditable
         editTypes = self.getEditTypes(selectedList)
-        if (editTypes & EDIT_TYPE_UNEDITABLE == EDIT_TYPE_UNEDITABLE):
+        if (editTypes & EDIT_TYPE_UNEDITABLE) == EDIT_TYPE_UNEDITABLE:
             return
         self.currEditTypes = editTypes
         if selectedList:
@@ -633,7 +635,7 @@ class DirectManipulationControl(DirectObject):
                 base.direct.widget.getMat(base.direct.selected.last))
         else:
             # Move the objects with the widget
-                base.direct.selected.moveWrtWidgetAll()
+            base.direct.selected.moveWrtWidgetAll()
         # Continue
         return Task.cont
 
@@ -811,11 +813,11 @@ class DirectManipulationControl(DirectObject):
         widgetAxis.normalize()
         if type == 'top?':
             # Check sign of angle between two vectors
-            return (widgetDir.dot(widgetAxis) < 0.)
+            return widgetDir.dot(widgetAxis) < 0.
         elif type == 'edge?':
             # Checking to see if we are viewing edge-on
             # Check angle between two vectors
-            return(abs(widgetDir.dot(widgetAxis)) < .2)
+            return abs(widgetDir.dot(widgetAxis)) < .2
 
     ### FREE MANIPULATION METHODS ###
     def xlateCamXZ(self, state):
@@ -1043,7 +1045,7 @@ class DirectManipulationControl(DirectObject):
         entry = base.direct.iRay.pickGeom(
             skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA)
         # MRM: Need to handle moving COA
-        if (entry != None) and (base.direct.selected.last != None):
+        if entry is not None and base.direct.selected.last is not None:
             # Record undo point
             base.direct.pushUndo(base.direct.selected)
             # Record wrt matrix
@@ -1064,9 +1066,9 @@ class ObjectHandles(NodePath, DirectObject):
         NodePath.__init__(self)
 
         # Load up object handles model and assign it to self
-        self.assign(loader.loadModel('models/misc/objectHandles'))
+        self.assign(base.loader.loadModel('models/misc/objectHandles'))
         self.setName(name)
-        self.scalingNode = self.getChild(0)
+        self.scalingNode = NodePath(self)
         self.scalingNode.setName('ohScalingNode')
         self.ohScalingFactor = 1.0
         self.directScalingFactor = 1.0
@@ -1206,7 +1208,7 @@ class ObjectHandles(NodePath, DirectObject):
         self.reparentTo(hidden)
 
     def enableHandles(self, handles):
-        if type(handles) == list:
+        if isinstance(handles, list):
             for handle in handles:
                 self.enableHandle(handle)
         elif handles == 'x':
@@ -1255,7 +1257,7 @@ class ObjectHandles(NodePath, DirectObject):
             self.zScaleGroup.reparentTo(self.zHandles)
 
     def disableHandles(self, handles):
-        if type(handles) == list:
+        if isinstance(handles, list):
             for handle in handles:
                 self.disableHandle(handle)
         elif handles == 'x':
@@ -1368,7 +1370,7 @@ class ObjectHandles(NodePath, DirectObject):
         self.setScalingFactor(1)
 
     def setScalingFactor(self, scaleFactor):
-        self.ohScalingFactor = self.ohScalingFactor * scaleFactor
+        self.ohScalingFactor = scaleFactor
         self.scalingNode.setScale(self.ohScalingFactor * self.directScalingFactor)
 
     def getScalingFactor(self):
@@ -1640,7 +1642,7 @@ class ObjectHandles(NodePath, DirectObject):
         # by comparing lineDir with plane normals.  The plane with the
         # largest dotProduct is most "normal"
         if axis == 'x':
-            if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))):
+            if abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS)):
                 self.hitPt.assign(
                     planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
             else:
@@ -1650,7 +1652,7 @@ class ObjectHandles(NodePath, DirectObject):
             self.hitPt.setY(0)
             self.hitPt.setZ(0)
         elif axis == 'y':
-            if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))):
+            if abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS)):
                 self.hitPt.assign(
                     planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
             else:
@@ -1660,7 +1662,7 @@ class ObjectHandles(NodePath, DirectObject):
             self.hitPt.setX(0)
             self.hitPt.setZ(0)
         elif axis == 'z':
-            if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))):
+            if abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS)):
                 self.hitPt.assign(
                     planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
             else:
@@ -1731,40 +1733,39 @@ class ObjectHandles(NodePath, DirectObject):
         return self.hitPt
 
 def drawBox(lines, center, sideLength):
-
-        l = sideLength * 0.5
-        lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
-
-        lines.moveTo(center[0] - l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
-
-        lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
-
-        lines.moveTo(center[0] + l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
-
-        lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
-
-        lines.moveTo(center[0] + l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
-        lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
-        lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
+    l = sideLength * 0.5
+    lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
+
+    lines.moveTo(center[0] - l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
+
+    lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
+
+    lines.moveTo(center[0] + l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
+
+    lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
+
+    lines.moveTo(center[0] + l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
+    lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
+    lines.drawTo(center[0] + l, center[1] + l, center[2] - l)

+ 12 - 12
direct/src/directtools/DirectSelection.py

@@ -1,4 +1,5 @@
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.MessengerGlobal import messenger
 from .DirectGlobals import *
 from .DirectUtil import *
 from .DirectGeometry import *
@@ -95,7 +96,7 @@ class SelectedNodePaths(DirectObject):
             dnp = self.getDeselectedDict(id)
             if dnp:
                 # Remove it from the deselected dictionary
-                del(self.deselectedDict[id])
+                del self.deselectedDict[id]
                 # Show its bounding box
                 dnp.highlight()
             else:
@@ -125,7 +126,7 @@ class SelectedNodePaths(DirectObject):
             # Hide its bounding box
             dnp.dehighlight()
             # Remove it from the selected dictionary
-            del(self.selectedDict[id])
+            del self.selectedDict[id]
             if dnp in self.selectedList: # [gjeon]
                 self.selectedList.remove(dnp)
             # And keep track of it in the deselected dictionary
@@ -308,7 +309,7 @@ class DirectBoundingBox:
         # Create a line segments object for the bbox
         lines = LineNodePath(hidden)
         lines.node().setName('bboxLines')
-        if (bboxColor):
+        if bboxColor:
             lines.setColor(VBase4(*bboxColor))
         else:
             lines.setColor(VBase4(1., 0., 0., 1.))
@@ -352,7 +353,7 @@ class DirectBoundingBox:
         return lines
 
     def setBoxColorScale(self, r, g, b, a):
-        if (self.lines):
+        if self.lines:
             self.lines.reset()
             self.lines = None
         self.lines = self.createBBoxLines((r, g, b, a))
@@ -529,7 +530,7 @@ class SelectionQueue(CollisionHandlerQueue):
             # Well, no way to tell.  Assume we're not backfacing.
             return 0
 
-        if direct:
+        if base.direct:
             cam = base.direct.cam
         else:
             cam = base.cam
@@ -564,13 +565,13 @@ class SelectionQueue(CollisionHandlerQueue):
             elif (skipFlags & SKIP_BACKFACE) and self.isEntryBackfacing(entry):
                 # Skip, if backfacing poly
                 pass
-            elif ((skipFlags & SKIP_CAMERA) and
-                  (camera in nodePath.getAncestors())):
+            elif (skipFlags & SKIP_CAMERA) and \
+                 (base.camera in nodePath.getAncestors()):
                 # Skip if parented to a camera.
                 pass
             # Can pick unpickable, use the first visible node
-            elif ((skipFlags & SKIP_UNPICKABLE) and
-                  (nodePath.getName() in self.unpickable)):
+            elif (skipFlags & SKIP_UNPICKABLE) and\
+                 (nodePath.getName() in self.unpickable):
                 # Skip if in unpickable list
                 pass
             elif base.direct and\
@@ -602,7 +603,7 @@ class SelectionRay(SelectionQueue):
         if xy:
             mx = xy[0]
             my = xy[1]
-        elif direct:
+        elif base.direct:
             mx = base.direct.dr.mouseX
             my = base.direct.dr.mouseY
         else:
@@ -613,7 +614,7 @@ class SelectionRay(SelectionQueue):
             mx = base.mouseWatcherNode.getMouseX()
             my = base.mouseWatcherNode.getMouseY()
 
-        if direct:
+        if base.direct:
             self.collider.setFromLens(base.direct.camNode, mx, my)
         else:
             self.collider.setFromLens(base.camNode, mx, my)
@@ -793,4 +794,3 @@ class SelectionSphere(SelectionQueue):
             targetNodePath = render
         self.collideWithBitMask(bitMask)
         return self.pick(targetNodePath, skipFlags)
-

+ 15 - 16
direct/src/directtools/DirectSession.py

@@ -4,6 +4,7 @@ from panda3d.core import *
 from .DirectUtil import *
 
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.BulletinBoardGlobal import bulletinBoard as bboard
 from direct.task import Task
 
 from .DirectGlobals import DIRECT_NO_MOD
@@ -96,13 +97,13 @@ class DirectSession(DirectObject):
         self.joybox = None
         self.radamec = None
         self.fastrak = []
-        if base.config.GetBool('want-vrpn', 0):
+        if ConfigVariableBool('want-vrpn', False):
             from direct.directdevices import DirectDeviceManager
             self.deviceManager = DirectDeviceManager.DirectDeviceManager()
             # Automatically create any devices specified in config file
-            joybox = base.config.GetString('vrpn-joybox-device', '')
-            radamec = base.config.GetString('vrpn-radamec-device', '')
-            fastrak = base.config.GetString('vrpn-fastrak-device', '')
+            joybox = ConfigVariableString('vrpn-joybox-device', '').value
+            radamec = ConfigVariableString('vrpn-radamec-device', '').value
+            fastrak = ConfigVariableString('vrpn-fastrak-device', '').value
             if joybox:
                 from direct.directdevices import DirectJoybox
                 self.joybox = DirectJoybox.DirectJoybox(joybox)
@@ -291,16 +292,15 @@ class DirectSession(DirectObject):
         self.passThroughKeys = ['v','b','l','p', 'r', 'shift-r', 's', 't','shift-a', 'w']
 
         if base.wantTk:
-            from direct.showbase import TkGlobal
             from direct.tkpanels import DirectSessionPanel
-            self.panel = DirectSessionPanel.DirectSessionPanel(parent = tkroot)
+            self.panel = DirectSessionPanel.DirectSessionPanel(parent = base.tkRoot)
         try:
             # Has the clusterMode been set externally (i.e. via the
             # bootstrap application?
             self.clusterMode = clusterMode
         except NameError:
             # Has the clusterMode been set via a config variable?
-            self.clusterMode = base.config.GetString("cluster-mode", '')
+            self.clusterMode = ConfigVariableString("cluster-mode", '').value
 
         if self.clusterMode == 'client':
             self.cluster = createClusterClient()
@@ -385,14 +385,12 @@ class DirectSession(DirectObject):
     def oobe(self):
         # If oobeMode was never set, set it to false and create the
         # structures we need to implement OOBE.
-        try:
-            self.oobeMode
-        except:
+        if not hasattr(self, 'oobeMode'):
             self.oobeMode = 0
 
             self.oobeCamera = hidden.attachNewNode('oobeCamera')
 
-            self.oobeVis = loader.loadModel('models/misc/camera')
+            self.oobeVis = base.loader.loadModel('models/misc/camera')
             if self.oobeVis:
                 self.oobeVis.node().setFinal(1)
 
@@ -669,6 +667,8 @@ class DirectSession(DirectObject):
         if not taskMgr.hasTaskNamed('resizeObjectHandles'):
             dnp = self.selected.last
             if dnp:
+                direct = base.direct
+
                 if self.manipulationControl.fMultiView:
                     for i in range(3):
                         sf = 30.0 * direct.drList[i].orthoFactor
@@ -708,7 +708,7 @@ class DirectSession(DirectObject):
             else:
                 self.widget.showWidget()
             editTypes = self.manipulationControl.getEditTypes([dnp])
-            if (editTypes & EDIT_TYPE_UNEDITABLE == EDIT_TYPE_UNEDITABLE):
+            if (editTypes & EDIT_TYPE_UNEDITABLE) == EDIT_TYPE_UNEDITABLE:
                 self.manipulationControl.disableWidgetMove()
             else:
                 self.manipulationControl.enableWidgetMove()
@@ -1069,8 +1069,10 @@ class DirectSession(DirectObject):
         for iRay in self.iRayList:
             iRay.removeUnpickable(item)
 
+
 class DisplayRegionContext(DirectObject):
     regionCount = 0
+
     def __init__(self, cam):
         self.cam = cam
         self.camNode = self.cam.node()
@@ -1091,10 +1093,7 @@ class DisplayRegionContext(DirectObject):
         # one display region per camera, since we are defining a
         # display region on a per-camera basis.  See note in
         # DisplayRegionList.__init__()
-        try:
-            self.dr = self.camNode.getDr(0)
-        except:
-            self.dr = self.camNode.getDisplayRegion(0)
+        self.dr = self.camNode.getDisplayRegion(0)
         left = self.dr.getLeft()
         right = self.dr.getRight()
         bottom = self.dr.getBottom()

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

@@ -2,6 +2,7 @@
 from .DirectGlobals import *
 from panda3d.core import VBase4
 from direct.task.Task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 
 # Routines to adjust values
 def ROUND_TO(value, divisor):
@@ -19,7 +20,7 @@ def getTkColorString(color):
     Print out a Tk compatible version of a color string
     """
     def toHex(intVal):
-        val = int(round(intVal))
+        val = int(intVal)
         if val < 16:
             return "0" + hex(val)[2:]
         else:
@@ -84,4 +85,3 @@ def getFileData(filename, separator = ','):
             data = [s.strip() for s in l.split(separator)]
             fileData.append(data)
     return fileData
-

+ 3 - 1
direct/src/directutil/DistributedLargeBlobSender.py

@@ -2,8 +2,10 @@
 
 from direct.distributed import DistributedObject
 from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.MessengerGlobal import messenger
 from . import LargeBlobSenderConsts
 
+
 class DistributedLargeBlobSender(DistributedObject.DistributedObject):
     """DistributedLargeBlobSender: for sending large chunks of data through
     the DC system"""
@@ -56,7 +58,7 @@ class DistributedLargeBlobSender(DistributedObject.DistributedObject):
         except OSError:
             DistributedLargeBlobSender.notify.error(
                 'could not access %s' % bPath)
-        f = file(filename, 'rb')
+        f = open(filename, 'rb')
         self.blob = f.read()
         f.close()
         os.unlink(filename)

+ 2 - 2
direct/src/directutil/DistributedLargeBlobSenderAI.py

@@ -42,7 +42,7 @@ class DistributedLargeBlobSenderAI(DistributedObjectAI.DistributedObjectAI):
                     break
             # NOTE: there's a small chance of a race condition here, if
             # the file is created by another AI just after the stat fails
-            f = file(filename, 'wb')
+            f = open(filename, 'wb')
             f.write(s)
             f.close()
             os.chdir(origDir)
@@ -50,7 +50,7 @@ class DistributedLargeBlobSenderAI(DistributedObjectAI.DistributedObjectAI):
                                       'setFilename', [filename])
         else:
             chunkSize = LargeBlobSenderConsts.ChunkSize
-            while len(s):
+            while len(s) > 0:
                 self.sendUpdateToAvatarId(self.targetAvId,
                                           'setChunk', [s[:chunkSize]])
                 s = s[chunkSize:]

+ 23 - 24
direct/src/directutil/Mopath.py

@@ -1,4 +1,5 @@
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.MessengerGlobal import messenger
 from direct.directtools.DirectGeometry import *
 
 from panda3d.core import NodePath, LineSegs
@@ -8,7 +9,7 @@ class Mopath(DirectObject):
     nameIndex = 1
 
     def __init__(self, name = None, fluid = 1, objectToLoad = None, upVectorNodePath = None, reverseUpVector = False):
-        if (name == None):
+        if name is None:
             name = 'mopath%d' % self.nameIndex
             self.nameIndex = self.nameIndex + 1
         self.name = name
@@ -35,24 +36,23 @@ class Mopath(DirectObject):
         return self.maxT * self.timeScale
 
     def loadFile(self, filename, fReset = 1):
-        nodePath = loader.loadModel(filename)
+        nodePath = base.loader.loadModel(filename)
         if nodePath:
             self.loadNodePath(nodePath)
             nodePath.removeNode()
         else:
             print('Mopath: no data in file: %s' % filename)
 
-
     def loadNodePath(self, nodePath, fReset = 1):
         if fReset:
             self.reset()
 
         self.__extractCurves(nodePath)
-        if (self.tNurbsCurve != []):
+        if self.tNurbsCurve != []:
             self.maxT = self.tNurbsCurve[-1].getMaxT()
-        elif (self.xyzNurbsCurve != None):
+        elif self.xyzNurbsCurve is not None:
             self.maxT = self.xyzNurbsCurve.getMaxT()
-        elif (self.hprNurbsCurve != None):
+        elif self.hprNurbsCurve is not None:
             self.maxT = self.hprNurbsCurve.getMaxT()
         else:
             print('Mopath: no valid curves in nodePath: %s' % nodePath)
@@ -74,11 +74,11 @@ class Mopath(DirectObject):
             elif node.getCurveType() == PCTHPR:
                 self.hprNurbsCurve = node
             elif node.getCurveType() == PCTNONE:
-                if (self.xyzNurbsCurve == None):
+                if self.xyzNurbsCurve is None:
                     self.xyzNurbsCurve = node
                 else:
                     print('Mopath: got a PCT_NONE curve and an XYZ Curve in nodePath: %s' % nodePath)
-            elif (node.getCurveType() == PCTT):
+            elif node.getCurveType() == PCTT:
                 self.tNurbsCurve.append(node)
         else:
             # Iterate over children if any
@@ -97,29 +97,29 @@ class Mopath(DirectObject):
 
     def getFinalState(self):
         pos = Point3(0)
-        if (self.xyzNurbsCurve != None):
+        if self.xyzNurbsCurve is not None:
             self.xyzNurbsCurve.getPoint(self.maxT, pos)
         hpr = Point3(0)
-        if (self.hprNurbsCurve != None):
+        if self.hprNurbsCurve is not None:
             self.hprNurbsCurve.getPoint(self.maxT, hpr)
         return (pos, hpr)
 
     def goTo(self, node, time):
-        if (self.xyzNurbsCurve == None) and (self.hprNurbsCurve == None):
+        if self.xyzNurbsCurve is None and self.hprNurbsCurve is None:
             print('Mopath: Mopath has no curves')
             return
         time /= self.timeScale
         self.playbackTime = self.calcTime(CLAMP(time, 0.0, self.maxT))
-        if (self.xyzNurbsCurve != None):
+        if self.xyzNurbsCurve is not None:
             self.xyzNurbsCurve.getPoint(self.playbackTime, self.posPoint)
             if self.fluid:
                 node.setFluidPos(self.posPoint)
             else:
                 node.setPos(self.posPoint)
-        if (self.hprNurbsCurve != None):
+        if self.hprNurbsCurve is not None:
             self.hprNurbsCurve.getPoint(self.playbackTime, self.hprPoint)
             node.setHpr(self.hprPoint)
-        elif (self.fFaceForward and (self.xyzNurbsCurve != None)):
+        elif self.fFaceForward and self.xyzNurbsCurve is not None:
             if self.faceForwardDelta:
                 # Look at a point a bit ahead in parametric time.
                 t = min(self.playbackTime + self.faceForwardDelta, self.xyzNurbsCurve.getMaxT())
@@ -133,18 +133,18 @@ class Mopath(DirectObject):
 
             # use the self.upVectorNodePath position if it exists to
             # create an up vector for lookAt
-            if (self.upVectorNodePath is None):
+            if self.upVectorNodePath is None:
                 node.lookAt(lookPoint)
             else:
-                if (self.reverseUpVector == False):
-                     node.lookAt(lookPoint,
-                                 self.upVectorNodePath.getPos() - self.posPoint)
+                if not self.reverseUpVector:
+                    node.lookAt(lookPoint,
+                                self.upVectorNodePath.getPos() - self.posPoint)
                 else:
-                     node.lookAt(lookPoint,
-                                 self.posPoint - self.upVectorNodePath.getPos())
+                    node.lookAt(lookPoint,
+                                self.posPoint - self.upVectorNodePath.getPos())
 
     def play(self, node, time = 0.0, loop = 0):
-        if (self.xyzNurbsCurve == None) and (self.hprNurbsCurve == None):
+        if self.xyzNurbsCurve is None and self.hprNurbsCurve is None:
             print('Mopath: Mopath has no curves')
             return
         self.node = node
@@ -161,11 +161,11 @@ class Mopath(DirectObject):
         time = globalClock.getFrameTime()
         dTime = time - task.lastTime
         task.lastTime = time
-        if (self.loop):
+        if self.loop:
             cTime = (task.currentTime + dTime) % self.getMaxT()
         else:
             cTime = task.currentTime + dTime
-        if ((self.loop == 0) and (cTime > self.getMaxT())):
+        if self.loop == 0 and cTime > self.getMaxT():
             self.stop()
             messenger.send(self.name + '-done')
             self.node = None
@@ -187,4 +187,3 @@ class Mopath(DirectObject):
             ls.drawTo(p)
 
         return NodePath(ls.create())
-

+ 138 - 24
direct/src/dist/FreezeTool.py

@@ -12,6 +12,7 @@ import io
 import distutils.sysconfig as sysconf
 import zipfile
 import importlib
+import warnings
 
 from . import pefile
 
@@ -61,7 +62,7 @@ except ImportError:
     def pytest_imports():
         return []
 
-hiddenImports = {
+defaultHiddenImports = {
     'pytest': pytest_imports(),
     'pkg_resources': [
         'pkg_resources.*.*',
@@ -77,8 +78,29 @@ hiddenImports = {
         'numpy.core._dtype_ctypes',
         'numpy.core._methods',
     ],
+    'pandas.compat': ['lzma', 'cmath'],
+    'pandas._libs.tslibs.conversion': ['pandas._libs.tslibs.base'],
 }
 
+
+# These are modules that import other modules but shouldn't pick them up as
+# dependencies (usually because they are optional).  This prevents picking up
+# unwanted dependencies.
+ignoreImports = {
+    'direct.showbase.PythonUtil': ['pstats', 'profile'],
+
+    'toml.encoder': ['numpy'],
+}
+
+if sys.version_info >= (3, 8):
+    # importlib.metadata is a "provisional" module introduced in Python 3.8 that
+    # conditionally pulls in dependency-rich packages like "email" and "pep517"
+    # (the latter of which is a thirdparty package!)  But it's only imported in
+    # one obscure corner, so we don't want to pull it in by default.
+    ignoreImports['importlib._bootstrap_external'] = ['importlib.metadata']
+    ignoreImports['importlib.metadata'] = ['pep517']
+
+
 # These are overrides for specific modules.
 overrideModules = {
     # Used by the warnings module, among others, to get line numbers.  Since
@@ -163,23 +185,23 @@ class CompilationEnvironment:
         if self.platform.startswith('win'):
             self.Python = sysconf.PREFIX
 
-            if ('VCINSTALLDIR' in os.environ):
+            if 'VCINSTALLDIR' in os.environ:
                 self.MSVC = os.environ['VCINSTALLDIR']
-            elif (Filename('/c/Program Files/Microsoft Visual Studio 9.0/VC').exists()):
+            elif Filename('/c/Program Files/Microsoft Visual Studio 9.0/VC').exists():
                 self.MSVC = Filename('/c/Program Files/Microsoft Visual Studio 9.0/VC').toOsSpecific()
-            elif (Filename('/c/Program Files (x86)/Microsoft Visual Studio 9.0/VC').exists()):
+            elif Filename('/c/Program Files (x86)/Microsoft Visual Studio 9.0/VC').exists():
                 self.MSVC = Filename('/c/Program Files (x86)/Microsoft Visual Studio 9.0/VC').toOsSpecific()
-            elif (Filename('/c/Program Files/Microsoft Visual Studio .NET 2003/Vc7').exists()):
+            elif Filename('/c/Program Files/Microsoft Visual Studio .NET 2003/Vc7').exists():
                 self.MSVC = Filename('/c/Program Files/Microsoft Visual Studio .NET 2003/Vc7').toOsSpecific()
             else:
                 print('Could not locate Microsoft Visual C++ Compiler! Try running from the Visual Studio Command Prompt.')
                 sys.exit(1)
 
-            if ('WindowsSdkDir' in os.environ):
+            if 'WindowsSdkDir' in os.environ:
                 self.PSDK = os.environ['WindowsSdkDir']
-            elif (platform.architecture()[0] == '32bit' and Filename('/c/Program Files/Microsoft Platform SDK for Windows Server 2003 R2').exists()):
+            elif platform.architecture()[0] == '32bit' and Filename('/c/Program Files/Microsoft Platform SDK for Windows Server 2003 R2').exists():
                 self.PSDK = Filename('/c/Program Files/Microsoft Platform SDK for Windows Server 2003 R2').toOsSpecific()
-            elif (os.path.exists(os.path.join(self.MSVC, 'PlatformSDK'))):
+            elif os.path.exists(os.path.join(self.MSVC, 'PlatformSDK')):
                 self.PSDK = os.path.join(self.MSVC, 'PlatformSDK')
             else:
                 print('Could not locate the Microsoft Windows Platform SDK! Try running from the Visual Studio Command Prompt.')
@@ -197,7 +219,7 @@ class CompilationEnvironment:
                 self.suffix64 = '\\amd64'
 
             # If it is run by makepanda, it handles the MSVC and PlatformSDK paths itself.
-            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.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'
@@ -211,7 +233,7 @@ class CompilationEnvironment:
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
 
         elif self.platform.startswith('osx_'):
-            # OSX
+            # macOS
             proc = self.platform.split('_', 1)[1]
             if proc == 'i386':
                 self.arch = '-arch i386'
@@ -219,6 +241,8 @@ class CompilationEnvironment:
                 self.arch = '-arch ppc'
             elif proc == 'amd64':
                 self.arch = '-arch x86_64'
+            elif proc in ('arm64', 'aarch64'):
+                self.arch = '-arch arm64'
             self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
@@ -233,7 +257,7 @@ class CompilationEnvironment:
             self.linkExe = "%(CC)s -o %(basename)s %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
             self.linkDll = "%(LDSHARED)s -o %(basename)s.so %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
 
-            if (os.path.isdir("/usr/PCBSD/local/lib")):
+            if os.path.isdir("/usr/PCBSD/local/lib"):
                 self.linkExe += " -L/usr/PCBSD/local/lib"
                 self.linkDll += " -L/usr/PCBSD/local/lib"
 
@@ -636,7 +660,9 @@ okMissing = [
     'EasyDialogs', 'SOCKS', 'ic', 'rourl2path', 'termios', 'vms_lib',
     'OverrideFrom23._Res', 'email', 'email.Utils', 'email.Generator',
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
-    'direct.extensions_native.extensions_darwin',
+    'direct.extensions_native.extensions_darwin', '_manylinux',
+    'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
+    'collections.Sequence', 'numpy_distutils',
     ]
 
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -751,7 +777,7 @@ class Freezer:
             return 'ModuleDef(%s)' % (', '.join(args))
 
     def __init__(self, previous = None, debugLevel = 0,
-                 platform = None, path=None):
+                 platform = None, path=None, hiddenImports=None):
         # Normally, we are freezing for our own platform.  Change this
         # if untrue.
         self.platform = platform or PandaSystem.getPlatform()
@@ -825,6 +851,11 @@ class Freezer:
                 if path:
                     modulefinder.AddPackagePath(moduleName, path[0])
 
+        # Module with non-obvious dependencies
+        self.hiddenImports = defaultHiddenImports.copy()
+        if hiddenImports is not None:
+            self.hiddenImports.update(hiddenImports)
+
         # Suffix/extension for Python C extension modules
         if self.platform == PandaSystem.getPlatform():
             self.moduleSuffixes = imp.get_suffixes()
@@ -986,7 +1017,7 @@ class Freezer:
 
         # Scan the directory, looking for .py files.
         modules = []
-        for basename in os.listdir(pathname):
+        for basename in sorted(os.listdir(pathname)):
             if basename.endswith('.py') and basename != '__init__.py':
                 modules.append(basename[:-3])
 
@@ -998,8 +1029,8 @@ class Freezer:
         if not newName:
             newName = moduleName
 
-        assert(moduleName.endswith('.*'))
-        assert(newName.endswith('.*'))
+        assert moduleName.endswith('.*')
+        assert newName.endswith('.*')
 
         mdefs = {}
 
@@ -1009,7 +1040,7 @@ class Freezer:
         parentNames = [(parentName, newParentName)]
 
         if parentName.endswith('.*'):
-            assert(newParentName.endswith('.*'))
+            assert newParentName.endswith('.*')
             # Another special case.  The parent name "*" means to
             # return all possible directories within a particular
             # directory.
@@ -1020,7 +1051,7 @@ class Freezer:
             modulePath = self.getModulePath(topName)
             if modulePath:
                 for dirname in modulePath:
-                    for basename in os.listdir(dirname):
+                    for basename in sorted(os.listdir(dirname)):
                         if os.path.exists(os.path.join(dirname, basename, '__init__.py')):
                             parentName = '%s.%s' % (topName, basename)
                             newParentName = '%s.%s' % (newTopName, basename)
@@ -1166,7 +1197,7 @@ class Freezer:
 
         # Check if any new modules we found have "hidden" imports
         for origName in list(self.mf.modules.keys()):
-            hidden = hiddenImports.get(origName, [])
+            hidden = self.hiddenImports.get(origName, [])
             for modname in hidden:
                 if modname.endswith('.*'):
                     mdefs = self._gatherSubmodules(modname, implicit = True)
@@ -1342,7 +1373,7 @@ class Freezer:
         for moduleName, module in list(self.mf.modules.items()):
             if module.__code__:
                 co = self.mf.replace_paths_in_code(module.__code__)
-                module.__code__ = co;
+                module.__code__ = co
 
     def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
@@ -1721,7 +1752,7 @@ class Freezer:
         return target
 
     def generateRuntimeFromStub(self, target, stub_file, use_console, fields={},
-                                log_append=False):
+                                log_append=False, log_filename_strftime=False):
         self.__replacePaths()
 
         # We must have a __main__ module to make an exe file.
@@ -1904,9 +1935,12 @@ class Freezer:
             # A null entry marks the end of the module table.
             blob += struct.pack(entry_layout, 0, 0, 0)
 
+            # These flags should match the enum in deploy-stub.c
             flags = 0
             if log_append:
                 flags |= 1
+            if log_filename_strftime:
+                flags |= 2
 
             # Compose the header we will be writing to the stub, to tell it
             # where to find the module data blob, as well as other variables.
@@ -1952,7 +1986,7 @@ class Freezer:
 
         if append_offset:
             # This is for legacy deploy-stub.
-            print("WARNING: Could not find blob header. Is deploy-stub outdated?")
+            warnings.warn("Could not find blob header. Is deploy-stub outdated?")
             blob += struct.pack('<Q', blob_offset)
 
         with open(target, 'wb') as f:
@@ -2215,7 +2249,7 @@ class Freezer:
 
                 strings = macho_data[stroff:stroff+strsize]
 
-                for i in range(nsyms):
+                for j in range(nsyms):
                     strx, type, sect, desc, value = struct.unpack_from(nlist_struct, macho_data, symoff)
                     symoff += nlist_size
                     name = strings[strx : strings.find(b'\0', strx)]
@@ -2488,11 +2522,19 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         if name in self.badmodules:
             self._add_badmodule(name, caller)
             return
+
+        if level <= 0 and caller and caller.__name__ in ignoreImports:
+            if name in ignoreImports[caller.__name__]:
+                return
+
         try:
             self.import_hook(name, caller, level=level)
         except ImportError as msg:
             self.msg(2, "ImportError:", str(msg))
             self._add_badmodule(name, caller)
+        except SyntaxError as msg:
+            self.msg(2, "SyntaxError:", str(msg))
+            self._add_badmodule(name, caller)
         else:
             if fromlist:
                 for sub in fromlist:
@@ -2506,6 +2548,78 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                         self.msg(2, "ImportError:", str(msg))
                         self._add_badmodule(fullname, caller)
 
+    def scan_code(self, co, m):
+        code = co.co_code
+        # This was renamed to scan_opcodes in Python 3.6
+        if hasattr(self, 'scan_opcodes_25'):
+            scanner = self.scan_opcodes_25
+        else:
+            scanner = self.scan_opcodes
+
+        for what, args in scanner(co):
+            if what == "store":
+                name, = args
+                m.globalnames[name] = 1
+            elif what in ("import", "absolute_import"):
+                fromlist, name = args
+                have_star = 0
+                if fromlist is not None:
+                    if "*" in fromlist:
+                        have_star = 1
+                    fromlist = [f for f in fromlist if f != "*"]
+                if what == "absolute_import":
+                    level = 0
+                else:
+                    level = -1
+                self._safe_import_hook(name, m, fromlist, level=level)
+                if have_star:
+                    # We've encountered an "import *". If it is a Python module,
+                    # the code has already been parsed and we can suck out the
+                    # global names.
+                    mm = None
+                    if m.__path__:
+                        # At this point we don't know whether 'name' is a
+                        # submodule of 'm' or a global module. Let's just try
+                        # the full name first.
+                        mm = self.modules.get(m.__name__ + "." + name)
+                    if mm is None:
+                        mm = self.modules.get(name)
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            elif what == "relative_import":
+                level, fromlist, name = args
+                parent = self.determine_parent(m, level=level)
+                if name:
+                    self._safe_import_hook(name, m, fromlist, level=level)
+                else:
+                    self._safe_import_hook(parent.__name__, None, fromlist, level=0)
+
+                if fromlist and "*" in fromlist:
+                    if name:
+                        mm = self.modules.get(parent.__name__ + "." + name)
+                    else:
+                        mm = self.modules.get(parent.__name__)
+
+                    if mm is not None:
+                        m.globalnames.update(mm.globalnames)
+                        m.starimports.update(mm.starimports)
+                        if mm.__code__ is None:
+                            m.starimports[name] = 1
+                    else:
+                        m.starimports[name] = 1
+            else:
+                # We don't expect anything else from the generator.
+                raise RuntimeError(what)
+
+        for c in co.co_consts:
+            if isinstance(c, type(co)):
+                self.scan_code(c, m)
+
     def find_module(self, name, path=None, parent=None):
         """ Finds a module with the indicated name on the given search path
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where
@@ -2593,7 +2707,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             except OSError:
                 self.msg(2, "can't list directory", dir)
                 continue
-            for name in names:
+            for name in sorted(names):
                 mod = None
                 for suff in self.suffixes:
                     n = len(suff)

+ 154 - 197
direct/src/dist/commands.py

@@ -4,9 +4,9 @@ See the :ref:`distribution` section of the programming manual for information
 on how to use these commands.
 """
 
-import collections
 import os
 import plistlib
+import pkg_resources
 import sys
 import subprocess
 import zipfile
@@ -16,7 +16,6 @@ import stat
 import struct
 import imp
 import string
-import time
 import tempfile
 
 import setuptools
@@ -24,20 +23,11 @@ import distutils.log
 
 from . import FreezeTool
 from . import pefile
+from . import installers
 from .icon import Icon
 import panda3d.core as p3d
 
 
-if sys.version_info < (3, 0):
-    # Warn the user.  They might be using Python 2 by accident.
-    print("=================================================================")
-    print("WARNING: You are using Python 2, which has reached the end of its")
-    print("WARNING: life as of January 1, 2020.  Please upgrade to Python 3.")
-    print("=================================================================")
-    sys.stdout.flush()
-    time.sleep(4.0)
-
-
 def _parse_list(input):
     if isinstance(input, str):
         input = input.strip().replace(',', '\n')
@@ -104,14 +94,18 @@ PACKAGE_DATA_DIRS = {
         ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
         ('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
     ],
+    'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
 }
 
 # Some dependencies have extra directories that need to be scanned for DLLs.
 # This dictionary maps wheel basenames (ie. the part of the .whl basename
-# before the first hyphen) to a list of directories inside the .whl.
+# before the first hyphen) to a list of tuples, the first value being the
+# directory inside the wheel, the second being which wheel to look in (or
+# None to look in its own wheel).
 
 PACKAGE_LIB_DIRS = {
-    'scipy':  ['scipy/extra-dll'],
+    'scipy':  [('scipy/extra-dll', None)],
+    'PyQt5':  [('PyQt5/Qt5/bin', 'PyQt5_Qt5')],
 }
 
 SITE_PY = u"""
@@ -196,6 +190,7 @@ class build_apps(setuptools.Command):
         self.extra_prc_data = ''
         self.default_prc_dir = None
         self.log_filename = None
+        self.log_filename_strftime = True
         self.log_append = False
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.use_optimized_wheels = True
@@ -213,13 +208,17 @@ class build_apps(setuptools.Command):
             'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll',
             'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll',
             'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll',
-            'setupapi.dll', 'version.dll',
+            'setupapi.dll', 'version.dll', 'userenv.dll', 'netapi32.dll',
+            'crypt32.dll', 'bcrypt.dll',
 
             # manylinux1/linux
             'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*',
             'libpthread.so.*', 'libc.so.*', 'ld-linux-x86-64.so.*',
-            'libgl.so.*', 'libx11.so.*', 'libreadline.so.*', 'libncursesw.so.*',
-            'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
+            'libgl.so.*', 'libx11.so.*', 'libncursesw.so.*', 'libz.so.*',
+            'librt.so.*', 'libutil.so.*', 'libnsl.so.1', 'libXext.so.6',
+            'libXrender.so.1', 'libICE.so.6', 'libSM.so.6', 'libEGL.so.1',
+            'libOpenGL.so.0', 'libGLdispatch.so.0', 'libGLX.so.0',
+            'libgobject-2.0.so.0', 'libgthread-2.0.so.0', 'libglib-2.0.so.0',
 
             # macOS
             '/usr/lib/libc++.1.dylib',
@@ -229,15 +228,47 @@ class build_apps(setuptools.Command):
             '/usr/lib/libSystem.*.dylib',
             '/usr/lib/libbz2.*.dylib',
             '/usr/lib/libedit.*.dylib',
+            '/usr/lib/libffi.dylib',
+            '/usr/lib/libauditd.0.dylib',
+            '/usr/lib/libgermantok.dylib',
+            '/usr/lib/liblangid.dylib',
+            '/usr/lib/libarchive.2.dylib',
+            '/usr/lib/libipsec.A.dylib',
+            '/usr/lib/libpanel.5.4.dylib',
+            '/usr/lib/libiodbc.2.1.18.dylib',
+            '/usr/lib/libhunspell-1.2.0.0.0.dylib',
+            '/usr/lib/libsqlite3.dylib',
+            '/usr/lib/libpam.1.dylib',
+            '/usr/lib/libtidy.A.dylib',
+            '/usr/lib/libDHCPServer.A.dylib',
+            '/usr/lib/libpam.2.dylib',
+            '/usr/lib/libXplugin.1.dylib',
+            '/usr/lib/libxslt.1.dylib',
+            '/usr/lib/libiodbcinst.2.1.18.dylib',
+            '/usr/lib/libBSDPClient.A.dylib',
+            '/usr/lib/libsandbox.1.dylib',
+            '/usr/lib/libform.5.4.dylib',
+            '/usr/lib/libbsm.0.dylib',
+            '/usr/lib/libMatch.1.dylib',
+            '/usr/lib/libresolv.9.dylib',
+            '/usr/lib/libcharset.1.dylib',
+            '/usr/lib/libxml2.2.dylib',
+            '/usr/lib/libiconv.2.dylib',
+            '/usr/lib/libScreenReader.dylib',
+            '/usr/lib/libdtrace.dylib',
+            '/usr/lib/libicucore.A.dylib',
+            '/usr/lib/libsasl2.2.dylib',
+            '/usr/lib/libpcap.A.dylib',
+            '/usr/lib/libexslt.0.dylib',
+            '/usr/lib/libcurl.4.dylib',
+            '/usr/lib/libncurses.5.4.dylib',
+            '/usr/lib/libxar.1.dylib',
+            '/usr/lib/libmenu.5.4.dylib',
             '/System/Library/**',
         ]
 
-        if sys.version_info >= (3, 5):
-            # Python 3.5+ requires at least Windows Vista to run anyway, so we
-            # shouldn't warn about DLLs that are shipped with Vista.
-            self.exclude_dependencies += ['bcrypt.dll']
-
         self.package_data_dirs = {}
+        self.hidden_imports = {}
 
         # We keep track of the zip files we've opened.
         self._zip_files = {}
@@ -271,6 +302,10 @@ class build_apps(setuptools.Command):
         self.platforms = _parse_list(self.platforms)
         self.plugins = _parse_list(self.plugins)
         self.extra_prc_files = _parse_list(self.extra_prc_files)
+        self.hidden_imports = {
+            key: _parse_list(value)
+            for key, value in _parse_dict(self.hidden_imports).items()
+        }
 
         if self.default_prc_dir is None:
             self.default_prc_dir = '<auto>etc' if not self.embed_prc_data else ''
@@ -366,7 +401,8 @@ class build_apps(setuptools.Command):
             abi_tag += 'm'
 
         whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
-        os.makedirs(whldir, exist_ok=True)
+        if not os.path.isdir(whldir):
+            os.makedirs(whldir)
 
         # Remove any .zip files. These are built from a VCS and block for an
         # interactive prompt on subsequent downloads.
@@ -469,11 +505,7 @@ class build_apps(setuptools.Command):
             icon.makeICNS(os.path.join(resdir, 'iconfile.icns'))
 
         with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
-            if hasattr(plistlib, 'dump'):
-                plistlib.dump(plist, f)
-            else:
-                plistlib.writePlist(plist, f)
-
+            plistlib.dump(plist, f)
 
     def build_runtimes(self, platform, use_wheels):
         """ Builds the distributions for the given platform. """
@@ -486,6 +518,7 @@ class build_apps(setuptools.Command):
 
         path = sys.path[:]
         p3dwhl = None
+        wheelpaths = []
 
         if use_wheels:
             wheelpaths = self.download_wheels(platform)
@@ -581,6 +614,12 @@ class build_apps(setuptools.Command):
                     # by default.  Switch it up if FMOD is not included.
                     if value not in self.plugins and value == 'p3fmod_audio' and 'p3openal_audio' in self.plugins:
                         self.warn("Missing audio plugin p3fmod_audio referenced in PRC data, replacing with p3openal_audio")
+                        value = 'p3openal_audio'
+
+                if var == 'aux-display':
+                    # Silently remove aux-display lines for missing plugins.
+                    if value not in self.plugins:
+                        continue
 
                 for plugin in check_plugins:
                     if plugin in value and plugin not in self.plugins:
@@ -633,13 +672,22 @@ class build_apps(setuptools.Command):
                     # Also look for more specific per-package cases, defined in
                     # PACKAGE_LIB_DIRS at the top of this file.
                     extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, [])
-                    for extra_dir in extra_dirs:
-                        search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+                    for extra_dir, search_in in extra_dirs:
+                        if not search_in:
+                            search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+                        else:
+                            for whl2 in wheelpaths:
+                                if os.path.basename(whl2).startswith(search_in + '-'):
+                                    search_path.append(os.path.join(whl2, extra_dir.replace('/', os.path.sep)))
 
             return search_path
 
         def create_runtime(appname, mainscript, use_console):
-            freezer = FreezeTool.Freezer(platform=platform, path=path)
+            freezer = FreezeTool.Freezer(
+                platform=platform,
+                path=path,
+                hiddenImports=self.hidden_imports
+            )
             freezer.addModule('__main__', filename=mainscript)
             freezer.addModule('site', filename='site.py', text=SITE_PY)
             for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []):
@@ -678,6 +726,10 @@ class build_apps(setuptools.Command):
             else:
                 temp_file = None
 
+            use_strftime = self.log_filename_strftime
+            if not self.log_filename or '%' not in self.log_filename:
+                use_strftime = False
+
             freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
                 'prc_data': prcexport if self.embed_prc_data else None,
                 'default_prc_dir': self.default_prc_dir,
@@ -690,7 +742,7 @@ class build_apps(setuptools.Command):
                 'prc_executable_args_envvar': None,
                 'main_dir': None,
                 'log_filename': self.expand_path(self.log_filename, platform),
-            }, self.log_append)
+            }, self.log_append, use_strftime)
             stub_file.close()
 
             if temp_file:
@@ -760,6 +812,7 @@ class build_apps(setuptools.Command):
         for module, source_path in freezer_extras:
             if source_path is not None:
                 # Rename panda3d/core.pyd to panda3d.core.pyd
+                source_path = os.path.normpath(source_path)
                 basename = os.path.basename(source_path)
                 if '.' in module:
                     basename = module.rsplit('.', 1)[0] + '.' + basename
@@ -769,6 +822,20 @@ class build_apps(setuptools.Command):
                 if len(parts) >= 3 and '-' in parts[-2]:
                     parts = parts[:-2] + parts[-1:]
                     basename = '.'.join(parts)
+
+                # Was this not found in a wheel?  Then we may have a problem,
+                # since it may be for the current platform instead of the target
+                # platform.
+                if use_wheels:
+                    found_in_wheel = False
+                    for whl in wheelpaths:
+                        whl = os.path.normpath(whl)
+                        if source_path.lower().startswith(os.path.join(whl, '').lower()):
+                            found_in_wheel = True
+                            break
+
+                    if not found_in_wheel:
+                        self.warn('{} was not found in any downloaded wheel, is a dependency missing from requirements.txt?'.format(basename))
             else:
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 if module in whl_modules:
@@ -872,6 +939,27 @@ class build_apps(setuptools.Command):
             return check_pattern(fname, include_copy_list) and \
                 not check_pattern(fname, ignore_copy_list)
 
+        def skip_directory(src):
+            # Provides a quick-out for directory checks.  NOT recursive.
+            fn = p3d.Filename.from_os_specific(os.path.normpath(src))
+            path = fn.get_fullpath()
+            fn.make_absolute()
+            abspath = fn.get_fullpath()
+
+            for pattern in ignore_copy_list:
+                if not pattern.pattern.endswith('/*') and \
+                   not pattern.pattern.endswith('/**'):
+                    continue
+
+                pattern_dir = p3d.Filename(pattern.pattern).get_dirname()
+                if abspath.startswith(pattern_dir + '/'):
+                    return True
+
+                if path.startswith(pattern_dir + '/'):
+                    return True
+
+            return False
+
         def copy_file(src, dst):
             src = os.path.normpath(src)
             dst = os.path.normpath(dst)
@@ -911,7 +999,12 @@ class build_apps(setuptools.Command):
 
         rootdir = os.getcwd()
         for dirname, subdirlist, filelist in os.walk(rootdir):
+            subdirlist.sort()
             dirpath = os.path.relpath(dirname, rootdir)
+            if skip_directory(dirpath):
+                self.announce('skipping directory {}'.format(dirpath))
+                continue
+
             for fname in filelist:
                 src = os.path.join(dirpath, fname)
                 dst = os.path.join(builddir, update_path(src))
@@ -1240,6 +1333,14 @@ class bdist_apps(setuptools.Command):
         # Everything else defaults to ['zip']
     }
 
+    DEFAULT_INSTALLER_FUNCS = {
+        'zip': installers.create_zip,
+        'gztar': installers.create_gztar,
+        'bztar': installers.create_bztar,
+        'xztar': installers.create_xztar,
+        'nsis': installers.create_nsis,
+    }
+
     description = 'bundle built Panda3D applications into distributable forms'
     user_options = build_apps.user_options + [
         ('dist-dir=', 'd', 'directory to put final built distributions in'),
@@ -1253,6 +1354,8 @@ class bdist_apps(setuptools.Command):
         self.installers = {}
         self.dist_dir = os.path.join(os.getcwd(), 'dist')
         self.skip_build = False
+        self.installer_functions = {}
+        self._current_platform = None
         for opt in self._build_apps_options():
             setattr(self, opt, None)
 
@@ -1264,145 +1367,19 @@ class bdist_apps(setuptools.Command):
             for key, value in _parse_dict(self.installers).items()
         }
 
-    def _get_archive_basedir(self):
-        return self.distribution.get_name()
-
-    def create_zip(self, basename, build_dir):
-        import zipfile
-
-        base_dir = self._get_archive_basedir()
-
-        with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
-            zf.write(build_dir, base_dir)
-
-            for dirpath, dirnames, filenames in os.walk(build_dir):
-                for name in sorted(dirnames):
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    zf.write(path, path.replace(build_dir, base_dir, 1))
-                for name in filenames:
-                    path = os.path.normpath(os.path.join(dirpath, name))
-                    if os.path.isfile(path):
-                        zf.write(path, path.replace(build_dir, base_dir, 1))
-
-    def create_tarball(self, basename, build_dir, tar_compression):
-        import tarfile
-
-        base_dir = self._get_archive_basedir()
-        build_cmd = self.get_finalized_command('build_apps')
-        binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
-
-        def tarfilter(tarinfo):
-            if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
-                tarinfo.mode = 0o755
-            else:
-                tarinfo.mode = 0o644
-            return tarinfo
-
-        with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
-            tf.add(build_dir, base_dir, filter=tarfilter)
-
-    def create_nsis(self, basename, build_dir, is_64bit):
-        # Get a list of build applications
-        build_cmd = self.get_finalized_command('build_apps')
-        apps = build_cmd.gui_apps.copy()
-        apps.update(build_cmd.console_apps)
-        apps = [
-            '{}.exe'.format(i)
-            for i in apps
-        ]
-
-        shortname = self.distribution.get_name()
+        tmp = self.DEFAULT_INSTALLER_FUNCS.copy()
+        tmp.update(self.installer_functions)
+        tmp.update({
+            entrypoint.name: entrypoint.load()
+            for entrypoint in pkg_resources.iter_entry_points('panda3d.bdist_apps.installers')
+        })
+        self.installer_functions = tmp
 
-        # Create the .nsi installer script
-        nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
-        nsifile.unlink()
-        nsi = open(nsifile.to_os_specific(), "w")
+    def get_archive_basedir(self):
+        return self.distribution.get_name()
 
-        # Some global info
-        nsi.write('Name "%s"\n' % shortname)
-        nsi.write('OutFile "%s"\n' % os.path.join(self.dist_dir, basename+'.exe'))
-        if is_64bit:
-            nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
-        else:
-            nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
-        nsi.write('SetCompress auto\n')
-        nsi.write('SetCompressor lzma\n')
-        nsi.write('ShowInstDetails nevershow\n')
-        nsi.write('ShowUninstDetails nevershow\n')
-        nsi.write('InstType "Typical"\n')
-
-        # Tell Vista that we require admin rights
-        nsi.write('RequestExecutionLevel admin\n')
-        nsi.write('\n')
-
-        # TODO offer run and desktop shortcut after we figure out how to deal
-        # with multiple apps
-
-        nsi.write('!include "MUI2.nsh"\n')
-        nsi.write('!define MUI_ABORTWARNING\n')
-        nsi.write('\n')
-        nsi.write('Var StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
-        # TODO license file
-        nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
-        nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
-        nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_PAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
-        nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
-        nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
-        nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
-        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
-
-        # This section defines the installer.
-        nsi.write('Section "" SecCore\n')
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        curdir = ""
-        nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
-        build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
-        for root, dirs, files in os.walk(build_dir):
-            for name in files:
-                basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
-                file = p3d.Filename(basefile)
-                file.makeAbsolute()
-                file.makeRelativeTo(nsi_dir)
-                outdir = p3d.Filename(basefile)
-                outdir.makeAbsolute()
-                outdir.makeRelativeTo(build_root_dir)
-                outdir = outdir.getDirname().replace('/', '\\')
-                if curdir != outdir:
-                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
-                    curdir = outdir
-                nsi.write('  File "%s"\n' % (file.toOsSpecific()))
-        nsi.write('  SetOutPath "$INSTDIR"\n')
-        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
-        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
-        for app in apps:
-            nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
-        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
-        nsi.write('SectionEnd\n')
-
-        # This section defines the uninstaller.
-        nsi.write('Section Uninstall\n')
-        nsi.write('  RMDir /r "$INSTDIR"\n')
-        nsi.write('  ; Desktop icon\n')
-        nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
-        nsi.write('  ; Start menu items\n')
-        nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
-        nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
-        nsi.write('SectionEnd\n')
-        nsi.close()
-
-        cmd = ['makensis']
-        for flag in ["V2"]:
-            cmd.append(
-                '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
-            )
-        cmd.append(nsifile.to_os_specific())
-        subprocess.check_call(cmd)
+    def get_current_platform(self):
+        return self._current_platform
 
     def run(self):
         build_cmd = self.distribution.get_command_obj('build_apps')
@@ -1424,35 +1401,15 @@ class bdist_apps(setuptools.Command):
             build_dir = os.path.join(build_base, platform)
             basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
             installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
+            self._current_platform = platform
 
             for installer in installers:
                 self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
+                if installer not in self.installer_functions:
+                    self.announce(
+                        '\tUnknown installer: {}'.format(installer),
+                        distutils.log.ERROR
+                    )
+                    continue
 
-                if installer == 'zip':
-                    self.create_zip(basename, build_dir)
-                elif installer in ('gztar', 'bztar', 'xztar'):
-                    compress = installer.replace('tar', '')
-                    if compress == 'bz':
-                        compress = 'bz2'
-
-                    self.create_tarball(basename, build_dir, compress)
-                elif installer == 'nsis':
-                    if not platform.startswith('win'):
-                        self.announce(
-                            '\tNSIS installer not supported for platform: {}'.format(platform),
-                            distutils.log.ERROR
-                        )
-                        continue
-                    try:
-                        subprocess.call(['makensis', '--version'])
-                    except OSError:
-                        self.announce(
-                            '\tCould not find makensis tool that is required to build NSIS installers',
-                            distutils.log.ERROR
-                        )
-                        # continue
-                    is_64bit = platform == 'win_amd64'
-                    self.create_nsis(basename, build_dir, is_64bit)
-
-                else:
-                    self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR)
+                self.installer_functions[installer](self, basename, build_dir)

+ 1 - 1
direct/src/dist/icon.py

@@ -150,7 +150,7 @@ class Icon:
 
         # ICO files only support resolutions up to 256x256.
         count = 0
-        for size in self.images.keys():
+        for size in self.images:
             if size < 256:
                 count += 1
             if size <= 256:

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

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

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

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

+ 1 - 1
direct/src/dist/pfreeze.py

@@ -105,7 +105,7 @@ def main(args=None):
         elif opt == '-h':
             usage(0)
         else:
-            print('illegal option: ' + flag)
+            print('illegal option: ' + opt)
             sys.exit(1)
 
     if not basename:

+ 3 - 1
direct/src/distributed/AsyncRequest.py

@@ -1,6 +1,7 @@
 #from otp.ai.AIBaseGlobal import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.MessengerGlobal import messenger
 from .ConnectionRepository import *
 from panda3d.core import ConfigVariableDouble, ConfigVariableInt, ConfigVariableBool
 
@@ -254,7 +255,8 @@ class AsyncRequest(DirectObject):
                         print("\n\nself.avatarId =", self.avatarId)
                     print("\nself.neededObjects =", self.neededObjects)
                     print("\ntimed out after %s seconds.\n\n"%(task.delayTime,))
-                    import pdb; pdb.set_trace()
+                    import pdb
+                    pdb.set_trace()
             self.delete()
             return Task.done
 

+ 7 - 4
direct/src/distributed/CRCache.py

@@ -1,8 +1,11 @@
 """CRCache module: contains the CRCache class"""
 
 from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.MessengerGlobal import messenger
+from direct.showbase.PythonUtil import safeRepr, itype
 from . import DistributedObject
 
+
 class CRCache:
     notify = DirectNotifyGlobal.directNotify.newCategory("CRCache")
 
@@ -41,7 +44,7 @@ class CRCache:
         for distObj in delayDeleted:
             if distObj.getDelayDeleteCount() != 0:
                 delayDeleteLeaks.append(distObj)
-        if len(delayDeleteLeaks):
+        if len(delayDeleteLeaks) > 0:
             s = 'CRCache.flush:'
             for obj in delayDeleteLeaks:
                 s += ('\n  could not delete %s (%s), delayDeletes=%s' %
@@ -76,7 +79,7 @@ class CRCache:
                 # if the cache is full, pop the oldest item
                 oldestDistObj = self.fifo.pop(0)
                 # and remove it from the dictionary
-                del(self.dict[oldestDistObj.getDoId()])
+                del self.dict[oldestDistObj.getDoId()]
                 # and delete it
                 oldestDistObj.deleteOrDelay()
                 if oldestDistObj.getDelayDeleteCount() <= 0:
@@ -93,7 +96,7 @@ class CRCache:
             # Find the object
             distObj = self.dict[doId]
             # Remove it from the dictionary
-            del(self.dict[doId])
+            del self.dict[doId]
             # Remove it from the fifo
             self.fifo.remove(distObj)
             # return the distObj
@@ -111,7 +114,7 @@ class CRCache:
         # Look it up
         distObj = self.dict[doId]
         # Remove it from the dict and fifo
-        del(self.dict[doId])
+        del self.dict[doId]
         self.fifo.remove(distObj)
         # and delete it
         distObj.deleteOrDelay()

+ 0 - 1
direct/src/distributed/CRDataCache.py

@@ -114,4 +114,3 @@ if __debug__:
     dc._stopMemLeakCheck()
     dc.destroy()
     del dc
-

+ 4 - 4
direct/src/distributed/CartesianGridBase.py

@@ -29,8 +29,8 @@ class CartesianGridBase:
         # Compute which zone we are in
         zoneId = int(self.startingZone + ((row * self.gridSize) + col))
 
-        if (wantRowAndCol):
-            return (zoneId,col,row)
+        if wantRowAndCol:
+            return (zoneId, col, row)
         else:
             return zoneId
 
@@ -113,9 +113,9 @@ class CartesianGridBase:
             else:
                 # in a middle column, only look at top and bottom rows
                 possibleRows = []
-                if (topOffset == radius):
+                if topOffset == radius:
                     possibleRows.append(0)
-                if (bottomOffset == radius):
+                if bottomOffset == radius:
                     possibleRows.append(bottomOffset + topOffset)
             #print "on column %s and looking at rows %s"%(currCol,possibleRows)
             for currRow in possibleRows:

+ 9 - 8
direct/src/distributed/ClientRepository.py

@@ -2,10 +2,11 @@
 
 from .ClientRepositoryBase import ClientRepositoryBase
 from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.MessengerGlobal import messenger
 from .MsgTypesCMU import *
 from .PyDatagram import PyDatagram
 from .PyDatagramIterator import PyDatagramIterator
-from panda3d.core import UniqueIdAllocator
+from panda3d.core import UniqueIdAllocator, Notify
 
 
 class ClientRepository(ClientRepositoryBase):
@@ -68,7 +69,7 @@ class ClientRepository(ClientRepositoryBase):
         zone = di.getUint32()
         for obj in self.doId2do.values():
             if obj.zoneId == zone:
-                if (self.isLocalId(obj.doId)):
+                if self.isLocalId(obj.doId):
                     self.resendGenerate(obj)
 
     def resendGenerate(self, obj):
@@ -114,12 +115,12 @@ class ClientRepository(ClientRepositoryBase):
             # repeat-generate, synthesized for the benefit of someone
             # else who just entered the zone.  Accept the new updates,
             # but don't make a formal generate.
-            assert(self.notify.debug("performing generate-update for %s %s" % (dclass.getName(), doId)))
+            assert self.notify.debug("performing generate-update for %s %s" % (dclass.getName(), doId))
             dclass.receiveUpdateBroadcastRequired(distObj, di)
             dclass.receiveUpdateOther(distObj, di)
             return
 
-        assert(self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId)))
+        assert self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId))
         dclass.startGenerate()
         # Create a new distributed object, and put it in the dictionary
         distObj = self.generateWithRequiredOtherFields(dclass, doId, di, 0, zoneId)
@@ -200,7 +201,7 @@ class ClientRepository(ClientRepositoryBase):
         if not dclass:
             self.notify.error("Unknown distributed class: %s" % (distObj.__class__))
         classDef = dclass.getClassDef()
-        if classDef == None:
+        if classDef is None:
             self.notify.error("Could not create an undefined %s object." % (
                 dclass.getName()))
 
@@ -289,13 +290,13 @@ class ClientRepository(ClientRepositoryBase):
         """ Returns true if this doId is one that we're the owner of,
         false otherwise. """
 
-        return ((doId >= self.doIdBase) and (doId < self.doIdLast))
+        return doId >= self.doIdBase and doId < self.doIdLast
 
     def haveCreateAuthority(self):
         """ Returns true if this client has been assigned a range of
         doId's it may use to create objects, false otherwise. """
 
-        return (self.doIdLast > self.doIdBase)
+        return self.doIdLast > self.doIdBase
 
     def getAvatarIdFromSender(self):
         """ Returns the doIdBase of the client that originally sent
@@ -306,7 +307,7 @@ class ClientRepository(ClientRepositoryBase):
     def handleDatagram(self, di):
         if self.notify.getDebug():
             print("ClientRepository received datagram:")
-            di.getDatagram().dumpHex(ostream)
+            di.getDatagram().dumpHex(Notify.out())
 
         msgType = self.getMsgType()
         self.currentSenderId = None

+ 18 - 16
direct/src/distributed/ClientRepositoryBase.py

@@ -1,16 +1,18 @@
 from panda3d.core import *
 from panda3d.direct import *
-from .MsgTypes import *
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from direct.directnotify import DirectNotifyGlobal
-from . import CRCache
 from direct.distributed.CRDataCache import CRDataCache
 from direct.distributed.ConnectionRepository import ConnectionRepository
-from direct.showbase import PythonUtil
+from direct.showbase.PythonUtil import safeRepr, itype, makeList
+from direct.showbase.MessengerGlobal import messenger
+from .MsgTypes import *
+from . import CRCache
 from . import ParentMgr
 from . import RelatedObjectMgr
-import time
 from .ClockDelta import *
+import time
 
 
 class ClientRepositoryBase(ConnectionRepository):
@@ -31,7 +33,7 @@ class ClientRepositoryBase(ConnectionRepository):
         ConnectionRepository.__init__(self, connectMethod, base.config, hasOwnerView = True, threadedNet = threadedNet)
         self.dcSuffix = dcSuffix
         if hasattr(self, 'setVerbose'):
-            if self.config.GetBool('verbose-clientrepository'):
+            if ConfigVariableBool('verbose-clientrepository', False):
                 self.setVerbose(1)
 
         self.context=100000
@@ -40,7 +42,7 @@ class ClientRepositoryBase(ConnectionRepository):
         self.deferredGenerates = []
         self.deferredDoIds = {}
         self.lastGenerate = 0
-        self.setDeferInterval(base.config.GetDouble('deferred-generate-interval', 0.2))
+        self.setDeferInterval(ConfigVariableDouble('deferred-generate-interval', 0.2).value)
         self.noDefer = False  # Set this True to temporarily disable deferring.
 
         self.recorder = base.recorder
@@ -67,7 +69,7 @@ class ClientRepositoryBase(ConnectionRepository):
 
         # Keep track of how recently we last sent a heartbeat message.
         # We want to keep these coming at heartbeatInterval seconds.
-        self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
+        self.heartbeatInterval = ConfigVariableDouble('heartbeat-interval', 10).value
         self.heartbeatStarted = 0
         self.lastHeartbeat = 0
 
@@ -155,7 +157,7 @@ class ClientRepositoryBase(ConnectionRepository):
         # Look up the dclass
         assert parentId == self.GameGlobalsId or parentId in self.doId2do
         dclass = self.dclassesByNumber[classId]
-        assert(self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId)))
+        assert self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId))
         dclass.startGenerate()
         # Create a new distributed object, and put it in the dictionary
         distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
@@ -189,7 +191,7 @@ class ClientRepositoryBase(ConnectionRepository):
                 for dg, di in updates:
                     # non-DC updates that need to be played back in-order are
                     # stored as (msgType, (dg, di))
-                    if type(di) is tuple:
+                    if isinstance(di, tuple):
                         msgType = dg
                         dg, di = di
                         self.replayDeferredGenerate(msgType, (dg, di))
@@ -248,7 +250,7 @@ class ClientRepositoryBase(ConnectionRepository):
             # ...it is not in the dictionary or the cache.
             # Construct a new one
             classDef = dclass.getClassDef()
-            if classDef == None:
+            if classDef is None:
                 self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
             distObj = classDef(self)
             distObj.dclass = dclass
@@ -296,7 +298,7 @@ class ClientRepositoryBase(ConnectionRepository):
             # ...it is not in the dictionary or the cache.
             # Construct a new one
             classDef = dclass.getClassDef()
-            if classDef == None:
+            if classDef is None:
                 self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
             distObj = classDef(self)
             distObj.dclass = dclass
@@ -339,7 +341,7 @@ class ClientRepositoryBase(ConnectionRepository):
             # ...it is not in the dictionary or the cache.
             # Construct a new one
             classDef = dclass.getOwnerClassDef()
-            if classDef == None:
+            if classDef is None:
                 self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName()))
             distObj = classDef(self)
             distObj.dclass = dclass
@@ -452,7 +454,7 @@ class ClientRepositoryBase(ConnectionRepository):
             # a dict and adding the avatar handles to that dict when they are created
             # then change/remove the old method. I didn't do that because I couldn't think
             # of a use for it. -JML
-            try :
+            try:
                 handle = self.identifyAvatar(doId)
                 if handle:
                     dclass = self.dclassesByName[handle.dclassName]
@@ -477,7 +479,7 @@ class ClientRepositoryBase(ConnectionRepository):
     def handleGoGetLost(self, di):
         # The server told us it's about to drop the connection on us.
         # Get ready!
-        if (di.getRemainingSize() > 0):
+        if di.getRemainingSize() > 0:
             self.bootedIndex = di.getUint16()
             self.bootedText = di.getString()
 
@@ -495,7 +497,7 @@ class ClientRepositoryBase(ConnectionRepository):
 
     def handleServerHeartbeat(self, di):
         # Got a heartbeat message from the server.
-        if base.config.GetBool('server-heartbeat-info', 1):
+        if ConfigVariableBool('server-heartbeat-info', True):
             self.notify.info("Server heartbeat.")
 
     def handleSystemMessage(self, di):
@@ -579,7 +581,7 @@ class ClientRepositoryBase(ConnectionRepository):
         return worldNP
 
     def isLive(self):
-        if base.config.GetBool('force-live', 0):
+        if ConfigVariableBool('force-live', False):
             return True
         return not (__dev__ or launcher.isTestServer())
 

+ 4 - 4
direct/src/distributed/ClockDelta.py

@@ -78,7 +78,7 @@ class ClockDelta(DirectObject.DirectObject):
         # representing infinite uncertainty, if we have never received
         # a time measurement.
 
-        if self.uncertainty == None:
+        if self.uncertainty is None:
             return None
 
         now = self.globalClock.getRealTime()
@@ -190,7 +190,7 @@ class ClockDelta(DirectObject.DirectObject):
         the new measurement was used, false if it was discarded.
         """
         oldUncertainty = self.getUncertainty()
-        if oldUncertainty != None:
+        if oldUncertainty is not None:
             self.notify.info(
                 'previous delta at %.3f s, +/- %.3f s.' %
                 (self.delta, oldUncertainty))
@@ -241,14 +241,14 @@ class ClockDelta(DirectObject.DirectObject):
         minutes of the current local time given in now, or
         getRealTime() if now is not specified.
         """
-        if now == None:
+        if now is None:
             now = self.globalClock.getRealTime()
 
         # Are we in non-real-time mode (i.e. filming a movie)?  If you
         # set movie-network-time 1, then we'll circumvent this logic
         # and always return now.
         if self.globalClock.getMode() == ClockObject.MNonRealTime and \
-           base.config.GetBool('movie-network-time', False):
+           ConfigVariableBool('movie-network-time', False):
             return now
 
         # First, determine what network time we have for 'now'.

+ 9 - 7
direct/src/distributed/ConnectionRepository.py

@@ -1,10 +1,12 @@
 from panda3d.core import *
 from panda3d.direct import *
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.distributed.DoInterestManager import DoInterestManager
 from direct.distributed.DoCollectionManager import DoCollectionManager
 from direct.showbase import GarbageReport
+from direct.showbase.MessengerGlobal import messenger
 from .PyDatagramIterator import PyDatagramIterator
 
 import inspect
@@ -175,7 +177,7 @@ class ConnectionRepository(
         def applyFieldValues(distObj, dclass, values):
             for i in range(dclass.getNumInheritedFields()):
                 field = dclass.getInheritedField(i)
-                if field.asMolecularField() == None:
+                if field.asMolecularField() is None:
                     value = values.get(field.getName(), None)
                     if value is None and field.isRequired():
                         # Gee, this could be better.  What would really be
@@ -214,7 +216,7 @@ class ConnectionRepository(
 
         # Construct a new one
         classDef = dclass.getClassDef()
-        if classDef == None:
+        if classDef is None:
             self.notify.error("Could not create an undefined %s object."%(
                 dclass.getName()))
         distObj = classDef(self)
@@ -252,7 +254,7 @@ class ConnectionRepository(
             dcFileNames = [dcFileNames]
 
         dcImports = {}
-        if dcFileNames == None:
+        if dcFileNames is None:
             readResult = dcFile.readAll()
             if not readResult:
                 self.notify.error("Could not read dc file.")
@@ -322,7 +324,7 @@ class ConnectionRepository(
                 classDef = dcImports.get(className)
 
             # Also try it without the dcSuffix.
-            if classDef == None:
+            if classDef is None:
                 className = dclass.getName()
                 classDef = dcImports.get(className)
             if classDef is None:
@@ -379,7 +381,7 @@ class ConnectionRepository(
             # in the DC file.
             for i in range(dcFile.getNumClasses()):
                 dclass = dcFile.getClass(i)
-                if ((dclass.getName()+ownerDcSuffix) in ownerImportSymbols):
+                if (dclass.getName()+ownerDcSuffix) in ownerImportSymbols:
                     number = dclass.getNumber()
                     className = dclass.getName() + ownerDcSuffix
 
@@ -583,7 +585,7 @@ class ConnectionRepository(
         # available.  Returns the HTTPClient (also self.http), or None
         # if not set.
 
-        if self.http == None:
+        if self.http is None:
             try:
                 self.http = HTTPClient()
             except:
@@ -662,7 +664,7 @@ class ConnectionRepository(
             self.setSimulatedDisconnect(0)
 
     def uniqueName(self, idString):
-        return ("%s-%s" % (idString, self.uniqueId))
+        return "%s-%s" % (idString, self.uniqueId)
 
 class GCTrigger:
     # used to trigger garbage collection

+ 13 - 10
direct/src/distributed/DistributedCamera.py

@@ -4,6 +4,11 @@ from direct.fsm.FSM import FSM
 from direct.interval.IntervalGlobal import *
 from direct.distributed.DistributedObject import DistributedObject
 
+
+_camera_id = ConfigVariableInt('camera-id', -1)
+_aware_of_cameras = ConfigVariableInt('aware-of-cameras', 0)
+
+
 class Fixture(NodePath, FSM):
     def __init__(self, id, parent, pos, hpr, fov):
         NodePath.__init__(self, 'cam-%s' % id)
@@ -12,7 +17,7 @@ class Fixture(NodePath, FSM):
         self.lens = PerspectiveLens()
         self.lens.setFov(base.camLens.getFov())
 
-        model = loader.loadModel('models/misc/camera', okMissing = True)
+        model = base.loader.loadModel('models/misc/camera', okMissing = True)
         model.reparentTo(self)
 
         self.reparentTo(parent)
@@ -60,15 +65,13 @@ class Fixture(NodePath, FSM):
 
     def setRecordingInProgress(self, inProgress):
         self.recordingInProgress = inProgress
-        if self.recordingInProgress and \
-           base.config.GetInt('camera-id', -1) >= 0:
+        if self.recordingInProgress and _camera_id.value >= 0:
             self.hide()
         else:
             self.show()
 
     def show(self):
-        if base.config.GetBool('aware-of-cameras',0) and \
-           not self.recordingInProgress:
+        if _aware_of_cameras and not self.recordingInProgress:
             NodePath.show(self)
 
     def getScaleIval(self):
@@ -99,7 +102,7 @@ class Fixture(NodePath, FSM):
 
     def enterStandby(self):
         self.show()
-        if self.id == base.config.GetInt('camera-id', -1):
+        if self.id == _camera_id.value:
             self.setColorScale(3,0,0,1)
             self.getScaleIval().loop()
         else:
@@ -116,7 +119,7 @@ class Fixture(NodePath, FSM):
             self.scaleIval.finish()
 
     def enterRecording(self):
-        if base.config.GetInt('camera-id', -1) == self.id:
+        if _camera_id.value == self.id:
             self.demand('Using')
         else:
             self.show()
@@ -129,8 +132,8 @@ class Fixture(NodePath, FSM):
 
     def enterUsing(self, args = []):
         localAvatar.b_setGameState('Camera')
-        camera.setPosHpr(0,0,0,0,0,0)
-        camera.reparentTo(self)
+        base.camera.setPosHpr(0,0,0,0,0,0)
+        base.camera.reparentTo(self)
         self.hide()
 
         base.cam.node().setLens(self.lens)
@@ -177,7 +180,7 @@ class DistributedCamera(DistributedObject):
         DistributedObject.__init__(self, cr)
         self.parent = None
         self.fixtures = {}
-        self.cameraId = base.config.GetInt('camera-id',0)
+        self.cameraId = _camera_id.value
 
     def __getitem__(self, index):
         return self.fixtures.get(index)

+ 2 - 2
direct/src/distributed/DistributedCameraOV.py

@@ -24,7 +24,7 @@ class DistributedCameraOV(DistributedObjectOV):
         self.fixtures = fixtures
 
     def storeToFile(self, name):
-        f = file('cameras-%s.txt' % name, 'w')
+        f = open('cameras-%s.txt' % name, 'w')
         f.writelines(self.getObject().pack())
         f.close()
 
@@ -35,7 +35,7 @@ class DistributedCameraOV(DistributedObjectOV):
 
     def loadFromFile(self, name):
         self.b_setFixtures([])
-        f = file('cameras-%s.txt' % name, 'r');
+        f = open('cameras-%s.txt' % name, 'r')
         for line in f.readlines():
             pos,hpr,fov = self.unpackFixture(line)
             self.addFixture([pos[0],pos[1],pos[2],

+ 9 - 9
direct/src/distributed/DistributedCartesianGrid.py

@@ -81,7 +81,7 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
 
     def handleChildArrive(self, child, zoneId):
         DistributedNode.handleChildArrive(self, child, zoneId)
-        if (zoneId >= self.startingZone):
+        if zoneId >= self.startingZone:
             if not child.gridParent:
                 child.gridParent = GridParent(child)
             child.gridParent.setGridParent(self, zoneId)
@@ -91,7 +91,7 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
 
     def handleChildArriveZone(self, child, zoneId):
         DistributedNode.handleChildArrive(self, child, zoneId)
-        if (zoneId >= self.startingZone):
+        if zoneId >= self.startingZone:
             if not child.gridParent:
                 child.gridParent = GridParent(child)
             child.gridParent.setGridParent(self, zoneId)
@@ -150,21 +150,21 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
 
         # sometimes we also need to remove vis avatar from
         # my parent if it is also a grid
-        if (clearAll):
+        if clearAll:
             if event is not None:
                 parentEvent = eventGroup.newEvent('%s.parent.removeInterest' % self.doId)
             else:
                 parentEvent = None
 
             ##HACK BANDAID FOR PVP INSTANCES
-            if(hasattr(self.cr.doId2do[self.parentId],"worldGrid")):
+            if hasattr(self.cr.doId2do[self.parentId], "worldGrid"):
                 self.cr.doId2do[self.parentId].worldGrid.stopProcessVisibility(event=parentEvent)
 
     def processVisibility(self, task):
-        if self.visAvatar == None:
+        if self.visAvatar is None:
             # no avatar to process visibility for
             return Task.done
-        if(self.visAvatar.isDisabled()):
+        if self.visAvatar.isDisabled():
             self.visAvatar = None
             return Task.done
         if self.visAvatar.gameFSM.state == 'Cutscene':
@@ -192,7 +192,7 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
         zoneId = int(self.startingZone + ((row * self.gridSize) + col))
         assert self.notify.debug("processVisibility: %s: row: %s col: %s zoneId: %s" %
                                  (self.doId, row, col, zoneId))
-        if (zoneId == self.visZone):
+        if zoneId == self.visZone:
             assert self.notify.debug(
                 "processVisibility: %s: interest did not change" % (self.doId))
             if self.visDirty:
@@ -314,7 +314,7 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
 
             # Load up grid parts to initialize grid object
             # Polygon used to mark grid plane
-            # self.gridBack = loader.loadModel('models/misc/gridBack')
+            # self.gridBack = base.loader.loadModel('models/misc/gridBack')
             # self.gridBack.reparentTo(self)
             # self.gridBack.setColor(0.2, 0.2, 0.2, 0.5)
 
@@ -397,7 +397,7 @@ class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
             dx = self.cellWidth * self.gridSize * .5
             for i in range(self.gridSize):
                 for j in range(self.gridSize):
-                    marker = loader.loadModel("models/misc/smiley")
+                    marker = base.loader.loadModel("models/misc/smiley")
                     marker.reparentTo(self.markerParent)
                     marker.setPos(i * self.cellWidth - dx,
                                   j * self.cellWidth - dx,

+ 9 - 9
direct/src/distributed/DistributedCartesianGridAI.py

@@ -3,6 +3,7 @@ from panda3d.core import *
 from panda3d.direct import *
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.task import Task
+from direct.task.TaskManagerGlobal import taskMgr
 from .DistributedNodeAI import DistributedNodeAI
 from .CartesianGridBase import CartesianGridBase
 
@@ -56,7 +57,7 @@ class DistributedCartesianGridAI(DistributedNodeAI, CartesianGridBase):
         # Put the avatar on the grid
         self.handleAvatarZoneChange(av, useZoneId)
 
-        if (not self.updateTaskStarted) and startAutoUpdate:
+        if startAutoUpdate and not self.updateTaskStarted:
             self.startUpdateGridTask()
 
     def removeObjectFromGrid(self, av):
@@ -97,27 +98,27 @@ class DistributedCartesianGridAI(DistributedNodeAI, CartesianGridBase):
     def updateGridTask(self, task=None):
         # Run through all grid objects and update their parents if needed
         missingObjs = []
-        for avId in self.gridObjects.keys():
+        for avId in list(self.gridObjects.keys()):
             av = self.gridObjects[avId]
             # handle a missing object after it is already gone?
-            if (av.isEmpty()):
+            if av.isEmpty():
                 task.setDelay(1.0)
                 del self.gridObjects[avId]
                 continue
             pos = av.getPos()
-            if ((pos[0] < 0 or pos[1] < 0) or
-                (pos[0] > self.cellWidth or pos[1] > self.cellWidth)):
+            if (pos[0] < 0 or pos[1] < 0) or \
+               (pos[0] > self.cellWidth or pos[1] > self.cellWidth):
                 # we are out of the bounds of this current cell
                 self.handleAvatarZoneChange(av)
         # Do this every second, not every frame
-        if (task):
+        if task:
             task.setDelay(1.0)
         return Task.again
 
     def handleAvatarZoneChange(self, av, useZoneId=-1):
         # Calculate zone id
         # Get position of av relative to this grid
-        if (useZoneId == -1):
+        if useZoneId == -1:
             pos = av.getPos(self)
             zoneId = self.getZoneFromXYZ(pos)
         else:
@@ -137,9 +138,8 @@ class DistributedCartesianGridAI(DistributedNodeAI, CartesianGridBase):
 
     def handleSetLocation(self, av, parentId, zoneId):
         pass
-        #if (av.parentId != parentId):
+        #if av.parentId != parentId:
             # parent changed, need to look up instance tree
             # to see if avatar's named area location information
             # changed
             #av.requestRegionUpdateTask(regionegionUid)
-

+ 4 - 8
direct/src/distributed/DistributedNode.py

@@ -9,9 +9,7 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
     """Distributed Node class:"""
 
     def __init__(self, cr):
-        try:
-            self.DistributedNode_initialized
-        except:
+        if not hasattr(self, 'DistributedNode_initialized'):
             self.DistributedNode_initialized = 1
             self.gotStringParentToken = 0
             DistributedObject.DistributedObject.__init__(self, cr)
@@ -28,9 +26,7 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
             DistributedObject.DistributedObject.disable(self)
 
     def delete(self):
-        try:
-            self.DistributedNode_deleted
-        except:
+        if not hasattr(self, 'DistributedNode_deleted'):
             self.DistributedNode_deleted = 1
             if not self.isEmpty():
                 self.removeNode()
@@ -78,7 +74,7 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
     ### setParent ###
 
     def b_setParent(self, parentToken):
-        if type(parentToken) == str:
+        if isinstance(parentToken, str):
             self.setParentStr(parentToken)
         else:
             self.setParent(parentToken)
@@ -86,7 +82,7 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
         self.d_setParent(parentToken)
 
     def d_setParent(self, parentToken):
-        if type(parentToken) == str:
+        if isinstance(parentToken, str):
             self.sendUpdate("setParentStr", [parentToken])
         else:
             self.sendUpdate("setParent", [parentToken])

+ 3 - 5
direct/src/distributed/DistributedNodeAI.py

@@ -6,9 +6,7 @@ from . import GridParent
 class DistributedNodeAI(DistributedObjectAI.DistributedObjectAI, NodePath):
     def __init__(self, air, name=None):
         # Be careful not to create multiple NodePath objects
-        try:
-            self.DistributedNodeAI_initialized
-        except:
+        if not hasattr(self, 'DistributedNodeAI_initialized'):
             self.DistributedNodeAI_initialized = 1
             DistributedObjectAI.DistributedObjectAI.__init__(self, air)
             if name is None:
@@ -47,14 +45,14 @@ class DistributedNodeAI(DistributedObjectAI.DistributedObjectAI, NodePath):
     ### setParent ###
 
     def b_setParent(self, parentToken):
-        if type(parentToken) == str:
+        if isinstance(parentToken, str):
             self.setParentStr(parentToken)
         else:
             self.setParent(parentToken)
         self.d_setParent(parentToken)
 
     def d_setParent(self, parentToken):
-        if type(parentToken) == type(''):
+        if isinstance(parentToken, str):
             self.sendUpdate("setParentStr", [parentToken])
         else:
             self.sendUpdate("setParent", [parentToken])

+ 4 - 6
direct/src/distributed/DistributedNodeUD.py

@@ -3,30 +3,28 @@ from .DistributedObjectUD import DistributedObjectUD
 class DistributedNodeUD(DistributedObjectUD):
     def __init__(self, air, name=None):
         # Be careful not to create multiple NodePath objects
-        try:
-            self.DistributedNodeUD_initialized
-        except:
+        if not hasattr(self, 'DistributedNodeUD_initialized'):
             self.DistributedNodeUD_initialized = 1
             DistributedObjectUD.__init__(self, air)
             if name is None:
                 name = self.__class__.__name__
 
     def b_setParent(self, parentToken):
-        if type(parentToken) == str:
+        if isinstance(parentToken, str):
             self.setParentStr(parentToken)
         else:
             self.setParent(parentToken)
         self.d_setParent(parentToken)
 
     def d_setParent(self, parentToken):
-        if type(parentToken) == type(''):
+        if isinstance(parentToken, str):
             self.sendUpdate("setParentStr", [parentToken])
         else:
             self.sendUpdate("setParent", [parentToken])
 
     def setParentStr(self, parentToken):
         self.notify.debugCall()
-        if len(parentTokenStr) > 0:
+        if len(parentToken) > 0:
             self.do_setParent(parentToken)
 
     def setParent(self, parentToken):

+ 23 - 28
direct/src/distributed/DistributedObject.py

@@ -2,6 +2,7 @@
 
 from panda3d.core import *
 from panda3d.direct import *
+from direct.showbase.MessengerGlobal import messenger
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.distributed.DistributedObjectBase import DistributedObjectBase
 #from PyDatagram import PyDatagram
@@ -43,9 +44,7 @@ class DistributedObject(DistributedObjectBase):
 
     def __init__(self, cr):
         assert self.notify.debugStateCall(self)
-        try:
-            self.DistributedObject_initialized
-        except:
+        if not hasattr(self, 'DistributedObject_initialized'):
             self.DistributedObject_initialized = 1
             DistributedObjectBase.__init__(self, cr)
 
@@ -96,7 +95,7 @@ class DistributedObject(DistributedObjectBase):
                     flags.append("cacheable")
 
                 flagStr = ""
-                if len(flags):
+                if len(flags) > 0:
                     flagStr = " (%s)" % (" ".join(flags))
 
                 print("%sfrom DistributedObject doId:%s, parent:%s, zone:%s%s" % (
@@ -125,8 +124,8 @@ class DistributedObject(DistributedObjectBase):
                     if field is not None:
                         p = DCPacker()
                         p.setUnpackData(field.getDefaultValue())
-                        len = p.rawUnpackUint16()/4
-                        for i in range(len):
+                        length = p.rawUnpackUint16() // 4
+                        for i in range(length):
                             zone = int(p.rawUnpackUint32())
                             autoInterests.add(zone)
                     autoInterests.update(autoInterests)
@@ -142,9 +141,9 @@ class DistributedObject(DistributedObjectBase):
         _getAutoInterests = None
         return list(autoInterests)
 
-    def setNeverDisable(self, bool):
-        assert bool == 1 or bool == 0
-        self.neverDisable = bool
+    def setNeverDisable(self, boolean):
+        assert boolean == 1 or boolean == 0
+        self.neverDisable = boolean
 
     def getNeverDisable(self):
         return self.neverDisable
@@ -156,31 +155,31 @@ class DistributedObject(DistributedObjectBase):
             self._cachedData = self.cr.doDataCache.popCachedData(self.doId)
 
     def setCachedData(self, name, data):
-        assert type(name) == type('')
+        assert isinstance(name, str)
         # ownership of the data passes to the repository data cache
         self.cr.doDataCache.setCachedData(self.doId, name, data)
 
     def hasCachedData(self, name):
-        assert type(name) == type('')
+        assert isinstance(name, str)
         if not hasattr(self, '_cachedData'):
             return False
         return name in self._cachedData
 
     def getCachedData(self, name):
-        assert type(name) == type('')
+        assert isinstance(name, str)
         # ownership of the data passes to the caller of this method
         data = self._cachedData[name]
         del self._cachedData[name]
         return data
 
     def flushCachedData(self, name):
-        assert type(name) == type('')
+        assert isinstance(name, str)
         # call this to throw out cached data from a previous instantiation
         self._cachedData[name].flush()
 
-    def setCacheable(self, bool):
-        assert bool == 1 or bool == 0
-        self.cacheable = bool
+    def setCacheable(self, boolean):
+        assert boolean == 1 or boolean == 0
+        self.cacheable = boolean
 
     def getCacheable(self):
         return self.cacheable
@@ -278,14 +277,13 @@ class DistributedObject(DistributedObjectBase):
         Inheritors should redefine this to take appropriate action on disable
         """
         assert self.notify.debug('disable(): %s' % (self.doId))
-        pass
 
     def isDisabled(self):
         """
         Returns true if the object has been disabled and/or deleted,
         or if it is brand new and hasn't yet been generated.
         """
-        return (self.activeState < ESGenerating)
+        return self.activeState < ESGenerating
 
     def isGenerated(self):
         """
@@ -293,17 +291,14 @@ class DistributedObject(DistributedObjectBase):
         and not yet disabled.
         """
         assert self.notify.debugStateCall(self)
-        return (self.activeState == ESGenerated)
+        return self.activeState == ESGenerated
 
     def delete(self):
         """
         Inheritors should redefine this to take appropriate action on delete
         """
         assert self.notify.debug('delete(): %s' % (self.doId))
-        try:
-            self.DistributedObject_deleted
-        except:
-            self.DistributedObject_deleted = 1
+        self.DistributedObject_deleted = 1
 
     def generate(self):
         """
@@ -374,10 +369,10 @@ class DistributedObject(DistributedObjectBase):
         self.cr.sendDeleteMsg(self.doId)
 
     def taskName(self, taskString):
-        return ("%s-%s" % (taskString, self.doId))
+        return "%s-%s" % (taskString, self.doId)
 
     def uniqueName(self, idString):
-        return ("%s-%s" % (idString, self.doId))
+        return "%s-%s" % (idString, self.doId)
 
     def getCallbackContext(self, callback, extraArgs = []):
         # Some objects implement a back-and-forth handshake operation
@@ -429,7 +424,7 @@ class DistributedObject(DistributedObjectBase):
         if tuple:
             callback, extraArgs = tuple
             completeArgs = args + extraArgs
-            if callback != None:
+            if callback is not None:
                 callback(*completeArgs)
             del self.__callbacks[context]
         else:
@@ -470,9 +465,9 @@ class DistributedObject(DistributedObjectBase):
         # doneBarrier() twice, or we have not received a barrier
         # context from the AI.  I think in either case it's ok to
         # silently ignore the error.
-        if self.__barrierContext != None:
+        if self.__barrierContext is not None:
             context, aiName = self.__barrierContext
-            if name == None or name == aiName:
+            if name is None or name == aiName:
                 assert self.notify.debug('doneBarrier(%s, %s)' % (context, aiName))
                 self.sendUpdate("setBarrierReady", [context])
                 self.__barrierContext = None

+ 27 - 32
direct/src/distributed/DistributedObjectAI.py

@@ -2,6 +2,7 @@
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.distributed.DistributedObjectBase import DistributedObjectBase
+from direct.showbase.MessengerGlobal import messenger
 from direct.showbase import PythonUtil
 from panda3d.core import *
 from panda3d.direct import *
@@ -13,9 +14,7 @@ class DistributedObjectAI(DistributedObjectBase):
     QuietZone = 1
 
     def __init__(self, air):
-        try:
-            self.DistributedObjectAI_initialized
-        except:
+        if not hasattr(self, 'DistributedObjectAI_initialized'):
             self.DistributedObjectAI_initialized = 1
             DistributedObjectBase.__init__(self, air)
 
@@ -66,11 +65,11 @@ class DistributedObjectAI(DistributedObjectBase):
                 flags = []
                 if self.__generated:
                     flags.append("generated")
-                if self.air == None:
+                if self.air is None:
                     flags.append("deleted")
 
                 flagStr = ""
-                if len(flags):
+                if len(flags) > 0:
                     flagStr = " (%s)" % (" ".join(flags))
 
                 print("%sfrom DistributedObject doId:%s, parent:%s, zone:%s%s" % (
@@ -119,24 +118,23 @@ class DistributedObjectAI(DistributedObjectBase):
                 # self.doId may not exist.  The __dict__ syntax works around that.
                 assert self.notify.debug('delete(): %s' % (self.__dict__.get("doId")))
 
-                if not self._DOAI_requestedDelete:
-                    # this logs every delete that was not requested by us.
-                    # TODO: this currently prints warnings for deletes of objects
-                    # that we did not create. We need to add a 'locally created'
-                    # flag to every object to filter these out.
-                    """
-                    DistributedObjectAI.notify.warning(
-                        'delete() called but requestDelete never called for %s: %s'
-                        % (self.__dict__.get('doId'), self.__class__.__name__))
-                        """
-                    """
-                    # print a stack trace so we can detect whether this is the
-                    # result of a network msg.
-                    # this is slow.
-                    from direct.showbase.PythonUtil import StackTrace
-                    DistributedObjectAI.notify.warning(
-                        'stack trace: %s' % StackTrace())
-                        """
+                #if not self._DOAI_requestedDelete:
+                #    # this logs every delete that was not requested by us.
+                #    # TODO: this currently prints warnings for deletes of objects
+                #    # that we did not create. We need to add a 'locally created'
+                #    # flag to every object to filter these out.
+                #
+                #    DistributedObjectAI.notify.warning(
+                #        'delete() called but requestDelete never called for %s: %s'
+                #        % (self.__dict__.get('doId'), self.__class__.__name__))
+                #
+                #    # print a stack trace so we can detect whether this is the
+                #    # result of a network msg.
+                #    # this is slow.
+                #    from direct.showbase.PythonUtil import StackTrace
+                #    DistributedObjectAI.notify.warning(
+                #        'stack trace: %s' % StackTrace())
+
                 self._DOAI_requestedDelete = False
 
                 self.releaseZoneData()
@@ -167,7 +165,7 @@ class DistributedObjectAI(DistributedObjectBase):
         Returns true if the object has been deleted,
         or if it is brand new and hasnt yet been generated.
         """
-        return self.air == None
+        return self.air is None
 
     def isGenerated(self):
         """
@@ -195,7 +193,6 @@ class DistributedObjectAI(DistributedObjectBase):
         Called after the object has been generated and all
         of its required fields filled in. Overwrite when needed.
         """
-        pass
 
     def b_setLocation(self, parentId, zoneId):
         self.d_setLocation(parentId, zoneId)
@@ -206,14 +203,13 @@ class DistributedObjectAI(DistributedObjectBase):
 
     def setLocation(self, parentId, zoneId):
         # Prevent Duplicate SetLocations for being Called
-        if (self.parentId == parentId) and (self.zoneId == zoneId):
+        if self.parentId == parentId and self.zoneId == zoneId:
             return
 
         oldParentId = self.parentId
         oldZoneId = self.zoneId
         self.air.storeObjectLocation(self, parentId, zoneId)
-        if ((oldParentId != parentId) or
-            (oldZoneId != zoneId)):
+        if oldParentId != parentId or oldZoneId != zoneId:
             self.releaseZoneData()
             messenger.send(self.getZoneChangeEvent(), [zoneId, oldZoneId])
             # if we are not going into the quiet zone, send a 'logical' zone
@@ -476,10 +472,10 @@ class DistributedObjectAI(DistributedObjectBase):
         self._DOAI_requestedDelete = True
 
     def taskName(self, taskString):
-        return ("%s-%s" % (taskString, self.doId))
+        return "%s-%s" % (taskString, self.doId)
 
     def uniqueName(self, idString):
-        return ("%s-%s" % (idString, self.doId))
+        return "%s-%s" % (idString, self.doId)
 
     def validate(self, avId, bool, msg):
         if not bool:
@@ -542,7 +538,7 @@ class DistributedObjectAI(DistributedObjectBase):
         avId = self.air.getAvatarIdFromSender()
         assert self.notify.debug('setBarrierReady(%s, %s)' % (context, avId))
         barrier = self.__barriers.get(context)
-        if barrier == None:
+        if barrier is None:
             # This may be None if a client was slow and missed an
             # earlier timeout.  Too bad.
             return
@@ -569,7 +565,6 @@ class DistributedObjectAI(DistributedObjectBase):
 
     def _retrieveCachedData(self):
         """ This is a no-op on the AI. """
-        pass
 
     def setAI(self, aiChannel):
         self.air.setAI(self.doId, aiChannel)

+ 1 - 8
direct/src/distributed/DistributedObjectBase.py

@@ -49,7 +49,6 @@ class DistributedObjectBase(DirectObject):
         """
         assert self.notify.debugCall()
         # Inheritors should override
-        pass
 
     def handleChildArriveZone(self, childObj, zoneId):
         """
@@ -60,7 +59,6 @@ class DistributedObjectBase(DirectObject):
         """
         assert self.notify.debugCall()
         # Inheritors should override
-        pass
 
     def handleChildLeave(self, childObj, zoneId):
         """
@@ -69,7 +67,6 @@ class DistributedObjectBase(DirectObject):
         """
         assert self.notify.debugCall()
         # Inheritors should override
-        pass
 
     def handleChildLeaveZone(self, childObj, zoneId):
         """
@@ -79,12 +76,10 @@ class DistributedObjectBase(DirectObject):
         """
         assert self.notify.debugCall()
         # Inheritors should override
-        pass
 
     def handleQueryObjectChildrenLocalDone(self, context):
         assert self.notify.debugCall()
         # Inheritors should override
-        pass
 
     def getParentObj(self):
         if self.parentId is None:
@@ -92,12 +87,10 @@ class DistributedObjectBase(DirectObject):
         return self.cr.doId2do.get(self.parentId)
 
     def hasParentingRules(self):
-        return self.dclass.getFieldByName('setParentingRules') != None
+        return self.dclass.getFieldByName('setParentingRules') is not None
 
     def delete(self):
         """
         Override this to handle cleanup right before this object
         gets deleted.
         """
-
-        pass

+ 0 - 1
direct/src/distributed/DistributedObjectGlobal.py

@@ -21,4 +21,3 @@ class DistributedObjectGlobal(DistributedObject):
         DistributedObject.__init__(self, cr)
         self.parentId = 0
         self.zoneId = 0
-

Some files were not shown because too many files changed in this diff