2
0
Эх сурвалжийг харах

Merge branch 'master' into shaderpipeline

rdb 5 жил өмнө
parent
commit
1921e1d20e
100 өөрчлөгдсөн 5347 нэмэгдсэн , 545 устгасан
  1. 22 0
      .editorconfig
  2. 16 0
      .github/codecov.yml
  3. 336 11
      .github/workflows/ci.yml
  4. 3 0
      .gitignore
  5. 2 2
      .travis.yml
  6. 190 0
      CMakeLists.txt
  7. 19 18
      README.md
  8. 78 0
      cmake/README.md
  9. 157 0
      cmake/install/Panda3DConfig.cmake
  10. 75 0
      cmake/macros/AddBisonTarget.cmake
  11. 77 0
      cmake/macros/AddFlexTarget.cmake
  12. 426 0
      cmake/macros/BuildMetalib.cmake
  13. 167 0
      cmake/macros/CompositeSources.cmake
  14. 400 0
      cmake/macros/Interrogate.cmake
  15. 506 0
      cmake/macros/PackageConfig.cmake
  16. 50 0
      cmake/macros/PerConfigOption.cmake
  17. 163 0
      cmake/macros/Python.cmake
  18. 9 0
      cmake/macros/README.md
  19. 37 0
      cmake/macros/RunPzip.cmake
  20. 21 0
      cmake/macros/Versioning.cmake
  21. 33 0
      cmake/modules/FindARToolKit.cmake
  22. 33 0
      cmake/modules/FindAssimp.cmake
  23. 175 0
      cmake/modules/FindCg.cmake
  24. 104 0
      cmake/modules/FindDirect3D9.cmake
  25. 21 0
      cmake/modules/FindEGL.cmake
  26. 25 0
      cmake/modules/FindEigen3.cmake
  27. 88 0
      cmake/modules/FindFCollada.cmake
  28. 116 0
      cmake/modules/FindFFMPEG.cmake
  29. 107 0
      cmake/modules/FindFFTW3.cmake
  30. 83 0
      cmake/modules/FindFMODEx.cmake
  31. 23 0
      cmake/modules/FindHarfBuzz.cmake
  32. 78 0
      cmake/modules/FindLibSquish.cmake
  33. 157 0
      cmake/modules/FindODE.cmake
  34. 20 0
      cmake/modules/FindOgg.cmake
  35. 92 0
      cmake/modules/FindOpenCV.cmake
  36. 88 0
      cmake/modules/FindOpenEXR.cmake
  37. 21 0
      cmake/modules/FindOpenGLES1.cmake
  38. 21 0
      cmake/modules/FindOpenGLES2.cmake
  39. 34 0
      cmake/modules/FindOpusFile.cmake
  40. 50 0
      cmake/modules/FindSWResample.cmake
  41. 50 0
      cmake/modules/FindSWScale.cmake
  42. 40 0
      cmake/modules/FindVRPN.cmake
  43. 34 0
      cmake/modules/FindVorbisFile.cmake
  44. 7 0
      cmake/modules/README.md
  45. 70 0
      cmake/scripts/ConcatenateToCXX.cmake
  46. 27 0
      cmake/scripts/CopyPattern.cmake
  47. 78 0
      cmake/scripts/CopyPython.cmake
  48. 28 0
      cmake/scripts/MakeComposite.cmake
  49. 10 0
      cmake/scripts/README.md
  50. 7 0
      cmake/templates/METADATA.in
  51. 9 0
      cmake/templates/metalib_init.cxx.in
  52. 9 0
      cmake/templates/metalib_init.h.in
  53. 23 0
      cmake/templates/win32_python/__init__.py
  54. 18 0
      contrib/CMakeLists.txt
  55. 54 0
      contrib/src/ai/CMakeLists.txt
  56. 14 0
      contrib/src/contribbase/CMakeLists.txt
  57. 52 0
      contrib/src/rplight/CMakeLists.txt
  58. 75 0
      direct/CMakeLists.txt
  59. 85 75
      direct/src/actor/Actor.py
  60. 6 8
      direct/src/cluster/ClusterMsgs.py
  61. 1 1
      direct/src/controls/ControlManager.py
  62. 3 0
      direct/src/dcparse/CMakeLists.txt
  63. 87 0
      direct/src/dcparser/CMakeLists.txt
  64. 1 3
      direct/src/dcparser/dcClass.cxx
  65. 0 8
      direct/src/dcparser/dcClass_ext.cxx
  66. 0 12
      direct/src/dcparser/dcField_ext.cxx
  67. 5 3
      direct/src/dcparser/dcPacker.I
  68. 1 3
      direct/src/dcparser/dcPackerCatalog.cxx
  69. 1 3
      direct/src/dcparser/dcPackerInterface.cxx
  70. 0 61
      direct/src/dcparser/dcPacker_ext.cxx
  71. 3 9
      direct/src/dcparser/dcSwitch.cxx
  72. 3 3
      direct/src/dcparser/dcbase.h
  73. 24 0
      direct/src/deadrec/CMakeLists.txt
  74. 14 0
      direct/src/directbase/CMakeLists.txt
  75. 3 8
      direct/src/directscripts/extract_docs.py
  76. 1 5
      direct/src/directtools/DirectSession.py
  77. 26 35
      direct/src/dist/FreezeTool.py
  78. 103 126
      direct/src/dist/commands.py
  79. 6 10
      direct/src/dist/pefile.py
  80. 35 0
      direct/src/distributed/CMakeLists.txt
  81. 1 6
      direct/src/distributed/NetMessenger.py
  82. 2 2
      direct/src/distributed/ServerRepository.py
  83. 1 16
      direct/src/distributed/cConnectionRepository.cxx
  84. 1 4
      direct/src/doc/howto.adjust
  85. 1 5
      direct/src/extensions_native/extension_native_helpers.py
  86. 27 0
      direct/src/fsm/FSM.py
  87. 3 11
      direct/src/gui/DirectEntry.py
  88. 5 11
      direct/src/gui/DirectFrame.py
  89. 3 9
      direct/src/gui/DirectGuiBase.py
  90. 27 0
      direct/src/gui/DirectScrollBar.py
  91. 4 2
      direct/src/gui/DirectScrolledFrame.py
  92. 16 22
      direct/src/gui/DirectScrolledList.py
  93. 32 2
      direct/src/gui/DirectSlider.py
  94. 1 7
      direct/src/gui/DirectWaitBar.py
  95. 1 6
      direct/src/gui/OnscreenGeom.py
  96. 1 8
      direct/src/gui/OnscreenImage.py
  97. 5 25
      direct/src/gui/OnscreenText.py
  98. 54 0
      direct/src/interval/CMakeLists.txt
  99. 2 5
      direct/src/interval/Interval.py
  100. 24 0
      direct/src/motiontrail/CMakeLists.txt

+ 22 - 0
.editorconfig

@@ -0,0 +1,22 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{py,pyw}]
+indent_style = space
+indent_size = 4
+
+[*.{h,c,cxx,cpp,I}]
+indent_style = space
+indent_size = 2
+
+[{CMakeLists.txt,*.cmake}]
+indent_style = space
+indent_size = 2
+
+[*.bat]
+end_of_line = crlf

+ 16 - 0
.github/codecov.yml

@@ -0,0 +1,16 @@
+coverage:
+  status:
+    project:
+      default:
+        threshold: 0.1
+    patch:
+      default:
+        threshold: 0.1
+codecov:
+  require_ci_to_pass: true
+  notify:
+    after_n_builds: 2
+    wait_for_ci: true
+comment:
+  require_changes: true
+  after_n_builds: 2

+ 336 - 11
.github/workflows/ci.yml

@@ -1,6 +1,330 @@
 name: Continuous Integration
 name: Continuous Integration
 on: [push, pull_request]
 on: [push, pull_request]
+
 jobs:
 jobs:
+  cmake:
+    name: CMake Buildsystem
+
+    strategy:
+      fail-fast: false
+
+      matrix:
+        profile:
+        - ubuntu-xenial-standard-unity-makefile
+        - ubuntu-bionic-coverage-ninja
+        - macos-eigen-coverage-unity-xcode
+        - macos-nometa-standard-makefile
+#       - windows-standard-unity-msvc # FIXME when GH Actions runners upgrade CMake to >=3.16.1
+        - windows-nopython-nometa-standard-msvc
+
+        include:
+        - profile: ubuntu-xenial-standard-unity-makefile
+          os: ubuntu-16.04
+          config: Standard
+          unity: YES
+          generator: Unix Makefiles
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: ubuntu-bionic-coverage-ninja
+          os: ubuntu-18.04
+          config: Coverage
+          unity: NO
+          generator: Ninja
+          compiler: Clang
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: macos-eigen-coverage-unity-xcode
+          os: macOS-latest
+          config: Coverage
+          unity: YES
+          generator: Xcode
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: YES
+
+        - profile: macos-nometa-standard-makefile
+          os: macOS-latest
+          config: Standard
+          unity: NO
+          generator: Unix Makefiles
+          compiler: Default
+          metalibs: NO
+          python: YES
+          eigen: NO
+
+        - profile: windows-standard-unity-msvc
+          os: windows-2019
+          config: Standard
+          unity: YES
+          generator: Visual Studio 16 2019
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: windows-nopython-nometa-standard-msvc
+          os: windows-2019
+          config: Standard
+          unity: NO
+          generator: Visual Studio 16 2019
+          compiler: Default
+          metalibs: NO
+          python: NO
+          eigen: NO
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        fetch-depth: 10
+
+    - name: Self-destruct makepanda
+      run: python makepanda/selfdestruct.py --yes
+
+    - name: Install dependencies (macOS)
+      if: runner.os == 'macOS'
+      run: |
+        curl -O https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-mac.tar.gz
+        tar -xf panda3d-1.10.5-tools-mac.tar.gz
+
+        brew install ccache
+
+        echo "##[set-env name=thirdpartyOption;]-D THIRDPARTY_DIRECTORY=../panda3d-1.10.5/thirdparty" -DHAVE_CG=OFF
+
+    - name: Install dependencies (Ubuntu)
+      if: startsWith(matrix.os, 'ubuntu')
+      run: >
+        sudo apt-get update
+
+        sudo apt-get install
+        build-essential ninja-build clang llvm ccache
+        bison flex
+        libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
+        libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
+        libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
+        python3-setuptools
+
+        # Workaround for CMake 3.12 finding this first:
+
+        sudo rm /usr/bin/x86_64-linux-gnu-python2.7-config
+
+    - name: Cache dependencies (Windows)
+      if: runner.os == 'Windows'
+      uses: actions/cache@v1
+      with:
+        path: thirdparty-tools
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.5-r1
+    - name: Install dependencies (Windows)
+      if: runner.os == 'Windows'
+      shell: powershell
+      run: |
+        if (!(Test-Path thirdparty-tools/panda3d-1.10.5)) {
+          $wc = New-Object System.Net.WebClient
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-win64.zip", "thirdparty-tools.zip")
+          Expand-Archive -Path thirdparty-tools.zip
+        }
+
+        echo "##[set-env name=thirdpartyOption;]-D THIRDPARTY_DIRECTORY=../thirdparty-tools/panda3d-1.10.5/thirdparty"
+
+    - name: ccache (non-Windows)
+      if: runner.os != 'Windows'
+      uses: actions/cache@v1
+      with:
+        path: ccache
+        key: ci-cmake-ccache-${{ matrix.profile }}
+
+    - name: Configure
+      shell: bash
+      env:
+        CMAKE_GENERATOR: "${{ matrix.generator }}"
+      run: >
+        mkdir build
+
+        cd build
+
+        if ${{ matrix.compiler == 'Clang' }}; then
+          if [[ "$CMAKE_GENERATOR" == *Studio*2019* ]]; then
+            export CMAKE_GENERATOR_TOOLSET=ClangCL thirdpartyOption="$thirdpartyOption -DHAVE_HARFBUZZ=NO"
+          elif [[ "$CMAKE_GENERATOR" == *Studio* ]]; then
+            export CMAKE_GENERATOR_TOOLSET=LLVM thirdpartyOption="$thirdpartyOption -DHAVE_HARFBUZZ=NO"
+          else
+            export CC=clang CXX=clang++
+          fi
+        fi
+
+        if ${{ runner.os != 'Windows' }}; then
+          compilerLauncher=$(echo -DCMAKE_C{,XX}_COMPILER_LAUNCHER=ccache)
+          echo "##[set-env name=CCACHE_DIR;]$(dirname $PWD)/ccache"
+        fi
+
+        cmake
+        ${compilerLauncher:-}
+        -D CMAKE_UNITY_BUILD=${{ matrix.unity }}
+        -D CMAKE_BUILD_TYPE="${{ matrix.config }}"
+        -D BUILD_METALIBS=${{ matrix.metalibs }}
+        -D HAVE_PYTHON=${{ matrix.python }}
+        -D HAVE_EIGEN=${{ matrix.eigen }}
+        ${thirdpartyOption:-}
+        ..
+
+    - name: Build (no Python)
+      if: contains(matrix.python, 'NO')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+
+    - name: Setup Python (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.5
+    - name: Configure (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.5
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.5)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest
+        $PYTHON_EXECUTABLE -m pytest ../tests
+      # END B
+
+    - name: Setup Python (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.6
+    - name: Configure (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.6
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.6)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Setup Python (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.7
+    - name: Configure (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.7
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.7)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Setup Python (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+    - name: Configure (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.8
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.8)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Upload coverage reports
+      if: always() && matrix.config == 'Coverage'
+      working-directory: build
+      shell: bash
+      env:
+        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+      run: |
+        shopt -s expand_aliases
+        if ${{ runner.os == 'macOS' }}; then alias llvm-profdata='xcrun llvm-profdata' llvm-cov='xcrun llvm-cov'; fi
+
+        python -m pip install coverage
+        python -m coverage combine $(find . -name '.coverage.*')
+
+        llvm-profdata merge pid-*.profraw -o coverage.profdata
+        llvm-cov show $(grep -Rl LLVM_PROFILE_FILE . | sed 's/^/-object /') -instr-profile=coverage.profdata > coverage.txt
+        bash <(curl -s https://codecov.io/bash) -y ../.github/codecov.yml
+
   makepanda:
   makepanda:
     strategy:
     strategy:
       matrix:
       matrix:
@@ -11,15 +335,16 @@ jobs:
     - name: Install dependencies (Ubuntu)
     - name: Install dependencies (Ubuntu)
       if: matrix.os == 'ubuntu-16.04'
       if: matrix.os == 'ubuntu-16.04'
       run: |
       run: |
+        sudo apt-get update
         sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
         sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
     - name: Get thirdparty packages (Windows)
     - name: Get thirdparty packages (Windows)
       if: runner.os == 'Windows'
       if: runner.os == 'Windows'
       shell: powershell
       shell: powershell
       run: |
       run: |
         $wc = New-Object System.Net.WebClient
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.4.1/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.5/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
@@ -28,26 +353,26 @@ jobs:
         mv panda3d-1.10.5/thirdparty thirdparty
         mv panda3d-1.10.5/thirdparty thirdparty
         rmdir panda3d-1.10.5
         rmdir panda3d-1.10.5
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
-    - name: Set up Python 3.7
+    - name: Set up Python 3.8
       uses: actions/setup-python@v1
       uses: actions/setup-python@v1
       with:
       with:
-        python-version: 3.7
-    - name: Build Python 3.7
+        python-version: 3.8
+    - name: Build Python 3.8
       run: |
       run: |
         python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
         python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
-    - name: Test Python 3.7
+    - name: Test Python 3.8
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install pytest
         python -m pip install pytest
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
-    - name: Set up Python 2.7
+    - name: Set up Python 3.7
       uses: actions/setup-python@v1
       uses: actions/setup-python@v1
       with:
       with:
-        python-version: 2.7
-    - name: Build Python 2.7
+        python-version: 3.7
+    - name: Build Python 3.7
       run: |
       run: |
-        python makepanda/makepanda.py --no-copy-python --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
-    - name: Test Python 2.7
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
+    - name: Test Python 3.7
       shell: bash
       shell: bash
       run: |
       run: |
         python -m pip install pytest
         python -m pip install pytest

+ 3 - 0
.gitignore

@@ -54,6 +54,9 @@ CTestTestfile.cmake
 Thumbs.db
 Thumbs.db
 ehthumbs.db
 ehthumbs.db
 
 
+# macOS
+.DS_Store
+
 # Python
 # Python
 __pycache__/
 __pycache__/
 *.pyc
 *.pyc

+ 2 - 2
.travis.yml

@@ -5,9 +5,9 @@ matrix:
     - compiler: clang
     - compiler: clang
       env: PYTHONV=python3 FLAGS=--installer
       env: PYTHONV=python3 FLAGS=--installer
     - compiler: clang
     - compiler: clang
-      env: PYTHONV=python2.7 FLAGS=--override=STDFLOAT_DOUBLE=1
+      env: PYTHONV=python3 FLAGS=--override=STDFLOAT_DOUBLE=1
     - compiler: gcc
     - compiler: gcc
-      env: PYTHONV=python2.7 FLAGS=--optimize=4
+      env: PYTHONV=python3 FLAGS=--optimize=4
       before_install:
       before_install:
         - export CC=gcc-4.7
         - export CC=gcc-4.7
         - export CXX=g++-4.7
         - export CXX=g++-4.7

+ 190 - 0
CMakeLists.txt

@@ -0,0 +1,190 @@
+cmake_minimum_required(VERSION 3.0.2)
+set(CMAKE_DISABLE_SOURCE_CHANGES ON) # Must go before project() below
+set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) # Must go before project() below
+
+if(CMAKE_VERSION VERSION_GREATER "3.11" OR POLICY CMP0072)
+  # Prefer GLVND over libGL when available; this will be enabled by default
+  # once the minimum CMake version is at least 3.11.
+  cmake_policy(SET CMP0072 NEW)
+endif()
+
+if(CMAKE_VERSION VERSION_GREATER "3.12" OR POLICY CMP0074)
+  # Needed for THIRDPARTY_DIRECTORY support; this will be enabled by default
+  # once the minimum CMake version is at least 3.12.
+  cmake_policy(SET CMP0074 NEW)
+endif()
+
+if(POLICY CMP0091)
+  # Needed for CMake to pass /MD flag properly with non-VC generators.
+  cmake_policy(SET CMP0091 NEW)
+endif()
+
+# Determine whether we are using a multi-config generator.
+if(CMAKE_VERSION VERSION_GREATER "3.8")
+  get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+else()
+  message(WARNING "Multi-configuration builds may not work properly when using
+a CMake < 3.9. Making a guess if this is a multi-config generator.")
+  if(DEFINED CMAKE_CONFIGURATION_TYPES)
+    set(IS_MULTICONFIG ON)
+  else()
+    set(IS_MULTICONFIG OFF)
+  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()
+
+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()
+
+# Figure out the version
+set(_s "[\\t ]*") # CMake doesn't support \s*
+file(STRINGS "setup.cfg" _version REGEX "^version${_s}=${_s}")
+string(REGEX REPLACE "^.*=${_s}" "" _version "${_version}")
+project(Panda3D VERSION ${_version})
+unset(_version)
+unset(_s)
+
+enable_testing()
+
+string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")
+
+# Add generic modules to cmake module path,
+# and add Panda3D specific modules to cmake module path
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/")
+
+if(CMAKE_VERSION VERSION_GREATER "3.8")
+  # When using the Xcode generator, don't append the platform name to the
+  # intermediate configuration directory.
+  set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME OFF)
+endif()
+
+# Include modules builtin to CMake
+include(GNUInstallDirs)     # Defines CMAKE_INSTALL_<dir> variables
+
+# Include global modules needed for configure scripts
+include(PackageConfig)      # Defines package_option
+include(PerConfigOption)    # Defines per_config_option
+
+# Configure Panda3D
+include(dtool/CompilerFlags.cmake)
+include(dtool/PandaVersion.cmake)
+include(dtool/Package.cmake)
+include(dtool/Config.cmake)
+
+# Include global modules
+include(AddBisonTarget)     # Defines add_bison_target function
+include(AddFlexTarget)      # Defines add_flex_target function
+include(BuildMetalib)       # Defines add_component_library AND add_metalib
+include(CompositeSources)   # Defines composite_sources function
+include(Python)             # Defines add_python_target AND install_python_package
+include(Interrogate)        # Defines target_interrogate AND add_python_module
+include(RunPzip)            # Defines run_pzip function
+include(Versioning)         # Hooks 'add_library' to apply VERSION/SOVERSION
+
+# Determine which trees to build.
+option(BUILD_DTOOL "Build the dtool source tree." ON)
+option(BUILD_PANDA "Build the panda source tree." ON)
+option(BUILD_DIRECT "Build the direct source tree." ON)
+option(BUILD_PANDATOOL "Build the pandatool source tree." ON)
+option(BUILD_CONTRIB "Build the contrib source tree." ON)
+option(BUILD_MODELS "Build/install the built-in models." ON)
+
+# Include Panda3D packages
+if(BUILD_DTOOL)
+  add_subdirectory(dtool "${CMAKE_BINARY_DIR}/dtool")
+endif()
+
+if(BUILD_PANDA)
+  add_subdirectory(panda "${CMAKE_BINARY_DIR}/panda")
+endif()
+
+if(BUILD_DIRECT)
+  add_subdirectory(direct "${CMAKE_BINARY_DIR}/direct")
+endif()
+
+if(BUILD_PANDATOOL)
+  add_subdirectory(pandatool "${CMAKE_BINARY_DIR}/pandatool")
+endif()
+
+if(BUILD_CONTRIB)
+  add_subdirectory(contrib "${CMAKE_BINARY_DIR}/contrib")
+endif()
+
+if(BUILD_MODELS)
+  run_pzip(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"
+                             -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
+                     COMMENT "Copying models/maps")
+  add_custom_command(TARGET dmodels
+                     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"
+                             -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
+                     COMMENT "Copying dmodels' assets")
+
+  install(DIRECTORY "${PANDA_OUTPUT_DIR}/models"
+    COMPONENT Models DESTINATION ${CMAKE_INSTALL_DATADIR}/panda3d)
+endif()
+
+if(INTERROGATE_PYTHON_INTERFACE)
+  # If we built the Python interface, run the test suite.  Note, we do NOT test
+  # for pytest before adding this test.  If the user doesn't have pytest, we'd
+  # like for the tests to fail.
+
+  # In the Coverage configuration, we also require pytest-cov
+
+  add_test(NAME pytest
+    COMMAND "${PYTHON_EXECUTABLE}" -m pytest "${PROJECT_SOURCE_DIR}/tests"
+    $<$<CONFIG:Coverage>:--cov=.>
+    WORKING_DIRECTORY "${PANDA_OUTPUT_DIR}")
+endif()
+
+# Generate the Panda3DConfig.cmake file so find_package(Panda3D) works, and
+# also register the build directory with CMake's package registry.
+
+file(COPY "${PROJECT_SOURCE_DIR}/cmake/install/Panda3DConfig.cmake"
+  DESTINATION "${PROJECT_BINARY_DIR}")
+install(FILES "${PROJECT_SOURCE_DIR}/cmake/install/Panda3DConfig.cmake"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D")
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+  "${PROJECT_BINARY_DIR}/Panda3DConfigVersion.cmake"
+  VERSION "${PROJECT_VERSION}"
+  COMPATIBILITY AnyNewerVersion)
+install(FILES "${PROJECT_BINARY_DIR}/Panda3DConfigVersion.cmake"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D")
+
+if(NOT CMAKE_CROSSCOMPILING)
+  export(PACKAGE Panda3D)
+endif()

+ 19 - 18
README.md

@@ -24,9 +24,9 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-4-1/).
+[this page](https://www.panda3d.org/download/sdk-1-10-6/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
-the following comand:
+the following command:
 
 
 ```bash
 ```bash
 pip install panda3d
 pip install panda3d
@@ -52,11 +52,11 @@ Building Panda3D
 Windows
 Windows
 -------
 -------
 
 
-You can build Panda3D with the Microsoft Visual C++ 2015 or 2017 compiler,
+You can build Panda3D with the Microsoft Visual C++ 2015, 2017 or 2019 compiler,
 which can be downloaded for free from the [Visual Studio site](https://visualstudio.microsoft.com/downloads/).
 which can be downloaded for free from the [Visual Studio site](https://visualstudio.microsoft.com/downloads/).
 You will also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
 You will also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
-and if you intend to target Windows XP, you will also need the
-[Windows 7.1 SDK](https://www.microsoft.com/en-us/download/details.aspx?id=8279).
+and if you intend to target Windows Vista, you will also need the
+[Windows 8.1 SDK](https://go.microsoft.com/fwlink/p/?LinkId=323507).
 
 
 You will also need to have the third-party dependency libraries available for
 You will also need to have the third-party dependency libraries available for
 the build scripts to use.  These are available from one of these two URLs,
 the build scripts to use.  These are available from one of these two URLs,
@@ -64,16 +64,17 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip
-https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-win32.zip
 
 
-After acquiring these dependencies, you may simply build Panda3D from the
-command prompt using the following command.  (Change `14.1` to `14` if you are
-using Visual C++ 2015 instead of 2017.  Add the `--windows-sdk=10` option if
-you don't need to support Windows XP and did not install the Windows 7.1 SDK.)
+After acquiring these dependencies, you can build Panda3D from the command
+prompt using the following command.  Change the `--msvc-version` option based
+on your version of Visual C++; 2019 is 14.2, 2017 is 14.1, and 2015 is 14.
+Remove the `--windows-sdk=10` option if you need to support Windows Vista,
+which requires the Windows 8.1 SDK.
 
 
 ```bash
 ```bash
-makepanda\makepanda.bat --everything --installer --msvc-version=14.1 --no-eigen --threads=2
+makepanda\makepanda.bat --everything --installer --msvc-version=14.2 --windows-sdk=10 --no-eigen --threads=2
 ```
 ```
 
 
 When the build succeeds, it will produce an .exe file that you can use to
 When the build succeeds, it will produce an .exe file that you can use to
@@ -116,7 +117,7 @@ sudo apt-get install build-essential pkg-config fakeroot python-dev libpng-dev l
 ```
 ```
 
 
 Once Panda3D has built, you can either install the .deb or .rpm package that
 Once Panda3D has built, you can either install the .deb or .rpm package that
-it produced, depending on which Linux distribution you are using.  For example,
+is produced, depending on which Linux distribution you are using.  For example,
 to install the package on Debian or Ubuntu, use this:
 to install the package on Debian or Ubuntu, use this:
 
 
 ```bash
 ```bash
@@ -135,7 +136,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:
@@ -145,7 +146,7 @@ python makepanda/makepanda.py --everything --installer
 ```
 ```
 
 
 You may target a specific minimum macOS version using the --osxtarget flag
 You may target a specific minimum macOS version using the --osxtarget flag
-followed by the release number, eg. 10.7 or 10.9.
+followed by the release number, eg. 10.9 or 10.14.
 
 
 If the build was successful, makepanda will have generated a .dmg file in
 If the build was successful, makepanda will have generated a .dmg file in
 the source directory containing the installer.  Simply open it and run the
 the source directory containing the installer.  Simply open it and run the
@@ -163,11 +164,11 @@ pkg install pkgconf bison png jpeg-turbo tiff freetype2 harfbuzz eigen squish op
 ```
 ```
 
 
 You will also need to choose which version of Python you want to use.
 You will also need to choose which version of Python you want to use.
-Install the appropriate package for it (such as `python2` or `python36`) and
+Install the appropriate package for it (such as `python37` or `python38`) and
 run the makepanda script with your chosen Python version:
 run the makepanda script with your chosen Python version:
 
 
 ```bash
 ```bash
-python3.6 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
+python3.7 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
 ```
 ```
 
 
 If successful, this will produce a .pkg file in the root of the source
 If successful, this will produce a .pkg file in the root of the source
@@ -211,7 +212,7 @@ Running Tests
 
 
 Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
 Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
 and run the `pytest` command.  If you have not installed Panda3D, you will
 and run the `pytest` command.  If you have not installed Panda3D, you will
-need to configure your enviroment by pointing the `PYTHONPATH` variable at
+need to configure your environment by pointing the `PYTHONPATH` variable at
 the `built` directory.  On Linux, you will also need to point the
 the `built` directory.  On Linux, you will also need to point the
 `LD_LIBRARY_PATH` variable at the `built/lib` directory.
 `LD_LIBRARY_PATH` variable at the `built/lib` directory.
 
 

+ 78 - 0
cmake/README.md

@@ -0,0 +1,78 @@
+Building with CMake
+-------------------
+
+On Windows and macOS, please ensure that you have the very latest version of
+CMake installed; older versions may work, but if not, please upgrade to the
+latest available version of CMake before requesting help.
+
+On systems that package CMake themselves (e.g. Linux distributions), we most
+likely support the provided version of CMake as long as the system itself is
+supported.
+
+CMake will also require that you already have your system's developer tools
+installed.
+
+The quickest way to build and install Panda with CMake is to install any
+third-party dependencies and then run:
+```sh
+mkdir build && cd build
+cmake ..
+cmake --build . --config Standard --parallel 4
+[sudo] cmake --build . --config Standard --target install
+```
+
+Note that, if you are targeting 64-bit on Windows, it is necessary to supply
+the `-A x64` option when first invoking `cmake` (as `cmake -A x64 ..`).
+
+CMake itself does not build Panda; rather, it generates project files for an
+existing IDE or build tool. To select a build tool, pass the `-G` option when
+first invoking CMake, (`cmake -G Ninja ..` is highly recommended on Linux).
+Some of these (Xcode, Visual Studio) support targeting multiple configurations
+(the `--config Standard`, above, selects the `Standard` configuration in those
+cases). Other build tools (Ninja, Makefiles, ...) do not support multiple
+configurations, and the `--config` option is ignored. To change the
+configuration in these cases (from `Standard`, the default), it is necessary to
+change the `CMAKE_BUILD_TYPE` variable as explained below.
+
+The configurations are:
+
+| Configuration  | Explanation                                            |
+| -------------- | ------------------------------------------------------ |
+| Standard       | Default; build provided to users of SDK                |
+| Release        | Distribution for end-users                             |
+| MinSizeRel     | Like Release, but optimized for size                   |
+| RelWithDebInfo | Like Release, but include debug symbols                |
+| Debug          | Do not optimize, enable optional debugging features    |
+| Coverage       | Like Debug, but profile code coverage; developers only |
+
+To configure CMake, it is recommended to use cmake-gui (`cmake-gui .`),
+or ccmake (`ccmake .`), however it is also possible to configure it entirely
+through CMake's command-line interface; see `man cmake` for more details.
+
+In general, the config variable for a particular third party library is:
+```
+	HAVE_<LIBRARY>=YES/NO   # Example: USE_JPEG
+```
+Panda subpackage building is handled by:
+```
+	BUILD_<SUBPACKAGE>=YES/NO   # Example: BUILD_DTOOL, BUILD_PANDA
+```
+Other configuration settings use their historical names (same names as in-source):
+```
+	# Examples
+	PANDA_DISTRIBUTOR="MyDistributor"
+	LINMATH_ALIGN=YES
+
+	# ... etc ...
+
+```
+
+For example, `makepanda.py --distributor X` becomes `cmake -DPANDA_DISTRIBUTOR=X`
+
+All found third-party libraries are enabled by default, and makepanda-style
+tools packages are searched in the same path as makepanda (however this may be
+overridden with the `THIRDPARTY_DIRECTORY` option).
+
+Most config settings are set to a sensible default for a typical PC/desktop
+Panda3D distribution. Third-party libraries and other settings can be enabled
+or disabled through configuration with the cmake GUI or CLI.

+ 157 - 0
cmake/install/Panda3DConfig.cmake

@@ -0,0 +1,157 @@
+# 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."
+#
+# Author: CFSworks (Dec. 11, 2018)
+
+# This file is installed to CMake's package search path, and is invoked for
+# find_package(Panda3D [COMPONENTS ...])
+#
+# The following components are available for importing:
+#
+#   Core      - The core Panda3D libraries; this component is always included.
+#
+#               Panda3D::Core::panda
+#               Panda3D::Core::pandaexpress
+#               etc.
+#
+#
+#   Python    - Python targets, which can be used for linking against the Python
+#               extension modules directly.  Note that this also imports the
+#               Python bindings for other requested components that have them.
+#
+#               Panda3D::Python::panda3d.core
+#               Panda3D::Python::panda3d.physics
+#               etc.
+#
+#
+#   Tools     - Various tools used in asset manipulation and debugging.
+#
+#               Panda3D::Tools::egg2bam
+#               Panda3D::Tools::egg-optchar
+#               Panda3D::Tools::pview
+#               etc.
+#
+#
+#   Direct    - Panda's "direct" Python framework; C++ support library.
+#
+#               Panda3D::Direct::p3direct
+#
+#
+#   Contrib   - Extensions not part of the Panda3D core, but contributed by the
+#               community.
+#
+#               Panda3D::Contrib::p3ai
+#               Panda3D::Contrib::p3rplight
+#
+#
+#   Framework - Panda's "p3framework" C++ framework.
+#
+#               Panda3D::Framework::p3framework
+#
+#
+#   Egg       - Support for the Egg file format.
+#
+#               Panda3D::Egg::pandaegg
+#
+#
+#   Bullet    - Support for Bullet physics.
+#
+#               Panda3D::Bullet::p3bullet
+#
+#
+#   ODE       - Support for the ODE physics engine.
+#
+#               Panda3D::ODE::p3ode
+#
+#
+#   FFmpeg    - Support for FFmpeg media format loading.
+#
+#               Panda3D::FFmpeg::p3ffmpeg
+#
+#
+#   OpenAL    - Support for OpenAL audio output.
+#
+#               Panda3D::OpenAL::p3openal_audio
+#
+#
+#   FMOD      - Support for FMOD audio output.
+#
+#               Panda3D::FMOD::p3fmod_audio
+#
+#
+#   OpenGL    - Support for OpenGL rendering.
+#
+#               Panda3D::OpenGL::pandagl
+#
+#
+#   DX9       - Support for Direct3D 9 rendering.
+#
+#               Panda3D::DX9::pandadx9
+#
+#
+#   OpenGLES1 - Support for OpenGL ES 1.x rendering.
+#
+#               Panda3D::OpenGLES1::pandagles
+#
+#
+#   OpenGLES2 - Support for OpenGL ES 2.x+ rendering.
+#
+#               Panda3D::OpenGLES2::pandagles2
+#
+#
+#   Vision    - Support for vision processing.
+#
+#               Panda3D::Vision::p3vision
+#
+#
+#   VRPN      - Support for connecting to a VRPN virtual reality server.
+#
+#               Panda3D::VRPN::p3vrpn
+
+if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 3.0)
+  message(FATAL_ERROR "CMake >= 3.0.2 required")
+endif()
+
+get_filename_component(_panda_config_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
+
+include("${_panda_config_prefix}/Panda3DPackages.cmake")
+
+set(_panda_components
+  Core Python Tools
+  Direct Contrib Framework Egg
+  Bullet ODE
+  FFmpeg
+  OpenAL FMOD
+  OpenGL DX9 OpenGLES1 OpenGLES2
+  Vision VRPN
+)
+
+set(Panda3D_FIND_REQUIRED_Core ON)
+
+foreach(_comp Core ${Panda3D_FIND_COMPONENTS})
+  if(";${_panda_components};" MATCHES ";${_comp};" AND
+     EXISTS "${_panda_config_prefix}/Panda3D${_comp}Targets.cmake")
+
+    include("${_panda_config_prefix}/Panda3D${_comp}Targets.cmake")
+
+    if(";${Panda3D_FIND_COMPONENTS};" MATCHES ";Python;" AND
+       EXISTS "${_panda_config_prefix}/Panda3D${_comp}PythonTargets.cmake")
+
+      include("${_panda_config_prefix}/Panda3D${_comp}PythonTargets.cmake")
+
+    endif()
+
+  elseif(Panda3D_FIND_REQUIRED_${_comp})
+
+    message(FATAL_ERROR "Panda3D REQUIRED component ${_comp} not found")
+
+  endif()
+
+endforeach(_comp)
+unset(_comp)
+
+unset(_panda_components)

+ 75 - 0
cmake/macros/AddBisonTarget.cmake

@@ -0,0 +1,75 @@
+# Filename: AddBisonTarget.cmake
+# Description: This file defines the function add_bison_target which instructs
+#   cmake to use bison on an input .yxx file.  If bison is not available on
+#   the system, add_bison_target tries to use .prebuilt .cxx files instead.
+#
+# Usage:
+#   add_bison_target(output_cxx input_yxx [DEFINES output_h] [PREFIX prefix])
+#
+
+# Define add_bison_target()
+function(add_bison_target output_cxx input_yxx)
+  set(arguments "")
+  set(outputs "${output_cxx}")
+  set(keyword "")
+
+  # Parse the extra arguments to the function.
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "DEFINES")
+      set(keyword "DEFINES")
+
+    elseif(arg STREQUAL "PREFIX")
+      set(keyword "PREFIX")
+
+    elseif(keyword STREQUAL "PREFIX")
+      list(APPEND arguments -p "${arg}")
+
+    elseif(keyword STREQUAL "DEFINES")
+      list(APPEND arguments --defines="${arg}")
+      list(APPEND outputs "${arg}")
+
+    else()
+      message(SEND_ERROR "Unexpected argument ${arg} to add_bison_target")
+
+    endif()
+  endforeach()
+
+  if(keyword STREQUAL arg AND NOT keyword STREQUAL "")
+    message(SEND_ERROR "Expected argument after ${keyword}")
+  endif()
+
+  if(HAVE_BISON)
+    get_source_file_property(input_yxx "${input_yxx}" LOCATION)
+
+    # If we have bison, we can of course just run it.
+    add_custom_command(
+      OUTPUT ${outputs}
+      COMMAND ${BISON_EXECUTABLE}
+        -o "${output_cxx}" ${arguments}
+        "${input_yxx}"
+      MAIN_DEPENDENCY "${input_yxx}"
+    )
+
+  else()
+    # Look for prebuilt versions of the outputs.
+    set(commands "")
+    set(depends "")
+
+    foreach(output ${outputs})
+      set(prebuilt_file "${output}.prebuilt")
+      get_filename_component(prebuilt_file "${prebuilt_file}" ABSOLUTE)
+
+      if(NOT EXISTS "${prebuilt_file}")
+        message(SEND_ERROR "Bison was not found and ${prebuilt_file} does not exist!")
+      endif()
+
+      list(APPEND depends "${prebuilt_file}")
+      list(APPEND commands COMMAND ${CMAKE_COMMAND} -E copy ${prebuilt_file} ${output})
+    endforeach()
+
+    add_custom_command(
+      OUTPUT ${outputs}
+      ${commands}
+      DEPENDS ${depends})
+  endif()
+endfunction(add_bison_target)

+ 77 - 0
cmake/macros/AddFlexTarget.cmake

@@ -0,0 +1,77 @@
+# Filename: AddFlexTarget.cmake
+# Description: This file defines the function add_flex_target which instructs
+#   cmake to use flex on an input .lxx file.  If flex is not available on
+#   the system, add_flex_target tries to use .prebuilt .cxx files instead.
+#
+# Usage:
+#   add_flex_target(output_cxx input_lxx [DEFINES output_h] [PREFIX prefix])
+#
+
+# Define add_flex_target()
+function(add_flex_target output_cxx input_lxx)
+  set(arguments "")
+  set(outputs "${output_cxx}")
+  set(keyword "")
+
+  # Parse the extra arguments to the function.
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "DEFINES")
+      set(keyword "DEFINES")
+
+    elseif(arg STREQUAL "PREFIX")
+      set(keyword "PREFIX")
+
+    elseif(arg STREQUAL "CASE_INSENSITIVE")
+      list(APPEND arguments "-i")
+
+    elseif(keyword STREQUAL "PREFIX")
+      list(APPEND arguments "-P${arg}")
+
+    elseif(keyword STREQUAL "DEFINES")
+      list(APPEND arguments "--header-file=${arg}")
+      list(APPEND outputs "${arg}")
+
+    else()
+      message(SEND_ERROR "Unexpected argument ${arg} to add_flex_target")
+
+    endif()
+  endforeach()
+
+  if(keyword STREQUAL arg AND NOT keyword STREQUAL "")
+    message(SEND_ERROR "Expected argument after ${keyword}")
+  endif()
+
+  if(HAVE_FLEX)
+    get_source_file_property(input_lxx "${input_lxx}" LOCATION)
+
+    # If we have flex, we can of course just run it.
+    add_custom_command(
+      OUTPUT ${outputs}
+      COMMAND ${FLEX_EXECUTABLE}
+        "-o${output_cxx}" ${arguments}
+        "${input_lxx}"
+      MAIN_DEPENDENCY "${input_lxx}")
+
+  else()
+    # Look for prebuilt versions of the outputs.
+    set(commands "")
+    set(depends "")
+
+    foreach(output ${outputs})
+      set(prebuilt_file "${output}.prebuilt")
+      get_filename_component(prebuilt_file "${prebuilt_file}" ABSOLUTE)
+
+      if(NOT EXISTS "${prebuilt_file}")
+        message(SEND_ERROR "Flex was not found and ${prebuilt_file} does not exist!")
+      endif()
+
+      list(APPEND depends "${prebuilt_file}")
+      list(APPEND commands COMMAND ${CMAKE_COMMAND} -E copy ${prebuilt_file} ${output})
+    endforeach()
+
+    add_custom_command(
+      OUTPUT ${outputs}
+      ${commands}
+      DEPENDS ${depends})
+  endif()
+endfunction(add_flex_target)

+ 426 - 0
cmake/macros/BuildMetalib.cmake

@@ -0,0 +1,426 @@
+# Filename: BuildMetalib.cmake
+#
+# Description: This file contains macros to build Panda3D's "metalibs" - these
+#   are special libraries that contain no unique code themselves and are
+#   instead just an agglomeration of the various component libraries that get
+#   linked into them. A library of libraries - a "metalibrary."
+
+#
+# Function: target_link_libraries(...)
+#
+# Overrides CMake's target_link_libraries() to support "linking" object
+# libraries. This is a partial reimplementation of CMake commit dc38970f83,
+# which is only available in CMake 3.12+
+#
+if(CMAKE_VERSION VERSION_LESS "3.12")
+  function(target_link_libraries target)
+    get_target_property(target_type "${target}" TYPE)
+    if(NOT target_type STREQUAL "OBJECT_LIBRARY")
+      _target_link_libraries("${target}" ${ARGN})
+      return()
+    endif()
+
+    foreach(library ${ARGN})
+      # This is a quick and dirty regex to tell targets apart from other stuff.
+      # It just checks if it's alphanumeric and starts with p3/panda.
+      if(library MATCHES "^(PKG::|p3|panda)[A-Za-z0-9]*$")
+        # We need to add "library"'s include directories to "target"
+        # (and transitively to INTERFACE_INCLUDE_DIRECTORIES so further
+        # dependencies will work)
+        set(include_directories "$<TARGET_PROPERTY:${library},INTERFACE_INCLUDE_DIRECTORIES>")
+        set_property(TARGET "${target}" APPEND PROPERTY INCLUDE_DIRECTORIES "${include_directories}")
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${include_directories}")
+
+        # SYSTEM include directories should still be reported as SYSTEM, so
+        # that warnings from those includes are suppressed
+        set(sys_include_directories
+          "$<TARGET_PROPERTY:${library},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>")
+        target_include_directories("${target}" SYSTEM PUBLIC "${sys_include_directories}")
+
+        # And for INTERFACE_COMPILE_DEFINITIONS as well
+        set(compile_definitions "$<TARGET_PROPERTY:${library},INTERFACE_COMPILE_DEFINITIONS>")
+        set_property(TARGET "${target}" APPEND PROPERTY COMPILE_DEFINITIONS "${compile_definitions}")
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "${compile_definitions}")
+
+        # Build up some generator expressions for determining whether `library`
+        # is a component library or not.
+        if(library MATCHES ".*::.*")
+          # "::" messes up CMake's genex parser; fortunately, a library whose
+          # name contains that is either an interface library or alias, and
+          # definitely not a component
+          set(is_component 0)
+          set(name_of_component "")
+          set(name_of_non_component "${library}")
+
+        else()
+          set(is_component "$<TARGET_PROPERTY:${library},IS_COMPONENT>")
+
+          # CMake complains if we lookup IS_COMPONENT on an INTERFACE library :(
+          set(is_object "$<STREQUAL:$<TARGET_PROPERTY:${library},TYPE>,OBJECT_LIBRARY>")
+          set(is_component "$<BOOL:$<${is_object}:${is_component}>>")
+
+          set(name_of_component "$<${is_component}:$<TARGET_NAME:${library}>>")
+          set(name_of_non_component "$<$<NOT:${is_component}>:$<TARGET_NAME:${library}>>")
+
+        endif()
+
+        # Libraries are only linked transitively if they aren't components.
+        set_property(TARGET "${target}" APPEND PROPERTY
+          INTERFACE_LINK_LIBRARIES "${name_of_non_component}")
+
+      else()
+        # This is a file path to an out-of-tree library - this needs to be
+        # recorded so that the metalib can link them. (They aren't needed at
+        # all for the object libraries themselves, so they don't have to work
+        # transitively.)
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${library}")
+
+      endif()
+
+    endforeach(library)
+
+  endfunction(target_link_libraries)
+endif()
+
+#
+# Function: add_component_library(target [SYMBOL building_symbol]
+#                                 [SOURCES] [[NOINIT]/[INIT func [header]]])
+#
+# Used very similarly to add_library.  You can specify a symbol with SYMBOL,
+# which works like CMake's own DEFINE_SYMBOL property: it's defined when
+# building the library, but not when building something that links against the
+# library.
+#
+# INIT specifies the init function that should be called from a metalib's init
+# function when this is added to a metalib.  The header parameter can further
+# clarify what header declares this function.  By default, this is
+# init_libTARGET and config_TARGET.h, respectively, where TARGET is the
+# target name (with 'p3' stripped off, if applicable).  The NOINIT keyword
+# suppresses this default.
+#
+# Note that this function gets to decide whether the component library is
+# OBJECT or SHARED, and whether the library is installed or not.  Also, as
+# a rule, component libraries may only be linked by other component libraries
+# in the same metalib - outside of the metalib, you must link the metalib
+# itself.
+#
+function(add_component_library target_name)
+  set(sources)
+  unset(symbol)
+
+  if(target_name MATCHES "^p3.*")
+    string(SUBSTRING "${target_name}" 2 -1 name_without_prefix)
+
+  else()
+    set(name_without_prefix "${target_name}")
+
+  endif()
+
+  set(init_func "init_lib${name_without_prefix}")
+  set(init_header "config_${name_without_prefix}.h")
+
+  set(symbol_keyword OFF)
+  set(init_keyword 0)
+  foreach(source ${ARGN})
+    if(source STREQUAL "SYMBOL")
+      set(symbol_keyword ON)
+      set(init_keyword 0)
+
+    elseif(source STREQUAL "INIT")
+      set(symbol_keyword OFF)
+      set(init_keyword 2)
+
+    elseif(source STREQUAL "NOINIT")
+      set(init_func)
+      set(init_header)
+
+    elseif(symbol_keyword)
+      set(symbol_keyword OFF)
+      set(symbol "${source}")
+
+    elseif(init_keyword EQUAL 2)
+      set(init_func "${source}")
+      set(init_keyword 1)
+
+    elseif(init_keyword EQUAL 1)
+      set(init_header "${source}")
+      set(init_keyword 0)
+
+    else()
+      list(APPEND sources "${source}")
+
+    endif()
+  endforeach()
+
+  if(BUILD_METALIBS)
+    # CMake 3.0.2 doesn't like .I/.N/.T files!  We let it know that they're only
+    # headers.
+    foreach(source ${sources})
+      if(source MATCHES "\\.[INT]$")
+        set_source_files_properties(${source} PROPERTIES HEADER_FILE_ONLY ON)
+      endif()
+    endforeach(source)
+
+    add_library("${target_name}" OBJECT ${sources})
+
+  else()
+    add_library("${target_name}" ${sources})
+
+  endif()
+
+  set_target_properties("${target_name}" PROPERTIES 
+    IS_COMPONENT ON
+    INIT_FUNCTION "${init_func}"
+    INIT_HEADER "${init_header}")
+
+  if(symbol)
+    set_property(TARGET "${target_name}" PROPERTY DEFINE_SYMBOL "${symbol}")
+
+    if(BUILD_METALIBS)
+      # ... DEFINE_SYMBOL is apparently not respected for object libraries?
+      set_property(TARGET "${target_name}" APPEND PROPERTY COMPILE_DEFINITIONS "${symbol}")
+
+      # Make sure other component libraries relying on this one inherit the
+      # symbol
+      set_property(TARGET "${target_name}" APPEND PROPERTY
+        INTERFACE_COMPILE_DEFINITIONS "$<$<BOOL:$<TARGET_PROPERTY:IS_COMPONENT>>:${symbol}>")
+    endif()
+  endif()
+
+  if(BUILD_METALIBS)
+    # Apparently neither is CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE?
+    set_property(TARGET "${target_name}" PROPERTY
+      INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}")
+
+    # If we're building dynamic libraries, the object library needs to be -fPIC
+    if(BUILD_SHARED_LIBS)
+      set_property(TARGET "${target_name}" PROPERTY
+        POSITION_INDEPENDENT_CODE ON)
+    endif()
+  endif()
+
+endfunction(add_component_library)
+
+#
+# Function: add_metalib(target [source1 source2]
+#                       [INCLUDE header1.h ...]
+#                       [INIT initfunc [initheader.h] [EXPORT type name expr]]
+#                       [COMPONENTS component1 ...])
+#
+# This is add_library, but for metalibs.
+#
+# The INIT keyword can specify an initialization function/header (which will be
+# autogenerated by this function) that calls the underlying component libs'
+# init functions.
+#
+# The EXPORT keyword exports the expression `expr`, which yields a value of
+# type `type`, as the undecorated (extern "C") symbol `name`
+#
+# The INCLUDE keyword allows the init file to pull in additional headers, which
+# may be useful for EXPORT.
+#
+function(add_metalib target_name)
+  set(components_keyword OFF)
+  set(init_keyword 0)
+  set(init_func)
+  set(init_header "${target_name}.h")
+  set(export_keyword 0)
+  set(export_declarations)
+  set(export_definitions)
+  set(component_init_headers)
+  set(components)
+  set(sources)
+
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "COMPONENTS")
+      set(components_keyword ON)
+      set(include_keyword OFF)
+      set(init_keyword 0)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "INCLUDE")
+      set(include_keyword ON)
+      set(components_keyword OFF)
+      set(init_keyword 0)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "INIT")
+      set(init_keyword 2)
+      set(components_keyword OFF)
+      set(include_keyword OFF)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "EXPORT")
+      if(NOT init_func)
+        message(FATAL_ERROR "EXPORT cannot be used before INIT")
+      endif()
+
+      set(export_keyword 3)
+      set(components_keyword OFF)
+      set(include_keyword OFF)
+      set(init_keyword 0)
+
+    elseif(components_keyword)
+      list(APPEND components "${arg}")
+
+    elseif(include_keyword)
+      set(component_init_headers
+        "${component_init_headers}#include \"${arg}\"\n")
+
+    elseif(init_keyword EQUAL 2)
+      set(init_func "${arg}")
+      set(init_keyword 1)
+
+    elseif(init_keyword EQUAL 1)
+      set(init_header "${arg}")
+      set(init_keyword 0)
+
+    elseif(export_keyword EQUAL 3)
+      set(_export_type "${arg}")
+      set(export_keyword 2)
+
+    elseif(export_keyword EQUAL 2)
+      set(_export_name "${arg}")
+      set(export_keyword 1)
+
+    elseif(export_keyword EQUAL 1)
+      set(export_declarations
+        "${export_declarations}\nextern \"C\" IMPORT_CLASS ${_export_type} ${_export_name}();")
+      set(export_definitions
+        "${export_definitions}\nextern \"C\" EXPORT_CLASS ${_export_type} ${_export_name}() { return ${arg}; }")
+      unset(_export_type)
+      unset(_export_name)
+      set(export_keyword 0)
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach()
+
+  string(REPLACE ";" "|" piped_components "${components}")
+  set(component_genex_regex ".*TARGET_PROPERTY:(${piped_components}),.*")
+
+  set(private_defines)
+  set(interface_defines)
+  set(includes)
+  set(libs)
+  set(component_init_funcs "")
+  foreach(component ${components})
+    if(NOT TARGET "${component}")
+      message(FATAL_ERROR
+        "Missing component library ${component} referenced by metalib ${target_name}!
+        (Component library targets must be created BEFORE add_metalib.)")
+    endif()
+
+    get_target_property(is_component "${component}" IS_COMPONENT)
+    if(NOT is_component)
+      message(FATAL_ERROR
+        "Attempted to metalink non-component ${component} into ${target_name}!")
+    endif()
+
+    get_target_property(component_init_header "${component}" INIT_HEADER)
+    get_target_property(component_init_func "${component}" INIT_FUNCTION)
+
+    if(component_init_header)
+      set(component_init_headers
+        "${component_init_headers}#include \"${component_init_header}\"\n")
+    endif()
+
+    if(component_init_func)
+      set(component_init_funcs
+        "${component_init_funcs}  ${component_init_func}();\n")
+    endif()
+
+    if(BUILD_METALIBS)
+      # This will be an object library we're pulling in; rather than link,
+      # let's try to "flatten" all of its properties into ours.
+
+      # Private defines: Just reference using a generator expression
+      list(APPEND private_defines "$<TARGET_PROPERTY:${component},COMPILE_DEFINITIONS>")
+
+      # Interface defines: Copy those, but filter out generator expressions
+      # referencing a component library
+      get_target_property(component_defines "${component}" INTERFACE_COMPILE_DEFINITIONS)
+      if(component_defines)
+        foreach(component_define ${component_defines})
+          if(component_define MATCHES "${component_genex_regex}")
+            # Filter, it's a genex referencing one of our components
+          elseif(component_define MATCHES ".*IS_COMPONENT.*")
+            # Filter, it's testing to see if the consumer is a component
+          else()
+            list(APPEND interface_defines ${component_define})
+          endif()
+        endforeach(component_define)
+      endif()
+
+      # Include directories: Filter out anything that references a component
+      # library or anything in the project path
+      get_target_property(component_includes "${component}" INTERFACE_INCLUDE_DIRECTORIES)
+      foreach(component_include ${component_includes})
+        if(component_include MATCHES "${component_genex_regex}")
+          # Ignore component references
+
+        elseif(component_include MATCHES "^${PROJECT_SOURCE_DIR}")
+          # Include path within project; should only be included when building
+          list(APPEND includes "$<BUILD_INTERFACE:${component_include}>")
+
+        else()
+          # Anything else gets included
+          list(APPEND includes "${component_include}")
+
+        endif()
+      endforeach(component_include)
+
+      # Link libraries: Filter out component libraries; we aren't linking
+      # against them, we're using their objects
+      get_target_property(component_libraries "${component}" INTERFACE_LINK_LIBRARIES)
+      foreach(component_library ${component_libraries})
+        if(NOT component_library)
+          # NOTFOUND - guess there are no INTERFACE_LINK_LIBRARIES
+
+        elseif(component_library MATCHES "${component_genex_regex}")
+          # Ignore component references
+
+        elseif(component_library MATCHES ".*(${piped_components}).*")
+          # Component library, ignore
+
+        else()
+          # Anything else gets included
+          list(APPEND libs "${component_library}")
+
+        endif()
+      endforeach(component_library)
+
+      # Consume this component's objects
+      list(APPEND sources "$<TARGET_OBJECTS:${component}>")
+
+    else() # NOT BUILD_METALIBS
+      list(APPEND libs "${component}")
+
+    endif()
+  endforeach()
+
+  if(init_func)
+    set(init_source_path "${CMAKE_CURRENT_BINARY_DIR}/init_${target_name}.cxx")
+    set(init_header_path "${CMAKE_CURRENT_BINARY_DIR}/${init_header}")
+
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/metalib_init.cxx.in"
+      "${init_source_path}")
+    list(APPEND sources "${init_source_path}")
+
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/metalib_init.h.in"
+      "${init_header_path}")
+    install(FILES "${init_header_path}" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)
+  endif()
+
+  add_library("${target_name}" ${sources})
+  target_compile_definitions("${target_name}"
+    PRIVATE ${private_defines}
+    INTERFACE ${interface_defines})
+  target_link_libraries("${target_name}" ${libs})
+  target_include_directories("${target_name}"
+    PUBLIC ${includes}
+    INTERFACE "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/panda3d>")
+
+endfunction(add_metalib)

+ 167 - 0
cmake/macros/CompositeSources.cmake

@@ -0,0 +1,167 @@
+# Filename: CompositeSources.cmake
+# Description: This file defines the function composite_sources which looks at
+#   a provided list of sources, generates _compositeN.cxx, and appends the
+#   composites to the list. The original files in the list are marked as headers
+#   so that they will be available in an IDE, but not compiled at build time.
+#
+# Usage:
+#   composite_sources(target source_var)
+#
+# Example:
+#   set(MY_SOURCES a.cxx b.cxx c.cxx)
+#   composite_sources(my_lib MY_SOURCES)
+#   add_library(my_lib ${MY_SOURCES})
+#
+
+
+# Settings for composite builds.  Should be moved to Config.cmake?
+set(CMAKE_UNITY_BUILD "ON" CACHE BOOL
+  "Enable unity builds; Panda defaults this to on.")
+
+set(CMAKE_UNITY_BUILD_BATCH_SIZE "30" CACHE STRING
+  "How many source files to build at a time through the unity build mechanism.
+  A high value will speed up the build dramatically but will be more memory
+  intensive than a low value.")
+
+set(COMPOSITE_SOURCE_EXTENSIONS ".cxx;.mm;.c" CACHE STRING
+  "Only files of these extensions will be composited.")
+
+set(COMPOSITE_SOURCE_EXCLUSIONS "" CACHE STRING
+  "A list of targets to skip when compositing sources. This is mainly
+desirable for CI builds.")
+
+set(COMPOSITE_GENERATOR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/MakeComposite.cmake")
+
+
+# Define composite_sources()
+function(composite_sources target sources_var)
+  if(NOT CMAKE_VERSION VERSION_LESS "3.16")
+    # CMake 3.16+ implements CMAKE_UNITY_BUILD* natively; no need to continue!
+
+    # Actually - <=3.16.2 has difficulty with multi-language support, so only
+    # allow .cxx in. Hopefully this can be removed soon.
+    foreach(_source ${${sources_var}})
+      get_filename_component(_source_ext "${_source}" EXT)
+      if(NOT _source_ext STREQUAL ".cxx")
+        set_source_files_properties(${_source} PROPERTIES
+          SKIP_UNITY_BUILD_INCLUSION YES)
+      endif()
+    endforeach(_source)
+
+    return()
+  endif()
+
+  if(NOT CMAKE_UNITY_BUILD)
+    # We've been turned off
+    return()
+  endif()
+
+  # How many sources were specified?
+  set(orig_sources ${${sources_var}})
+  set(sources ${orig_sources})
+  list(LENGTH sources num_sources)
+
+  # Don't composite if in the list of exclusions, and don't bother compositing
+  # with too few sources
+  list (FIND COMPOSITE_SOURCE_EXCLUSIONS ${target} _index)
+  if(num_sources LESS 2 OR ${CMAKE_UNITY_BUILD_BATCH_SIZE} LESS 2 OR ${_index} GREATER -1)
+    return()
+  endif()
+
+  # Sort each source file into a list.
+  foreach(source ${sources})
+    get_filename_component(extension "${source}" EXT)
+    get_source_file_property(generated "${source}" GENERATED)
+    get_source_file_property(is_header "${source}" HEADER_FILE_ONLY)
+    get_source_file_property(skip_compositing "${source}" SKIP_UNITY_BUILD_INCLUSION)
+
+    # Check if we can safely add this to a composite file.
+    if(NOT generated AND NOT is_header AND NOT skip_compositing AND
+        ";${COMPOSITE_SOURCE_EXTENSIONS};" MATCHES ";${extension};")
+
+      if(NOT DEFINED sources_${extension})
+        set(sources_${extension})
+      endif()
+
+      # Append it to one of the lists.
+      list(APPEND sources_${extension} "${source}")
+    endif()
+  endforeach(source)
+
+  # Now, put it all into one big list!
+  set(sorted_sources)
+  foreach(extension ${COMPOSITE_SOURCE_EXTENSIONS})
+    if(DEFINED sources_${extension})
+      list(APPEND sorted_sources ${sources_${extension}})
+    endif()
+  endforeach(extension)
+
+  set(composite_files)
+  set(composite_sources)
+
+  # Fill in composite_ext so we can kick off the loop.
+  list(GET sorted_sources 0 first_source)
+  get_filename_component(first_source_ext "${first_source}" EXT)
+  set(composite_ext ${first_source_ext})
+
+  while(num_sources GREATER 0)
+    # Pop the first element and adjust the sorted_sources length accordingly.
+    list(GET sorted_sources 0 source)
+    list(REMOVE_AT sorted_sources 0)
+    list(LENGTH sorted_sources num_sources)
+
+    # Add this file to our composite_sources buffer.
+    list(APPEND composite_sources ${source})
+    list(LENGTH composite_sources num_composite_sources)
+
+    # Get the next source file's extension, so we can see if we're done with
+    # this set of source files.
+    if(num_sources GREATER 0)
+      list(GET sorted_sources 0 next_source)
+      get_filename_component(next_extension "${next_source}" EXT)
+    else()
+      set(next_extension "")
+    endif()
+
+    # Check if this is the point where we should cut the file.
+    if(num_sources EQUAL 0 OR NOT num_composite_sources LESS ${CMAKE_UNITY_BUILD_BATCH_SIZE}
+       OR NOT composite_ext STREQUAL next_extension)
+      # It's pointless to make a composite source from just one file.
+      if(num_composite_sources GREATER 1)
+
+        # Figure out the name of our composite file.
+        list(LENGTH composite_files index)
+        math(EXPR index "1+${index}")
+        set(composite_file "${CMAKE_CURRENT_BINARY_DIR}/${target}_composite${index}${composite_ext}")
+        list(APPEND composite_files "${composite_file}")
+
+        # Set HEADER_FILE_ONLY to prevent it from showing up in the
+        # compiler command, but still show up in the IDE environment.
+        set_source_files_properties(${composite_sources} PROPERTIES HEADER_FILE_ONLY ON)
+
+        # We'll interrogate the composite files, so exclude the original sources.
+        set_source_files_properties(${composite_sources} PROPERTIES WRAP_EXCLUDE YES)
+
+        # Finally, add the target that generates the composite file.
+        add_custom_command(
+          OUTPUT "${composite_file}"
+          COMMAND ${CMAKE_COMMAND}
+            -DCOMPOSITE_FILE="${composite_file}"
+            -DCOMPOSITE_SOURCES="${composite_sources}"
+            -P "${COMPOSITE_GENERATOR}"
+          DEPENDS ${composite_sources})
+      endif()
+
+      # Reset for the next composite file.
+      set(composite_sources "")
+      set(composite_ext ${next_extension})
+    endif()
+  endwhile()
+
+  set_source_files_properties(${composite_files} PROPERTIES GENERATED YES)
+
+  # The new files are added to the existing files, which means the old files
+  # are still there, but they won't be compiled due to the HEADER_FILE_ONLY setting.
+  set(${sources_var} ${orig_sources} ${composite_files} PARENT_SCOPE)
+
+endfunction(composite_sources)

+ 400 - 0
cmake/macros/Interrogate.cmake

@@ -0,0 +1,400 @@
+# Filename: Interrogate.cmake
+#
+# Description: This file contains macros and functions that are used to invoke
+#   interrogate, to generate wrappers for Python and/or other languages.
+#
+# Functions:
+#   target_interrogate(target [ALL] [source1 [source2 ...]])
+#   add_python_module(module [lib1 [lib2 ...]])
+#   add_python_target(target [source1 [source2 ...]])
+#
+
+set(IGATE_FLAGS -DCPPPARSER -D__cplusplus -Dvolatile -Dmutable)
+
+# In addition, Interrogate needs to know if this is a 64-bit build:
+include(CheckTypeSize)
+check_type_size(long CMAKE_SIZEOF_LONG)
+if(CMAKE_SIZEOF_LONG EQUAL 8)
+  list(APPEND IGATE_FLAGS "-D_LP64")
+endif()
+
+
+# This is a list of regexes that are applied to every filename. If one of the
+# regexes matches, that file will not be passed to Interrogate.
+set(INTERROGATE_EXCLUDE_REGEXES
+  ".*\\.I$"
+  ".*\\.N$"
+  ".*\\.c$"
+  ".*\\.lxx$"
+  ".*\\.yxx$"
+  ".*_src\\..*"
+)
+
+if(WIN32)
+  list(APPEND IGATE_FLAGS -D_X86_ -D__STDC__=1 -D "_declspec(param)=" -D "__declspec(param)=" -D_near -D_far -D__near -D__far -D_WIN32 -D__stdcall)
+endif()
+if(MSVC_VERSION)
+  list(APPEND IGATE_FLAGS "-D_MSC_VER=${MSVC_VERSION}")
+endif()
+if(INTERROGATE_VERBOSE)
+  list(APPEND IGATE_FLAGS "-v")
+endif()
+
+set(IMOD_FLAGS -python-native)
+
+# This stores the names of every module added to the Interrogate system:
+set(ALL_INTERROGATE_MODULES CACHE INTERNAL "Internal variable")
+
+#
+# Function: target_interrogate(target [ALL] [source1 [source2 ...]])
+# NB. This doesn't actually invoke interrogate, but merely adds the
+# sources to the list of scan sources associated with the target.
+# Interrogate will be invoked when add_python_module is called.
+# If ALL is specified, all of the sources from the associated
+# target are added.
+#
+function(target_interrogate target)
+  set(sources)
+  set(extensions)
+  set(want_all OFF)
+  set(extensions_keyword OFF)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "ALL")
+      set(want_all ON)
+
+    elseif(arg STREQUAL "EXTENSIONS")
+      set(extensions_keyword ON)
+
+    elseif(extensions_keyword)
+      list(APPEND extensions "${arg}")
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach()
+
+  # If ALL was specified, pull in all sources from the target.
+  if(want_all)
+    get_target_property(target_sources "${target}" SOURCES)
+    list(APPEND sources ${target_sources})
+  endif()
+
+  list(REMOVE_DUPLICATES sources)
+
+  # Now let's get everything's absolute path, so that it can be passed
+  # through a property while still preserving the reference.
+  set(absolute_sources)
+  foreach(source ${sources})
+    get_source_file_property(exclude "${source}" WRAP_EXCLUDE)
+    if(NOT exclude)
+      get_source_file_property(location "${source}" LOCATION)
+      list(APPEND absolute_sources ${location})
+    endif()
+  endforeach(source)
+
+  set(absolute_extensions)
+  foreach(extension ${extensions})
+    get_source_file_property(location "${extension}" LOCATION)
+    list(APPEND absolute_extensions ${location})
+  endforeach(extension)
+
+  set_target_properties("${target}" PROPERTIES
+    IGATE_SOURCES "${absolute_sources}")
+  set_target_properties("${target}" PROPERTIES
+    IGATE_EXTENSIONS "${absolute_extensions}")
+
+  # CMake has no property for determining the source directory where the
+  # target was originally added. interrogate_sources makes use of this
+  # property (if it is set) in order to make all paths on the command-line
+  # relative to it, thereby shortening the command-line even more.
+  # Since this is not an Interrogate-specific property, it is not named with
+  # an IGATE_ prefix.
+  set_target_properties("${target}" PROPERTIES
+    TARGET_SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}")
+
+  # Also store where the build files are kept, so the Interrogate output can go
+  # there as well.
+  set_target_properties("${target}" PROPERTIES
+    TARGET_BINDIR "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
+
+endfunction(target_interrogate)
+
+#
+# Function: interrogate_sources(target output database language_flags module)
+#
+# This function actually runs a component-level interrogation against 'target'.
+# It generates the outfile.cxx (output) and dbfile.in (database) files, which
+# can then be used during the interrogate_module step to produce language
+# bindings.
+#
+# The target must first have had sources selected with target_interrogate.
+# Failure to do so will result in an error.
+#
+function(interrogate_sources target output database language_flags)
+  get_target_property(sources "${target}" IGATE_SOURCES)
+  get_target_property(extensions "${target}" IGATE_EXTENSIONS)
+
+  if(NOT sources)
+    message(FATAL_ERROR
+      "Cannot interrogate ${target} unless it's run through target_interrogate first!")
+  endif()
+
+  get_target_property(srcdir "${target}" TARGET_SRCDIR)
+  if(NOT srcdir)
+    # No TARGET_SRCDIR was set, so we'll do everything relative to our
+    # current binary dir instead:
+    set(srcdir "${CMAKE_CURRENT_BINARY_DIR}")
+  endif()
+
+  set(scan_sources)
+  set(nfiles)
+  foreach(source ${sources})
+    get_filename_component(source_basename "${source}" NAME)
+
+    # Only certain sources should actually be scanned by Interrogate. The
+    # rest are merely dependencies. This uses the exclusion regex above in
+    # order to determine what files are okay:
+    set(exclude OFF)
+    foreach(regex ${INTERROGATE_EXCLUDE_REGEXES})
+      if("${source_basename}" MATCHES "${regex}")
+        set(exclude ON)
+      endif()
+    endforeach(regex)
+
+    if(NOT exclude)
+      # This file is to be scanned by Interrogate. In order to avoid
+      # cluttering up the command line, we should first make it relative:
+      file(RELATIVE_PATH rel_source "${srcdir}" "${source}")
+      list(APPEND scan_sources "${rel_source}")
+
+      # Also see if this file has a .N counterpart, which has directives
+      # specific for Interrogate. If there is a .N file, we add it as a dep,
+      # so that CMake will rerun Interrogate if the .N files are modified:
+      get_filename_component(source_path "${source}" PATH)
+      get_filename_component(source_name_we "${source}" NAME_WE)
+      set(nfile "${source_path}/${source_name_we}.N")
+      if(EXISTS "${nfile}")
+        list(APPEND nfiles "${nfile}")
+      endif()
+    endif()
+  endforeach(source)
+
+  # Also add extensions, in relative-path form
+  foreach(extension ${extensions})
+    file(RELATIVE_PATH rel_extension "${srcdir}" "${extension}")
+    list(APPEND scan_sources "${rel_extension}")
+  endforeach(extension)
+
+  # Interrogate also needs the include paths, so we'll extract them from the
+  # target. These are available via a generator expression.
+
+  # When we read the INTERFACE_INCLUDE_DIRECTORIES property, we need to read it
+  # from a target that has the IS_INTERROGATE=1 property.
+  # (See PackageConfig.cmake for an explanation why.)
+  # The problem is, custom commands are not targets, so we can't put target
+  # properties on them. And if you try to use the $<TARGET_PROPERTY:prop>
+  # generator expression from the context of a custom command, it'll instead
+  # read the property from the most recent actual target. As a workaround for
+  # this, we create a fake target with the IS_INTERROGATE property set and pull
+  # the INTERFACE_INCLUDE_DIRECTORIES property out through that.
+  # I hate it, but such is CMake.
+  add_custom_target(${target}_igate_internal)
+  set_target_properties(${target}_igate_internal PROPERTIES
+    IS_INTERROGATE 1
+    INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>")
+
+  # Note, the \t is a workaround for a CMake bug where using a plain space in
+  # a JOIN will cause it to be escaped. Tabs are not escaped and will
+  # separate correctly.
+  set(include_flags "-I$<JOIN:$<TARGET_PROPERTY:${target}_igate_internal,INTERFACE_INCLUDE_DIRECTORIES>,\t-I>")
+
+  # Get the compiler definition flags. These must be passed to Interrogate
+  # in the same way that they are passed to the compiler so that Interrogate
+  # will preprocess each file in the same way.
+  set(_compile_defs "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
+  if(NOT CMAKE_HOST_WIN32)
+    # Win32's command-line parser doesn't understand "'"
+    # that's fine, it also ignores '"'
+    set(_q "'")
+  endif()
+  set(_compile_defs_flags "-D${_q}$<JOIN:${_compile_defs},${_q}\t-D${_q}>${_q}")
+  # We may have just ended up with -D'' if there are no flags; filter that
+  set(define_flags
+    "$<$<NOT:$<STREQUAL:${_compile_defs_flags},-D${_q}${_q}>>:${_compile_defs_flags}>")
+
+  # Some of the definitions may be specified using -D flags in the global
+  # CXX_FLAGS variables; parse those out (this also picks up NDEBUG)
+  set(_configs ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE} "<ALL>")
+  list(REMOVE_DUPLICATES _configs)
+  foreach(_config ${_configs})
+    if(_config STREQUAL "<ALL>")
+      set(flags "${CMAKE_CXX_FLAGS}")
+    else()
+      string(TOUPPER "${_config}" _CONFIG)
+      set(flags "${CMAKE_CXX_FLAGS_${_CONFIG}}")
+    endif()
+
+    # Convert "/D define1" and "-Ddefine2" flags, interspersed with other
+    # compiler nonsense, into a basic "-Ddefine1 -Ddefine2" string
+    string(REGEX MATCHALL "[/-]D[ \t]*[A-Za-z0-9_]+" igate_flags "${flags}")
+    string(REPLACE ";" " " igate_flags "${igate_flags}")
+    string(REPLACE "/D" "-D" igate_flags "${igate_flags}")
+
+    if(_config STREQUAL "<ALL>")
+      list(APPEND define_flags "${igate_flags}")
+    else()
+      list(APPEND define_flags "$<$<CONFIG:${_config}>:${igate_flags}>")
+    endif()
+  endforeach(_config)
+
+  get_filename_component(output_directory "${output}" DIRECTORY)
+  get_filename_component(database_directory "${database}" DIRECTORY)
+
+  add_custom_command(
+    OUTPUT "${output}" "${database}"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${output_directory}"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${database_directory}"
+    COMMAND host_interrogate
+      -oc "${output}"
+      -od "${database}"
+      -srcdir "${srcdir}"
+      -library ${target}
+      ${INTERROGATE_OPTIONS}
+      ${IGATE_FLAGS}
+      ${language_flags}
+      ${define_flags}
+      -S "${PROJECT_SOURCE_DIR}/dtool/src/interrogatedb"
+      -S "${PROJECT_SOURCE_DIR}/dtool/src/parser-inc"
+      -S "${PYTHON_INCLUDE_DIRS}"
+      ${include_flags}
+      ${scan_sources}
+
+    DEPENDS host_interrogate ${sources} ${extensions} ${nfiles}
+    COMMENT "Interrogating ${target}")
+
+  # Propagate the target's compile definitions to the output file
+  set_source_files_properties("${output}" PROPERTIES
+    COMPILE_DEFINITIONS "$<TARGET_PROPERTY:${target},INTERFACE_COMPILE_DEFINITIONS>")
+
+endfunction(interrogate_sources)
+
+#
+# Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...]
+#    [IMPORT mod1 ...])
+# Uses interrogate to create a Python module. If the LINK keyword is specified,
+# the Python module is linked against the specified libraries instead of those
+# listed before. The IMPORT keyword makes the output module import another
+# Python module when it's initialized.
+#
+function(add_python_module module)
+  if(NOT INTERROGATE_PYTHON_INTERFACE)
+    return()
+  endif()
+
+  set(targets)
+  set(component "Python")
+  set(link_targets)
+  set(import_flags)
+  set(infiles_rel)
+  set(infiles_abs)
+  set(sources_abs)
+  set(extensions)
+
+  set(keyword)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "COMPONENT")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "LINK")
+      list(APPEND link_targets "${arg}")
+      set(keyword)
+
+    elseif(keyword STREQUAL "IMPORT")
+      list(APPEND import_flags "-import" "${arg}")
+      set(keyword)
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+      set(keyword)
+
+    else()
+      list(APPEND targets "${arg}")
+
+    endif()
+  endforeach(arg)
+
+  if(NOT link_targets)
+    set(link_targets ${targets})
+  endif()
+
+  string(REGEX REPLACE "^.*\\." "" modname "${module}")
+
+  foreach(target ${targets})
+    get_target_property(workdir_abs "${target}" TARGET_BINDIR)
+    if(NOT workdir_abs)
+      # No TARGET_BINDIR was set, so we'll just use our current directory:
+      set(workdir_abs "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
+    endif()
+    # Keep command lines short
+    file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
+
+    interrogate_sources(${target}
+      "${workdir_abs}/${target}_igate.cxx"
+      "${workdir_abs}/${target}.in"
+      "-python-native;-module;${module}")
+
+    get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
+    list(APPEND infiles_rel "${workdir_rel}/${target}.in")
+    list(APPEND infiles_abs "${workdir_abs}/${target}.in")
+    list(APPEND sources_abs "${workdir_abs}/${target}_igate.cxx")
+    list(APPEND extensions ${target_extensions})
+  endforeach(target)
+
+  add_custom_command(
+    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
+    COMMAND host_interrogate_module
+      -oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+      -module ${modname} -library ${modname}
+      ${import_flags}
+      ${INTERROGATE_MODULE_OPTIONS}
+      ${IMOD_FLAGS} ${infiles_rel}
+    DEPENDS host_interrogate_module ${infiles_abs}
+    COMMENT "Generating module ${module}")
+
+  # CMake chokes on ${CMAKE_CFG_INTDIR} in source paths when unity builds are
+  # enabled. The easiest way out of this is to skip unity for those paths.
+  # Since generated Interrogate .cxx files are pretty big already, this doesn't
+  # really inconvenience us at all.
+  set_source_files_properties(
+    "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    ${sources_abs} PROPERTIES
+    SKIP_UNITY_BUILD_INCLUSION YES)
+
+  add_python_target(${module} COMPONENT "${component}" EXPORT "${component}"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    ${sources_abs} ${extensions})
+  target_link_libraries(${module} ${link_targets})
+
+  if(CMAKE_VERSION VERSION_LESS "3.11")
+    # CMake <3.11 doesn't allow generator expressions on source files, so we
+    # need to copy them to our target, which does allow them.
+
+    foreach(source ${sources_abs})
+      get_source_file_property(compile_definitions "${source}" COMPILE_DEFINITIONS)
+      if(compile_definitions)
+        set_property(TARGET ${module} APPEND PROPERTY
+          COMPILE_DEFINITIONS ${compile_definitions})
+
+        set_source_files_properties("${source}" PROPERTIES COMPILE_DEFINITIONS "")
+      endif()
+    endforeach(source)
+  endif()
+
+  list(APPEND ALL_INTERROGATE_MODULES "${module}")
+  set(ALL_INTERROGATE_MODULES "${ALL_INTERROGATE_MODULES}" CACHE INTERNAL "Internal variable")
+endfunction(add_python_module)

+ 506 - 0
cmake/macros/PackageConfig.cmake

@@ -0,0 +1,506 @@
+# Filename: PackageConfig.cmake
+#
+# This module defines functions which find and configure libraries
+# and packages for Panda3D.
+#
+# Assumes an attempt to find the package has already been made with
+# find_package(). (i.e. relies on packagename_FOUND variable)
+#
+# The packages are added as imported/interface libraries in the PKG::
+# namespace.  If the package is not found (or disabled by the user),
+# a dummy package will be created instead.  Therefore, it is safe
+# to link against the PKG::PACKAGENAME target unconditionally.
+#
+# Function: package_option
+# Usage:
+#   package_option(package_name package_doc_string
+#                  [DEFAULT ON | OFF]
+#                  [IMPORTED_AS CMake::Imported::Target [...]]
+#                  [FOUND_AS find_name]
+#                  [LICENSE license])
+#
+# Examples:
+#   package_option(LIBNAME "Enables LIBNAME support." DEFAULT OFF)
+#
+#       If no default is given, the default in normal
+#       builds is to enable all found third-party packages.
+#       In builds for redistribution, there is the additional requirement that
+#       the package be suitably-licensed.
+#
+#       FOUND_AS indicates the name of the CMake find_package() module, which
+#       may differ from Panda3D's internal name for that package.
+#
+#       IMPORTED_AS is used to indicate that the find_package() may have
+#       provided one or more IMPORTED targets, and that if at least one is
+#       found, the IMPORTED target(s) should be used instead of the
+#       variables provided by find_package()
+#
+#
+# Function: package_status
+# Usage:
+#   package_status(package_name "Package description" ["Config summary"])
+#
+# Examples:
+#   package_status(OpenAL "OpenAL Audio Output")
+#   package_status(ROCKET "Rocket" "without Python bindings")
+#
+#
+# Function: show_packages
+# Usage:
+#   show_packages()
+#
+#   This prints the package usage report using the information provided in
+#   calls to package_status above.
+#
+
+set(_ALL_PACKAGE_OPTIONS CACHE INTERNAL "Internal variable")
+
+#
+# package_option
+#
+# In order to make sure no third-party licenses are inadvertently violated,
+# this imposes a few rules regarding license:
+# 1) If there is no license, no special restrictions.
+# 2) If there is a license, but the build is not flagged for redistribution,
+#    no special restrictions.
+# 3) If there is a license, and this is for redistribution, the package is
+#    forcibly defaulted off and must be explicitly enabled, unless the license
+#    matches a list of licenses suitable for redistribution.
+#
+function(package_option name)
+  # Parse the arguments.
+  set(command)
+  set(default)
+  set(found_as "${name}")
+  set(imported_as)
+  set(license "")
+  set(cache_string)
+
+  string(TOUPPER "${name}" name)
+
+  foreach(arg ${ARGN})
+    if(command STREQUAL "DEFAULT")
+      set(default "${arg}")
+      set(command)
+
+    elseif(command STREQUAL "FOUND_AS")
+      set(found_as "${arg}")
+      set(command)
+
+    elseif(command STREQUAL "LICENSE")
+      set(license "${arg}")
+      set(command)
+
+    elseif(arg STREQUAL "DEFAULT")
+      set(command "DEFAULT")
+
+    elseif(arg STREQUAL "FOUND_AS")
+      set(command "FOUND_AS")
+
+    elseif(arg STREQUAL "LICENSE")
+      set(command "LICENSE")
+
+    elseif(arg STREQUAL "IMPORTED_AS")
+      set(command "IMPORTED_AS")
+
+    elseif(command STREQUAL "IMPORTED_AS")
+      list(APPEND imported_as "${arg}")
+
+    else()
+      # Yes, a list, because semicolons can be in there, and
+      # that gets split up into multiple args, so we have to
+      # join it back together here.
+      list(APPEND cache_string "${arg}")
+
+    endif()
+  endforeach()
+
+  if(command AND NOT command STREQUAL "IMPORTED_AS")
+    message(SEND_ERROR "${command} in package_option takes an argument")
+  endif()
+
+  # If the default is not set, we set it.
+  if(NOT DEFINED default)
+    if(IS_DIST_BUILD)
+      # Accept things that don't have a configured license
+      if(license STREQUAL "")
+        set(default "${${found_as}_FOUND}")
+
+      else()
+        list(FIND PANDA_DIST_USE_LICENSES ${license} license_index)
+        # If the license isn't in the accept listed, don't use the package
+        if(${license_index} EQUAL "-1")
+          set(default OFF)
+
+        else()
+          set(default "${${found_as}_FOUND}")
+
+        endif()
+
+      endif()
+
+    else()
+      set(default "${${found_as}_FOUND}")
+
+    endif()
+  endif()
+
+  # If it was set by the user but not found, display an error.
+  string(TOUPPER "${found_as}" FOUND_AS)
+  if(HAVE_${name} AND NOT ${found_as}_FOUND AND NOT ${FOUND_AS}_FOUND)
+    message(SEND_ERROR "NOT FOUND: ${name}.  Disable HAVE_${name} to continue.")
+  endif()
+
+  # Prevent the function from being called twice.
+  #   This would indicate a cmake error.
+  if(";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
+    message(SEND_ERROR "package_option(${name}) was called twice.
+                        This is a bug in the cmake build scripts.")
+
+  else()
+    list(APPEND _ALL_PACKAGE_OPTIONS "${name}")
+    set(_ALL_PACKAGE_OPTIONS "${_ALL_PACKAGE_OPTIONS}" CACHE INTERNAL "Internal variable")
+
+  endif()
+
+  set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
+
+  # Create the INTERFACE library used to depend on this package.
+  add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
+
+  # Explicitly record the package's include directories as system include
+  # directories.  CMake does do this automatically for INTERFACE libraries, but
+  # it does it by discovering all transitive links first, then reading
+  # INTERFACE_INCLUDE_DIRECTORIES for those which are INTERFACE libraries.  So,
+  # this would be broken for the metalib system (pre CMake 3.12) which doesn't
+  # "link" the object libraries.
+  if(CMAKE_VERSION VERSION_LESS "3.12")
+    set_target_properties(PKG::${name} PROPERTIES
+      INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
+      "$<TARGET_PROPERTY:PKG::${name},INTERFACE_INCLUDE_DIRECTORIES>")
+  endif()
+
+  # Create the option, and if it actually is enabled, populate the INTERFACE
+  # library created above
+  option("HAVE_${name}" "${cache_string}" "${default}")
+  if(HAVE_${name})
+    set(use_variables ON)
+
+    # This is gross, but we actually want to hide package include directories
+    # from Interrogate to make sure it relies on parser-inc instead, so we'll
+    # use some generator expressions to do that.
+    set(_is_not_interface_lib
+      "$<NOT:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,INTERFACE_LIBRARY>>")
+    set(_is_not_interrogate
+      "$<NOT:$<BOOL:$<${_is_not_interface_lib}:$<TARGET_PROPERTY:IS_INTERROGATE>>>>")
+
+    foreach(implib ${imported_as})
+      if(TARGET ${implib})
+        # We found one of the implibs, so we don't need to use variables
+        # (below) anymore
+        set(use_variables OFF)
+
+        # Hide it from Interrogate
+        target_link_libraries(PKG::${name} INTERFACE
+          "$<${_is_not_interrogate}:$<TARGET_NAME:${implib}>>")
+      endif()
+    endforeach(implib)
+
+    if(use_variables)
+      if(DEFINED ${found_as}_INCLUDE_DIRS)
+        set(includes ${${found_as}_INCLUDE_DIRS})
+      elseif(DEFINED ${found_as}_INCLUDE_DIR)
+        set(includes "${${found_as}_INCLUDE_DIR}")
+      elseif(DEFINED ${FOUND_AS}_INCLUDE_DIRS)
+        set(includes ${${FOUND_AS}_INCLUDE_DIRS})
+      else()
+        set(includes "${${FOUND_AS}_INCLUDE_DIR}")
+      endif()
+
+      if(DEFINED ${found_as}_LIBRARIES)
+        set(libs ${${found_as}_LIBRARIES})
+      elseif(DEFINED ${found_as}_LIBRARY)
+        set(libs "${${found_as}_LIBRARY}")
+      elseif(DEFINED ${FOUND_AS}_LIBRARIES)
+        set(libs ${${FOUND_AS}_LIBRARIES})
+      else()
+        set(libs "${${FOUND_AS}_LIBRARY}")
+      endif()
+
+      target_link_libraries(PKG::${name} INTERFACE ${libs})
+
+      # Hide it from Interrogate
+      set_target_properties(PKG::${name} PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "$<${_is_not_interrogate}:${includes}>")
+    endif()
+  endif()
+endfunction(package_option)
+
+set(_ALL_CONFIG_PACKAGES CACHE INTERNAL "Internal variable")
+
+#
+# package_status
+#
+function(package_status name desc)
+  set(note "")
+  foreach(arg ${ARGN})
+    set(note "${arg}")
+  endforeach()
+
+  string(TOUPPER "${name}" name)
+
+  if(NOT ";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
+    message(SEND_ERROR "package_status(${name}) was called before package_option(${name}).
+                        This is a bug in the cmake build scripts.")
+    return()
+  endif()
+
+  if(";${_ALL_CONFIG_PACKAGES};" MATCHES ";${name};")
+    message(SEND_ERROR "package_status(${name}) was called twice.
+                        This is a bug in the cmake build scripts.")
+
+  else()
+    list(APPEND _ALL_CONFIG_PACKAGES "${name}")
+    set(_ALL_CONFIG_PACKAGES "${_ALL_CONFIG_PACKAGES}" CACHE INTERNAL "Internal variable")
+
+  endif()
+
+  set(PANDA_PACKAGE_DESC_${name} "${desc}" PARENT_SCOPE)
+  set(PANDA_PACKAGE_NOTE_${name} "${note}" PARENT_SCOPE)
+endfunction()
+
+#
+# show_packages
+#
+function(show_packages)
+  message("")
+  message("Configuring support for the following optional third-party packages:")
+
+  foreach(package ${_ALL_CONFIG_PACKAGES})
+    set(desc "${PANDA_PACKAGE_DESC_${package}}")
+    set(note "${PANDA_PACKAGE_NOTE_${package}}")
+    if(HAVE_${package})
+      if(NOT note STREQUAL "")
+        message("+ ${desc} (${note})")
+      else()
+        message("+ ${desc}")
+      endif()
+
+    else()
+      if(NOT ${package}_FOUND)
+        set(reason "not found")
+      elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
+        set(reason "not requested")
+      else()
+        set(reason "disabled")
+      endif()
+
+      message("- ${desc} (${reason})")
+
+    endif()
+  endforeach()
+endfunction()
+
+#
+# export_packages(filename)
+#
+# Generates an includable CMake file that contains definitions for every PKG::
+# package defined.
+#
+function(export_packages filename)
+  set(exports "# Exports for Panda3D PKG:: packages\n")
+
+  foreach(pkg ${_ALL_PACKAGE_OPTIONS})
+    set(exports "${exports}\n# Create imported target PKG::${pkg}\n")
+    set(exports "${exports}add_library(PKG::${pkg} INTERFACE IMPORTED)\n\n")
+
+    set(exports "${exports}set_target_properties(PKG::${pkg} PROPERTIES\n")
+    foreach(prop
+        INTERFACE_COMPILE_DEFINITIONS
+        INTERFACE_COMPILE_FEATURES
+        INTERFACE_COMPILE_OPTIONS
+        INTERFACE_INCLUDE_DIRECTORIES
+        INTERFACE_LINK_DEPENDS
+        INTERFACE_LINK_DIRECTORIES
+        INTERFACE_LINK_OPTIONS
+        INTERFACE_POSITION_INDEPENDENT_CODE
+       #INTERFACE_SYSTEM_INCLUDE_DIRECTORIES  # Let the consumer dictate this
+        INTERFACE_SOURCES)
+
+      set(prop_ex "$<TARGET_PROPERTY:PKG::${pkg},${prop}>")
+      set(exports "${exports}$<$<BOOL:${prop_ex}>:  ${prop} \"${prop_ex}\"\n>")
+
+    endforeach(prop)
+
+    # Ugh, INTERFACE_LINK_LIBRARIES isn't transitive.  Fine.  Take care of it
+    # by hand:
+    set(libraries)
+    set(stack "PKG::${pkg}")
+    set(history)
+    while(stack)
+      # Remove head item from stack
+      unset(head)
+      while(NOT DEFINED head)
+        if(NOT stack)
+          break()
+        endif()
+
+        list(GET stack 0 head)
+        list(REMOVE_AT stack 0)
+
+        # Don't visit anything twice
+        list(FIND history "${head}" _index)
+        if(_index GREATER -1)
+          unset(head)
+        endif()
+      endwhile()
+
+      if(head)
+        list(APPEND history "${head}")
+      else()
+        break()
+      endif()
+
+      # If head isn't a target, add it to `libraries`, else recurse
+      if(TARGET "${head}")
+        get_target_property(link_libs "${head}" INTERFACE_LINK_LIBRARIES)
+        if(link_libs)
+          list(APPEND stack ${link_libs})
+        endif()
+
+        get_target_property(type "${head}" TYPE)
+        if(NOT type STREQUAL "INTERFACE_LIBRARY")
+          get_target_property(imported_location "${head}" IMPORTED_LOCATION)
+          get_target_property(imported_implib "${head}" IMPORTED_IMPLIB)
+          if(imported_implib)
+            list(APPEND libraries ${imported_implib})
+          elseif(imported_location)
+            list(APPEND libraries ${imported_location})
+          endif()
+
+          get_target_property(configs "${head}" IMPORTED_CONFIGURATIONS)
+          if(configs AND NOT imported_location)
+            foreach(config ${configs})
+              get_target_property(imported_location "${head}" IMPORTED_LOCATION_${config})
+
+              # Prefer IMPORTED_IMPLIB where present
+              get_target_property(imported_implib "${head}" IMPORTED_IMPLIB_${config})
+              if(imported_implib)
+                set(imported_location "${imported_implib}")
+              endif()
+
+              if(imported_location)
+                if(configs MATCHES ".*;.*")
+                  set(_bling "$<1:$>") # genex-escaped $
+                  list(APPEND libraries "${_bling}<${_bling}<CONFIG:${config}>:${imported_location}>")
+
+                else()
+                  list(APPEND libraries ${imported_location})
+
+                endif()
+              endif()
+            endforeach(config)
+          endif()
+
+        elseif(CMAKE_VERSION VERSION_GREATER "3.8")
+          # This is an INTERFACE_LIBRARY, and CMake is new enough to support
+          # IMPORTED_IMPLIB
+          get_target_property(imported_libname "${head}" IMPORTED_LIBNAME)
+          if(imported_libname)
+            list(APPEND libraries ${imported_libname})
+          endif()
+
+        endif()
+
+      elseif("${head}" MATCHES "\\$<TARGET_NAME:\([^>]+\)>")
+        string(REGEX REPLACE ".*\\$<TARGET_NAME:\([^>]+\)>.*" "\\1" match "${head}")
+        list(APPEND stack "${match}")
+
+      else()
+        list(APPEND libraries "${head}")
+
+      endif()
+    endwhile(stack)
+
+    set(exports "${exports}  INTERFACE_LINK_LIBRARIES \"${libraries}\"\n")
+
+    set(exports "${exports})\n")
+  endforeach(pkg)
+
+  # file(GENERATE) does not like $<LINK_ONLY:...> (and it's meant to be
+  # consumed by our importer) so we escape it
+  set(_bling "$<1:$>") # genex-escaped $
+  string(REPLACE "$<LINK_ONLY:" "${_bling}<LINK_ONLY:" exports "${exports}")
+
+  file(GENERATE OUTPUT "${filename}" CONTENT "${exports}")
+endfunction(export_packages)
+
+#
+# export_targets(set [NAMESPACE namespace] [COMPONENT component])
+#
+# Export targets in the export set named by "set"
+#
+# NAMESPACE overrides the namespace prefixed to the exported targets; it
+# defaults to "Panda3D::[set]::" if no explicit override is given.
+#
+# COMPONENT overrides the install component for the generated .cmake file; it
+# defaults to "[set]" if no explicit override is given.
+#
+function(export_targets set)
+  set(namespace "Panda3D::${set}::")
+  set(component "${set}")
+  set(keyword)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "NAMESPACE" OR
+       arg STREQUAL "COMPONENT")
+
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "NAMESPACE")
+      set(namespace "${arg}")
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+
+    else()
+      message(FATAL_ERROR "export_targets() given unexpected arg: ${arg}")
+
+    endif()
+  endforeach(arg)
+
+  export(EXPORT "${set}" NAMESPACE "${namespace}"
+    FILE "${PROJECT_BINARY_DIR}/Panda3D${set}Targets.cmake")
+  install(EXPORT "${set}" NAMESPACE "${namespace}"
+    FILE "Panda3D${set}Targets.cmake"
+    COMPONENT "${component}" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D)
+
+endfunction(export_targets)
+
+#
+# find_package
+#
+# This override implements CMAKE_FIND_PACKAGE_PREFER_CONFIG on versions of
+# CMake too old to include it.
+#
+if(CMAKE_VERSION VERSION_LESS "3.15")
+  macro(find_package name)
+    if(";${ARGN};" MATCHES ";(CONFIG|MODULE|NO_MODULE);")
+      # Caller explicitly asking for a certain mode; so be it.
+      _find_package(${ARGV})
+
+    elseif(CMAKE_FIND_PACKAGE_PREFER_CONFIG)
+      # Try CONFIG
+      _find_package("${name}" CONFIG ${ARGN})
+
+      if(NOT ${name}_FOUND)
+        # CONFIG didn't work, fall back to MODULE
+        _find_package("${name}" MODULE ${ARGN})
+      endif()
+
+    else()
+      # Default behavior
+      _find_package(${ARGV})
+
+    endif()
+  endmacro(find_package)
+endif()

+ 50 - 0
cmake/macros/PerConfigOption.cmake

@@ -0,0 +1,50 @@
+# Filename: PerConfigOption.cmake
+#
+# This contains a convenience function for defining per-config options.
+# In single-config generators, it will set the option based on the defined
+# CMAKE_BUILD_TYPE.  In multi-config generators, it will create separate
+# options, one per config.
+#
+# Function: per_config_option
+# Usage:
+#   option(name "help string" [Config1] [Config2] [...ConfigN])
+#
+# Example:
+#   per_config_option(DO_DEBUGGING "Enables debugging." Debug Standard)
+
+set(_PER_CONFIG_OPTIONS CACHE INTERNAL "Internal variable")
+
+function(per_config_option name help)
+  set(_configs ${ARGN})
+
+  # In single-config generatotrs, we simply create one config.
+  if(NOT IS_MULTICONFIG)
+    list(FIND _configs "${CMAKE_BUILD_TYPE}" _index)
+    if(${_index} GREATER -1)
+      option("${name}" "${help}" ON)
+    else()
+      option("${name}" "${help}" OFF)
+    endif()
+
+  elseif(DEFINED "${name}")
+    # It's been explicitly defined, so that makes it not a multi-configuration
+    # variable anymore.
+    option("${name}" "${help}")
+    return()
+
+  else()
+    foreach(_config ${CMAKE_CONFIGURATION_TYPES})
+      string(TOUPPER "${_config}" _config_upper)
+      list(FIND _configs "${_config}" _index)
+      if(${_index} GREATER -1)
+        option("${name}_${_config_upper}" "${help}" ON)
+      else()
+        option("${name}_${_config_upper}" "${help}" OFF)
+      endif()
+    endforeach()
+
+  endif()
+
+  list(APPEND _PER_CONFIG_OPTIONS "${name}")
+  set(_PER_CONFIG_OPTIONS "${_PER_CONFIG_OPTIONS}" CACHE INTERNAL "Internal variable")
+endfunction(per_config_option)

+ 163 - 0
cmake/macros/Python.cmake

@@ -0,0 +1,163 @@
+# Filename: Python.cmake
+#
+# Description: This file provides support functions for building/installing
+#   Python extension modules and/or pure-Python packages.
+#
+# Functions:
+#   add_python_target(target [source1 [source2 ...]])
+#   install_python_package(path [ARCH/LIB])
+#
+
+#
+# Function: add_python_target(target [EXPORT exp] [COMPONENT comp]
+#                                    [source1 [source2 ...]])
+# Build the provided source(s) as a Python extension module, linked against the
+# Python runtime library.
+#
+# Note that this also takes care of installation, unlike other target creation
+# commands in CMake.  The EXPORT and COMPONENT keywords allow passing the
+# corresponding options to install(), but default to "Python" otherwise.
+#
+function(add_python_target target)
+  if(NOT HAVE_PYTHON)
+    return()
+  endif()
+
+  string(REGEX REPLACE "^.*\\." "" basename "${target}")
+  set(sources)
+  set(component "Python")
+  set(export "Python")
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "COMPONENT")
+      set(keyword "component")
+
+    elseif(arg STREQUAL "EXPORT")
+      set(keyword "export")
+
+    elseif(keyword)
+      set(${keyword} "${arg}")
+      unset(keyword)
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach(arg)
+
+  string(REGEX REPLACE "\\.[^.]+$" "" namespace "${target}")
+  string(REPLACE "." "/" slash_namespace "${namespace}")
+
+  add_library(${target} ${MODULE_TYPE} ${sources})
+  target_link_libraries(${target} PKG::PYTHON)
+
+  if(BUILD_SHARED_LIBS)
+    set(_outdir "${PANDA_OUTPUT_DIR}/${slash_namespace}")
+
+    set_target_properties(${target} PROPERTIES
+      LIBRARY_OUTPUT_DIRECTORY "${_outdir}"
+      OUTPUT_NAME "${basename}"
+      PREFIX ""
+      SUFFIX "${PYTHON_EXTENSION_SUFFIX}")
+
+    # This is explained over in CompilerFlags.cmake
+    foreach(_config ${CMAKE_CONFIGURATION_TYPES})
+      string(TOUPPER "${_config}" _config)
+      set_target_properties(${target} PROPERTIES
+        LIBRARY_OUTPUT_DIRECTORY_${_config} "${_outdir}")
+    endforeach(_config)
+
+    if(PYTHON_ARCH_INSTALL_DIR)
+      install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION "${PYTHON_ARCH_INSTALL_DIR}/${slash_namespace}")
+    endif()
+
+  else()
+    set_target_properties(${target} PROPERTIES
+      OUTPUT_NAME "${basename}"
+      PREFIX "libpy.${namespace}.")
+
+    install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+  endif()
+
+endfunction(add_python_target)
+
+#
+# Function: install_python_package(name [SOURCE path] [ARCH/LIB] [COMPONENT component])
+#
+# Installs the Python package `name` (which may have its source at `path`).
+#
+# The package is copied to (or created in) the build directory so that the user
+# may import it before the install step.
+#
+# Note that this handles more than just installation; it will also invoke
+# Python's compileall utility to pregenerate .pyc/.pyo files.  This will only
+# happen if the Python interpreter is found.
+#
+# The ARCH or LIB keyword may be used to specify whether this package should be
+# installed into Python's architecture-dependent or architecture-independent
+# package path.  The default, if unspecified, is LIB.
+#
+# The COMPONENT keyword overrides the install component (see CMake's
+# documentation for more information on what this does).  The default is
+# "Python".
+#
+function(install_python_package package_name)
+  set(type "LIB")
+  unset(keyword)
+  set(component "Python")
+  unset(src_path)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "ARCH")
+      set(type "ARCH")
+
+    elseif(arg STREQUAL "LIB")
+      set(type "LIB")
+
+    elseif(arg STREQUAL "COMPONENT")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+      unset(keyword)
+
+    elseif(arg STREQUAL "SOURCE")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "SOURCE")
+      set(src_path "${arg}")
+      unset(keyword)
+
+    else()
+      message(FATAL_ERROR "install_python_package got unexpected argument: ${ARGN}")
+
+    endif()
+  endforeach(arg)
+
+  if(NOT DEFINED src_path AND type STREQUAL "ARCH" AND WIN32 AND NOT CYGWIN)
+    # Win32 needs a special fixup so the DLLs in "bin" can be on the path;
+    # let's set src_path to the directory containing our fixup __init__.py
+    set(src_path "${CMAKE_SOURCE_DIR}/cmake/templates/win32_python")
+  endif()
+
+  set(path "${PANDA_OUTPUT_DIR}/${package_name}")
+
+  set(args -D "OUTPUT_DIR=${path}")
+  if(src_path)
+    list(APPEND args -D "SOURCE_DIR=${src_path}")
+  endif()
+  if(PYTHON_EXECUTABLE)
+    list(APPEND args -D "PYTHON_EXECUTABLES=${PYTHON_EXECUTABLE}")
+  endif()
+  add_custom_target(${package_name} ALL
+    COMMAND ${CMAKE_COMMAND}
+      ${args}
+      -P "${CMAKE_SOURCE_DIR}/cmake/scripts/CopyPython.cmake")
+
+  set(dir "${PYTHON_${type}_INSTALL_DIR}")
+  if(dir)
+    install(DIRECTORY "${path}" DESTINATION "${dir}"
+      COMPONENT "${component}"
+      FILES_MATCHING REGEX "\\.py[co]?$")
+  endif()
+
+endfunction(install_python_package)

+ 9 - 0
cmake/macros/README.md

@@ -0,0 +1,9 @@
+Directory Info
+--------------
+**Directory:** /cmake/macros  
+**License:** Unlicense  
+**Description:** This directory is used for CMake modules which may be _unsafe_
+to use outside of a Panda3D project.  These modules may rely on Panda3D specific
+cmake variables, Panda3D's directory structure, or some other dependency.
+They are not intended to be included in other projects directly, though you
+are free to copy and adapt them to your own needs.

+ 37 - 0
cmake/macros/RunPzip.cmake

@@ -0,0 +1,37 @@
+function(run_pzip target_name source destination glob)
+  file(GLOB_RECURSE files RELATIVE "${source}" "${source}/${glob}")
+
+  set(dstfiles "")
+  foreach(filename ${files})
+    string(REGEX REPLACE "^/" "" filename "${filename}")
+
+    get_filename_component(dstdir "${destination}/${filename}" DIRECTORY)
+
+    if(TARGET host_pzip)
+      set(dstfile "${filename}.pz")
+      list(APPEND dstfiles "${destination}/${dstfile}")
+
+      add_custom_command(OUTPUT "${destination}/${dstfile}"
+        COMMAND ${CMAKE_COMMAND} -E make_directory "${dstdir}"
+        COMMAND host_pzip -o "${destination}/${dstfile}" "${source}/${filename}"
+        DEPENDS host_pzip
+        COMMENT "")
+
+    else()
+      # If pzip isn't built, we just copy instead.
+      list(APPEND dstfiles "${destination}/${filename}")
+
+      add_custom_command(OUTPUT "${destination}/${filename}"
+        COMMAND ${CMAKE_COMMAND} -E
+          copy_if_different "${source}/${filename}" "${destination}/${filename}"
+        COMMENT "")
+
+    endif()
+
+  endforeach(filename)
+
+  add_custom_target(${target_name} ALL
+    DEPENDS ${dstfiles}
+    WORKING_DIRECTORY "${destination}")
+
+endfunction(run_pzip)

+ 21 - 0
cmake/macros/Versioning.cmake

@@ -0,0 +1,21 @@
+# Filename: Versioning.cmake
+#
+# Description: Contains an override for add_library to set the
+#   VERSION and SOVERSION properties on all shared libraries, automatically, to
+#   the project version.
+#
+# Functions:
+#   add_library(...)
+#
+
+function(add_library target_name)
+  _add_library("${target_name}" ${ARGN})
+
+  get_target_property(type "${target_name}" TYPE)
+  if(type STREQUAL "SHARED_LIBRARY")
+    set_target_properties("${target_name}" PROPERTIES
+      VERSION "${PROJECT_VERSION}"
+      SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
+  endif()
+
+endfunction(add_library)

+ 33 - 0
cmake/modules/FindARToolKit.cmake

@@ -0,0 +1,33 @@
+# Filename: FindARToolKit.cmake
+# Authors: CFSworks (3 Nov, 2018)
+#
+# Usage:
+#   find_package(ARToolKit [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ARTOOLKIT_FOUND       - system has ARToolKit
+#   ARTOOLKIT_INCLUDE_DIR - the include directory containing ARToolKit header files
+#   ARTOOLKIT_LIBRARIES   - the paths to the ARToolKit client libraries
+#
+
+find_path(ARTOOLKIT_INCLUDE_DIR "AR/ar.h")
+
+find_library(ARTOOLKIT_AR_LIBRARY
+  NAMES "AR" "libAR")
+
+find_library(ARTOOLKIT_ARMulti_LIBRARY
+  NAMES "ARMulti" "libARMulti")
+
+mark_as_advanced(ARTOOLKIT_INCLUDE_DIR ARTOOLKIT_AR_LIBRARY ARTOOLKIT_ARMulti_LIBRARY)
+
+set(ARTOOLKIT_LIBRARIES)
+if(ARTOOLKIT_AR_LIBRARY)
+  list(APPEND ARTOOLKIT_LIBRARIES "${ARTOOLKIT_AR_LIBRARY}")
+endif()
+if(ARTOOLKIT_ARMulti_LIBRARY)
+  list(APPEND ARTOOLKIT_LIBRARIES "${ARTOOLKIT_ARMulti_LIBRARY}")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(ARToolKit DEFAULT_MSG
+  ARTOOLKIT_INCLUDE_DIR ARTOOLKIT_LIBRARIES)

+ 33 - 0
cmake/modules/FindAssimp.cmake

@@ -0,0 +1,33 @@
+# Filename: FindAssimp.cmake
+# Authors: CFSworks (9 Nov, 2018)
+#
+# Usage:
+#   find_package(Assimp [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ASSIMP_FOUND        - system has Assimp
+#   ASSIMP_INCLUDE_DIR  - the path to the location of the assimp/ directory
+#   ASSIMP_LIBRARIES    - the libraries to link against for Assimp
+#
+
+find_path(ASSIMP_INCLUDE_DIR
+  NAMES "assimp/Importer.hpp")
+
+find_library(ASSIMP_ASSIMP_LIBRARY
+  NAMES "assimp")
+
+find_library(ASSIMP_IRRXML_LIBRARY
+  NAMES "IrrXML")
+
+if(ASSIMP_ASSIMP_LIBRARY)
+  set(ASSIMP_LIBRARIES "${ASSIMP_ASSIMP_LIBRARY}")
+
+  if(ASSIMP_IRRXML_LIBRARY)
+    list(APPEND ASSIMP_LIBRARIES "${ASSIMP_IRRXML_LIBRARY}")
+  endif()
+endif()
+
+mark_as_advanced(ASSIMP_INCLUDE_DIR ASSIMP_LIBRARIES)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Assimp DEFAULT_MSG ASSIMP_INCLUDE_DIR ASSIMP_LIBRARIES)

+ 175 - 0
cmake/modules/FindCg.cmake

@@ -0,0 +1,175 @@
+# Filename: FindCg.cmake
+# Author: kestred (8 Dec, 2013)
+#
+# Usage:
+#   find_package(Cg [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   CG_FOUND         - system has NvidiaCg
+#   CG_INCLUDE_DIR   - the NvidiaCg include directory
+#   CG_INCLUDE_DIRS  - directories for all NvidiaCg components
+#   CG_LIBRARY_DIR   - the NvidiaCg library directory
+#   CG_LIBRARY       - the path to the library binary
+#   CG_LIBRARIES     - the paths to the Cg library and each library below.
+#
+#   CGGL_FOUND       - system has CgGL
+#   CGGL_INCLUDE_DIR - the CgGL include directory
+#   CGGL_LIBRARY_DIR - the CgGL library directory
+#   CGGL_LIBRARY     - the path to the library binary
+#
+
+
+### Define macros to find each sublibrary ###
+
+# Find Cg for OpenGL
+macro(find_cggl)
+  if(APPLE)
+    # GL support is built-in on Apple
+    set(CGGL_LIBRARY "${CG_LIBRARY}")
+    set(CGGL_LIBRARY_DIR "${CG_LIBRARY_DIR}")
+    set(CGGL_INCLUDE_DIR "${CG_INCLUDE_DIR}")
+  endif()
+
+  if(Cg_FIND_QUIETLY)
+    set(CgGL_FIND_QUIETLY TRUE)
+  endif()
+  if(NOT CGGL_LIBRARY_DIR OR NOT CGGL_INCLUDE_DIR)
+    # Find the include directory
+    find_path(CGGL_INCLUDE_DIR
+      NAMES "cgGL.h"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg/include"
+            "/usr/include"
+            "/usr/local/include"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit/include" # Gentoo
+      PATH_SUFFIXES "" "Cg" "cg"
+      DOC "The path to NvidiaCgGL's include directory."
+    )
+
+    # Find the library directory
+    find_library(CGGL_LIBRARY
+      NAMES "CgGL" "libCgGL"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg"
+            "/usr"
+            "/usr/lib/x86_64-linux-gnu"
+            "/usr/local"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit" # Gentoo
+      PATH_SUFFIXES "" "lib" "lib32" "lib64"
+      DOC "The filepath to NvidiaCgGL's libary binary."
+    )
+    get_filename_component(CGGL_LIBRARY_DIR "${CGGL_LIBRARY}" PATH)
+    set(CGGL_LIBRARY_DIR "${CGGL_LIBRARY_DIR}" CACHE PATH "The path to the CgGL library directory.") # Library path
+
+    mark_as_advanced(CGGL_INCLUDE_DIR)
+    mark_as_advanced(CGGL_LIBRARY_DIR)
+    mark_as_advanced(CGGL_LIBRARY)
+  endif()
+
+  find_package_handle_standard_args(CgGL DEFAULT_MSG CGGL_LIBRARY CGGL_INCLUDE_DIR CGGL_LIBRARY_DIR)
+
+endmacro()
+
+
+# Find Cg for Direct3D 9
+macro(find_cgd3d9)
+  if(Cg_FIND_QUIETLY)
+    set(CgD3D9_FIND_QUIETLY TRUE)
+  endif()
+  if(NOT CGD3D9_LIBRARY_DIR OR NOT CGD3D9_INCLUDE_DIR)
+    # Find the include directory
+    find_path(CGD3D9_INCLUDE_DIR
+      NAMES "cgD3D9.h"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg/include"
+            "/usr/include"
+            "/usr/local/include"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit/include" # Gentoo
+      PATH_SUFFIXES "" "Cg" "cg"
+      DOC "The path to NvidiaCgD3D9's include directory."
+    )
+
+    # Find the library directory
+    find_library(CGD3D9_LIBRARY
+      NAMES "CgD3D9" "libCgD3D9"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg"
+            "/usr"
+            "/usr/lib/x86_64-linux-gnu"
+            "/usr/local"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit" # Gentoo
+      PATH_SUFFIXES "" "lib" "lib32" "lib64"
+      DOC "The filepath to NvidiaCgD3D9's libary binary."
+    )
+    get_filename_component(CGD3D9_LIBRARY_DIR "${CGD3D9_LIBRARY}" PATH)
+    set(CGD3D9_LIBRARY_DIR "${CGD3D9_LIBRARY_DIR}" CACHE PATH "The path to the CgD3D9 library directory.") # Library path
+
+    mark_as_advanced(CGD3D9_INCLUDE_DIR)
+    mark_as_advanced(CGD3D9_LIBRARY_DIR)
+    mark_as_advanced(CGD3D9_LIBRARY)
+  endif()
+
+  find_package_handle_standard_args(CgD3D9 DEFAULT_MSG CGD3D9_LIBRARY CGD3D9_INCLUDE_DIR CGD3D9_LIBRARY_DIR)
+
+endmacro()
+
+
+
+# Find base Nvidia Cg
+if(NOT CG_LIBRARY_DIR OR NOT CG_INCLUDE_DIRS)
+  # Find the include directory
+  find_path(CG_INCLUDE_DIR
+    NAMES "Cg/cg.h"
+    PATHS "C:/Program Files/Cg"
+          "C:/Program Files/NVIDIA Corporation/Cg/include"
+          "/usr/include"
+          "/usr/local/include"
+          "/opt/Cg"
+          "/opt/nvidia-cg-toolkit/include" # Gentoo
+    PATH_SUFFIXES "" "Cg" "cg"
+    DOC "The path to NvidiaCg's include directory."
+  )
+
+  # Find the library directory
+  find_library(CG_LIBRARY
+    NAMES "Cg" "libCg"
+    PATHS "C:/Program Files/Cg"
+          "C:/Program Files/NVIDIA Corporation/Cg"
+          "/usr"
+          "/usr/lib/x86_64-linux-gnu"
+          "/usr/local"
+          "/opt/Cg"
+          "/opt/nvidia-cg-toolkit" # Gentoo
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+  get_filename_component(CG_LIBRARY_DIR "${CG_LIBRARY}" PATH)
+  set(CG_LIBRARY_DIR "${CG_LIBRARY_DIR}" CACHE PATH "The path to NvidiaCG's library directory.") # Library path
+
+  string(REGEX REPLACE "/Cg$" "" CG_BASE_INCLUDE_DIR "${CG_INCLUDE_DIR}")
+  set(CG_INCLUDE_DIRS ${CG_BASE_INCLUDE_DIR} ${CG_INCLUDE_DIR})
+
+  mark_as_advanced(CG_INCLUDE_DIRS)
+  mark_as_advanced(CG_INCLUDE_DIR)
+  mark_as_advanced(CG_LIBRARY_DIR)
+  mark_as_advanced(CG_LIBRARY)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Cg DEFAULT_MSG CG_LIBRARY CG_INCLUDE_DIRS CG_LIBRARY_DIR)
+
+if(CG_INCLUDE_DIR AND CG_LIBRARY_DIR)
+  find_cggl()
+  find_cgd3d9()
+
+  set(CG_LIBRARIES ${CG_LIBRARY})
+  if(CGGL_LIBRARY)
+    list(APPEND CG_LIBRARIES "${CGGL_LIBRARY}")
+  endif()
+  if(CGD3D9_LIBRARY)
+    list(APPEND CG_LIBRARIES "${CGD3D9_LIBRARY}")
+  endif()
+endif()

+ 104 - 0
cmake/modules/FindDirect3D9.cmake

@@ -0,0 +1,104 @@
+# Filename: FindDirect3D9.cmake
+# Authors: CFSworks (26 Oct, 2018)
+#
+# Usage:
+#   find_package(Direct3D9 [REQUIRED] [QUIET])
+#
+# This supports the following components:
+#   d3dx9
+#   dxerr
+#   dxguid
+#
+# Once done this will define:
+#   DIRECT3D9_FOUND       - system has Direct3D 9.x
+#   DIRECT3D9_INCLUDE_DIR - the include directory containing d3d9.h - note that
+#                           this will be empty if it's part of the Windows SDK.
+#   DIRECT3D9_LIBRARY     - the path to d3d9.lib
+#   DIRECT3D9_LIBRARIES   - the path to d3d9.lib and all extra component
+#                           libraries
+#
+
+include(CheckIncludeFile)
+
+if(Direct3D9_FIND_QUIETLY)
+  if(DEFINED CMAKE_REQUIRED_QUIET)
+    set(_OLD_CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET})
+  endif()
+  # Suppress check_include_file messages
+  set(CMAKE_REQUIRED_QUIET ON)
+endif()
+
+check_include_file("d3d9.h" SYSTEM_INCLUDE_D3D9_H)
+mark_as_advanced(SYSTEM_INCLUDE_D3D9_H)
+
+if(Direct3D9_FIND_QUIETLY)
+  if(DEFINED _OLD_CMAKE_REQUIRED_QUIET)
+    set(CMAKE_REQUIRED_QUIET ${_OLD_CMAKE_REQUIRED_QUIET})
+    unset(_OLD_CMAKE_REQUIRED_QUIET)
+  else()
+    unset(CMAKE_REQUIRED_QUIET)
+  endif()
+endif()
+
+if(SYSTEM_INCLUDE_D3D9_H
+    AND NOT Direct3D9_FIND_REQUIRED_d3dx9
+    AND NOT Direct3D9_FIND_REQUIRED_dxerr)
+  # It's available as #include <d3d9.h> - easy enough.  We'll use "." as a way
+  # of saying "We found it, but please erase this variable later."
+  set(DIRECT3D9_INCLUDE_DIR ".")
+
+  # Since d3d9.h is on the search path, we can pretty much assume d3d9.lib is
+  # as well.
+  set(DIRECT3D9_LIBRARY "d3d9.lib")
+
+  # And dxguid.lib, why not
+  set(DIRECT3D9_dxguid_LIBRARY "dxguid.lib")
+
+else()
+  # We could not find it easily - maybe it's installed separately as part of
+  # the DirectX SDK?
+
+  find_path(DIRECT3D9_INCLUDE_DIR
+    NAMES d3d9.h
+    PATHS "$ENV{DXSDK_DIR}/Include")
+
+  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+    set(dx_lib_path "$ENV{DXSDK_DIR}/Lib/x64/")
+  else()
+    set(dx_lib_path "$ENV{DXSDK_DIR}/Lib/x86/")
+  endif()
+
+  find_library(DIRECT3D9_LIBRARY d3d9 "${dx_lib_path}" NO_DEFAULT_PATH)
+
+  find_library(DIRECT3D9_d3dx9_LIBRARY d3dx9 "${dx_lib_path}" NO_DEFAULT_PATH)
+  find_library(DIRECT3D9_dxerr_LIBRARY dxerr "${dx_lib_path}" NO_DEFAULT_PATH)
+  find_library(DIRECT3D9_dxguid_LIBRARY dxguid "${dx_lib_path}" NO_DEFAULT_PATH)
+
+  unset(dx_lib_path)
+endif()
+
+mark_as_advanced(DIRECT3D9_INCLUDE_DIR DIRECT3D9_LIBRARY)
+set(DIRECT3D9_LIBRARIES "${DIRECT3D9_LIBRARY}")
+
+foreach(_component d3dx9 dxerr dxguid)
+  if(DIRECT3D9_${_component}_LIBRARY)
+    set(Direct3D9_${_component}_FOUND ON)
+    list(FIND Direct3D9_FIND_COMPONENTS "${_component}" _index)
+    if(${_index} GREATER -1)
+      list(APPEND DIRECT3D9_LIBRARIES "${DIRECT3D9_${_component}_LIBRARY}")
+    endif()
+    unset(_index)
+  endif()
+endforeach(_component)
+unset(_component)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Direct3D9 HANDLE_COMPONENTS
+  REQUIRED_VARS DIRECT3D9_INCLUDE_DIR DIRECT3D9_LIBRARY DIRECT3D9_LIBRARIES)
+
+# See above - if we found the include as part of the system path, we don't want
+# to actually modify the include search path, but we need a non-empty string to
+# satisfy find_package_handle_standard_args()
+if(DIRECT3D9_INCLUDE_DIR STREQUAL ".")
+  set(DIRECT3D9_INCLUDE_DIR "")
+endif()

+ 21 - 0
cmake/modules/FindEGL.cmake

@@ -0,0 +1,21 @@
+# Filename: FindEGL.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(EGL [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   EGL_FOUND        - system has EGL
+#   EGL_INCLUDE_DIR  - the include directory containing EGL/egl.h
+#   EGL_LIBRARY      - the library to link against for EGL
+#
+
+find_path(EGL_INCLUDE_DIR "EGL/egl.h")
+
+find_library(EGL_LIBRARY
+  NAMES "EGL")
+
+mark_as_advanced(EGL_INCLUDE_DIR EGL_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(EGL DEFAULT_MSG EGL_INCLUDE_DIR EGL_LIBRARY)

+ 25 - 0
cmake/modules/FindEigen3.cmake

@@ -0,0 +1,25 @@
+# Filename: FindEigen3.cmake
+# Authors: kestred (13 Dec, 2013)
+#
+# Usage:
+#   find_package(Eigen3 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   EIGEN_FOUND        - system has any version of Eigen
+#   EIGEN3_FOUND       - system has Eigen3
+#   EIGEN3_INCLUDE_DIR - the Eigen3 include directory
+#
+
+find_path(EIGEN3_INCLUDE_DIR
+  NAMES signature_of_eigen3_matrix_library
+  PATH_SUFFIXES eigen3 eigen)
+
+mark_as_advanced(EIGEN3_INCLUDE_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR)
+
+if(EIGEN3_FOUND)
+  set(EIGEN_FOUND TRUE)
+  set(EIGEN_INCLUDE_DIR ${EIGEN3_INCLUDE_DIR})
+endif()

+ 88 - 0
cmake/modules/FindFCollada.cmake

@@ -0,0 +1,88 @@
+# Filename: FindFCollada.cmake
+# Author: CFSworks (17 Mar, 2019)
+#
+# Usage:
+#   find_package(FCollada [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FCOLLADA_FOUND       - system has FCollada
+#   FCOLLADA_INCLUDE_DIR - the FCollada include directory
+#
+#   FCOLLADA_RELEASE_LIBRARY - the filepath of the FCollada release library
+#   FCOLLADA_DEBUG_LIBRARY   - the filepath of the FCollada debug library
+#
+#   FCollada::FCollada - The recommended FCollada library to link against
+#
+
+# Find the FCollada include files
+find_path(FCOLLADA_INCLUDE_DIR "FCollada.h" PATH_SUFFIXES "FCollada")
+
+# Find the library built for release
+find_library(FCOLLADA_RELEASE_LIBRARY
+  NAMES "FCollada" "libFCollada"
+  "FColladaS" "libFColladaS"
+  "FColladaU" "libFColladaU"
+  "FColladaSU" "libFColladaSU"
+)
+
+# Find the library built for debug
+find_library(FCOLLADA_DEBUG_LIBRARY
+  NAMES "FColladaD" "libFColladaD"
+  "FColladaSD" "libFColladaSD"
+  "FColladaUD" "libFColladaUD"
+  "FColladaSUD" "libFColladaSUD"
+)
+
+mark_as_advanced(FCOLLADA_INCLUDE_DIR)
+mark_as_advanced(FCOLLADA_RELEASE_LIBRARY)
+mark_as_advanced(FCOLLADA_DEBUG_LIBRARY)
+
+set(_defines)
+if(FCOLLADA_RELEASE_LIBRARY MATCHES "FCollada[^/]*U" OR
+    FCOLLADA_DEBUG_LIBRARY MATCHES "FCollada[^/]*U")
+  list(APPEND _defines "UNICODE")
+endif()
+if(NOT MSVC AND
+    NOT FCOLLADA_RELEASE_LIBRARY MATCHES "FCollada[^/]*S" AND
+    NOT FCOLLADA_DEBUG_LIBRARY MATCHES "FCollada[^/]*S")
+  list(APPEND _defines "FCOLLADA_DLL")
+endif()
+
+# Identify the configs which we have available
+set(_configs)
+if(FCOLLADA_INCLUDE_DIR)
+  if(FCOLLADA_RELEASE_LIBRARY)
+    list(APPEND _configs RELEASE)
+  endif()
+  if(FCOLLADA_DEBUG_LIBRARY)
+    list(APPEND _configs DEBUG)
+  endif()
+
+  if(_configs)
+    set(_HAS_FCOLLADA_LIBRARY ON)
+    add_library(FCollada::FCollada UNKNOWN IMPORTED GLOBAL)
+
+    set_target_properties(FCollada::FCollada PROPERTIES
+      INTERFACE_COMPILE_DEFINITIONS "${_defines}"
+      INTERFACE_INCLUDE_DIRECTORIES "${FCOLLADA_INCLUDE_DIR}")
+
+  endif()
+
+endif()
+
+foreach(_config ${_configs})
+  set_property(TARGET FCollada::FCollada
+    APPEND PROPERTY IMPORTED_CONFIGURATIONS "${_config}")
+
+  set_target_properties(FCollada::FCollada PROPERTIES
+    IMPORTED_LOCATION_${_config} "${FCOLLADA_${_config}_LIBRARY}")
+
+endforeach(_config)
+unset(_config)
+unset(_configs)
+unset(_defines)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FCollada DEFAULT_MSG FCOLLADA_INCLUDE_DIR _HAS_FCOLLADA_LIBRARY)
+
+unset(_HAS_FCOLLADA_LIBRARY)

+ 116 - 0
cmake/modules/FindFFMPEG.cmake

@@ -0,0 +1,116 @@
+# Filename: FindFFMPEG.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(FFMPEG [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FFMPEG_FOUND       - system has ffmpeg
+#   FFMPEG_INCLUDE_DIR - the ffmpeg include directory
+#   FFMPEG_LIBRARIES   - the path to the library binary
+#
+#   FFMPEG_LIBAVCODEC  - the path to the libavcodec library binary
+#   FFMPEG_LIBAVFORMAT - the path to the libavformat library binary
+#   FFMPEG_LIBAVUTIL   - the path to the libavutil library binary
+#
+
+# Find the libffmpeg include files
+find_path(FFMPEG_INCLUDE_DIR
+  NAMES "libavcodec/avcodec.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libavcodec library
+find_library(FFMPEG_LIBAVCODEC
+  NAMES "avcodec"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libavformat library
+find_library(FFMPEG_LIBAVFORMAT
+  NAMES "avformat"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libavutil library
+find_library(FFMPEG_LIBAVUTIL
+  NAMES "avutil"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(FFMPEG_INCLUDE_DIR)
+mark_as_advanced(FFMPEG_LIBAVCODEC)
+mark_as_advanced(FFMPEG_LIBAVFORMAT)
+mark_as_advanced(FFMPEG_LIBAVUTIL)
+
+# Translate library into library directory
+if(FFMPEG_LIBAVCODEC)
+  unset(FFMPEG_LIBRARY_DIR CACHE)
+  get_filename_component(FFMPEG_LIBRARY_DIR "${FFMPEG_LIBAVCODEC}" PATH)
+  set(FFMPEG_LIBRARY_DIR "${FFMPEG_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+set(FFMPEG_LIBRARIES)
+if(FFMPEG_LIBAVCODEC)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVCODEC}")
+endif()
+if(FFMPEG_LIBAVFORMAT)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVFORMAT}")
+endif()
+if(FFMPEG_LIBAVUTIL)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVUTIL}")
+endif()
+
+if(APPLE)
+  # When statically built for Apple, FFMPEG may have dependencies on these
+  # additional frameworks and libraries.
+
+  find_library(APPLE_COREVIDEO_LIBRARY CoreVideo)
+  if(APPLE_COREVIDEO_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_COREVIDEO_LIBRARY}")
+  endif()
+
+  find_library(APPLE_VDA_LIBRARY VideoDecodeAcceleration)
+  if(APPLE_VDA_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_VDA_LIBRARY}")
+  endif()
+
+  find_library(APPLE_ICONV_LIBRARY iconv)
+  if(APPLE_ICONV_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_ICONV_LIBRARY}")
+  endif()
+
+  find_library(APPLE_BZ2_LIBRARY bz2)
+  if(APPLE_BZ2_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_BZ2_LIBRARY}")
+  endif()
+endif()
+
+mark_as_advanced(FFMPEG_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFMPEG DEFAULT_MSG FFMPEG_LIBRARIES FFMPEG_LIBAVCODEC FFMPEG_LIBAVFORMAT FFMPEG_LIBAVUTIL FFMPEG_INCLUDE_DIR FFMPEG_LIBRARY_DIR)

+ 107 - 0
cmake/modules/FindFFTW3.cmake

@@ -0,0 +1,107 @@
+# Filename: FindFFTW.cmake
+# Author: Unknown (???), kestred (29 Nov, 2013)
+#
+# Usage:
+#   find_package(FFTW [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FFTW3_FOUND       - true if fftw is found on the system
+#   FFTW3_INCLUDE_DIR - the fftw include directory
+#   FFTW3_LIBRARY_DIR - the fftw library directory
+#   FFTW3_LIBRARY     - the path to the library binary
+#
+# The following variables will be checked by the function
+#   FFTW3_ROOT - if set, the libraries are exclusively searched under this path
+#
+
+# Check if we can use PkgConfig
+find_package(PkgConfig QUIET)
+
+#Determine from PKG
+if(PKG_CONFIG_FOUND AND NOT FFTW3_ROOT)
+  pkg_check_modules(PKG_FFTW QUIET "fftw3")
+endif()
+
+if(FFTW3_ROOT)
+  # Try to find headers under root
+  find_path(FFTW3_INCLUDE_DIR
+    NAMES "fftw3.h"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "include"
+    NO_DEFAULT_PATH
+  )
+
+  # Try to find library under root
+  find_library(FFTW3_LIBRARY
+    NAMES "fftw3"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+
+  find_library(FFTW3_FFTWF_LIBRARY
+    NAMES "fftw3f"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+
+  find_library(FFTW3_FFTWL_LIBRARY
+    NAMES "fftw3l"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+else()
+  # Find headers the normal way
+  find_path(FFTW3_INCLUDE_DIR
+    NAMES "fftw3.h"
+    PATHS ${PKG_FFTW3_INCLUDE_DIRS}
+          ${INCLUDE_INSTALL_DIR}
+          "/usr/include"
+          "/usr/local/include"
+    PATH_SUFFIXES "" "fftw"
+  )
+
+  # Find library the normal way
+  find_library(FFTW3_LIBRARY
+    NAMES "fftw3"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+
+  find_library(FFTW3_FFTWF_LIBRARY
+    NAMES "fftw3f"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+
+
+  find_library(FFTW3_FFTWL_LIBRARY
+    NAMES "fftw3l"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+endif()
+
+# Get the directory conaining FFTW3_LIBRARY
+get_filename_component(FFTW3_LIBRARY_DIR "${FFTW3_LIBRARY}" PATH)
+set(FFTW3_LIBRARY_DIR "${FFTW3_LIBRARY_DIR}" CACHE PATH "The path to fftw's library directory.") # Library path
+
+mark_as_advanced(FFTW3_INCLUDE_DIR)
+mark_as_advanced(FFTW3_LIBRARY_DIR)
+mark_as_advanced(FFTW3_LIBRARY)
+mark_as_advanced(FFTW3_FFTWF_LIBRARY)
+mark_as_advanced(FFTW3_FFTWL_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFTW3 DEFAULT_MSG FFTW3_LIBRARY FFTW3_INCLUDE_DIR FFTW3_LIBRARY_DIR)

+ 83 - 0
cmake/modules/FindFMODEx.cmake

@@ -0,0 +1,83 @@
+# Filename: FindFMODEx.cmake
+# Author: kestred (8 Dec, 2013)
+#
+# Usage:
+#   find_pcackage(FMODEx [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FMODEX_FOUND       - system has FMOD Ex
+#   FMODEX_INCLUDE_DIR - the FMOD Ex include directory
+#   FMODEX_LIBRARY_DIR - the FMOD Ex library directory
+#   FMODEX_LIBRARY     - the path to the library binary
+#
+#   FMODEX_32_LIBRARY - the filepath of the FMOD Ex SDK 32-bit library
+#   FMOXEX_64_LIBRARY - the filepath of the FMOD Ex SDK 64-bit library
+#
+
+# Find the include directory
+find_path(FMODEX_INCLUDE_DIR
+  NAMES "fmod.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+        "/opt/fmodex/include"
+        "/opt/fmodex/api/inc"
+        "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API Win32/api/inc"
+  PATH_SUFFIXES "" "fmodex/fmod" "fmodex/fmod3" "fmod" "fmod3"
+  DOC "The path to FMOD Ex's include directory."
+)
+
+# Find the 32-bit library
+find_library(FMODEX_32_LIBRARY
+  NAMES "fmodex_vc" "fmodex_bc" "fmodex" "fmodexL" "libfmodex" "libfmodexL" "fmodex_vc" "fmodexL_vc"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/X11R6"
+        "/usr/local/X11R6"
+        "/sw"
+        "/opt"
+        "/opt/local"
+        "/opt/csw"
+        "/opt/fmodex"
+        "/opt/fmodex/api"
+        "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API Win32/api/lib"
+  PATH_SUFFIXES "" "lib" "lib32"
+)
+
+# Find the 64-bit library
+find_library(FMODEX_64_LIBRARY
+  NAMES "fmodex64" "libfmodex64" "fmodexL64" "libfmodexL64" "fmodex64_vc" "fmodexL64_vc"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/X11R6"
+        "/usr/local/X11R6"
+        "/sw"
+        "/opt"
+        "/opt/local"
+        "/opt/csw"
+        "/opt/fmodex"
+        "/opt/fmodex/api"
+        "/usr/freeware"
+  PATH_SUFFIXES "" "lib" "lib64"
+)
+
+if(FMODEX_32_LIBRARY)
+  set(FMODEX_LIBRARY ${FMODEX_32_LIBRARY} CACHE FILEPATH "The filepath to FMOD Ex's library binary.")
+elseif(FMODEX_64_LIBRARY)
+  set(FMODEX_LIBRARY ${FMODEX_64_LIBRARY} CACHE FILEPATH "The filepath to FMOD Ex's library binary.")
+endif()
+
+get_filename_component(FMODEX_LIBRARY_DIR "${FMODEX_LIBRARY}" PATH)
+set(FMODEX_LIBRARY_DIR "${FMODEX_LIBRARY_DIR}" CACHE PATH "The path to FMOD Ex's library directory.")
+
+mark_as_advanced(FMODEX_INCLUDE_DIR)
+mark_as_advanced(FMODEX_LIBRARY_DIR)
+mark_as_advanced(FMODEX_LIBRARY)
+mark_as_advanced(FMODEX_32_LIBRARY)
+mark_as_advanced(FMODEX_64_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FMODEx DEFAULT_MSG FMODEX_LIBRARY FMODEX_INCLUDE_DIR FMODEX_LIBRARY_DIR)

+ 23 - 0
cmake/modules/FindHarfBuzz.cmake

@@ -0,0 +1,23 @@
+# Filename: FindHarfBuzz.cmake
+# Authors: CFSworks (2 Nov, 2018)
+#
+# Usage:
+#   find_package(HarfBuzz [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   HARFBUZZ_FOUND       - system has HarfBuzz
+#   HARFBUZZ_INCLUDE_DIR - the include directory containing hb.h
+#   HARFBUZZ_LIBRARY     - the path to the HarfBuzz library
+#
+
+find_path(HARFBUZZ_INCLUDE_DIR
+  NAMES "hb.h"
+  PATH_SUFFIXES "harfbuzz")
+
+find_library(HARFBUZZ_LIBRARY
+  NAMES "harfbuzz")
+
+mark_as_advanced(HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(HarfBuzz DEFAULT_MSG HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY)

+ 78 - 0
cmake/modules/FindLibSquish.cmake

@@ -0,0 +1,78 @@
+# Filename: FindLibSquish.cmake
+# Author: kestred (7 Dec, 2013)
+#
+# Usage:
+#   find_package(LibSquish [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   LIBSQUISH_FOUND       - system has libsquish
+#   LIBSQUISH_INCLUDE_DIR - the libsquish include directory
+#   LIBSQUISH_LIBRARY_DIR - the libsquish library directory
+#   LIBSQUISH_LIBRARY     - the path to the library binary
+#
+#   LIBSQUISH_RELEASE_LIBRARY - the filepath of the libsquish release library
+#   LIBSQUISH_DEBUG_LIBRARY   - the filepath of the libsquish debug library
+#
+
+# Find the libsquish include files
+find_path(LIBSQUISH_INCLUDE_DIR
+  NAMES "squish.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "" "cppunit"
+)
+
+# Find the libsquish library built for release
+find_library(LIBSQUISH_RELEASE_LIBRARY
+  NAMES "squish" "libsquish"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libsquish library built for debug
+find_library(LIBSQUISH_DEBUG_LIBRARY
+  NAMES "squishd" "libsquishd"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+
+mark_as_advanced(LIBSQUISH_INCLUDE_DIR)
+mark_as_advanced(LIBSQUISH_RELEASE_LIBRARY)
+mark_as_advanced(LIBSQUISH_DEBUG_LIBRARY)
+
+# Choose library
+if(CMAKE_BUILD_TYPE MATCHES "Debug" AND LIBSQUISH_DEBUG_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_DEBUG_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+elseif(LIBSQUISH_RELEASE_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_RELEASE_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+elseif(LIBSQUISH_DEBUG_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_DEBUG_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+endif()
+
+# Translate library into library directory
+if(LIBSQUISH_LIBRARY)
+	unset(LIBSQUISH_LIBRARY_DIR CACHE)
+	get_filename_component(LIBSQUISH_LIBRARY_DIR "${LIBSQUISH_LIBRARY}" PATH)
+	set(LIBSQUISH_LIBRARY_DIR "${LIBSQUISH_LIBRARY_DIR}" CACHE PATH "The path to libsquish's library directory.") # Library path
+endif()
+
+mark_as_advanced(LIBSQUISH_LIBRARY)
+mark_as_advanced(LIBSQUISH_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(LibSquish DEFAULT_MSG LIBSQUISH_LIBRARY LIBSQUISH_INCLUDE_DIR LIBSQUISH_LIBRARY_DIR)

+ 157 - 0
cmake/modules/FindODE.cmake

@@ -0,0 +1,157 @@
+# Filename: FindODE.cmake
+# Author: CFSworks (7 Feb, 2014)
+#
+# Usage:
+#   find_package(ODE [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ODE_FOUND       - system has ode
+#   ODE_INCLUDE_DIR - the ode include directory
+#
+#   ODE_RELEASE_LIBRARY - the filepath of the ode release library
+#   ODE_DEBUG_LIBRARY   - the filepath of the ode debug library
+#
+#   ODE_SINGLE_DEBUG_LIBRARY   - the filepath of the single-precision ode debug library
+#   ODE_SINGLE_RELEASE_LIBRARY   - the filepath of the single-precision ode release library
+#   ODE_DOUBLE_DEBUG_LIBRARY   - the filepath of the double-precision ode debug library
+#   ODE_DOUBLE_RELEASE_LIBRARY   - the filepath of the double-precision ode release library
+#
+#   ODE::ODE        - The recommended ODE library to link against
+#   ODE::ODE_single - If available, this links against single-precision ODE
+#   ODE::ODE_double - If available, this links against double-precision ODE
+#
+
+# Find the libode include files
+find_path(ODE_INCLUDE_DIR "ode/ode.h")
+
+# Find the libode library built for release
+find_library(ODE_RELEASE_LIBRARY
+  NAMES "ode" "libode")
+
+# Find the libode library built for debug
+find_library(ODE_DEBUG_LIBRARY
+  NAMES "oded" "liboded")
+
+# Find the single-precision library built for release
+find_library(ODE_SINGLE_RELEASE_LIBRARY
+  NAMES "ode_single" "libode_single")
+
+# Find the single-precision library built for debug
+find_library(ODE_SINGLE_DEBUG_LIBRARY
+  NAMES "ode_singled" "libode_singled")
+
+# Find the double-precision library built for release
+find_library(ODE_DOUBLE_RELEASE_LIBRARY
+  NAMES "ode_double" "libode_double")
+
+# Find the double-precision library built for debug
+find_library(ODE_DOUBLE_DEBUG_LIBRARY
+  NAMES "ode_doubled" "libode_doubled")
+
+# Find libccd, which ODE sometimes links against, so we want to let the linker
+# know about it if it's present.
+find_library(ODE_LIBCCD_LIBRARY
+  NAMES "ccd" "libccd")
+
+mark_as_advanced(ODE_INCLUDE_DIR)
+mark_as_advanced(ODE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_SINGLE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_SINGLE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_DOUBLE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_DOUBLE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_LIBCCD_LIBRARY)
+
+# Define targets for both precisions (and unspecified)
+foreach(_precision _single _double "")
+  string(TOUPPER "${_precision}" _PRECISION)
+
+  if(EXISTS "${ODE${_PRECISION}_RELEASE_LIBRARY}" OR
+      EXISTS "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+    if(NOT TARGET ODE::ODE${_precision})
+      add_library(ODE::ODE${_precision} UNKNOWN IMPORTED GLOBAL)
+
+      set_target_properties(ODE::ODE${_precision} PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "${ODE_INCLUDE_DIR}")
+
+      if(ODE_LIBCCD_LIBRARY)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          INTERFACE_LINK_LIBRARIES "${ODE_LIBCCD_LIBRARY}")
+      endif()
+
+      if(EXISTS "${ODE${_PRECISION}_RELEASE_LIBRARY}")
+        set_property(TARGET ODE::ODE${_precision} APPEND PROPERTY
+          IMPORTED_CONFIGURATIONS RELEASE)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
+          IMPORTED_LOCATION_RELEASE "${ODE${_PRECISION}_RELEASE_LIBRARY}")
+      endif()
+
+      if(EXISTS "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+        set_property(TARGET ODE::ODE${_precision} APPEND PROPERTY
+          IMPORTED_CONFIGURATIONS DEBUG)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
+          IMPORTED_LOCATION_DEBUG "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+      endif()
+
+      # If this has a precision, we should be sure to define
+      # dIDESINGLE/dIDEDOUBLE to keep it consistent
+      if(_precision)
+        string(REPLACE "_" "dIDE" _precision_symbol "${_PRECISION}")
+
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          INTERFACE_COMPILE_DEFINITIONS "${_precision_symbol}")
+
+        unset(_precision_symbol)
+      endif()
+
+    endif()
+  endif()
+endforeach(_precision)
+unset(_precision)
+unset(_PRECISION)
+
+# OKAY.  If everything went well, we have ODE::ODE_single and/or
+# ODE::ODE_double.  We might even have an ODE::ODE, but if not, we need to
+# alias one of the other two to it.
+if(NOT TARGET ODE::ODE)
+  if(TARGET ODE::ODE_single)
+    set(_copy_from "ODE::ODE_single")
+  elseif(TARGET ODE::ODE_double)
+    set(_copy_from "ODE::ODE_double")
+  endif()
+
+  if(DEFINED _copy_from)
+    add_library(ODE::ODE UNKNOWN IMPORTED GLOBAL)
+
+    foreach(_prop
+        INTERFACE_INCLUDE_DIRECTORIES
+        INTERFACE_COMPILE_DEFINITIONS
+        INTERFACE_LINK_LIBRARIES
+        IMPORTED_CONFIGURATIONS
+        IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE
+        IMPORTED_LOCATION_RELEASE
+        IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG
+        IMPORTED_LOCATION_DEBUG)
+
+      get_target_property(_value "${_copy_from}" "${_prop}")
+      if(_value)
+        set_target_properties(ODE::ODE PROPERTIES "${_prop}" "${_value}")
+      endif()
+      unset(_value)
+    endforeach(_prop)
+    unset(_prop)
+  endif()
+
+  unset(_copy_from)
+endif()
+
+if(TARGET ODE::ODE)
+  set(_HAS_ODE_LIBRARY ON)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(ODE DEFAULT_MSG ODE_INCLUDE_DIR _HAS_ODE_LIBRARY)
+
+unset(_HAS_ODE_LIBRARY)

+ 20 - 0
cmake/modules/FindOgg.cmake

@@ -0,0 +1,20 @@
+# Filename: FindOgg.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(Ogg [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OGG_FOUND       - system has Ogg
+#   OGG_INCLUDE_DIR - the include directory containing ogg/
+#   OGG_LIBRARY     - the path to the ogg library
+#
+
+find_path(OGG_INCLUDE_DIR NAMES "ogg/ogg.h")
+
+find_library(OGG_LIBRARY NAMES "ogg" "libogg_static")
+
+mark_as_advanced(OGG_INCLUDE_DIR OGG_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Ogg DEFAULT_MSG OGG_INCLUDE_DIR OGG_LIBRARY)

+ 92 - 0
cmake/modules/FindOpenCV.cmake

@@ -0,0 +1,92 @@
+# Filename: FindOpenCV.cmake
+# Authors: CFSworks (3 Nov, 2018)
+#
+# Usage:
+#   find_package(OpenCV [REQUIRED] [QUIET])
+#
+# This supports the following components:
+#   calib3d
+#   contrib
+#   core
+#   features2d
+#   flann
+#   gpu
+#   highgui
+#   imgproc
+#   legacy
+#   ml
+#   nonfree
+#   objdetect
+#   photo
+#   stitching
+#   superres
+#   video
+#   videoio
+#   videostab
+#
+# Once done this will define:
+#   OPENCV_FOUND        - system has OpenCV
+#   OpenCV_INCLUDE_DIRS - the include dir(s) containing OpenCV header files
+#   OpenCV_comp_LIBRARY - the path to the OpenCV library for the particular
+#                         component
+#   OpenCV_LIBS         - the paths to the OpenCV libraries for the requested
+#                         component(s)
+#   OpenCV_VERSION_MAJOR- a "best guess" of the major version (X.x)
+#   OpenCV_VERSION_MINOR- a "best guess" of the minor version (x.X)
+#
+
+set(OpenCV_INCLUDE_DIRS)
+
+find_path(OpenCV_V1_INCLUDE_DIR
+  NAMES "cv.h"
+  PATH_SUFFIXES "opencv")
+mark_as_advanced(OpenCV_V1_INCLUDE_DIR)
+if(OpenCV_V1_INCLUDE_DIR)
+  list(APPEND OpenCV_INCLUDE_DIRS "${OpenCV_V1_INCLUDE_DIR}")
+
+  # This is a wild guess:
+  set(OpenCV_VERSION_MAJOR 1)
+  set(OpenCV_VERSION_MINOR 0)
+endif()
+
+find_path(OpenCV_V2_INCLUDE_DIR "opencv2/core/version.hpp")
+mark_as_advanced(OpenCV_V2_INCLUDE_DIR)
+if(OpenCV_V2_INCLUDE_DIR)
+  list(APPEND OpenCV_INCLUDE_DIRS "${OpenCV_V2_INCLUDE_DIR}")
+
+  file(STRINGS "${OpenCV_V2_INCLUDE_DIR}/opencv2/core/version.hpp"
+    _version_major REGEX "#define CV_VERSION_EPOCH")
+  file(STRINGS "${OpenCV_V2_INCLUDE_DIR}/opencv2/core/version.hpp"
+    _version_minor REGEX "#define CV_VERSION_MAJOR")
+
+  string(REGEX REPLACE "[^0-9]" "" OpenCV_VERSION_MAJOR "${_version_major}")
+  string(REGEX REPLACE "[^0-9]" "" OpenCV_VERSION_MINOR "${_version_minor}")
+  unset(_version_major)
+  unset(_version_minor)
+endif()
+
+set(OpenCV_LIBS)
+foreach(_component calib3d contrib core features2d flann gpu highgui imgproc
+                  legacy ml nonfree objdetect photo stitching superres video
+                  videoio videostab)
+
+  list(FIND OpenCV_FIND_COMPONENTS "${_component}" _index)
+  if(_index GREATER -1 OR _component STREQUAL "core")
+    if(NOT OpenCV_${_component}_LIBRARY)
+      find_library(OpenCV_${_component}_LIBRARY
+        NAMES "opencv_${_component}")
+    endif()
+
+    if(OpenCV_${_component}_LIBRARY)
+      list(APPEND OpenCV_LIBS "${OpenCV_${_component}_LIBRARY}")
+      set(OpenCV_${_component}_FOUND ON)
+    endif()
+  endif()
+  unset(_index)
+endforeach(_component)
+unset(_component)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenCV HANDLE_COMPONENTS
+  REQUIRED_VARS OpenCV_INCLUDE_DIRS OpenCV_LIBS
+  OpenCV_VERSION_MAJOR OpenCV_VERSION_MINOR)

+ 88 - 0
cmake/modules/FindOpenEXR.cmake

@@ -0,0 +1,88 @@
+# Filename: FindOpenEXR.cmake
+# Authors: CFSworks (5 Nov, 2018)
+#
+# Usage:
+#   find_package(OpenEXR [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENEXR_FOUND       - system has OpenEXR
+#   OPENEXR_INCLUDE_DIR - the include directory containing OpenEXR header files
+#   OPENEXR_LIBRARIES   - the path to the OpenEXR libraries
+#
+
+find_path(OPENEXR_INCLUDE_DIR
+  "ImfVersion.h"
+  PATH_SUFFIXES "OpenEXR")
+
+mark_as_advanced(OPENEXR_INCLUDE_DIR)
+
+find_library(OPENEXR_imf_LIBRARY
+  NAMES "IlmImf")
+
+if(OPENEXR_imf_LIBRARY)
+  get_filename_component(_imf_dir "${OPENEXR_imf_LIBRARY}" DIRECTORY)
+  find_library(OPENEXR_imfutil_LIBRARY
+    NAMES "IlmImfUtil"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_ilmthread_LIBRARY
+    NAMES "IlmThread"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_iex_LIBRARY
+    NAMES "Iex"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_iexmath_LIBRARY
+    NAMES "IexMath"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_imath_LIBRARY
+    NAMES "Imath"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_half_LIBRARY
+    NAMES "Half"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  unset(_imf_dir)
+endif()
+
+mark_as_advanced(
+  OPENEXR_imf_LIBRARY
+  OPENEXR_imfutil_LIBRARY
+  OPENEXR_ilmthread_LIBRARY
+  OPENEXR_iex_LIBRARY
+  OPENEXR_iexmath_LIBRARY
+  OPENEXR_imath_LIBRARY
+  OPENEXR_half_LIBRARY
+)
+
+set(OPENEXR_LIBRARIES
+  ${OPENEXR_imf_LIBRARY}
+  ${OPENEXR_imfutil_LIBRARY}
+  ${OPENEXR_ilmthread_LIBRARY}
+  ${OPENEXR_iex_LIBRARY}
+  ${OPENEXR_iexmath_LIBRARY}
+  ${OPENEXR_imath_LIBRARY}
+  ${OPENEXR_half_LIBRARY}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenEXR DEFAULT_MSG
+  OPENEXR_INCLUDE_DIR OPENEXR_LIBRARIES
+
+  OPENEXR_imf_LIBRARY
+  OPENEXR_imfutil_LIBRARY
+  OPENEXR_ilmthread_LIBRARY
+  OPENEXR_iex_LIBRARY
+  OPENEXR_iexmath_LIBRARY
+  OPENEXR_imath_LIBRARY
+  OPENEXR_half_LIBRARY
+)

+ 21 - 0
cmake/modules/FindOpenGLES1.cmake

@@ -0,0 +1,21 @@
+# Filename: FindOpenGLES1.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(OpenGLES1 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENGLES1_FOUND       - system has OpenGL ES 1.x
+#   OPENGLES1_INCLUDE_DIR - the include directory containing GLES/gl.h
+#   OPENGLES1_LIBRARY     - the library to link against for OpenGL ES 1.x
+#
+
+find_path(OPENGLES1_INCLUDE_DIR "GLES/gl.h")
+
+find_library(OPENGLES1_LIBRARY
+  NAMES "GLESv1" "GLESv1_CM" "GLES_CM")
+
+mark_as_advanced(OPENGLES1_INCLUDE_DIR OPENGLES1_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenGLES1 DEFAULT_MSG OPENGLES1_INCLUDE_DIR OPENGLES1_LIBRARY)

+ 21 - 0
cmake/modules/FindOpenGLES2.cmake

@@ -0,0 +1,21 @@
+# Filename: FindOpenGLES2.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(OpenGLES2 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENGLES2_FOUND        - system has OpenGL ES 2.x
+#   OPENGLES2_INCLUDE_DIR  - the include directory containing GLES2/gl2.h
+#   OPENGLES2_LIBRARY      - the library to link against for OpenGL ES 2.x
+#
+
+find_path(OPENGLES2_INCLUDE_DIR "GLES2/gl2.h")
+
+find_library(OPENGLES2_LIBRARY
+  NAMES "GLESv2")
+
+mark_as_advanced(OPENGLES2_INCLUDE_DIR OPENGLES2_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG OPENGLES2_INCLUDE_DIR OPENGLES2_LIBRARY)

+ 34 - 0
cmake/modules/FindOpusFile.cmake

@@ -0,0 +1,34 @@
+# Filename: FindOpusFile.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(OpusFile [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPUSFILE_FOUND        - system has Ogg and opusfile
+#   OPUSFILE_INCLUDE_DIRS - the include directory/ies containing opus/ and ogg/
+#   OPUSFILE_LIBRARIES    - the paths to the opus and opusfile libraries
+#
+
+# Find Ogg
+find_package(Ogg QUIET)
+
+# Find Opus
+find_path(OPUS_INCLUDE_DIR NAMES "opus/opusfile.h")
+
+find_library(OPUS_opus_LIBRARY NAMES "opus")
+find_library(OPUS_opusfile_LIBRARY NAMES "opusfile")
+
+mark_as_advanced(OPUS_INCLUDE_DIR OPUS_opus_LIBRARY OPUS_opusfile_LIBRARY)
+
+# Define output variables
+set(OPUSFILE_INCLUDE_DIRS ${OPUS_INCLUDE_DIR} "${OPUS_INCLUDE_DIR}/opus")
+if(NOT OGG_INCLUDE_DIR STREQUAL OPUS_INCLUDE_DIR)
+  list(APPEND OPUSFILE_INCLUDE_DIRS ${OGG_INCLUDE_DIR})
+endif()
+set(OPUSFILE_LIBRARIES ${OPUS_opusfile_LIBRARY} ${OPUS_opus_LIBRARY} ${OGG_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpusFile DEFAULT_MSG
+  Ogg_FOUND
+  OPUS_INCLUDE_DIR OPUS_opus_LIBRARY OPUS_opusfile_LIBRARY)

+ 50 - 0
cmake/modules/FindSWResample.cmake

@@ -0,0 +1,50 @@
+# Filename: FindSWResample.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(SWResample [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   SWRESAMPLE_FOUND       - system has ffmpeg's libswresample
+#   SWRESAMPLE_INCLUDE_DIR - the libswresample include directory
+#   SWRESAMPLE_LIBRARY     - the path to the library binary
+#
+
+# Find the libswresample include files
+find_path(SWRESAMPLE_INCLUDE_DIR
+  NAMES "libswresample/swresample.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libswresample library
+find_library(SWRESAMPLE_LIBRARY
+  NAMES "swresample"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(SWRESAMPLE_INCLUDE_DIR)
+mark_as_advanced(SWRESAMPLE_LIBRARY)
+
+# Translate library into library directory
+if(SWRESAMPLE_LIBRARY)
+  unset(SWRESAMPLE_LIBRARY_DIR CACHE)
+  get_filename_component(SWRESAMPLE_LIBRARY_DIR "${SWRESAMPLE_LIBRARY}" PATH)
+  set(SWRESAMPLE_LIBRARY_DIR "${SWRESAMPLE_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+mark_as_advanced(SWRESAMPLE_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SWResample DEFAULT_MSG SWRESAMPLE_LIBRARY SWRESAMPLE_INCLUDE_DIR SWRESAMPLE_LIBRARY_DIR)

+ 50 - 0
cmake/modules/FindSWScale.cmake

@@ -0,0 +1,50 @@
+# Filename: FindSWScale.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(SWScale [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   SWSCALE_FOUND       - system has ffmpeg's libswscale
+#   SWSCALE_INCLUDE_DIR - the libswscale include directory
+#   SWSCALE_LIBRARY     - the path to the library binary
+#
+
+# Find the libswscale include files
+find_path(SWSCALE_INCLUDE_DIR
+  NAMES "libswscale/swscale.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libswscale library
+find_library(SWSCALE_LIBRARY
+  NAMES "swscale"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(SWSCALE_INCLUDE_DIR)
+mark_as_advanced(SWSCALE_LIBRARY)
+
+# Translate library into library directory
+if(SWSCALE_LIBRARY)
+  unset(SWSCALE_LIBRARY_DIR CACHE)
+  get_filename_component(SWSCALE_LIBRARY_DIR "${SWSCALE_LIBRARY}" PATH)
+  set(SWSCALE_LIBRARY_DIR "${SWSCALE_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+mark_as_advanced(SWSCALE_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SWScale DEFAULT_MSG SWSCALE_LIBRARY SWSCALE_INCLUDE_DIR SWSCALE_LIBRARY_DIR)

+ 40 - 0
cmake/modules/FindVRPN.cmake

@@ -0,0 +1,40 @@
+# Filename: FindVRPN.cmake
+# Authors: CFSworks (2 Nov, 2018)
+#
+# Usage:
+#   find_package(VRPN [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   VRPN_FOUND       - system has VRPN
+#   VRPN_INCLUDE_DIR - the include directory containing VRPN header files
+#   VRPN_LIBRARIES   - the path to the VRPN client libraries
+#
+
+find_path(VRPN_INCLUDE_DIR "vrpn_Connection.h")
+
+find_library(VRPN_vrpn_LIBRARY
+  NAMES "vrpn")
+
+mark_as_advanced(VRPN_INCLUDE_DIR VRPN_vrpn_LIBRARY)
+
+if(VRPN_vrpn_LIBRARY)
+  get_filename_component(_vrpn_dir "${VRPN_vrpn_LIBRARY}" DIRECTORY)
+  find_library(VRPN_quat_LIBRARY
+    NAMES "quat"
+    PATHS "${_vrpn_dir}"
+    NO_DEFAULT_PATH)
+
+  unset(_vrpn_dir)
+  mark_as_advanced(VRPN_quat_LIBRARY)
+endif()
+
+set(VRPN_LIBRARIES)
+if(VRPN_vrpn_LIBRARY)
+  list(APPEND VRPN_LIBRARIES "${VRPN_vrpn_LIBRARY}")
+endif()
+if(VRPN_quat_LIBRARY)
+  list(APPEND VRPN_LIBRARIES "${VRPN_quat_LIBRARY}")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(VRPN DEFAULT_MSG VRPN_INCLUDE_DIR VRPN_LIBRARIES)

+ 34 - 0
cmake/modules/FindVorbisFile.cmake

@@ -0,0 +1,34 @@
+# Filename: FindVorbisFile.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(VorbisFile [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   VORBISFILE_FOUND        - system has Ogg and vorbisfile
+#   VORBISFILE_INCLUDE_DIRS - the include directory/ies containing vorbis/ and ogg/
+#   VORBISFILE_LIBRARIES    - the paths to the vorbis and vorbisfile libraries
+#
+
+# Find Ogg
+find_package(Ogg QUIET)
+
+# Find Vorbis
+find_path(VORBIS_INCLUDE_DIR NAMES "vorbis/vorbisfile.h")
+
+find_library(VORBIS_vorbis_LIBRARY NAMES "vorbis" "libvorbis_static")
+find_library(VORBIS_vorbisfile_LIBRARY NAMES "vorbisfile" "libvorbisfile_static")
+
+mark_as_advanced(VORBIS_INCLUDE_DIR VORBIS_vorbis_LIBRARY VORBIS_vorbisfile_LIBRARY)
+
+# Define output variables
+set(VORBISFILE_INCLUDE_DIRS ${VORBIS_INCLUDE_DIR})
+if(NOT OGG_INCLUDE_DIR STREQUAL VORBIS_INCLUDE_DIR)
+  list(APPEND VORBISFILE_INCLUDE_DIRS ${OGG_INCLUDE_DIR})
+endif()
+set(VORBISFILE_LIBRARIES ${VORBIS_vorbisfile_LIBRARY} ${VORBIS_vorbis_LIBRARY} ${OGG_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(VorbisFile DEFAULT_MSG
+  Ogg_FOUND
+  VORBIS_INCLUDE_DIR VORBIS_vorbis_LIBRARY VORBIS_vorbisfile_LIBRARY)

+ 7 - 0
cmake/modules/README.md

@@ -0,0 +1,7 @@
+Directory Info
+--------------
+**Directory:** /cmake/modules
+**License:** Unlicense  
+**Description:** This directory is used for standard CMake modules which are safe
+to use outside of a Panda3D project.  This should be the normal location for
+most FindXYZZY packages that are written for Panda3D.

+ 70 - 0
cmake/scripts/ConcatenateToCXX.cmake

@@ -0,0 +1,70 @@
+# Filename: ConcatenateToCXX.cmake
+#
+# Description: When run, creates a single C++ file which includes a const char[]
+#   containing the bytes from one or more files.
+#
+#   There is a {SYMBOL_NAME}_size symbol defined as well, storing the total
+#   number of bytes in the concatenated input files.
+#
+#   A single null terminator byte is added for the benefit of programs that
+#   simply treat the data array as a string.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D OUTPUT_FILE="out.cxx" -D SYMBOL_NAME=data -D INPUT_FILES="a.bin b.bin" -P ConcatenateToCXX.cmake
+#
+
+if(NOT CMAKE_SCRIPT_MODE_FILE)
+  message(FATAL_ERROR "ConcatenateToCXX.cmake should not be included but run in script mode.")
+  return()
+endif()
+
+if(NOT DEFINED OUTPUT_FILE)
+  message(FATAL_ERROR "OUTPUT_FILE should be defined when running ConcatenateToCXX.cmake!")
+  return()
+endif()
+
+if(NOT DEFINED SYMBOL_NAME)
+  set(SYMBOL_NAME "data")
+endif()
+
+file(WRITE "${OUTPUT_FILE}" "/* Generated by CMake.  DO NOT EDIT. */
+
+extern const char ${SYMBOL_NAME}[];
+extern const int ${SYMBOL_NAME}_size;
+
+const char ${SYMBOL_NAME}[] = {\n")
+
+set(byte_count 0)
+separate_arguments(INPUT_FILES)
+foreach(infile ${INPUT_FILES})
+  file(APPEND "${OUTPUT_FILE}" "  /* ${infile} */\n")
+
+  set(offset 0)
+  while(1)
+    # Read up to 1024 bytes from the input file
+    file(READ "${infile}" data LIMIT 1024 OFFSET ${offset} HEX)
+    math(EXPR offset "${offset} + 1024")
+
+    # If 'data' is empty, we're done
+    if(NOT data)
+      break()
+    endif()
+
+    # Count the bytes we're adding
+    string(LENGTH "${data}" strlen)
+    math(EXPR byte_count "${byte_count} + (${strlen} / 2)")
+
+    # Format runs of up to 32 hex chars by indenting and giving a newline
+    string(REGEX REPLACE
+      "(...?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?)" " \\1\n"
+      data "${data}")
+    # Format each byte (2 hex chars) in each line with 0x prefix and comma suffix
+    string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" " 0x\\1," data "${data}")
+    file(APPEND "${OUTPUT_FILE}" "${data}")
+  endwhile()
+endforeach(infile)
+
+file(APPEND "${OUTPUT_FILE}" "  0\n};
+
+extern const int ${SYMBOL_NAME}_size = ${byte_count};\n")

+ 27 - 0
cmake/scripts/CopyPattern.cmake

@@ -0,0 +1,27 @@
+# Filename: CopyPattern.cmake
+#
+# Description: This is a standalone version of CMake's file(COPY) command so we
+#              can use all of its features during build-time instead of
+#              config-time.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D SOURCE=[source directory]
+#         -D DESTINATION=[destination directory]
+#         -D FILES_MATCHING="[globbing patterns passed to file(COPY)]"
+#         -P CopyPattern.cmake
+if(NOT DEFINED SOURCE OR NOT DEFINED DESTINATION)
+    message(SEND_ERROR "CopyPattern.cmake requires SOURCE and DESTINATION to be
+defined.")
+endif()
+
+if(DEFINED FILES_MATCHING)
+  separate_arguments(FILES_MATCHING UNIX_COMMAND ${FILES_MATCHING})
+
+  file(COPY "${SOURCE}"
+       DESTINATION "${DESTINATION}"
+       FILES_MATCHING ${FILES_MATCHING})
+else()
+  file(COPY "${SOURCE}"
+       DESTINATION "${DESTINATION}")
+endif()

+ 78 - 0
cmake/scripts/CopyPython.cmake

@@ -0,0 +1,78 @@
+# Filename: CopyPython.cmake
+#
+# Description: When run, copies Python files from a source directory to a
+#   build directory located somewhere in the project's binary path. If
+#   PYTHON_EXECUTABLES is provided, it will also invoke compileall in the
+#   destination dir(s) to precache .pyc/.pyo files.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D OUTPUT_DIR="/out/dir/Release"
+#         -D SOURCE_DIR="/panda3d/direct/src/"
+#         -D PYTHON_EXECUTABLES="/usr/bin/python3.6"
+#         -P CopyPython.cmake
+#
+
+if(NOT CMAKE_SCRIPT_MODE_FILE)
+  message(FATAL_ERROR "CopyPython.cmake should not be included but run in script mode.")
+  return()
+endif()
+
+if(NOT DEFINED OUTPUT_DIR)
+  message(FATAL_ERROR "OUTPUT_DIR should be defined when running CopyPython.cmake!")
+  return()
+endif()
+
+# Ensure OUTPUT_DIR exists
+file(MAKE_DIRECTORY ${OUTPUT_DIR})
+
+# If there's a SOURCE_DIR, glob for .py files and copy
+# (this is done by hand to avoid the CMake bug where it creates empty dirs)
+if(DEFINED SOURCE_DIR)
+  file(GLOB_RECURSE py_files RELATIVE "${SOURCE_DIR}" "${SOURCE_DIR}/*.py")
+  foreach(py_file ${py_files})
+    get_filename_component(py_file_parent "${py_file}" DIRECTORY)
+    file(TIMESTAMP "${SOURCE_DIR}/${py_file}" src_stamp)
+    file(TIMESTAMP "${OUTPUT_DIR}/${py_file}" dst_stamp)
+
+    # The file is only copied if:
+    # - there's an __init__.py in its dir (or file is in the root) (i.e. file belongs to a package), and
+    # - the modification timestamp differs (i.e. file changed or never copied)
+    if((py_file_parent STREQUAL "." OR NOT py_file_parent
+        OR EXISTS "${SOURCE_DIR}/${py_file_parent}/__init__.py")
+       AND NOT src_stamp STREQUAL dst_stamp)
+
+      file(COPY "${SOURCE_DIR}/${py_file}" DESTINATION "${OUTPUT_DIR}/${py_file_parent}")
+      set(changed YES)
+    endif()
+
+  endforeach(py_file)
+
+else()
+  # No SOURCE_DIR; assume we're outdated since our caller might be populating
+  # the OUTPUT_DIR themselves
+  set(changed YES)
+
+endif()
+
+# Make sure Python recognizes OUTPUT_DIR as a package
+if(NOT EXISTS "${OUTPUT_DIR}/__init__.py")
+  file(WRITE "${OUTPUT_DIR}/__init__.py" "")
+  set(changed YES)
+endif()
+
+# Generate .pyc files for each Python version, if our caller wants
+if(changed AND DEFINED PYTHON_EXECUTABLES)
+  foreach(interp ${PYTHON_EXECUTABLES})
+    execute_process(
+      COMMAND "${interp}" -m compileall .
+      OUTPUT_QUIET
+      WORKING_DIRECTORY "${OUTPUT_DIR}")
+
+    execute_process(
+      COMMAND "${interp}" -O -m compileall .
+      OUTPUT_QUIET
+      WORKING_DIRECTORY "${OUTPUT_DIR}")
+
+  endforeach(interp)
+endif()

+ 28 - 0
cmake/scripts/MakeComposite.cmake

@@ -0,0 +1,28 @@
+# Filename: MakeComposite.cmake
+#
+# Description: When run, creates a single C++ file which includes multiple
+#   other C++ files, to help facilitate unity builds.
+#   Unity builds provide significantly increased compile speed.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -P MakeComposite.cmake -D COMPOSITE_FILE="x_composite1.cxx" -D COMPOSITE_SOURCES="a.cxx b.cxx"
+#
+
+if(CMAKE_SCRIPT_MODE_FILE)
+  if(NOT DEFINED COMPOSITE_FILE)
+    message(FATAL_ERROR "COMPOSITE_FILE should be defined when running MakeComposite.cmake!")
+    return()
+  endif()
+
+  file(WRITE "${COMPOSITE_FILE}" "/* Generated by CMake.  DO NOT EDIT. */\n")
+
+  separate_arguments(COMPOSITE_SOURCES)
+  foreach(source ${COMPOSITE_SOURCES})
+    file(APPEND "${COMPOSITE_FILE}" "#include \"${source}\"\n")
+  endforeach()
+
+else()
+  message(SEND_ERROR "MakeComposite.cmake should not be included but run in script mode.")
+
+endif()

+ 10 - 0
cmake/scripts/README.md

@@ -0,0 +1,10 @@
+Directory Info
+--------------
+**Directory:** /cmake/scripts  
+**License:** Unlicense  
+**Description:** This directory is used for cmake files which are not meant to
+be included using CMake's normal include() directive.  Typically, files in this
+directory will be invoked as a custom command/target in the form of:
+```
+ cmake -P <CustomScriptName.cmake> [... other options ...]
+```

+ 7 - 0
cmake/templates/METADATA.in

@@ -0,0 +1,7 @@
+Metadata-Version: 2.0
+Name: Panda3D
+Version: ${PROJECT_VERSION}
+License: BSD
+Home-page: https://www.panda3d.org/
+Author: Panda3D Team
+Author-email: [email protected]

+ 9 - 0
cmake/templates/metalib_init.cxx.in

@@ -0,0 +1,9 @@
+#include "dtoolbase.h"
+
+@component_init_headers@
+
+EXPORT_CLASS void
+@init_func@() {
+@component_init_funcs@
+}
+@export_definitions@

+ 9 - 0
cmake/templates/metalib_init.h.in

@@ -0,0 +1,9 @@
+#ifndef _METALIB_INIT_@target_name@
+#define _METALIB_INIT_@target_name@
+
+#include "dtoolbase.h"
+
+IMPORT_CLASS void @init_func@();
+@export_declarations@
+
+#endif

+ 23 - 0
cmake/templates/win32_python/__init__.py

@@ -0,0 +1,23 @@
+def _fixup_dlls():
+    try:
+        path = __path__[0]
+    except (NameError, IndexError):
+        return # Not a package, or not on filesystem
+
+    import os
+
+    relpath = os.path.relpath(path, __path__[-1])
+    dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath))
+    if not os.path.isdir(dll_path):
+        return
+
+    if hasattr(os, 'add_dll_directory'):
+        os.add_dll_directory(dll_path)
+    else:
+        os_path = os.environ.get('PATH', '')
+        os_path = os_path.split(os.pathsep) if os_path else []
+        os_path.insert(0, dll_path)
+        os.environ['PATH'] = os.pathsep.join(os_path)
+
+_fixup_dlls()
+del _fixup_dlls

+ 18 - 0
contrib/CMakeLists.txt

@@ -0,0 +1,18 @@
+if(NOT BUILD_PANDA)
+  message(FATAL_ERROR "Cannot build contrib without panda!  Please enable the BUILD_PANDA option.")
+endif()
+
+# Include source directories
+add_subdirectory(src/ai)
+add_subdirectory(src/contribbase)
+add_subdirectory(src/rplight)
+
+if(HAVE_PYTHON)
+  add_python_module(panda3d.ai p3ai
+    IMPORT panda3d.core COMPONENT ContribPython)
+
+  add_python_module(panda3d._rplight p3rplight
+    IMPORT panda3d.core COMPONENT ContribPython)
+endif()
+
+export_targets(Contrib COMPONENT ContribDevel)

+ 54 - 0
contrib/src/ai/CMakeLists.txt

@@ -0,0 +1,54 @@
+set(P3AI_HEADERS
+  aiBehaviors.h
+  aiCharacter.h
+  aiGlobals.h
+  aiNode.h
+  aiPathFinder.h
+  aiWorld.h
+  arrival.h
+  config_ai.h
+  evade.h
+  flee.h
+  flock.h
+  meshNode.h
+  obstacleAvoidance.h
+  pathFind.h
+  pathFollow.h
+  pursue.h
+  seek.h
+  wander.h
+)
+
+set(P3AI_SOURCES
+  aiBehaviors.cxx
+  aiCharacter.cxx
+  aiNode.cxx
+  aiPathFinder.cxx
+  aiWorld.cxx
+  arrival.cxx
+  config_ai.cxx
+  evade.cxx
+  flee.cxx
+  flock.cxx
+  meshNode.cxx
+  obstacleAvoidance.cxx
+  pathFind.cxx
+  pathFollow.cxx
+  pursue.cxx
+  seek.cxx
+  wander.cxx
+)
+
+composite_sources(p3ai P3AI_SOURCES)
+add_library(p3ai ${P3AI_HEADERS} ${P3AI_SOURCES})
+set_target_properties(p3ai PROPERTIES DEFINE_SYMBOL BUILDING_PANDAAI)
+target_link_libraries(p3ai p3contribbase)
+target_interrogate(p3ai ALL)
+
+install(TARGETS p3ai
+  EXPORT Contrib COMPONENT Contrib
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+  ARCHIVE COMPONENT ContribDevel)
+install(FILES ${P3AI_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 14 - 0
contrib/src/contribbase/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(P3CONTRIBBASE_SOURCES
+  contribbase.cxx
+)
+
+set(P3CONTRIBBASE_HEADERS
+  contribbase.h contribsymbols.h
+)
+
+# Yes, INTERFACE: don't build it, there's no code!
+add_library(p3contribbase INTERFACE)
+target_link_libraries(p3contribbase INTERFACE panda)
+
+install(TARGETS p3contribbase EXPORT Contrib COMPONENT Contrib)
+install(FILES ${P3CONTRIBBASE_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 52 - 0
contrib/src/rplight/CMakeLists.txt

@@ -0,0 +1,52 @@
+set(P3RPLIGHT_HEADERS
+  config_rplight.h
+  gpuCommand.h gpuCommand.I
+  gpuCommandList.h
+  iesDataset.h
+  internalLightManager.h internalLightManager.I
+  pointerSlotStorage.h
+  pssmCameraRig.h pssmCameraRig.I
+  rpLight.h rpLight.I
+  rpPointLight.h rpPointLight.I
+  rpSpotLight.h rpSpotLight.I
+  shadowAtlas.h shadowAtlas.I
+  shadowManager.h shadowManager.I
+  shadowSource.h shadowSource.I
+  tagStateManager.h tagStateManager.I
+)
+
+set(P3RPLIGHT_SOURCES
+  config_rplight.cxx
+  gpuCommand.cxx
+  gpuCommandList.cxx
+  iesDataset.cxx
+  internalLightManager.cxx
+  pssmCameraRig.cxx
+  rpLight.cxx
+  rpPointLight.cxx
+  rpSpotLight.cxx
+  shadowAtlas.cxx
+  shadowManager.cxx
+  shadowSource.cxx
+  tagStateManager.cxx
+)
+
+composite_sources(p3rplight P3RPLIGHT_SOURCES)
+# STATIC, because it doesn't contain any EXPCL_ stuff
+add_library(p3rplight STATIC ${P3RPLIGHT_HEADERS} ${P3RPLIGHT_SOURCES})
+target_link_libraries(p3rplight p3contribbase)
+target_interrogate(p3rplight ALL)
+
+if(MODULE_TYPE STREQUAL "MODULE")
+  # Because it's STATIC (see above), we need to make it PIC so it can link into
+  # a Python module
+  set_target_properties(p3rplight PROPERTIES POSITION_INDEPENDENT_CODE ON)
+endif()
+
+install(TARGETS p3rplight
+  EXPORT Contrib COMPONENT Contrib
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+  ARCHIVE COMPONENT ContribDevel)
+install(FILES ${P3RPLIGHT_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 75 - 0
direct/CMakeLists.txt

@@ -0,0 +1,75 @@
+if(NOT BUILD_PANDA)
+  message(FATAL_ERROR "Cannot build direct without panda!  Please enable the BUILD_PANDA option.")
+endif()
+
+# Include source directories which have C++ components:
+add_subdirectory(src/dcparse)
+add_subdirectory(src/dcparser)
+add_subdirectory(src/deadrec)
+add_subdirectory(src/directbase)
+#add_subdirectory(src/directd)
+#add_subdirectory(src/directdServer)
+add_subdirectory(src/distributed)
+add_subdirectory(src/interval)
+add_subdirectory(src/motiontrail)
+add_subdirectory(src/showbase)
+
+set(P3DIRECT_COMPONENTS
+  p3dcparser p3deadrec
+  p3interval p3motiontrail p3showbase
+)
+if(HAVE_PYTHON)
+  list(APPEND P3DIRECT_COMPONENTS p3distributed)
+endif()
+
+set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "DirectDevel")
+add_metalib(p3direct INIT init_libdirect direct.h COMPONENTS ${P3DIRECT_COMPONENTS})
+unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
+set_property(TARGET p3direct PROPERTY LINKER_LANGUAGE "CXX")
+
+# Installation:
+install(TARGETS p3direct
+  EXPORT Direct COMPONENT Direct
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  ARCHIVE COMPONENT DirectDevel)
+
+if(HAVE_PYTHON)
+  # Now for the Python side of everything
+
+  add_python_module(panda3d.direct
+    p3dcparser p3deadrec p3distributed p3interval
+    p3motiontrail p3showbase LINK p3direct IMPORT panda3d.core COMPONENT Direct)
+
+  # Copy Python source files into the build directory
+  install_python_package(direct SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/src" LIB COMPONENT Direct)
+
+  # This bit is to generate the 'pandac' compatibility shim. It's deprecated now,
+  # but in older versions of Panda3D, one would use
+  # from pandac.PandaModules import *
+  # instead of
+  # from panda3d.FOO import *
+  # Generate PandaModules:
+  file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py"
+    "\"This module is deprecated.  Import from panda3d.core and other panda3d.* modules instead.\"
+
+print(\"Warning: pandac.PandaModules is deprecated, import from panda3d.core instead\")\n")
+
+  foreach(module ${ALL_INTERROGATE_MODULES})
+    string(REGEX REPLACE "^.*\\." "" module_name "${module}")
+    file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py" "
+try:
+    from ${module} import *
+except ImportError as err:
+    if not (\"No module named\" in str(err) and \"${module_name}\" in str(err)):
+        raise
+")
+  endforeach()
+
+  # Now install ourselves:
+  install_python_package(pandac SOURCE "${CMAKE_CURRENT_BINARY_DIR}/pandac" LIB COMPONENT Direct)
+endif()
+
+# "Direct" component contains both the Python stuff and the non-Python stuff,
+# because direct has a pretty solid dependency on Python.
+export_targets(Direct NAMESPACE "Panda3D::Direct::" COMPONENT DirectDevel)

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

@@ -212,7 +212,7 @@ class Actor(DirectObject, NodePath):
         self.__LODCenter = Point3(0, 0, 0)
         self.__LODCenter = Point3(0, 0, 0)
         self.switches = None
         self.switches = None
 
 
-        if (other == None):
+        if other is None:
             # act like a normal constructor
             # act like a normal constructor
 
 
             # create base hierarchy
             # create base hierarchy
@@ -352,7 +352,7 @@ class Actor(DirectObject, NodePath):
             self.gotName = other.gotName
             self.gotName = other.gotName
 
 
             # copy the scene graph elements of other
             # copy the scene graph elements of other
-            if (overwrite):
+            if overwrite:
                 otherCopy = other.copyTo(NodePath())
                 otherCopy = other.copyTo(NodePath())
                 otherCopy.detachNode()
                 otherCopy.detachNode()
                 # assign these elements to ourselve (overwrite)
                 # assign these elements to ourselve (overwrite)
@@ -372,7 +372,7 @@ class Actor(DirectObject, NodePath):
             self.switches = other.switches
             self.switches = other.switches
             self.__LODNode = self.find('**/+LODNode')
             self.__LODNode = self.find('**/+LODNode')
             self.__hasLOD = 0
             self.__hasLOD = 0
-            if (not self.__LODNode.isEmpty()):
+            if not self.__LODNode.isEmpty():
                 self.__hasLOD = 1
                 self.__hasLOD = 1
 
 
 
 
@@ -418,7 +418,7 @@ class Actor(DirectObject, NodePath):
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
 
 
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef == None:
+        if partDef is None:
             Actor.notify.error("no part named: %s" % (partName))
             Actor.notify.error("no part named: %s" % (partName))
 
 
         self.__doListJoints(0, partDef.getBundle(),
         self.__doListJoints(0, partDef.getBundle(),
@@ -491,7 +491,7 @@ class Actor(DirectObject, NodePath):
                 for animName, file, animControl in animInfo:
                 for animName, file, animControl in animInfo:
                     print('    Anim: %s' % animName)
                     print('    Anim: %s' % animName)
                     print('      File: %s' % file)
                     print('      File: %s' % file)
-                    if animControl == None:
+                    if animControl is None:
                         print(' (not loaded)')
                         print(' (not loaded)')
                     else:
                     else:
                         print('      NumFrames: %d PlayRate: %0.2f' %
                         print('      NumFrames: %d PlayRate: %0.2f' %
@@ -500,7 +500,12 @@ class Actor(DirectObject, NodePath):
 
 
     def cleanup(self):
     def cleanup(self):
         """
         """
-        Actor cleanup function
+        This method should be called when intending to destroy the Actor, and
+        cleans up any additional resources stored on the Actor class before
+        removing the underlying node using `removeNode()`.
+
+        Note that `removeNode()` itself is not sufficient to destroy actors,
+        which is why this method exists.
         """
         """
         self.stop(None)
         self.stop(None)
         self.clearPythonData()
         self.clearPythonData()
@@ -512,6 +517,11 @@ class Actor(DirectObject, NodePath):
             self.removeNode()
             self.removeNode()
 
 
     def removeNode(self):
     def removeNode(self):
+        """
+        You should call `cleanup()` for Actor objects instead, since
+        :meth:`~panda3d.core.NodePath.removeNode()` is not sufficient for
+        completely destroying Actor objects.
+        """
         if self.__geomNode and (self.__geomNode.getNumChildren() > 0):
         if self.__geomNode and (self.__geomNode.getNumChildren() > 0):
             assert self.notify.warning("called actor.removeNode() on %s without calling cleanup()" % self.getName())
             assert self.notify.warning("called actor.removeNode() on %s without calling cleanup()" % self.getName())
         NodePath.removeNode(self)
         NodePath.removeNode(self)
@@ -525,7 +535,7 @@ class Actor(DirectObject, NodePath):
 
 
     def flush(self):
     def flush(self):
         """
         """
-        Actor flush function
+        Actor flush function.  Used by `cleanup()`.
         """
         """
         self.clearPythonData()
         self.clearPythonData()
 
 
@@ -557,14 +567,14 @@ class Actor(DirectObject, NodePath):
         bundles = []
         bundles = []
 
 
         for lodName, partBundleDict in self.__partBundleDict.items():
         for lodName, partBundleDict in self.__partBundleDict.items():
-            if partName == None:
+            if partName is None:
                 for partDef in partBundleDict.values():
                 for partDef in partBundleDict.values():
                     bundles.append(partDef.getBundle())
                     bundles.append(partDef.getBundle())
 
 
             else:
             else:
                 subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
                 subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
                 partDef = partBundleDict.get(subpartDef.truePartName)
                 partDef = partBundleDict.get(subpartDef.truePartName)
-                if partDef != None:
+                if partDef is not None:
                     bundles.append(partDef.getBundle())
                     bundles.append(partDef.getBundle())
                 else:
                 else:
                     Actor.notify.warning("Couldn't find part: %s" % (partName))
                     Actor.notify.warning("Couldn't find part: %s" % (partName))
@@ -635,7 +645,7 @@ class Actor(DirectObject, NodePath):
         Set the node that switches actor geometry in and out.
         Set the node that switches actor geometry in and out.
         If one is not supplied as an argument, make one
         If one is not supplied as an argument, make one
         """
         """
-        if (node == None):
+        if node is None:
             node = LODNode.makeDefaultLod("lod")
             node = LODNode.makeDefaultLod("lod")
 
 
         if self.__LODNode:
         if self.__LODNode:
@@ -685,7 +695,7 @@ class Actor(DirectObject, NodePath):
         self.switches[lodName] = [inDist, outDist]
         self.switches[lodName] = [inDist, outDist]
         # add the switch distance info
         # add the switch distance info
         self.__LODNode.node().addSwitch(inDist, outDist)
         self.__LODNode.node().addSwitch(inDist, outDist)
-        if center != None:
+        if center is not None:
             self.setCenter(center)
             self.setCenter(center)
 
 
     def setLOD(self, lodName, inDist=0, outDist=0):
     def setLOD(self, lodName, inDist=0, outDist=0):
@@ -724,7 +734,7 @@ class Actor(DirectObject, NodePath):
         return self.__hasLOD
         return self.__hasLOD
 
 
     def setCenter(self, center):
     def setCenter(self, center):
-        if center == None:
+        if center is None:
             center = Point3(0, 0, 0)
             center = Point3(0, 0, 0)
         self.__LODCenter = center
         self.__LODCenter = center
         if self.__LODNode:
         if self.__LODNode:
@@ -782,7 +792,7 @@ class Actor(DirectObject, NodePath):
         Returns True if any joint has changed as a result of this,
         Returns True if any joint has changed as a result of this,
         False otherwise. """
         False otherwise. """
 
 
-        if lodName == None:
+        if lodName is None:
             lodNames = self.getLODNames()
             lodNames = self.getLODNames()
         else:
         else:
             lodNames = [lodName]
             lodNames = [lodName]
@@ -790,7 +800,7 @@ class Actor(DirectObject, NodePath):
         anyChanged = False
         anyChanged = False
         if lod < len(lodNames):
         if lod < len(lodNames):
             lodName = lodNames[lod]
             lodName = lodNames[lod]
-            if partName == None:
+            if partName is None:
                 partBundleDict = self.__partBundleDict[lodName]
                 partBundleDict = self.__partBundleDict[lodName]
                 partNames = list(partBundleDict.keys())
                 partNames = list(partBundleDict.keys())
             else:
             else:
@@ -906,11 +916,11 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
-        if partName == None:
+        if partName is None:
             partName, animDict = next(iter(animControlDict.items()))
             partName, animDict = next(iter(animControlDict.items()))
         else:
         else:
             animDict = animControlDict.get(partName)
             animDict = animControlDict.get(partName)
-            if animDict == None:
+            if animDict is None:
                 # part was not present
                 # part was not present
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 return None
                 return None
@@ -931,11 +941,11 @@ class Actor(DirectObject, NodePath):
         in dictionary.  NOTE: only returns info for an arbitrary LOD
         in dictionary.  NOTE: only returns info for an arbitrary LOD
         """
         """
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
-        if partName == None:
+        if partName is None:
             partName, animDict = next(iter(animControlDict.items()))
             partName, animDict = next(iter(animControlDict.items()))
         else:
         else:
             animDict = animControlDict.get(partName)
             animDict = animControlDict.get(partName)
-            if animDict == None:
+            if animDict is None:
                 # part was not present
                 # part was not present
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 return None
                 return None
@@ -969,7 +979,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef != None:
+        if partDef is not None:
             return partDef.partBundleNP
             return partDef.partBundleNP
         return None
         return None
 
 
@@ -984,7 +994,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef != None:
+        if partDef is not None:
             return partDef.getBundle()
             return partDef.getBundle()
         return None
         return None
 
 
@@ -1001,9 +1011,9 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         # remove the part
         # remove the part
-        if (partName in partBundleDict):
+        if partName in partBundleDict:
             partBundleDict[partName].partBundleNP.removeNode()
             partBundleDict[partName].partBundleNP.removeNode()
-            del(partBundleDict[partName])
+            del partBundleDict[partName]
 
 
         # find the corresponding anim control dict
         # find the corresponding anim control dict
         if self.mergeLODBundles:
         if self.mergeLODBundles:
@@ -1014,8 +1024,8 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         # remove the animations
         # remove the animations
-        if (partName in partDict):
-            del(partDict[partName])
+        if partName in partDict:
+            del partDict[partName]
 
 
     def hidePart(self, partName, lodName="lodRoot"):
     def hidePart(self, partName, lodName="lodRoot"):
         """
         """
@@ -1093,7 +1103,7 @@ class Actor(DirectObject, NodePath):
         if node is None:
         if node is None:
             node = partDef.partBundleNP.attachNewNode(jointName)
             node = partDef.partBundleNP.attachNewNode(jointName)
 
 
-        if (joint):
+        if joint:
             if localTransform:
             if localTransform:
                 joint.addLocalTransform(node.node())
                 joint.addLocalTransform(node.node())
             else:
             else:
@@ -1125,7 +1135,7 @@ class Actor(DirectObject, NodePath):
         # Get a handle to the joint.
         # Get a handle to the joint.
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
 
 
-        if (joint):
+        if joint:
             joint.clearNetTransforms()
             joint.clearNetTransforms()
             joint.clearLocalTransforms()
             joint.clearLocalTransforms()
         else:
         else:
@@ -1139,11 +1149,11 @@ class Actor(DirectObject, NodePath):
         joints=[]
         joints=[]
         pattern = GlobPattern(jointName)
         pattern = GlobPattern(jointName)
 
 
-        if lodName == None and self.mergeLODBundles:
+        if lodName is None and self.mergeLODBundles:
             # Get the common bundle.
             # Get the common bundle.
             partBundleDicts = [self.__commonBundleHandles]
             partBundleDicts = [self.__commonBundleHandles]
 
 
-        elif lodName == None:
+        elif lodName is None:
             # Get all LOD's.
             # Get all LOD's.
             partBundleDicts = self.__partBundleDict.values()
             partBundleDicts = self.__partBundleDict.values()
         else:
         else:
@@ -1232,7 +1242,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
 
 
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
-        if joint == None:
+        if joint is None:
             Actor.notify.warning("no joint named %s!" % (jointName))
             Actor.notify.warning("no joint named %s!" % (jointName))
             return None
             return None
         return joint.getDefaultValue()
         return joint.getDefaultValue()
@@ -1252,7 +1262,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
 
 
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
-        if joint == None:
+        if joint is None:
             Actor.notify.warning("no joint named %s!" % (jointName))
             Actor.notify.warning("no joint named %s!" % (jointName))
             return None
             return None
         return joint.getTransformState()
         return joint.getTransformState()
@@ -1277,7 +1287,7 @@ class Actor(DirectObject, NodePath):
         anyGood = False
         anyGood = False
         for bundleDict in self.__partBundleDict.values():
         for bundleDict in self.__partBundleDict.values():
             bundle = bundleDict[trueName].getBundle()
             bundle = bundleDict[trueName].getBundle()
-            if node == None:
+            if node is None:
                 node = self.attachNewNode(ModelNode(jointName))
                 node = self.attachNewNode(ModelNode(jointName))
                 joint = bundle.findChild(jointName)
                 joint = bundle.findChild(jointName)
                 if joint and isinstance(joint, MovingPartMatrix):
                 if joint and isinstance(joint, MovingPartMatrix):
@@ -1299,7 +1309,7 @@ class Actor(DirectObject, NodePath):
         optimal than controlJoint() for cases in which the transform
         optimal than controlJoint() for cases in which the transform
         is not intended to be animated during the lifetime of the
         is not intended to be animated during the lifetime of the
         Actor. """
         Actor. """
-        if transform == None:
+        if transform is None:
             transform = TransformState.makePosHprScale(pos, hpr, scale)
             transform = TransformState.makePosHprScale(pos, hpr, scale)
 
 
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
@@ -1330,7 +1340,7 @@ class Actor(DirectObject, NodePath):
             partDef = partBundleDict.get(subpartDef.truePartName)
             partDef = partBundleDict.get(subpartDef.truePartName)
             if partDef:
             if partDef:
                 joint = partDef.partBundleNP.find("**/" + jointName)
                 joint = partDef.partBundleNP.find("**/" + jointName)
-                if (joint.isEmpty()):
+                if joint.isEmpty():
                     Actor.notify.warning("%s not found!" % (jointName))
                     Actor.notify.warning("%s not found!" % (jointName))
                 else:
                 else:
                     return path.instanceTo(joint)
                     return path.instanceTo(joint)
@@ -1350,7 +1360,7 @@ class Actor(DirectObject, NodePath):
                 anotherPartDef = partBundleDict.get(anotherPartName)
                 anotherPartDef = partBundleDict.get(anotherPartName)
                 if anotherPartDef:
                 if anotherPartDef:
                     joint = anotherPartDef.partBundleNP.find("**/" + jointName)
                     joint = anotherPartDef.partBundleNP.find("**/" + jointName)
-                    if (joint.isEmpty()):
+                    if joint.isEmpty():
                         Actor.notify.warning("%s not found!" % (jointName))
                         Actor.notify.warning("%s not found!" % (jointName))
                     else:
                     else:
                         partDef.partBundleNP.reparentTo(joint)
                         partDef.partBundleNP.reparentTo(joint)
@@ -1395,10 +1405,10 @@ class Actor(DirectObject, NodePath):
         root under the given lod.
         root under the given lod.
         """
         """
         # check to see if we are working within an lod
         # check to see if we are working within an lod
-        if lodName != None:
+        if lodName is not None:
             # find the named lod node
             # find the named lod node
             lodRoot = self.__LODNode.find(str(lodName))
             lodRoot = self.__LODNode.find(str(lodName))
-            if root == None:
+            if root is None:
                 # no need to look further
                 # no need to look further
                 root = lodRoot
                 root = lodRoot
             else:
             else:
@@ -1406,7 +1416,7 @@ class Actor(DirectObject, NodePath):
                 root = lodRoot.find("**/" + root)
                 root = lodRoot.find("**/" + root)
         else:
         else:
             # start search from self if no root and no lod given
             # start search from self if no root and no lod given
-            if root == None:
+            if root is None:
                 root = self
                 root = self
 
 
         frontParts = root.findAllMatches("**/" + frontPartName)
         frontParts = root.findAllMatches("**/" + frontPartName)
@@ -1426,7 +1436,7 @@ class Actor(DirectObject, NodePath):
 
 
         # Find the back part.
         # Find the back part.
         backPart = root.find("**/" + backPartName)
         backPart = root.find("**/" + backPartName)
-        if (backPart.isEmpty()):
+        if backPart.isEmpty():
             Actor.notify.warning("no part named %s!" % (backPartName))
             Actor.notify.warning("no part named %s!" % (backPartName))
             return
             return
 
 
@@ -1442,7 +1452,7 @@ class Actor(DirectObject, NodePath):
 
 
 
 
     def fixBounds(self, partName = None):
     def fixBounds(self, partName = None):
-        if(partName == None):
+        if partName is None:
             #iterate through everything
             #iterate through everything
             for lodData in self.__partBundleDict.values():
             for lodData in self.__partBundleDict.values():
                 for partData in lodData.values():
                 for partData in lodData.values():
@@ -1473,7 +1483,7 @@ class Actor(DirectObject, NodePath):
         in this actor
         in this actor
         """
         """
         # if no part name specified fix all parts
         # if no part name specified fix all parts
-        if (part==None):
+        if part is None:
             part = self
             part = self
 
 
         # update all characters first
         # update all characters first
@@ -1531,12 +1541,12 @@ class Actor(DirectObject, NodePath):
         Play the given animation on the given part of the actor.
         Play the given animation on the given part of the actor.
         If no part is specified, try to play on all parts. NOTE:
         If no part is specified, try to play on all parts. NOTE:
         plays over ALL LODs"""
         plays over ALL LODs"""
-        if fromFrame == None:
+        if fromFrame is None:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
                 control.play()
                 control.play()
         else:
         else:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
-                if toFrame == None:
+                if toFrame is None:
                     control.play(fromFrame, control.getNumFrames() - 1)
                     control.play(fromFrame, control.getNumFrames() - 1)
                 else:
                 else:
                     control.play(fromFrame, toFrame)
                     control.play(fromFrame, toFrame)
@@ -1550,12 +1560,12 @@ class Actor(DirectObject, NodePath):
         all LOD's
         all LOD's
         """
         """
 
 
-        if fromFrame == None:
+        if fromFrame is None:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
                 control.loop(restart)
                 control.loop(restart)
         else:
         else:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
-                if toFrame == None:
+                if toFrame is None:
                     control.loop(restart, fromFrame, control.getNumFrames() - 1)
                     control.loop(restart, fromFrame, control.getNumFrames() - 1)
                 else:
                 else:
                     control.loop(restart, fromFrame, toFrame)
                     control.loop(restart, fromFrame, toFrame)
@@ -1567,11 +1577,11 @@ class Actor(DirectObject, NodePath):
         restarting at zero frame if requested. If no part name
         restarting at zero frame if requested. If no part name
         is given then try to loop on all parts. NOTE: loops on
         is given then try to loop on all parts. NOTE: loops on
         all LOD's"""
         all LOD's"""
-        if fromFrame == None:
+        if fromFrame is None:
             fromFrame = 0
             fromFrame = 0
 
 
         for control in self.getAnimControls(animName, partName):
         for control in self.getAnimControls(animName, partName):
-            if toFrame == None:
+            if toFrame is None:
                 control.pingpong(restart, fromFrame, control.getNumFrames() - 1)
                 control.pingpong(restart, fromFrame, control.getNumFrames() - 1)
             else:
             else:
                 control.pingpong(restart, fromFrame, toFrame)
                 control.pingpong(restart, fromFrame, toFrame)
@@ -1623,11 +1633,11 @@ class Actor(DirectObject, NodePath):
         Config.prc variable.
         Config.prc variable.
         """
         """
         for bundle in self.getPartBundles(partName = partName):
         for bundle in self.getPartBundles(partName = partName):
-            if blendType != None:
+            if blendType is not None:
                 bundle.setBlendType(blendType)
                 bundle.setBlendType(blendType)
-            if animBlend != None:
+            if animBlend is not None:
                 bundle.setAnimBlendFlag(animBlend)
                 bundle.setAnimBlendFlag(animBlend)
-            if frameBlend != None:
+            if frameBlend is not None:
                 bundle.setFrameBlendFlag(frameBlend)
                 bundle.setFrameBlendFlag(frameBlend)
 
 
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
@@ -1706,15 +1716,15 @@ class Actor(DirectObject, NodePath):
 
 
         partDict = self.__animControlDict.get(lodName)
         partDict = self.__animControlDict.get(lodName)
         # if this assertion fails, named lod was not present
         # if this assertion fails, named lod was not present
-        assert partDict != None
+        assert partDict is not None
 
 
         animDict = partDict.get(partName)
         animDict = partDict.get(partName)
-        if animDict == None:
+        if animDict is None:
             # part was not present
             # part was not present
             Actor.notify.warning("couldn't find part: %s" % (partName))
             Actor.notify.warning("couldn't find part: %s" % (partName))
         else:
         else:
             anim = animDict.get(animName)
             anim = animDict.get(animName)
-            if anim == None:
+            if anim is None:
                 # anim was not present
                 # anim was not present
                 assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                 assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                 pass
                 pass
@@ -1750,7 +1760,7 @@ class Actor(DirectObject, NodePath):
         If lodName is None or omitted, all LOD's are returned.
         If lodName is None or omitted, all LOD's are returned.
         """
         """
 
 
-        if partName == None and self.__subpartsComplete:
+        if partName is None and self.__subpartsComplete:
             # If we have the __subpartsComplete flag, and no partName
             # If we have the __subpartsComplete flag, and no partName
             # is specified, it really means to play the animation on
             # is specified, it really means to play the animation on
             # all subparts, not on the overall Actor.
             # all subparts, not on the overall Actor.
@@ -1759,12 +1769,12 @@ class Actor(DirectObject, NodePath):
         controls = []
         controls = []
         # build list of lodNames and corresponding animControlDicts
         # build list of lodNames and corresponding animControlDicts
         # requested.
         # requested.
-        if lodName == None or self.mergeLODBundles:
+        if lodName is None or self.mergeLODBundles:
             # Get all LOD's
             # Get all LOD's
             animControlDictItems = self.__animControlDict.items()
             animControlDictItems = self.__animControlDict.items()
         else:
         else:
             partDict = self.__animControlDict.get(lodName)
             partDict = self.__animControlDict.get(lodName)
-            if partDict == None:
+            if partDict is None:
                 Actor.notify.warning("couldn't find lod: %s" % (lodName))
                 Actor.notify.warning("couldn't find lod: %s" % (lodName))
                 animControlDictItems = []
                 animControlDictItems = []
             else:
             else:
@@ -1773,7 +1783,7 @@ class Actor(DirectObject, NodePath):
         for lodName, partDict in animControlDictItems:
         for lodName, partDict in animControlDictItems:
             # Now, build the list of partNames and the corresponding
             # Now, build the list of partNames and the corresponding
             # animDicts.
             # animDicts.
-            if partName == None:
+            if partName is None:
                 # Get all main parts, but not sub-parts.
                 # Get all main parts, but not sub-parts.
                 animDictItems = []
                 animDictItems = []
                 for thisPart, animDict in partDict.items():
                 for thisPart, animDict in partDict.items():
@@ -1791,14 +1801,14 @@ class Actor(DirectObject, NodePath):
 
 
                 for pName in partNameList:
                 for pName in partNameList:
                     animDict = partDict.get(pName)
                     animDict = partDict.get(pName)
-                    if animDict == None:
+                    if animDict is None:
                         # Maybe it's a subpart that hasn't been bound yet.
                         # Maybe it's a subpart that hasn't been bound yet.
                         subpartDef = self.__subpartDict.get(pName)
                         subpartDef = self.__subpartDict.get(pName)
                         if subpartDef:
                         if subpartDef:
                             animDict = {}
                             animDict = {}
                             partDict[pName] = animDict
                             partDict[pName] = animDict
 
 
-                    if animDict == None:
+                    if animDict is None:
                         # part was not present
                         # part was not present
                         Actor.notify.warning("couldn't find part: %s" % (pName))
                         Actor.notify.warning("couldn't find part: %s" % (pName))
                     else:
                     else:
@@ -1824,7 +1834,7 @@ class Actor(DirectObject, NodePath):
                         names = animDict.keys()
                         names = animDict.keys()
                     for animName in names:
                     for animName in names:
                         anim = animDict.get(animName)
                         anim = animDict.get(animName)
-                        if anim == None and partName != None:
+                        if anim is None and partName is not None:
                             for pName in partNameList:
                             for pName in partNameList:
                                 # Maybe it's a subpart that hasn't been bound yet.
                                 # Maybe it's a subpart that hasn't been bound yet.
                                 subpartDef = self.__subpartDict.get(pName)
                                 subpartDef = self.__subpartDict.get(pName)
@@ -1835,14 +1845,14 @@ class Actor(DirectObject, NodePath):
                                         anim = anim.makeCopy()
                                         anim = anim.makeCopy()
                                         animDict[animName] = anim
                                         animDict[animName] = anim
 
 
-                        if anim == None:
+                        if anim is None:
                             # anim was not present
                             # anim was not present
                             assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                             assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                             pass
                             pass
                         else:
                         else:
                             # bind the animation first if we need to
                             # bind the animation first if we need to
                             animControl = anim.animControl
                             animControl = anim.animControl
-                            if animControl == None:
+                            if animControl is None:
                                 animControl = self.__bindAnimToPart(
                                 animControl = self.__bindAnimToPart(
                                     animName, thisPart, lodName,
                                     animName, thisPart, lodName,
                                     allowAsyncBind = allowAsyncBind)
                                     allowAsyncBind = allowAsyncBind)
@@ -1869,7 +1879,7 @@ class Actor(DirectObject, NodePath):
         if isinstance(modelPath, NodePath):
         if isinstance(modelPath, NodePath):
             # If we got a NodePath instead of a string, use *that* as
             # If we got a NodePath instead of a string, use *that* as
             # the model directly.
             # the model directly.
-            if (copy):
+            if copy:
                 model = modelPath.copyTo(NodePath())
                 model = modelPath.copyTo(NodePath())
             else:
             else:
                 model = modelPath
                 model = modelPath
@@ -1898,15 +1908,15 @@ class Actor(DirectObject, NodePath):
             if model is not None:
             if model is not None:
                 model = NodePath(model)
                 model = NodePath(model)
 
 
-        if (model == None):
+        if model is None:
             raise IOError("Could not load Actor model %s" % (modelPath))
             raise IOError("Could not load Actor model %s" % (modelPath))
 
 
-        if (model.node().isOfType(Character.getClassType())):
+        if model.node().isOfType(Character.getClassType()):
             bundleNP = model
             bundleNP = model
         else:
         else:
             bundleNP = model.find("**/+Character")
             bundleNP = model.find("**/+Character")
 
 
-        if (bundleNP.isEmpty()):
+        if bundleNP.isEmpty():
             Actor.notify.warning("%s is not a character!" % (modelPath))
             Actor.notify.warning("%s is not a character!" % (modelPath))
             model.reparentTo(self.__geomNode)
             model.reparentTo(self.__geomNode)
         else:
         else:
@@ -1923,7 +1933,7 @@ class Actor(DirectObject, NodePath):
             # Now extract out the Character and integrate it with
             # Now extract out the Character and integrate it with
             # the Actor.
             # the Actor.
 
 
-            if (lodName!="lodRoot"):
+            if lodName != "lodRoot":
                 # parent to appropriate node under LOD switch
                 # parent to appropriate node under LOD switch
                 bundleNP.reparentTo(self.__LODNode.find(str(lodName)))
                 bundleNP.reparentTo(self.__LODNode.find(str(lodName)))
             else:
             else:
@@ -1965,7 +1975,7 @@ class Actor(DirectObject, NodePath):
             self.gotName = 1
             self.gotName = 1
 
 
         bundleDict = self.__partBundleDict.get(lodName, None)
         bundleDict = self.__partBundleDict.get(lodName, None)
-        if bundleDict == None:
+        if bundleDict is None:
             # make a dictionary to store these parts in
             # make a dictionary to store these parts in
             bundleDict = {}
             bundleDict = {}
             self.__partBundleDict[lodName] = bundleDict
             self.__partBundleDict[lodName] = bundleDict
@@ -2254,7 +2264,7 @@ class Actor(DirectObject, NodePath):
             for lodName in lodNames:
             for lodName in lodNames:
                 for partName in partNames:
                 for partName in partNames:
                     for animDef in self.__animControlDict[lodName][partName].values():
                     for animDef in self.__animControlDict[lodName][partName].values():
-                        if animDef.animControl != None:
+                        if animDef.animControl is not None:
                             # Try to clear any control effects before we let
                             # Try to clear any control effects before we let
                             # our handle on them go. This is especially
                             # our handle on them go. This is especially
                             # important if the anim control was blending
                             # important if the anim control was blending
@@ -2266,12 +2276,12 @@ class Actor(DirectObject, NodePath):
                 for partName in partNames:
                 for partName in partNames:
                     for anim in anims:
                     for anim in anims:
                         animDef = self.__animControlDict[lodName][partName].get(anim)
                         animDef = self.__animControlDict[lodName][partName].get(anim)
-                        if animDef and animDef.animControl != None:
+                        if animDef and animDef.animControl is not None:
                             # Try to clear any control effects before we let
                             # Try to clear any control effects before we let
                             # our handle on them go. This is especially
                             # our handle on them go. This is especially
                             # important if the anim control was blending
                             # important if the anim control was blending
                             # animations.
                             # animations.
-                            animDef.animControl.getPart().clearControlEffects()
+                            animDef.animControl.getPart().setControlEffect(animDef.animControl, 0.0)
                             animDef.animControl = None
                             animDef.animControl = None
 
 
 
 
@@ -2329,19 +2339,19 @@ class Actor(DirectObject, NodePath):
 
 
         partDict = self.__animControlDict[lodName]
         partDict = self.__animControlDict[lodName]
         animDict = partDict.get(partName)
         animDict = partDict.get(partName)
-        if animDict == None:
+        if animDict is None:
             # It must be a subpart that hasn't been bound yet.
             # It must be a subpart that hasn't been bound yet.
             animDict = {}
             animDict = {}
             partDict[partName] = animDict
             partDict[partName] = animDict
 
 
         anim = animDict.get(animName)
         anim = animDict.get(animName)
-        if anim == None:
+        if anim is None:
             # It must be a subpart that hasn't been bound yet.
             # It must be a subpart that hasn't been bound yet.
             anim = partDict[subpartDef.truePartName].get(animName)
             anim = partDict[subpartDef.truePartName].get(animName)
             anim = anim.makeCopy()
             anim = anim.makeCopy()
             animDict[animName] = anim
             animDict[animName] = anim
 
 
-        if anim == None:
+        if anim is None:
             Actor.notify.error("actor has no animation %s", animName)
             Actor.notify.error("actor has no animation %s", animName)
 
 
         # only bind if not already bound!
         # only bind if not already bound!
@@ -2396,7 +2406,7 @@ class Actor(DirectObject, NodePath):
 
 
                 # find the part in our tree
                 # find the part in our tree
                 bundleNP = partLod.find("**/%s%s"%(Actor.partPrefix,partName))
                 bundleNP = partLod.find("**/%s%s"%(Actor.partPrefix,partName))
-                if (bundleNP != None):
+                if bundleNP is not None:
                     # store the part bundle
                     # store the part bundle
                     self.__prepareBundle(bundleNP, partDef.partModel,
                     self.__prepareBundle(bundleNP, partDef.partModel,
                                          partName, lodName)
                                          partName, lodName)
@@ -2468,7 +2478,7 @@ class Actor(DirectObject, NodePath):
         else:
         else:
             lodNames = [lodName]
             lodNames = [lodName]
 
 
-        if partName == None and self.__subpartsComplete:
+        if partName is None and self.__subpartsComplete:
             partNames = self.__subpartDict.keys()
             partNames = self.__subpartDict.keys()
         else:
         else:
             partNames = [partName]
             partNames = [partName]

+ 6 - 8
direct/src/cluster/ClusterMsgs.py

@@ -31,18 +31,16 @@ CLUSTER_DAEMON_PORT = 8001
 CLUSTER_SERVER_PORT = 1970
 CLUSTER_SERVER_PORT = 1970
 
 
 # Precede command string with ! to tell server to execute command string
 # Precede command string with ! to tell server to execute command string
-# NOTE: Had to stick with the import __builtin__ scheme, at startup,
-# __builtins__ is a module, not a dictionary, like it is inside of a module
 # Note, this startup string obviates the need to set any cluster related
 # Note, this startup string obviates the need to set any cluster related
 # config variables in the client Configrc files
 # config variables in the client Configrc files
 SERVER_STARTUP_STRING = (
 SERVER_STARTUP_STRING = (
     '!bash ppython -c ' +
     '!bash ppython -c ' +
-    '"import __builtin__; ' +
-    '__builtin__.clusterMode = \'server\';' +
-    '__builtin__.clusterServerPort = %s;' +
-    '__builtin__.clusterSyncFlag = %d;' +
-    '__builtin__.clusterDaemonClient = \'%s\';' +
-    '__builtin__.clusterDaemonPort = %d;'
+    '"import builtins; ' +
+    'builtins.clusterMode = \'server\';' +
+    'builtins.clusterServerPort = %s;' +
+    'builtins.clusterSyncFlag = %d;' +
+    'builtins.clusterDaemonClient = \'%s\';' +
+    'builtins.clusterDaemonPort = %d;'
     'from direct.directbase.DirectStart import *; run()"')
     'from direct.directbase.DirectStart import *; run()"')
 
 
 class ClusterMsgHandler:
 class ClusterMsgHandler:

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

@@ -144,7 +144,7 @@ class ControlManager:
     def delete(self):
     def delete(self):
         assert self.notify.debugCall(id(self))
         assert self.notify.debugCall(id(self))
         self.disable()
         self.disable()
-        for controls in self.controls.keys():
+        for controls in list(self.controls.keys()):
             self.remove(controls)
             self.remove(controls)
         del self.controls
         del self.controls
         del self.currentControls
         del self.currentControls

+ 3 - 0
direct/src/dcparse/CMakeLists.txt

@@ -0,0 +1,3 @@
+add_executable(p3dcparse dcparse.cxx)
+target_link_libraries(p3dcparse p3direct)
+install(TARGETS p3dcparse EXPORT Direct COMPONENT Direct DESTINATION ${CMAKE_INSTALL_BINDIR})

+ 87 - 0
direct/src/dcparser/CMakeLists.txt

@@ -0,0 +1,87 @@
+set(P3DCPARSER_HEADERS
+  dcAtomicField.h dcAtomicField.I
+  dcClass.h dcClass.I
+  dcDeclaration.h
+  dcField.h dcField.I
+  dcFile.h dcFile.I
+  dcKeyword.h dcKeywordList.h
+  dcLexer.lxx dcLexerDefs.h
+  dcMolecularField.h
+  dcParser.yxx dcParserDefs.h
+  dcSubatomicType.h
+  dcPackData.h dcPackData.I
+  dcPacker.h dcPacker.I
+  dcPackerCatalog.h dcPackerCatalog.I
+  dcPackerInterface.h dcPackerInterface.I
+  dcParameter.h
+  dcClassParameter.h
+  dcArrayParameter.h
+  dcSimpleParameter.h
+  dcSwitchParameter.h
+  dcNumericRange.h dcNumericRange.I
+  dcSwitch.h
+  dcTypedef.h
+  dcbase.h
+  dcindent.h
+  dcmsgtypes.h
+  hashGenerator.h
+  primeNumberGenerator.h
+)
+
+set(P3DCPARSER_SOURCES
+  dcAtomicField.cxx
+  dcClass.cxx
+  dcDeclaration.cxx
+  dcField.cxx
+  dcFile.cxx
+  dcKeyword.cxx
+  dcKeywordList.cxx
+  dcMolecularField.cxx
+  dcSubatomicType.cxx
+  dcPackData.cxx
+  dcPacker.cxx
+  dcPackerCatalog.cxx
+  dcPackerInterface.cxx
+  dcParameter.cxx
+  dcClassParameter.cxx
+  dcArrayParameter.cxx
+  dcSimpleParameter.cxx
+  dcSwitchParameter.cxx
+  dcSwitch.cxx
+  dcTypedef.cxx
+  dcindent.cxx
+  hashGenerator.cxx
+  primeNumberGenerator.cxx
+)
+
+set(P3DCPARSER_IGATEEXT
+  dcClass_ext.cxx dcClass_ext.h
+  dcField_ext.cxx dcField_ext.h
+  dcPacker_ext.cxx dcPacker_ext.h
+)
+
+add_bison_target(dcParser.cxx dcParser.yxx DEFINES dcParser.h PREFIX dcyy)
+add_flex_target(dcLexer.cxx dcLexer.lxx CASE_INSENSITIVE PREFIX dcyy)
+
+# These cannot be interrogated, and are excluded from the composites.
+set(P3DCPARSER_PARSER_SOURCES
+    dcParser.cxx
+    dcLexer.cxx)
+
+composite_sources(p3dcparser P3DCPARSER_SOURCES)
+add_component_library(p3dcparser NOINIT SYMBOL BUILDING_DIRECT_DCPARSER
+  ${P3DCPARSER_HEADERS} ${P3DCPARSER_SOURCES} ${P3DCPARSER_PARSER_SOURCES})
+target_compile_definitions(p3dcparser PUBLIC WITHIN_PANDA)
+target_link_libraries(p3dcparser p3directbase panda)
+target_interrogate(p3dcparser ${P3DCPARSER_HEADERS} ${P3DCPARSER_SOURCES}
+  EXTENSIONS ${P3DCPARSER_IGATEEXT})
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3dcparser
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DCPARSER_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

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

@@ -85,9 +85,7 @@ DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
  */
  */
 DCClass::
 DCClass::
 ~DCClass() {
 ~DCClass() {
-  if (_constructor != nullptr) {
-    delete _constructor;
-  }
+  delete _constructor;
 
 
   Fields::iterator fi;
   Fields::iterator fi;
   for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
   for (fi = _fields.begin(); fi != _fields.end(); ++fi) {

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

@@ -540,11 +540,7 @@ client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
 
 
   for (int i = 0; i < num_optional_fields; i++) {
   for (int i = 0; i < num_optional_fields; i++) {
     PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
     PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
     std::string field_name = PyUnicode_AsUTF8(py_field_name);
     std::string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-    std::string field_name = PyString_AsString(py_field_name);
-#endif
     Py_XDECREF(py_field_name);
     Py_XDECREF(py_field_name);
 
 
     DCField *field = _this->get_field_by_name(field_name);
     DCField *field = _this->get_field_by_name(field_name);
@@ -621,11 +617,7 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
 
 
     for (int i = 0; i < num_optional_fields; ++i) {
     for (int i = 0; i < num_optional_fields; ++i) {
       PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
       PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
-#if PY_MAJOR_VERSION >= 3
       std::string field_name = PyUnicode_AsUTF8(py_field_name);
       std::string field_name = PyUnicode_AsUTF8(py_field_name);
-#else
-      std::string field_name = PyString_AsString(py_field_name);
-#endif
       Py_XDECREF(py_field_name);
       Py_XDECREF(py_field_name);
 
 
       DCField *field = _this->get_field_by_name(field_name);
       DCField *field = _this->get_field_by_name(field_name);

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

@@ -266,22 +266,14 @@ get_pystr(PyObject *value) {
 
 
   PyObject *str = PyObject_Str(value);
   PyObject *str = PyObject_Str(value);
   if (str != nullptr) {
   if (str != nullptr) {
-#if PY_MAJOR_VERSION >= 3
     std::string result = PyUnicode_AsUTF8(str);
     std::string result = PyUnicode_AsUTF8(str);
-#else
-    std::string result = PyString_AsString(str);
-#endif
     Py_DECREF(str);
     Py_DECREF(str);
     return result;
     return result;
   }
   }
 
 
   PyObject *repr = PyObject_Repr(value);
   PyObject *repr = PyObject_Repr(value);
   if (repr != nullptr) {
   if (repr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
     std::string result = PyUnicode_AsUTF8(repr);
     std::string result = PyUnicode_AsUTF8(repr);
-#else
-    std::string result = PyString_AsString(repr);
-#endif
     Py_DECREF(repr);
     Py_DECREF(repr);
     return result;
     return result;
   }
   }
@@ -289,11 +281,7 @@ get_pystr(PyObject *value) {
   if (value->ob_type != nullptr) {
   if (value->ob_type != nullptr) {
     PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
     PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
     if (typestr != nullptr) {
     if (typestr != nullptr) {
-#if PY_MAJOR_VERSION >= 3
       std::string result = PyUnicode_AsUTF8(typestr);
       std::string result = PyUnicode_AsUTF8(typestr);
-#else
-      std::string result = PyString_AsString(typestr);
-#endif
       Py_DECREF(typestr);
       Py_DECREF(typestr);
       return result;
       return result;
     }
     }

+ 5 - 3
direct/src/dcparser/dcPacker.I

@@ -1124,7 +1124,9 @@ operator new(size_t size) {
  */
  */
 INLINE void DCPacker::StackElement::
 INLINE void DCPacker::StackElement::
 operator delete(void *ptr) {
 operator delete(void *ptr) {
-  StackElement *obj = (StackElement *)ptr;
-  obj->_next = _deleted_chain;
-  _deleted_chain = obj;
+  if (ptr != nullptr) {
+    StackElement *obj = (StackElement *)ptr;
+    obj->_next = _deleted_chain;
+    _deleted_chain = obj;
+  }
 }
 }

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

@@ -44,9 +44,7 @@ DCPackerCatalog(const DCPackerCatalog &copy) :
  */
  */
 DCPackerCatalog::
 DCPackerCatalog::
 ~DCPackerCatalog() {
 ~DCPackerCatalog() {
-  if (_live_catalog != nullptr) {
-    delete _live_catalog;
-  }
+  delete _live_catalog;
 
 
   SwitchCatalogs::iterator si;
   SwitchCatalogs::iterator si;
   for (si = _switch_catalogs.begin(); si != _switch_catalogs.end(); ++si) {
   for (si = _switch_catalogs.begin(); si != _switch_catalogs.end(); ++si) {

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

@@ -60,9 +60,7 @@ DCPackerInterface(const DCPackerInterface &copy) :
  */
  */
 DCPackerInterface::
 DCPackerInterface::
 ~DCPackerInterface() {
 ~DCPackerInterface() {
-  if (_catalog != nullptr) {
-    delete _catalog;
-  }
+  delete _catalog;
 }
 }
 
 
 /**
 /**

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

@@ -40,12 +40,6 @@ pack_object(PyObject *object) {
       _this->pack_int64(PyLong_AsLongLong(object));
       _this->pack_int64(PyLong_AsLongLong(object));
       return;
       return;
     }
     }
-#if PY_MAJOR_VERSION < 3
-    else if (PyInt_Check(object)) {
-      _this->pack_int64(PyInt_AsLong(object));
-      return;
-    }
-#endif
     break;
     break;
 
 
   case PT_uint64:
   case PT_uint64:
@@ -53,14 +47,6 @@ pack_object(PyObject *object) {
       _this->pack_uint64(PyLong_AsUnsignedLongLong(object));
       _this->pack_uint64(PyLong_AsUnsignedLongLong(object));
       return;
       return;
     }
     }
-#if PY_MAJOR_VERSION < 3
-    else if (PyInt_Check(object)) {
-      PyObject *obj1 = PyNumber_Long(object);
-      _this->pack_int(PyLong_AsUnsignedLongLong(obj1));
-      Py_DECREF(obj1);
-      return;
-    }
-#endif
     break;
     break;
 
 
   case PT_int:
   case PT_int:
@@ -68,12 +54,6 @@ pack_object(PyObject *object) {
       _this->pack_int(PyLong_AsLong(object));
       _this->pack_int(PyLong_AsLong(object));
       return;
       return;
     }
     }
-#if PY_MAJOR_VERSION < 3
-    else if (PyInt_Check(object)) {
-      _this->pack_int(PyInt_AsLong(object));
-      return;
-    }
-#endif
     break;
     break;
 
 
   case PT_uint:
   case PT_uint:
@@ -81,14 +61,6 @@ pack_object(PyObject *object) {
       _this->pack_uint(PyLong_AsUnsignedLong(object));
       _this->pack_uint(PyLong_AsUnsignedLong(object));
       return;
       return;
     }
     }
-#if PY_MAJOR_VERSION < 3
-    else if (PyInt_Check(object)) {
-      PyObject *obj1 = PyNumber_Long(object);
-      _this->pack_uint(PyLong_AsUnsignedLong(obj1));
-      Py_DECREF(obj1);
-      return;
-    }
-#endif
     break;
     break;
 
 
   default:
   default:
@@ -97,15 +69,10 @@ pack_object(PyObject *object) {
 
 
   if (PyLong_Check(object)) {
   if (PyLong_Check(object)) {
     _this->pack_int(PyLong_AsLong(object));
     _this->pack_int(PyLong_AsLong(object));
-#if PY_MAJOR_VERSION < 3
-  } else if (PyInt_Check(object)) {
-    _this->pack_int(PyInt_AS_LONG(object));
-#endif
   } else if (PyFloat_Check(object)) {
   } else if (PyFloat_Check(object)) {
     _this->pack_double(PyFloat_AS_DOUBLE(object));
     _this->pack_double(PyFloat_AS_DOUBLE(object));
   } else if (PyLong_Check(object)) {
   } else if (PyLong_Check(object)) {
     _this->pack_int64(PyLong_AsLongLong(object));
     _this->pack_int64(PyLong_AsLongLong(object));
-#if PY_MAJOR_VERSION >= 3
   } else if (PyUnicode_Check(object)) {
   } else if (PyUnicode_Check(object)) {
     const char *buffer;
     const char *buffer;
     Py_ssize_t length;
     Py_ssize_t length;
@@ -120,15 +87,6 @@ pack_object(PyObject *object) {
     if (buffer) {
     if (buffer) {
       _this->pack_blob(vector_uchar(buffer, buffer + length));
       _this->pack_blob(vector_uchar(buffer, buffer + length));
     }
     }
-#else
-  } else if (PyString_Check(object) || PyUnicode_Check(object)) {
-    char *buffer;
-    Py_ssize_t length;
-    PyString_AsStringAndSize(object, &buffer, &length);
-    if (buffer) {
-      _this->pack_string(std::string(buffer, length));
-    }
-#endif
   } else {
   } else {
     // For some reason, PySequence_Check() is incorrectly reporting that a
     // For some reason, PySequence_Check() is incorrectly reporting that a
     // class instance is a sequence, even if it doesn't provide __len__, so we
     // class instance is a sequence, even if it doesn't provide __len__, so we
@@ -230,26 +188,14 @@ unpack_object() {
   case PT_int:
   case PT_int:
     {
     {
       int value = _this->unpack_int();
       int value = _this->unpack_int();
-#if PY_MAJOR_VERSION >= 3
       object = PyLong_FromLong(value);
       object = PyLong_FromLong(value);
-#else
-      object = PyInt_FromLong(value);
-#endif
     }
     }
     break;
     break;
 
 
   case PT_uint:
   case PT_uint:
     {
     {
       unsigned int value = _this->unpack_uint();
       unsigned int value = _this->unpack_uint();
-#if PY_MAJOR_VERSION >= 3
       object = PyLong_FromLong(value);
       object = PyLong_FromLong(value);
-#else
-      if (value & 0x80000000) {
-        object = PyLong_FromUnsignedLong(value);
-      } else {
-        object = PyInt_FromLong(value);
-      }
-#endif
     }
     }
     break;
     break;
 
 
@@ -268,25 +214,18 @@ unpack_object() {
     break;
     break;
 
 
   case PT_blob:
   case PT_blob:
-#if PY_MAJOR_VERSION >= 3
     {
     {
       std::string str;
       std::string str;
       _this->unpack_string(str);
       _this->unpack_string(str);
       object = PyBytes_FromStringAndSize(str.data(), str.size());
       object = PyBytes_FromStringAndSize(str.data(), str.size());
     }
     }
     break;
     break;
-#endif
-    // On Python 2, fall through to below.
 
 
   case PT_string:
   case PT_string:
     {
     {
       std::string str;
       std::string str;
       _this->unpack_string(str);
       _this->unpack_string(str);
-#if PY_MAJOR_VERSION >= 3
       object = PyUnicode_FromStringAndSize(str.data(), str.size());
       object = PyUnicode_FromStringAndSize(str.data(), str.size());
-#else
-      object = PyString_FromStringAndSize(str.data(), str.size());
-#endif
     }
     }
     break;
     break;
 
 

+ 3 - 9
direct/src/dcparser/dcSwitch.cxx

@@ -42,21 +42,15 @@ DCSwitch::
   nassertv(_key_parameter != nullptr);
   nassertv(_key_parameter != nullptr);
   delete _key_parameter;
   delete _key_parameter;
 
 
-  Cases::iterator ci;
-  for (ci = _cases.begin(); ci != _cases.end(); ++ci) {
-    SwitchCase *dcase = (*ci);
+  for (SwitchCase *dcase : _cases) {
     delete dcase;
     delete dcase;
   }
   }
 
 
-  CaseFields::iterator fi;
-  for (fi = _case_fields.begin(); fi != _case_fields.end(); ++fi) {
-    SwitchFields *fields = (*fi);
+  for (SwitchFields *fields : _case_fields) {
     delete fields;
     delete fields;
   }
   }
 
 
-  Fields::iterator ni;
-  for (ni = _nested_fields.begin(); ni != _nested_fields.end(); ++ni) {
-    DCField *field = (*ni);
+  for (DCField *field : _nested_fields) {
     delete field;
     delete field;
   }
   }
 }
 }

+ 3 - 3
direct/src/dcparser/dcbase.h

@@ -36,12 +36,12 @@
 
 
 #else  // WITHIN_PANDA
 #else  // WITHIN_PANDA
 
 
-#ifdef WIN32
+#ifdef _MSC_VER
 /* C4786: 255 char debug symbols */
 /* C4786: 255 char debug symbols */
 #pragma warning (disable : 4786)
 #pragma warning (disable : 4786)
 /* C4503: decorated name length exceeded */
 /* C4503: decorated name length exceeded */
 #pragma warning (disable : 4503)
 #pragma warning (disable : 4503)
-#endif  /* WIN32_VC */
+#endif  /* _MSC_VER */
 
 
 #include <iostream>
 #include <iostream>
 #include <fstream>
 #include <fstream>
@@ -54,7 +54,7 @@
 // These header files are needed to compile dcLexer.cxx, the output from flex.
 // These header files are needed to compile dcLexer.cxx, the output from flex.
 // flex doesn't create a perfectly windows-friendly source file right out of
 // flex doesn't create a perfectly windows-friendly source file right out of
 // the box.
 // the box.
-#ifdef WIN32
+#ifdef _WIN32
 #include <io.h>
 #include <io.h>
 #include <malloc.h>
 #include <malloc.h>
 #else
 #else

+ 24 - 0
direct/src/deadrec/CMakeLists.txt

@@ -0,0 +1,24 @@
+set(P3DEADREC_HEADERS
+  config_deadrec.h
+  smoothMover.h smoothMover.I
+)
+
+set(P3DEADREC_SOURCES
+  config_deadrec.cxx
+  smoothMover.cxx
+)
+
+add_component_library(p3deadrec SYMBOL BUILDING_DIRECT_DEADREC
+  ${P3DEADREC_HEADERS} ${P3DEADREC_SOURCES})
+target_link_libraries(p3deadrec p3directbase panda)
+target_interrogate(p3deadrec ALL)
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3deadrec
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DEADREC_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 14 - 0
direct/src/directbase/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(P3DIRECTBASE_SOURCES
+  directbase.cxx
+)
+
+set(P3DIRECTBASE_HEADERS
+  directbase.h directsymbols.h
+)
+
+# Yes, INTERFACE: don't build it, there's no code!
+add_library(p3directbase INTERFACE)
+target_link_libraries(p3directbase INTERFACE panda)
+
+install(TARGETS p3directbase EXPORT Direct COMPONENT Direct)
+install(FILES ${P3DIRECTBASE_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 3 - 8
direct/src/directscripts/extract_docs.py

@@ -9,7 +9,7 @@ from __future__ import print_function
 
 
 __all__ = []
 __all__ = []
 
 
-import os, sys
+import os
 from distutils import sysconfig
 from distutils import sysconfig
 import panda3d, pandac
 import panda3d, pandac
 from panda3d.interrogatedb import *
 from panda3d.interrogatedb import *
@@ -309,13 +309,8 @@ if __name__ == "__main__":
     processModule(handle, "core")
     processModule(handle, "core")
 
 
     # Determine the suffix for the extension modules.
     # Determine the suffix for the extension modules.
-    if sys.version_info >= (3, 0):
-        import _imp
-        ext_suffix = _imp.extension_suffixes()[0]
-    elif sys.platform == "win32":
-        ext_suffix = ".pyd"
-    else:
-        ext_suffix = ".so"
+    import _imp
+    ext_suffix = _imp.extension_suffixes()[0]
 
 
     for lib in os.listdir(os.path.dirname(panda3d.__file__)):
     for lib in os.listdir(os.path.dirname(panda3d.__file__)):
         if lib.endswith(ext_suffix) and not lib.startswith('core.'):
         if lib.endswith(ext_suffix) and not lib.startswith('core.'):

+ 1 - 5
direct/src/directtools/DirectSession.py

@@ -1,5 +1,4 @@
 import math
 import math
-import sys
 
 
 from panda3d.core import *
 from panda3d.core import *
 from .DirectUtil import *
 from .DirectUtil import *
@@ -942,10 +941,7 @@ class DirectSession(DirectObject):
 
 
     def getAndSetName(self, nodePath):
     def getAndSetName(self, nodePath):
         """ Prompt user for new node path name """
         """ Prompt user for new node path name """
-        if sys.version_info >= (3, 0):
-            from tkinter.simpledialog import askstring
-        else:
-            from tkSimpleDialog import askstring
+        from tkinter.simpledialog import askstring
         newName = askstring('Node Path: ' + nodePath.getName(),
         newName = askstring('Node Path: ' + nodePath.getName(),
                             'Enter new name:')
                             'Enter new name:')
         if newName:
         if newName:

+ 26 - 35
direct/src/dist/FreezeTool.py

@@ -11,6 +11,7 @@ import struct
 import io
 import io
 import distutils.sysconfig as sysconf
 import distutils.sysconfig as sysconf
 import zipfile
 import zipfile
+import importlib
 
 
 from . import pefile
 from . import pefile
 
 
@@ -35,21 +36,15 @@ isDebugBuild = (python.lower().endswith('_d'))
 # NB. if encodings are removed, be sure to remove them from the shortcut in
 # NB. if encodings are removed, be sure to remove them from the shortcut in
 # deploy-stub.c.
 # deploy-stub.c.
 startupModules = [
 startupModules = [
-    'imp', 'encodings', 'encodings.*',
+    'imp', 'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
+    'importlib.util',
 ]
 ]
-if sys.version_info >= (3, 0):
-    # Modules specific to Python 3
-    startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util']
-else:
-    # Modules specific to Python 2
-    startupModules += []
 
 
 # These are some special init functions for some built-in Python modules that
 # These are some special init functions for some built-in Python modules that
 # deviate from the standard naming convention.  A value of None means that a
 # deviate from the standard naming convention.  A value of None means that a
 # dummy entry should be written to the inittab.
 # dummy entry should be written to the inittab.
 builtinInitFuncs = {
 builtinInitFuncs = {
     'builtins': None,
     'builtins': None,
-    '__builtin__': None,
     'sys': None,
     'sys': None,
     'exceptions': None,
     'exceptions': None,
     '_warnings': '_PyWarnings_Init',
     '_warnings': '_PyWarnings_Init',
@@ -75,6 +70,7 @@ hiddenImports = {
     'datetime': ['_strptime'],
     'datetime': ['_strptime'],
     'keyring.backends': ['keyring.backends.*'],
     'keyring.backends': ['keyring.backends.*'],
     'matplotlib.font_manager': ['encodings.mac_roman'],
     'matplotlib.font_manager': ['encodings.mac_roman'],
+    'matplotlib.backends._backend_tk': ['tkinter'],
     'direct.particles': ['direct.particles.ParticleManagerGlobal'],
     'direct.particles': ['direct.particles.ParticleManagerGlobal'],
     'numpy.core._multiarray_umath': [
     'numpy.core._multiarray_umath': [
         'numpy.core._internal',
         'numpy.core._internal',
@@ -83,12 +79,6 @@ hiddenImports = {
     ],
     ],
 }
 }
 
 
-if sys.version_info >= (3,):
-    hiddenImports['matplotlib.backends._backend_tk'] = ['tkinter']
-else:
-    hiddenImports['matplotlib.backends._backend_tk'] = ['Tkinter']
-
-
 # These are overrides for specific modules.
 # These are overrides for specific modules.
 overrideModules = {
 overrideModules = {
     # Used by the warnings module, among others, to get line numbers.  Since
     # Used by the warnings module, among others, to get line numbers.  Since
@@ -1310,11 +1300,7 @@ class Freezer:
 
 
     def __addPyc(self, multifile, filename, code, compressionLevel):
     def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
         if code:
-            data = imp.get_magic() + b'\0\0\0\0'
-
-            if sys.version_info >= (3, 0):
-                data += b'\0\0\0\0'
-
+            data = imp.get_magic() + b'\0\0\0\0\0\0\0\0'
             data += marshal.dumps(code)
             data += marshal.dumps(code)
 
 
             stream = StringStream(data)
             stream = StringStream(data)
@@ -1671,10 +1657,7 @@ class Freezer:
                     # initmodule or PyInit_module function.
                     # initmodule or PyInit_module function.
                     modname = mod.split('.')[-1]
                     modname = mod.split('.')[-1]
                     libfile = modname + '.lib'
                     libfile = modname + '.lib'
-                    if sys.version_info >= (3, 0):
-                        symbolName = 'PyInit_' + modname
-                    else:
-                        symbolName = 'init' + modname
+                    symbolName = 'PyInit_' + modname
                     os.system('lib /nologo /def /export:%s /name:%s.pyd /out:%s' % (symbolName, modname, libfile))
                     os.system('lib /nologo /def /export:%s /name:%s.pyd /out:%s' % (symbolName, modname, libfile))
                     extraLink.append(libfile)
                     extraLink.append(libfile)
                     cleanFiles += [libfile, modname + '.exp']
                     cleanFiles += [libfile, modname + '.exp']
@@ -1778,10 +1761,7 @@ class Freezer:
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                 else:
                 else:
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
-                if sys.version_info >= (3, 2):
-                    code = compile(code, moduleName, 'exec', optimize=2)
-                else:
-                    code = compile(code, moduleName, 'exec')
+                code = compile(code, moduleName, 'exec', optimize=2)
                 code = marshal.dumps(code)
                 code = marshal.dumps(code)
                 moduleList.append((moduleName, len(pool), len(code)))
                 moduleList.append((moduleName, len(pool), len(code)))
                 pool += code
                 pool += code
@@ -2273,7 +2253,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                 except KeyError:
                 except KeyError:
                     return None
                     return None
 
 
-                if sys.version_info >= (3, 0) and 'b' not in mode:
+                if 'b' not in mode:
                     return io.TextIOWrapper(fp, encoding='utf8')
                     return io.TextIOWrapper(fp, encoding='utf8')
                 return fp
                 return fp
 
 
@@ -2349,12 +2329,23 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             code += b'\n' if isinstance(code, bytes) else '\n'
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec')
             co = compile(code, pathname, 'exec')
         elif type == imp.PY_COMPILED:
         elif type == imp.PY_COMPILED:
-            try:
-                marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read())
-            except ImportError as exc:
-                self.msgout(2, "raise ImportError: " + str(exc), pathname)
-                raise
-            co = marshal.loads(marshal_data)
+            if sys.version_info >= (3, 7):
+                try:
+                    data = fp.read()
+                    importlib._bootstrap_external._classify_pyc(data, fqname, {})
+                except ImportError as exc:
+                    self.msgout(2, "raise ImportError: " + str(exc), pathname)
+                    raise
+
+                co = marshal.loads(memoryview(data)[16:])
+            else:
+                try:
+                    marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read())
+                except ImportError as exc:
+                    self.msgout(2, "raise ImportError: " + str(exc), pathname)
+                    raise
+
+                co = marshal.loads(marshal_data)
         else:
         else:
             co = None
             co = None
 
 
@@ -2464,7 +2455,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
         # If we found folders on the path with this module name without an
         # If we found folders on the path with this module name without an
         # __init__.py file, we should consider this a namespace package.
         # __init__.py file, we should consider this a namespace package.
-        if ns_dirs and sys.version_info >= (3, 3):
+        if ns_dirs:
             return (None, ns_dirs, ('', '', _PKG_NAMESPACE_DIRECTORY))
             return (None, ns_dirs, ('', '', _PKG_NAMESPACE_DIRECTORY))
 
 
         raise ImportError(name)
         raise ImportError(name)

+ 103 - 126
direct/src/dist/commands.py

@@ -8,7 +8,6 @@ from __future__ import print_function
 
 
 import collections
 import collections
 import os
 import os
-import pip
 import plistlib
 import plistlib
 import sys
 import sys
 import subprocess
 import subprocess
@@ -31,26 +30,21 @@ from .icon import Icon
 import panda3d.core as p3d
 import panda3d.core as p3d
 
 
 
 
-if 'basestring' not in globals():
-    basestring = str
-
-
 if sys.version_info < (3, 0):
 if sys.version_info < (3, 0):
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     FileNotFoundError = IOError
     FileNotFoundError = IOError
 
 
     # Warn the user.  They might be using Python 2 by accident.
     # Warn the user.  They might be using Python 2 by accident.
     print("=================================================================")
     print("=================================================================")
-    print("WARNING: You are using Python 2, which will soon be discontinued.")
-    print("WARNING: Please use Python 3 for best results and continued")
-    print("WARNING: support after the EOL date of December 31st, 2019.")
+    print("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("=================================================================")
     print("=================================================================")
     sys.stdout.flush()
     sys.stdout.flush()
     time.sleep(4.0)
     time.sleep(4.0)
 
 
 
 
 def _parse_list(input):
 def _parse_list(input):
-    if isinstance(input, basestring):
+    if isinstance(input, str):
         input = input.strip().replace(',', '\n')
         input = input.strip().replace(',', '\n')
         if input:
         if input:
             return [item.strip() for item in input.split('\n') if item.strip()]
             return [item.strip() for item in input.split('\n') if item.strip()]
@@ -113,6 +107,7 @@ PACKAGE_DATA_DIRS = {
         ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
         ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
         ('cefpython3/locals/*', 'locals', {}),
         ('cefpython3/locals/*', 'locals', {}),
         ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
         ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
+        ('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
     ],
     ],
 }
 }
 
 
@@ -124,45 +119,7 @@ PACKAGE_LIB_DIRS = {
     'scipy':  ['scipy/extra-dll'],
     'scipy':  ['scipy/extra-dll'],
 }
 }
 
 
-# site.py for Python 2.
-SITE_PY2 = u"""
-import sys
-
-sys.frozen = True
-
-# Override __import__ to set __file__ for frozen modules.
-prev_import = __import__
-def __import__(*args, **kwargs):
-    mod = prev_import(*args, **kwargs)
-    if mod:
-        mod.__file__ = sys.executable
-    return mod
-
-# Add our custom __import__ version to the global scope, as well as a builtin
-# definition for __file__ so that it is available in the module itself.
-import __builtin__
-__builtin__.__import__ = __import__
-__builtin__.__file__ = sys.executable
-del __builtin__
-
-# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
-import os
-tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
-if os.path.isdir(tcl_dir):
-    for dir in os.listdir(tcl_dir):
-        sub_dir = os.path.join(tcl_dir, dir)
-        if os.path.isdir(sub_dir):
-            if dir.startswith('tcl'):
-                os.environ['TCL_LIBRARY'] = sub_dir
-            if dir.startswith('tk'):
-                os.environ['TK_LIBRARY'] = sub_dir
-            if dir.startswith('tix'):
-                os.environ['TIX_LIBRARY'] = sub_dir
-del os
-"""
-
-# site.py for Python 3.
-SITE_PY3 = u"""
+SITE_PY = u"""
 import sys
 import sys
 from _frozen_importlib import _imp, FrozenImporter
 from _frozen_importlib import _imp, FrozenImporter
 
 
@@ -210,8 +167,6 @@ if os.path.isdir(tcl_dir):
 del os
 del os
 """
 """
 
 
-SITE_PY = SITE_PY3 if sys.version_info >= (3,) else SITE_PY2
-
 
 
 class build_apps(setuptools.Command):
 class build_apps(setuptools.Command):
     description = 'build Panda3D applications'
     description = 'build Panda3D applications'
@@ -271,6 +226,7 @@ class build_apps(setuptools.Command):
             'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
             'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
 
 
             # macOS
             # macOS
+            '/usr/lib/libc++.1.dylib',
             '/usr/lib/libstdc++.*.dylib',
             '/usr/lib/libstdc++.*.dylib',
             '/usr/lib/libz.*.dylib',
             '/usr/lib/libz.*.dylib',
             '/usr/lib/libobjc.*.dylib',
             '/usr/lib/libobjc.*.dylib',
@@ -393,29 +349,22 @@ class build_apps(setuptools.Command):
         directory containing the Python runtime libraries, which will be added
         directory containing the Python runtime libraries, which will be added
         to sys.path."""
         to sys.path."""
 
 
-        self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO)
-
-        whldir = os.path.join(self.build_base, '__whl_cache__')
+        import pip
 
 
-        #TODO find a better way to get abi tag than from internal/private pip APIs
-        if hasattr(pip, 'pep425tags'):
-            pep425tags = pip.pep425tags
-            wheel = pip.wheel
-        else:
-            from pip._internal import pep425tags, wheel
+        self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO)
 
 
-        abi_tag = pep425tags.get_abi_tag()
+        whlcache = os.path.join(self.build_base, '__whl_cache__')
 
 
-        if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')):
-            abi_tag = abi_tag.replace('u', '')
+        pip_version = int(pip.__version__.split('.')[0])
+        if pip_version < 9:
+            raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__))
 
 
-        # For these distributions, we need to append 'u' on Linux
-        if abi_tag in ('cp26m', 'cp27m', 'cp32m') and not platform.startswith('win') and not platform.startswith('macosx'):
-            abi_tag += 'u'
+        abi_tag = 'cp%d%d' % (sys.version_info[:2])
+        if sys.version_info < (3, 8):
+            abi_tag += 'm'
 
 
-        pip_version = pip.__version__.split('.')
-        if int(pip_version[0]) < 9:
-            raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__))
+        whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
+        os.makedirs(whldir, exist_ok=True)
 
 
         # Remove any .zip files. These are built from a VCS and block for an
         # Remove any .zip files. These are built from a VCS and block for an
         # interactive prompt on subsequent downloads.
         # interactive prompt on subsequent downloads.
@@ -443,19 +392,12 @@ class build_apps(setuptools.Command):
 
 
         subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
         subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
 
 
-        # Now figure out which of the downloaded wheels are relevant to us.
-        tags = pep425tags.get_supported(platform=platform, abi=abi_tag)
-        wheelpaths = []
-        for filename in os.listdir(whldir):
-            try:
-                whl = wheel.Wheel(filename)
-            except wheel.InvalidWheelFilename:
-                continue
-
-            if whl.supported(tags):
-                wheelpaths.append(os.path.join(whldir, filename))
-
-        return wheelpaths
+        # Return a list of paths to the downloaded whls
+        return [
+            os.path.join(whldir, filename)
+            for filename in os.listdir(whldir)
+            if filename.endswith('.whl')
+        ]
 
 
     def update_pe_resources(self, appname, runtime):
     def update_pe_resources(self, appname, runtime):
         """Update resources (e.g., icons) in windows PE file"""
         """Update resources (e.g., icons) in windows PE file"""
@@ -581,15 +523,19 @@ class build_apps(setuptools.Command):
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             etcdir = os.path.join(libdir, '..', 'etc')
             etcdir = os.path.join(libdir, '..', 'etc')
 
 
-            for fn in os.listdir(etcdir):
+            etcfiles = os.listdir(etcdir)
+            etcfiles.sort(reverse=True)
+            for fn in etcfiles:
                 if fn.lower().endswith('.prc'):
                 if fn.lower().endswith('.prc'):
                     with open(os.path.join(etcdir, fn)) as f:
                     with open(os.path.join(etcdir, fn)) as f:
                         prcstring += f.read()
                         prcstring += f.read()
         else:
         else:
             etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')]
             etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')]
+            etcfiles.sort(reverse=True)
             for fn in etcfiles:
             for fn in etcfiles:
                 with p3dwhl.open(fn) as f:
                 with p3dwhl.open(fn) as f:
                     prcstring += f.read().decode('utf8')
                     prcstring += f.read().decode('utf8')
+
         user_prcstring = self.extra_prc_data
         user_prcstring = self.extra_prc_data
         for fn in self.extra_prc_files:
         for fn in self.extra_prc_files:
             with open(fn) as f:
             with open(fn) as f:
@@ -608,12 +554,33 @@ class build_apps(setuptools.Command):
             for ln in prcstr.split('\n'):
             for ln in prcstr.split('\n'):
                 ln = ln.strip()
                 ln = ln.strip()
                 useline = True
                 useline = True
+
                 if ln.startswith('#') or not ln:
                 if ln.startswith('#') or not ln:
                     continue
                     continue
-                if 'model-cache-dir' in ln:
-                    ln = ln.replace('/panda3d', '/{}'.format(self.distribution.get_name()))
+
+                words = ln.split(None, 1)
+                if not words:
+                    continue
+                var = words[0]
+                value = words[1] if len(words) > 1 else ''
+
+                # Strip comment after value.
+                c = value.find(' #')
+                if c > 0:
+                    value = value[:c].rstrip()
+
+                if var == 'model-cache-dir' and value:
+                    value = value.replace('/panda3d', '/{}'.format(self.distribution.get_name()))
+
+                if var == 'audio-library-name':
+                    # We have the default set to p3fmod_audio on macOS in 1.10,
+                    # but this can be unexpected as other platforms use OpenAL
+                    # 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")
+
                 for plugin in check_plugins:
                 for plugin in check_plugins:
-                    if plugin in ln and plugin not in self.plugins:
+                    if plugin in value and plugin not in self.plugins:
                         useline = False
                         useline = False
                         if warn_on_missing_plugin:
                         if warn_on_missing_plugin:
                             self.warn(
                             self.warn(
@@ -621,7 +588,10 @@ class build_apps(setuptools.Command):
                             )
                             )
                         break
                         break
                 if useline:
                 if useline:
-                    out.append(ln)
+                    if value:
+                        out.append(var + ' ' + value)
+                    else:
+                        out.append(var)
             return out
             return out
         prcexport = parse_prc(prcstring, 0) + parse_prc(user_prcstring, 1)
         prcexport = parse_prc(prcstring, 0) + parse_prc(user_prcstring, 1)
 
 
@@ -639,6 +609,28 @@ class build_apps(setuptools.Command):
         freezer_modules = set()
         freezer_modules = set()
         freezer_modpaths = set()
         freezer_modpaths = set()
         ext_suffixes = set()
         ext_suffixes = set()
+
+        def get_search_path_for(source_path):
+            search_path = [os.path.dirname(source_path)]
+            if use_wheels:
+                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
+
+                # If the .whl containing this file has a .libs directory, add
+                # it to the path.  This is an auditwheel/numpy convention.
+                if '.whl' + os.sep in source_path:
+                    whl, wf = source_path.split('.whl' + os.path.sep)
+                    whl += '.whl'
+                    rootdir = wf.split(os.path.sep, 1)[0]
+                    search_path.append(os.path.join(whl, rootdir, '.libs'))
+
+                    # Also look for more specific per-package cases, defined in
+                    # PACKAGE_LIB_DIRS at the top of this file.
+                    whl_name = os.path.basename(whl).split('-', 1)[0]
+                    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)))
+            return search_path
+
         def create_runtime(appname, mainscript, use_console):
         def create_runtime(appname, mainscript, use_console):
             freezer = FreezeTool.Freezer(platform=platform, path=path)
             freezer = FreezeTool.Freezer(platform=platform, path=path)
             freezer.addModule('__main__', filename=mainscript)
             freezer.addModule('__main__', filename=mainscript)
@@ -766,11 +758,10 @@ class build_apps(setuptools.Command):
                     basename = module.rsplit('.', 1)[0] + '.' + basename
                     basename = module.rsplit('.', 1)[0] + '.' + basename
 
 
                 # Remove python version string
                 # Remove python version string
-                if sys.version_info >= (3, 0):
-                    parts = basename.split('.')
-                    if len(parts) >= 3 and '-' in parts[-2]:
-                        parts = parts[:-2] + parts[-1:]
-                        basename = '.'.join(parts)
+                parts = basename.split('.')
+                if len(parts) >= 3 and '-' in parts[-2]:
+                    parts = parts[:-2] + parts[-1:]
+                    basename = '.'.join(parts)
             else:
             else:
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 # Builtin module, but might not be builtin in wheel libs, so double check
                 if module in whl_modules:
                 if module in whl_modules:
@@ -781,35 +772,16 @@ class build_apps(setuptools.Command):
                     continue
                     continue
 
 
             # If this is a dynamic library, search for dependencies.
             # If this is a dynamic library, search for dependencies.
-            search_path = [os.path.dirname(source_path)]
-            if use_wheels:
-                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
-
-                # If the .whl containing this file has a .libs directory, add
-                # it to the path.  This is an auditwheel/numpy convention.
-                if '.whl' + os.sep in source_path:
-                    whl, wf = source_path.split('.whl' + os.path.sep)
-                    whl += '.whl'
-                    rootdir = wf.split(os.path.sep, 1)[0]
-                    search_path.append(os.path.join(whl, rootdir, '.libs'))
-
-                    # Also look for more specific per-package cases, defined in
-                    # PACKAGE_LIB_DIRS at the top of this file.
-                    whl_name = os.path.basename(whl).split('-', 1)[0]
-                    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)))
-
             target_path = os.path.join(builddir, basename)
             target_path = os.path.join(builddir, basename)
+            search_path = get_search_path_for(source_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
 
 
         # Copy over the tcl directory.
         # Copy over the tcl directory.
         #TODO: get this to work on non-Windows platforms.
         #TODO: get this to work on non-Windows platforms.
         if sys.platform == "win32" and platform.startswith('win'):
         if sys.platform == "win32" and platform.startswith('win'):
             tcl_dir = os.path.join(sys.prefix, 'tcl')
             tcl_dir = os.path.join(sys.prefix, 'tcl')
-            tkinter_name = 'tkinter' if sys.version_info >= (3, 0) else 'Tkinter'
 
 
-            if os.path.isdir(tcl_dir) and tkinter_name in freezer_modules:
+            if os.path.isdir(tcl_dir) and 'tkinter' in freezer_modules:
                 self.announce('Copying Tcl files', distutils.log.INFO)
                 self.announce('Copying Tcl files', distutils.log.INFO)
                 os.makedirs(os.path.join(builddir, 'tcl'))
                 os.makedirs(os.path.join(builddir, 'tcl'))
 
 
@@ -832,7 +804,7 @@ class build_apps(setuptools.Command):
                 whlfile = self._get_zip_file(whl)
                 whlfile = self._get_zip_file(whl)
                 filenames = whlfile.namelist()
                 filenames = whlfile.namelist()
                 for source_pattern, target_dir, flags in datadesc:
                 for source_pattern, target_dir, flags in datadesc:
-                    srcglob = p3d.GlobPattern(source_pattern)
+                    srcglob = p3d.GlobPattern(source_pattern.lower())
                     source_dir = os.path.dirname(source_pattern)
                     source_dir = os.path.dirname(source_pattern)
                     # Relocate the target dir to the build directory.
                     # Relocate the target dir to the build directory.
                     target_dir = target_dir.replace('/', os.sep)
                     target_dir = target_dir.replace('/', os.sep)
@@ -846,12 +818,15 @@ class build_apps(setuptools.Command):
                             relpath = wf[len(source_dir) + 1:]
                             relpath = wf[len(source_dir) + 1:]
                             source_path = os.path.join(whl, wf)
                             source_path = os.path.join(whl, wf)
                             target_path = os.path.join(target_dir, relpath)
                             target_path = os.path.join(target_dir, relpath)
-                            self.copy(source_path, target_path)
 
 
                             if 'PKG_DATA_MAKE_EXECUTABLE' in flags:
                             if 'PKG_DATA_MAKE_EXECUTABLE' in flags:
+                                search_path = get_search_path_for(source_path)
+                                self.copy_with_dependencies(source_path, target_path, search_path)
                                 mode = os.stat(target_path).st_mode
                                 mode = os.stat(target_path).st_mode
                                 mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
                                 mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
                                 os.chmod(target_path, mode)
                                 os.chmod(target_path, mode)
+                            else:
+                                self.copy(source_path, target_path)
 
 
         # Copy Game Files
         # Copy Game Files
         self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
         self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
@@ -1179,18 +1154,20 @@ class build_apps(setuptools.Command):
                     dylib = dylib.replace('@loader_path/../Frameworks/', '')
                     dylib = dylib.replace('@loader_path/../Frameworks/', '')
                 elif dylib.startswith('@executable_path/../Frameworks/'):
                 elif dylib.startswith('@executable_path/../Frameworks/'):
                     dylib = dylib.replace('@executable_path/../Frameworks/', '')
                     dylib = dylib.replace('@executable_path/../Frameworks/', '')
-                elif dylib.startswith('@loader_path/'):
-                    dylib = dylib.replace('@loader_path/', '')
-
-                    # Do we need to flatten the relative reference?
-                    if '/' in dylib and flatten:
-                        new_dylib = '@loader_path/' + os.path.basename(dylib)
-                        str_size = len(cmd_data) - 16
-                        if len(new_dylib) < str_size:
-                            fp.seek(-str_size, os.SEEK_CUR)
-                            fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0'))
-                        else:
-                            self.warn('Unable to rewrite dependency {}'.format(orig))
+                else:
+                    for prefix in ('@loader_path/', '@rpath/'):
+                        if dylib.startswith(prefix):
+                            dylib = dylib.replace(prefix, '')
+
+                            # Do we need to flatten the relative reference?
+                            if '/' in dylib and flatten:
+                                new_dylib = prefix + os.path.basename(dylib)
+                                str_size = len(cmd_data) - 16
+                                if len(new_dylib) < str_size:
+                                    fp.seek(-str_size, os.SEEK_CUR)
+                                    fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0'))
+                                else:
+                                    self.warn('Unable to rewrite dependency {}'.format(orig))
 
 
                 load_dylibs.append(dylib)
                 load_dylibs.append(dylib)
 
 

+ 6 - 10
direct/src/dist/pefile.py

@@ -10,11 +10,7 @@ from collections import namedtuple
 from array import array
 from array import array
 import time
 import time
 from io import BytesIO
 from io import BytesIO
-import sys
 
 
-if sys.version_info >= (3, 0):
-    unicode = str
-    unichr = chr
 
 
 # Define some internally used structures.
 # Define some internally used structures.
 RVASize = namedtuple('RVASize', ('addr', 'size'))
 RVASize = namedtuple('RVASize', ('addr', 'size'))
@@ -38,7 +34,7 @@ def _unpack_wstring(mem, offs=0):
     name = ""
     name = ""
     for i in range(name_len):
     for i in range(name_len):
         offs += 2
         offs += 2
-        name += unichr(*unpack('<H', mem[offs:offs+2]))
+        name += chr(*unpack('<H', mem[offs:offs+2]))
     return name
     return name
 
 
 def _padded(n, boundary):
 def _padded(n, boundary):
@@ -208,7 +204,7 @@ class VersionInfoResource(object):
         if isinstance(value, dict):
         if isinstance(value, dict):
             type = 1
             type = 1
             value_length = 0
             value_length = 0
-        elif isinstance(value, bytes) or isinstance(value, unicode):
+        elif isinstance(value, bytes) or isinstance(value, str):
             type = 1
             type = 1
             value_length = len(value) * 2 + 2
             value_length = len(value) * 2 + 2
         else:
         else:
@@ -227,7 +223,7 @@ class VersionInfoResource(object):
         if isinstance(value, dict):
         if isinstance(value, dict):
             for key2, value2 in sorted(value.items(), key=lambda x:x[0]):
             for key2, value2 in sorted(value.items(), key=lambda x:x[0]):
                 self._pack_info(data, key2, value2)
                 self._pack_info(data, key2, value2)
-        elif isinstance(value, bytes) or isinstance(value, unicode):
+        elif isinstance(value, bytes) or isinstance(value, str):
             for c in value:
             for c in value:
                 data += pack('<H', ord(c))
                 data += pack('<H', ord(c))
             data += b'\x00\x00'
             data += b'\x00\x00'
@@ -294,7 +290,7 @@ class VersionInfoResource(object):
         c, = unpack('<H', data[offset:offset+2])
         c, = unpack('<H', data[offset:offset+2])
         offset += 2
         offset += 2
         while c:
         while c:
-            key += unichr(c)
+            key += chr(c)
             c, = unpack('<H', data[offset:offset+2])
             c, = unpack('<H', data[offset:offset+2])
             offset += 2
             offset += 2
 
 
@@ -309,7 +305,7 @@ class VersionInfoResource(object):
                 c, = unpack('<H', data[offset:offset+2])
                 c, = unpack('<H', data[offset:offset+2])
                 offset += 2
                 offset += 2
                 while c:
                 while c:
-                    value += unichr(c)
+                    value += chr(c)
                     c, = unpack('<H', data[offset:offset+2])
                     c, = unpack('<H', data[offset:offset+2])
                     offset += 2
                     offset += 2
             else:
             else:
@@ -705,7 +701,7 @@ class PEFile(object):
 
 
         Returns the newly created Section object. """
         Returns the newly created Section object. """
 
 
-        if isinstance(name, unicode):
+        if isinstance(name, str):
             name = name.encode('ascii')
             name = name.encode('ascii')
 
 
         section = Section()
         section = Section()

+ 35 - 0
direct/src/distributed/CMakeLists.txt

@@ -0,0 +1,35 @@
+if(NOT HAVE_PYTHON)
+  return()
+endif()
+
+set(P3DISTRIBUTED_HEADERS
+  config_distributed.h
+  cConnectionRepository.h
+  cConnectionRepository.I
+  cDistributedSmoothNodeBase.h
+  cDistributedSmoothNodeBase.I
+)
+
+set(P3DISTRIBUTED_SOURCES
+  config_distributed.cxx
+)
+
+set(P3DISTRIBUTED_IGATEEXT
+  cConnectionRepository.cxx
+  cDistributedSmoothNodeBase.cxx
+)
+
+add_component_library(p3distributed NOINIT SYMBOL BUILDING_DIRECT_DISTRIBUTED
+  ${P3DISTRIBUTED_HEADERS} ${P3DISTRIBUTED_SOURCES})
+target_link_libraries(p3distributed p3directbase p3dcparser panda)
+target_interrogate(p3distributed ALL EXTENSIONS ${P3DISTRIBUTED_IGATEEXT})
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3distributed
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DISTRIBUTED_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 1 - 6
direct/src/distributed/NetMessenger.py

@@ -2,12 +2,7 @@
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.distributed.PyDatagram import PyDatagram
 from direct.distributed.PyDatagram import PyDatagram
 from direct.showbase.Messenger import Messenger
 from direct.showbase.Messenger import Messenger
-
-import sys
-if sys.version_info >= (3, 0):
-    from pickle import dumps, loads
-else:
-    from cPickle import dumps, loads
+from pickle import dumps, loads
 
 
 
 
 # Messages do not need to be in the MESSAGE_TYPES list.
 # Messages do not need to be in the MESSAGE_TYPES list.

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

@@ -628,7 +628,7 @@ class ServerRepository:
         del self.clientsByConnection[client.connection]
         del self.clientsByConnection[client.connection]
         del self.clientsByDoIdBase[client.doIdBase]
         del self.clientsByDoIdBase[client.doIdBase]
 
 
-        id = client.doIdBase / self.doIdRange
+        id = client.doIdBase // self.doIdRange
         self.idAllocator.free(id)
         self.idAllocator.free(id)
 
 
         self.qcr.removeConnection(client.connection)
         self.qcr.removeConnection(client.connection)
@@ -689,7 +689,7 @@ class ServerRepository:
     def clientHardDisconnectTask(self, task):
     def clientHardDisconnectTask(self, task):
         """ client did not tell us he was leaving but we lost connection to
         """ client did not tell us he was leaving but we lost connection to
         him, so we need to update our data and tell others """
         him, so we need to update our data and tell others """
-        for client in self.clientsByConnection.values():
+        for client in list(self.clientsByConnection.values()):
             if not self.qcr.isConnectionOk(client.connection):
             if not self.qcr.isConnectionOk(client.connection):
                 self.handleClientDisconnect(client)
                 self.handleClientDisconnect(client)
         return Task.cont
         return Task.cont

+ 1 - 16
direct/src/distributed/cConnectionRepository.cxx

@@ -411,19 +411,12 @@ send_datagram(const Datagram &dg) {
     if (!result && _bdc.IsConnected()) {
     if (!result && _bdc.IsConnected()) {
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
       std::ostringstream s;
       std::ostringstream s;
-
-#if PY_VERSION_HEX >= 0x03030000
-      PyObject *exc_type = PyExc_ConnectionError;
-#else
-      PyObject *exc_type = PyExc_OSError;
-#endif
-
       s << endl << "Error sending message: " << endl;
       s << endl << "Error sending message: " << endl;
       dg.dump_hex(s);
       dg.dump_hex(s);
       s << "Message data: " << dg.get_data() << endl;
       s << "Message data: " << dg.get_data() << endl;
 
 
       string message = s.str();
       string message = s.str();
-      PyErr_SetString(exc_type, message.c_str());
+      PyErr_SetString(PyExc_ConnectionError, message.c_str());
 #endif
 #endif
     }
     }
     return result;
     return result;
@@ -922,22 +915,14 @@ describe_message(std::ostream &out, const string &prefix,
     if (_python_repository != nullptr) {
     if (_python_repository != nullptr) {
       PyObject *msgId = PyLong_FromLong(msg_type);
       PyObject *msgId = PyLong_FromLong(msg_type);
       nassertv(msgId != nullptr);
       nassertv(msgId != nullptr);
-#if PY_MAJOR_VERSION >= 3
       PyObject *methodName = PyUnicode_FromString("_getMsgName");
       PyObject *methodName = PyUnicode_FromString("_getMsgName");
-#else
-      PyObject *methodName = PyString_FromString("_getMsgName");
-#endif
       nassertv(methodName != nullptr);
       nassertv(methodName != nullptr);
 
 
       PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
       PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
                                                     msgId, nullptr);
                                                     msgId, nullptr);
       nassertv(result != nullptr);
       nassertv(result != nullptr);
 
 
-#if PY_MAJOR_VERSION >= 3
       msgName += string(PyUnicode_AsUTF8(result));
       msgName += string(PyUnicode_AsUTF8(result));
-#else
-      msgName += string(PyString_AsString(result));
-#endif
 
 
       Py_DECREF(methodName);
       Py_DECREF(methodName);
       Py_DECREF(msgId);
       Py_DECREF(msgId);

+ 1 - 4
direct/src/doc/howto.adjust

@@ -52,10 +52,7 @@ of the slider to change settings.  Click on:
 
 
 You can pack multiple sliders into a single panel:
 You can pack multiple sliders into a single panel:
 
 
-if sys.version_info >= (3, 0):
-    from tkinter import *
-else:
-    from Tkinter import *
+from tkinter import *
 
 
 def func1(x):
 def func1(x):
    print '1:', x
    print '1:', x

+ 1 - 5
direct/src/extensions_native/extension_native_helpers.py

@@ -1,17 +1,13 @@
 __all__ = ["Dtool_ObjectToDict", "Dtool_funcToMethod"]
 __all__ = ["Dtool_ObjectToDict", "Dtool_funcToMethod"]
 
 
-import sys
 
 
 def Dtool_ObjectToDict(cls, name, obj):
 def Dtool_ObjectToDict(cls, name, obj):
     cls.DtoolClassDict[name] = obj
     cls.DtoolClassDict[name] = obj
 
 
+
 def Dtool_funcToMethod(func, cls, method_name=None):
 def Dtool_funcToMethod(func, cls, method_name=None):
     """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method.
     """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method.
     The new method is accessible to any instance immediately."""
     The new method is accessible to any instance immediately."""
-    if sys.version_info < (3, 0):
-        func.im_class = cls
-        func.im_func = func
-        func.im_self = None
     func.__func__ = func
     func.__func__ = func
     func.__self__ = None
     func.__self__ = None
     if not method_name:
     if not method_name:

+ 27 - 0
direct/src/fsm/FSM.py

@@ -153,6 +153,12 @@ class FSM(DirectObject):
     # must be approved by some filter function.
     # must be approved by some filter function.
     defaultTransitions = None
     defaultTransitions = None
 
 
+    # An enum class for special states like the DEFAULT or ANY state,
+    # that should be treatened by the FSM in a special way
+    class EnumStates():
+        ANY = 1
+        DEFAULT = 2
+
     def __init__(self, name):
     def __init__(self, name):
         self.fsmLock = RLock()
         self.fsmLock = RLock()
         self._name = name
         self._name = name
@@ -382,6 +388,27 @@ class FSM(DirectObject):
                 # accept it.
                 # accept it.
                 return (request,) + args
                 return (request,) + args
 
 
+            elif FSM.EnumStates.ANY in self.defaultTransitions.get(self.state, []):
+                # Whenever we have a '*' as our to transition, we allow
+                # to transit to any other state
+                return (request,) + args
+
+            elif request in self.defaultTransitions.get(FSM.EnumStates.ANY, []):
+                # If the requested state is in the default transitions
+                # from any state list, we also alow to transit to the
+                # new state
+                return (request,) + args
+
+            elif FSM.EnumStates.ANY in self.defaultTransitions.get(FSM.EnumStates.ANY, []):
+                # This is like we had set the defaultTransitions to None.
+                # Any state can transit to any other state
+                return (request,) + args
+
+            elif request in self.defaultTransitions.get(FSM.EnumStates.DEFAULT, []):
+                # This is the fallback state that we use whenever no
+                # other trnasition was possible
+                return (request,) + args
+
             # If self.defaultTransitions is not None, it is an error
             # If self.defaultTransitions is not None, it is an error
             # to request a direct state transition (capital letter
             # to request a direct state transition (capital letter
             # request) not listed in defaultTransitions and not
             # request) not listed in defaultTransitions and not

+ 3 - 11
direct/src/gui/DirectEntry.py

@@ -12,7 +12,6 @@ from direct.showbase import ShowBaseGlobal
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .DirectFrame import *
 from .DirectFrame import *
 from .OnscreenText import OnscreenText
 from .OnscreenText import OnscreenText
-import sys
 # import this to make sure it gets pulled into the publish
 # import this to make sure it gets pulled into the publish
 import encodings.utf_8
 import encodings.utf_8
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
@@ -274,16 +273,9 @@ class DirectEntry(DirectFrame):
         does not change the current cursor position.  Also see
         does not change the current cursor position.  Also see
         enterText(). """
         enterText(). """
 
 
-        if sys.version_info >= (3, 0):
-            assert not isinstance(text, bytes)
-            self.unicodeText = True
-            self.guiItem.setWtext(text)
-        else:
-            self.unicodeText = isinstance(text, unicode)
-            if self.unicodeText:
-                self.guiItem.setWtext(text)
-            else:
-                self.guiItem.setText(text)
+        assert not isinstance(text, bytes)
+        self.unicodeText = True
+        self.guiItem.setWtext(text)
 
 
     def get(self, plain = False):
     def get(self, plain = False):
         """ Returns the text currently showing in the typable region.
         """ Returns the text currently showing in the typable region.

+ 5 - 11
direct/src/gui/DirectFrame.py

@@ -24,12 +24,6 @@ from .DirectGuiBase import *
 from .OnscreenImage import OnscreenImage
 from .OnscreenImage import OnscreenImage
 from .OnscreenGeom import OnscreenGeom
 from .OnscreenGeom import OnscreenGeom
 from .OnscreenText import OnscreenText
 from .OnscreenText import OnscreenText
-import sys
-
-if sys.version_info >= (3, 0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 
 
 class DirectFrame(DirectGuiWidget):
 class DirectFrame(DirectGuiWidget):
@@ -105,7 +99,7 @@ class DirectFrame(DirectGuiWidget):
             self["text"] = text
             self["text"] = text
 
 
         text = self["text"]
         text = self["text"]
-        if text is None or isinstance(text, stringType):
+        if text is None or isinstance(text, str):
             text_list = (text,) * self['numStates']
             text_list = (text,) * self['numStates']
         else:
         else:
             text_list = text
             text_list = text
@@ -126,7 +120,7 @@ class DirectFrame(DirectGuiWidget):
         geom = self["geom"]
         geom = self["geom"]
         if geom is None or \
         if geom is None or \
            isinstance(geom, NodePath) or \
            isinstance(geom, NodePath) or \
-           isinstance(geom, stringType):
+           isinstance(geom, str):
             geom_list = (geom,) * self['numStates']
             geom_list = (geom,) * self['numStates']
         else:
         else:
             geom_list = geom
             geom_list = geom
@@ -147,11 +141,11 @@ class DirectFrame(DirectGuiWidget):
         if image is None or \
         if image is None or \
            isinstance(image, NodePath) or \
            isinstance(image, NodePath) or \
            isinstance(image, Texture) or \
            isinstance(image, Texture) or \
-           isinstance(image, stringType) or \
+           isinstance(image, str) or \
            isinstance(image, Filename) or \
            isinstance(image, Filename) or \
            (len(image) == 2 and \
            (len(image) == 2 and \
-            isinstance(image[0], stringType) and \
-            isinstance(image[1], stringType)):
+            isinstance(image[0], str) and \
+            isinstance(image[1], str)):
             image_list = (image,) * self['numStates']
             image_list = (image,) * self['numStates']
         else:
         else:
             image_list = image
             image_list = image

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

@@ -97,12 +97,6 @@ from .OnscreenImage import *
 from direct.directtools.DirectUtil import ROUND_TO
 from direct.directtools.DirectUtil import ROUND_TO
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
 from direct.task import Task
 from direct.task import Task
-import sys
-
-if sys.version_info >= (3, 0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 guiObjectCollector = PStatCollector("Client::GuiObjects")
 guiObjectCollector = PStatCollector("Client::GuiObjects")
 
 
@@ -960,7 +954,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
         # Convert None, and string arguments
         # Convert None, and string arguments
         if relief == None:
         if relief == None:
             relief = PGFrameStyle.TNone
             relief = PGFrameStyle.TNone
-        elif isinstance(relief, stringType):
+        elif isinstance(relief, str):
             # Convert string to frame style int
             # Convert string to frame style int
             relief = DGG.FrameStyleDict[relief]
             relief = DGG.FrameStyleDict[relief]
         # Set style
         # Set style
@@ -1001,14 +995,14 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
         textures = self['frameTexture']
         textures = self['frameTexture']
         if textures == None or \
         if textures == None or \
            isinstance(textures, Texture) or \
            isinstance(textures, Texture) or \
-           isinstance(textures, stringType):
+           isinstance(textures, str):
             textures = (textures,) * self['numStates']
             textures = (textures,) * self['numStates']
         for i in range(self['numStates']):
         for i in range(self['numStates']):
             if i >= len(textures):
             if i >= len(textures):
                 texture = textures[-1]
                 texture = textures[-1]
             else:
             else:
                 texture = textures[i]
                 texture = textures[i]
-            if isinstance(texture, stringType):
+            if isinstance(texture, str):
                 texture = loader.loadTexture(texture)
                 texture = loader.loadTexture(texture)
             if texture:
             if texture:
                 self.frameStyle[i].setTexture(texture)
                 self.frameStyle[i].setTexture(texture)

+ 27 - 0
direct/src/gui/DirectScrollBar.py

@@ -88,6 +88,8 @@ class DirectScrollBar(DirectFrame):
             else:
             else:
                 self.incButton['frameSize'] = (f[0], f[1], f[2]*0.05, f[3]*0.05)
                 self.incButton['frameSize'] = (f[0], f[1], f[2]*0.05, f[3]*0.05)
 
 
+        self._lastOrientation = self['orientation']
+
         self.guiItem.setThumbButton(self.thumb.guiItem)
         self.guiItem.setThumbButton(self.thumb.guiItem)
         self.guiItem.setLeftButton(self.decButton.guiItem)
         self.guiItem.setLeftButton(self.decButton.guiItem)
         self.guiItem.setRightButton(self.incButton.guiItem)
         self.guiItem.setRightButton(self.incButton.guiItem)
@@ -140,13 +142,38 @@ class DirectScrollBar(DirectFrame):
 
 
     def setOrientation(self):
     def setOrientation(self):
         if self['orientation'] == DGG.HORIZONTAL:
         if self['orientation'] == DGG.HORIZONTAL:
+            if self._lastOrientation in (DGG.VERTICAL, DGG.VERTICAL_INVERTED):
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                f = self.decButton['frameSize']
+                self.decButton['frameSize'] = (f[2], f[3], f[0], f[1])
+                f = self.incButton['frameSize']
+                self.incButton['frameSize'] = (f[2], f[3], f[0], f[1])
             self.guiItem.setAxis(Vec3(1, 0, 0))
             self.guiItem.setAxis(Vec3(1, 0, 0))
         elif self['orientation'] == DGG.VERTICAL:
         elif self['orientation'] == DGG.VERTICAL:
+            if self._lastOrientation == DGG.HORIZONTAL:
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                f = self.decButton['frameSize']
+                self.decButton['frameSize'] = (f[2], f[3], f[0], f[1])
+                f = self.incButton['frameSize']
+                self.incButton['frameSize'] = (f[2], f[3], f[0], f[1])
             self.guiItem.setAxis(Vec3(0, 0, -1))
             self.guiItem.setAxis(Vec3(0, 0, -1))
         elif self['orientation'] == DGG.VERTICAL_INVERTED:
         elif self['orientation'] == DGG.VERTICAL_INVERTED:
+            if self._lastOrientation == DGG.HORIZONTAL:
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                f = self.decButton['frameSize']
+                self.decButton['frameSize'] = (f[2], f[3], f[0], f[1])
+                f = self.incButton['frameSize']
+                self.incButton['frameSize'] = (f[2], f[3], f[0], f[1])
             self.guiItem.setAxis(Vec3(0, 0, 1))
             self.guiItem.setAxis(Vec3(0, 0, 1))
         else:
         else:
             raise ValueError('Invalid value for orientation: %s' % (self['orientation']))
             raise ValueError('Invalid value for orientation: %s' % (self['orientation']))
+        self._lastOrientation = self['orientation']
 
 
     def setManageButtons(self):
     def setManageButtons(self):
         self.guiItem.setManagePieces(self['manageButtons'])
         self.guiItem.setManagePieces(self['manageButtons'])

+ 4 - 2
direct/src/gui/DirectScrolledFrame.py

@@ -77,9 +77,11 @@ class DirectScrolledFrame(DirectFrame):
         self.initialiseoptions(DirectScrolledFrame)
         self.initialiseoptions(DirectScrolledFrame)
 
 
     def setScrollBarWidth(self):
     def setScrollBarWidth(self):
+        if self.fInit: return
+
         w = self['scrollBarWidth']
         w = self['scrollBarWidth']
-        self.verticalScroll["frameSize"] = (-w / 2.0, w / 2.0, -1, 1)
-        self.horizontalScroll["frameSize"] = (-1, 1, -w / 2.0, w / 2.0)
+        self.verticalScroll["frameSize"] = (-w / 2.0, w / 2.0, self.verticalScroll["frameSize"][2], self.verticalScroll["frameSize"][3])
+        self.horizontalScroll["frameSize"] = (self.horizontalScroll["frameSize"][0], self.horizontalScroll["frameSize"][1], -w / 2.0, w / 2.0)
 
 
     def setCanvasSize(self):
     def setCanvasSize(self):
         f = self['canvasSize']
         f = self['canvasSize']

+ 16 - 22
direct/src/gui/DirectScrolledList.py

@@ -13,12 +13,6 @@ from direct.directnotify import DirectNotifyGlobal
 from direct.task.Task import Task
 from direct.task.Task import Task
 from .DirectFrame import *
 from .DirectFrame import *
 from .DirectButton import *
 from .DirectButton import *
-import sys
-
-if sys.version_info >= (3,0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 
 
 class DirectScrolledListItem(DirectButton):
 class DirectScrolledListItem(DirectButton):
@@ -71,7 +65,7 @@ class DirectScrolledList(DirectFrame):
         # so we can modify it without mangling the user's list
         # so we can modify it without mangling the user's list
         if 'items' in kw:
         if 'items' in kw:
             for item in kw['items']:
             for item in kw['items']:
-                if not isinstance(item, stringType):
+                if not isinstance(item, str):
                     break
                     break
             else:
             else:
                 # we get here if every item in 'items' is a string
                 # we get here if every item in 'items' is a string
@@ -116,7 +110,7 @@ class DirectScrolledList(DirectFrame):
                                               DirectFrame, (self,),
                                               DirectFrame, (self,),
                                               )
                                               )
         for item in self["items"]:
         for item in self["items"]:
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 item.reparentTo(self.itemFrame)
                 item.reparentTo(self.itemFrame)
 
 
         self.initialiseoptions(DirectScrolledList)
         self.initialiseoptions(DirectScrolledList)
@@ -134,7 +128,7 @@ class DirectScrolledList(DirectFrame):
         else:
         else:
             self.maxHeight = 0.0
             self.maxHeight = 0.0
             for item in self["items"]:
             for item in self["items"]:
-                if not isinstance(item, stringType):
+                if not isinstance(item, str):
                     self.maxHeight = max(self.maxHeight, item.getHeight())
                     self.maxHeight = max(self.maxHeight, item.getHeight())
 
 
     def setScrollSpeed(self):
     def setScrollSpeed(self):
@@ -182,7 +176,7 @@ class DirectScrolledList(DirectFrame):
         if len(self["items"]) == 0:
         if len(self["items"]) == 0:
             return 0
             return 0
 
 
-        if isinstance(self["items"][0], stringType):
+        if isinstance(self["items"][0], str):
             self.notify.warning("getItemIndexForItemID: cant find itemID for non-class list items!")
             self.notify.warning("getItemIndexForItemID: cant find itemID for non-class list items!")
             return 0
             return 0
 
 
@@ -210,7 +204,7 @@ class DirectScrolledList(DirectFrame):
         numItemsVisible = self["numItemsVisible"]
         numItemsVisible = self["numItemsVisible"]
         numItemsTotal = len(self["items"])
         numItemsTotal = len(self["items"])
         if(centered):
         if(centered):
-            self.index = index - (numItemsVisible/2)
+            self.index = index - (numItemsVisible // 2)
         else:
         else:
             self.index = index
             self.index = index
 
 
@@ -248,7 +242,7 @@ class DirectScrolledList(DirectFrame):
 
 
         # Hide them all
         # Hide them all
         for item in self["items"]:
         for item in self["items"]:
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 item.hide()
                 item.hide()
 
 
         # Then show the ones in range, and stack their positions
         # Then show the ones in range, and stack their positions
@@ -258,7 +252,7 @@ class DirectScrolledList(DirectFrame):
             #print "stacking buttontext[", i,"]", self["items"][i]["text"]
             #print "stacking buttontext[", i,"]", self["items"][i]["text"]
             # If the item is a 'str', then it has not been created (scrolled list is 'as needed')
             # If the item is a 'str', then it has not been created (scrolled list is 'as needed')
             #  Therefore, use the the function given to make it or just make it a frame
             #  Therefore, use the the function given to make it or just make it a frame
-            if isinstance(item, stringType):
+            if isinstance(item, str):
                 if self['itemMakeFunction']:
                 if self['itemMakeFunction']:
                     # If there is a function to create the item
                     # If there is a function to create the item
                     item = self['itemMakeFunction'](item, i, self['itemMakeExtraArgs'])
                     item = self['itemMakeFunction'](item, i, self['itemMakeExtraArgs'])
@@ -290,7 +284,7 @@ class DirectScrolledList(DirectFrame):
             # Therefore, use the the function given to make it or
             # Therefore, use the the function given to make it or
             # just make it a frame
             # just make it a frame
             #print "Making " + str(item)
             #print "Making " + str(item)
-            if isinstance(item, stringType):
+            if isinstance(item, str):
                 if self['itemMakeFunction']:
                 if self['itemMakeFunction']:
                     # If there is a function to create the item
                     # If there is a function to create the item
                     item = self['itemMakeFunction'](item, i, self['itemMakeExtraArgs'])
                     item = self['itemMakeFunction'](item, i, self['itemMakeExtraArgs'])
@@ -355,16 +349,16 @@ class DirectScrolledList(DirectFrame):
         Add this string and extraArg to the list
         Add this string and extraArg to the list
         """
         """
         assert self.notify.debugStateCall(self)
         assert self.notify.debugStateCall(self)
-        if not isinstance(item, stringType):
+        if not isinstance(item, str):
             # cant add attribs to non-classes (like strings & ints)
             # cant add attribs to non-classes (like strings & ints)
             item.itemID = self.nextItemID
             item.itemID = self.nextItemID
             self.nextItemID += 1
             self.nextItemID += 1
         self['items'].append(item)
         self['items'].append(item)
-        if not isinstance(item, stringType):
+        if not isinstance(item, str):
             item.reparentTo(self.itemFrame)
             item.reparentTo(self.itemFrame)
         if refresh:
         if refresh:
             self.refresh()
             self.refresh()
-        if not isinstance(item, stringType):
+        if not isinstance(item, str):
             return item.itemID  # to pass to scrollToItemID
             return item.itemID  # to pass to scrollToItemID
 
 
     def removeItem(self, item, refresh=1):
     def removeItem(self, item, refresh=1):
@@ -379,7 +373,7 @@ class DirectScrolledList(DirectFrame):
             if hasattr(self, "currentSelected") and self.currentSelected is item:
             if hasattr(self, "currentSelected") and self.currentSelected is item:
                 del self.currentSelected
                 del self.currentSelected
             self["items"].remove(item)
             self["items"].remove(item)
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 item.reparentTo(ShowBaseGlobal.hidden)
                 item.reparentTo(ShowBaseGlobal.hidden)
             self.refresh()
             self.refresh()
             return 1
             return 1
@@ -397,7 +391,7 @@ class DirectScrolledList(DirectFrame):
             if (hasattr(item, 'destroy') and hasattr(item.destroy, '__call__')):
             if (hasattr(item, 'destroy') and hasattr(item.destroy, '__call__')):
                 item.destroy()
                 item.destroy()
             self["items"].remove(item)
             self["items"].remove(item)
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 item.reparentTo(ShowBaseGlobal.hidden)
                 item.reparentTo(ShowBaseGlobal.hidden)
             self.refresh()
             self.refresh()
             return 1
             return 1
@@ -419,7 +413,7 @@ class DirectScrolledList(DirectFrame):
             if hasattr(self, "currentSelected") and self.currentSelected is item:
             if hasattr(self, "currentSelected") and self.currentSelected is item:
                 del self.currentSelected
                 del self.currentSelected
             self["items"].remove(item)
             self["items"].remove(item)
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 #RAU possible leak here, let's try to do the right thing
                 #RAU possible leak here, let's try to do the right thing
                 #item.reparentTo(ShowBaseGlobal.hidden)
                 #item.reparentTo(ShowBaseGlobal.hidden)
                 item.removeNode()
                 item.removeNode()
@@ -444,7 +438,7 @@ class DirectScrolledList(DirectFrame):
             if (hasattr(item, 'destroy') and hasattr(item.destroy, '__call__')):
             if (hasattr(item, 'destroy') and hasattr(item.destroy, '__call__')):
                 item.destroy()
                 item.destroy()
             self["items"].remove(item)
             self["items"].remove(item)
-            if not isinstance(item, stringType):
+            if not isinstance(item, str):
                 #RAU possible leak here, let's try to do the right thing
                 #RAU possible leak here, let's try to do the right thing
                 #item.reparentTo(ShowBaseGlobal.hidden)
                 #item.reparentTo(ShowBaseGlobal.hidden)
                 item.removeNode()
                 item.removeNode()
@@ -469,7 +463,7 @@ class DirectScrolledList(DirectFrame):
 
 
     def getSelectedText(self):
     def getSelectedText(self):
         assert self.notify.debugStateCall(self)
         assert self.notify.debugStateCall(self)
-        if isinstance(self['items'][self.index], stringType):
+        if isinstance(self['items'][self.index], str):
           return self['items'][self.index]
           return self['items'][self.index]
         else:
         else:
           return self['items'][self.index]['text']
           return self['items'][self.index]['text']

+ 32 - 2
direct/src/gui/DirectSlider.py

@@ -10,6 +10,7 @@ from panda3d.core import *
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .DirectFrame import *
 from .DirectFrame import *
 from .DirectButton import *
 from .DirectButton import *
+from math import isnan
 
 
 """
 """
 import DirectSlider
 import DirectSlider
@@ -40,7 +41,7 @@ class DirectSlider(DirectFrame):
             ('extraArgs',      [],                 None),
             ('extraArgs',      [],                 None),
             )
             )
 
 
-        if kw.get('orientation') == DGG.VERTICAL:
+        if kw.get('orientation') in (DGG.VERTICAL, DGG.VERTICAL_INVERTED):
             # These are the default options for a vertical layout.
             # These are the default options for a vertical layout.
             optiondefs += (
             optiondefs += (
                 ('frameSize',      (-0.08, 0.08, -1, 1),   None),
                 ('frameSize',      (-0.08, 0.08, -1, 1),   None),
@@ -71,6 +72,8 @@ class DirectSlider(DirectFrame):
             else:
             else:
                 self.thumb['frameSize'] = (f[0], f[1], f[2]*0.05, f[3]*0.05)
                 self.thumb['frameSize'] = (f[0], f[1], f[2]*0.05, f[3]*0.05)
 
 
+        self._lastOrientation = self['orientation']
+
         self.guiItem.setThumbButton(self.thumb.guiItem)
         self.guiItem.setThumbButton(self.thumb.guiItem)
 
 
         # Bind command function
         # Bind command function
@@ -89,12 +92,15 @@ class DirectSlider(DirectFrame):
     def __setValue(self):
     def __setValue(self):
         # This is the internal function that is called when
         # This is the internal function that is called when
         # self['value'] is directly assigned.
         # self['value'] is directly assigned.
-        self.guiItem.setValue(self['value'])
+        value = self['value']
+        assert not isnan(value)
+        self.guiItem.setValue(value)
 
 
     def setValue(self, value):
     def setValue(self, value):
         # This is the public function that is meant to be called by a
         # This is the public function that is meant to be called by a
         # user that doesn't like to use (or doesn't understand) the
         # user that doesn't like to use (or doesn't understand) the
         # preferred interface of self['value'].
         # preferred interface of self['value'].
+        assert not isnan(value)
         self['value'] = value
         self['value'] = value
 
 
     def getValue(self):
     def getValue(self):
@@ -111,11 +117,35 @@ class DirectSlider(DirectFrame):
 
 
     def setOrientation(self):
     def setOrientation(self):
         if self['orientation'] == DGG.HORIZONTAL:
         if self['orientation'] == DGG.HORIZONTAL:
+            if self._lastOrientation in (DGG.VERTICAL, DGG.VERTICAL_INVERTED):
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                tf = self.thumb['frameSize']
+                self.thumb['frameSize'] = (tf[2], tf[3], tf[0], tf[1])
             self.guiItem.setAxis(Vec3(1, 0, 0))
             self.guiItem.setAxis(Vec3(1, 0, 0))
+            self['frameVisibleScale'] = (1, 0.25)
         elif self['orientation'] == DGG.VERTICAL:
         elif self['orientation'] == DGG.VERTICAL:
+            if self._lastOrientation == DGG.HORIZONTAL:
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                tf = self.thumb['frameSize']
+                self.thumb['frameSize'] = (tf[2], tf[3], tf[0], tf[1])
             self.guiItem.setAxis(Vec3(0, 0, 1))
             self.guiItem.setAxis(Vec3(0, 0, 1))
+            self['frameVisibleScale'] = (0.25, 1)
+        elif self['orientation'] == DGG.VERTICAL_INVERTED:
+            if self._lastOrientation == DGG.HORIZONTAL:
+                fpre = self['frameSize']
+                # swap frameSize width and height to keep custom frameSizes
+                self['frameSize'] = (fpre[2], fpre[3], fpre[0], fpre[1])
+                tf = self.thumb['frameSize']
+                self.thumb['frameSize'] = (tf[2], tf[3], tf[0], tf[1])
+            self.guiItem.setAxis(Vec3(0, 0, -1))
+            self['frameVisibleScale'] = (0.25, 1)
         else:
         else:
             raise ValueError('Invalid value for orientation: %s' % (self['orientation']))
             raise ValueError('Invalid value for orientation: %s' % (self['orientation']))
+        self._lastOrientation = self['orientation']
 
 
     def destroy(self):
     def destroy(self):
         if (hasattr(self, 'thumb')):
         if (hasattr(self, 'thumb')):

+ 1 - 7
direct/src/gui/DirectWaitBar.py

@@ -9,12 +9,6 @@ __all__ = ['DirectWaitBar']
 from panda3d.core import *
 from panda3d.core import *
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .DirectFrame import *
 from .DirectFrame import *
-import sys
-
-if sys.version_info >= (3, 0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 """
 """
 import DirectWaitBar
 import DirectWaitBar
@@ -102,7 +96,7 @@ class DirectWaitBar(DirectFrame):
         """Updates the bar texture, which you can set using bar['barTexture']."""
         """Updates the bar texture, which you can set using bar['barTexture']."""
         # this must be a single texture (or a string).
         # this must be a single texture (or a string).
         texture = self['barTexture']
         texture = self['barTexture']
-        if isinstance(texture, stringType):
+        if isinstance(texture, str):
             texture = loader.loadTexture(texture)
             texture = loader.loadTexture(texture)
         if texture:
         if texture:
             self.barStyle.setTexture(texture)
             self.barStyle.setTexture(texture)

+ 1 - 6
direct/src/gui/OnscreenGeom.py

@@ -4,12 +4,7 @@ __all__ = ['OnscreenGeom']
 
 
 from panda3d.core import *
 from panda3d.core import *
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
-import sys
 
 
-if sys.version_info >= (3, 0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 class OnscreenGeom(DirectObject, NodePath):
 class OnscreenGeom(DirectObject, NodePath):
     def __init__(self, geom = None,
     def __init__(self, geom = None,
@@ -98,7 +93,7 @@ class OnscreenGeom(DirectObject, NodePath):
         # Assign geometry
         # Assign geometry
         if isinstance(geom, NodePath):
         if isinstance(geom, NodePath):
             self.assign(geom.copyTo(parent, sort))
             self.assign(geom.copyTo(parent, sort))
-        elif isinstance(geom, stringType):
+        elif isinstance(geom, str):
             self.assign(loader.loadModel(geom))
             self.assign(loader.loadModel(geom))
             self.reparentTo(parent, sort)
             self.reparentTo(parent, sort)
 
 

+ 1 - 8
direct/src/gui/OnscreenImage.py

@@ -8,12 +8,6 @@ __all__ = ['OnscreenImage']
 
 
 from panda3d.core import *
 from panda3d.core import *
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
-import sys
-
-if sys.version_info >= (3, 0):
-    stringType = str
-else:
-    stringType = basestring
 
 
 
 
 class OnscreenImage(DirectObject, NodePath):
 class OnscreenImage(DirectObject, NodePath):
@@ -106,8 +100,7 @@ class OnscreenImage(DirectObject, NodePath):
         # Assign geometry
         # Assign geometry
         if isinstance(image, NodePath):
         if isinstance(image, NodePath):
             self.assign(image.copyTo(parent, sort))
             self.assign(image.copyTo(parent, sort))
-        elif isinstance(image, stringType) or \
-             isinstance(image, Texture):
+        elif isinstance(image, str) or isinstance(image, Texture):
             if isinstance(image, Texture):
             if isinstance(image, Texture):
                 # It's a Texture
                 # It's a Texture
                 tex = image
                 tex = image

+ 5 - 25
direct/src/gui/OnscreenText.py

@@ -8,7 +8,6 @@ __all__ = ['OnscreenText', 'Plain', 'ScreenTitle', 'ScreenPrompt', 'NameConfirm'
 
 
 from panda3d.core import *
 from panda3d.core import *
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
-import sys
 
 
 ## These are the styles of text we might commonly see.  They set the
 ## These are the styles of text we might commonly see.  They set the
 ## overall appearance of the text according to one of a number of
 ## overall appearance of the text according to one of a number of
@@ -281,34 +280,15 @@ class OnscreenText(NodePath):
         self.textNode.clearText()
         self.textNode.clearText()
 
 
     def setText(self, text):
     def setText(self, text):
-        if sys.version_info >= (3, 0):
-            assert not isinstance(text, bytes)
-            self.unicodeText = True
-        else:
-            self.unicodeText = isinstance(text, unicode)
-
-        if self.unicodeText:
-            self.textNode.setWtext(text)
-        else:
-            self.textNode.setText(text)
+        assert not isinstance(text, bytes)
+        self.textNode.setWtext(text)
 
 
     def appendText(self, text):
     def appendText(self, text):
-        if sys.version_info >= (3, 0):
-            assert not isinstance(text, bytes)
-            self.unicodeText = True
-        else:
-            self.unicodeText = isinstance(text, unicode)
-
-        if self.unicodeText:
-            self.textNode.appendWtext(text)
-        else:
-            self.textNode.appendText(text)
+        assert not isinstance(text, bytes)
+        self.textNode.appendWtext(text)
 
 
     def getText(self):
     def getText(self):
-        if self.unicodeText:
-            return self.textNode.getWtext()
-        else:
-            return self.textNode.getText()
+        return self.textNode.getWtext()
 
 
     text = property(getText, setText)
     text = property(getText, setText)
 
 

+ 54 - 0
direct/src/interval/CMakeLists.txt

@@ -0,0 +1,54 @@
+set(P3INTERVAL_HEADERS
+  config_interval.h
+  cInterval.I cInterval.h
+  cIntervalManager.I cIntervalManager.h
+  cConstraintInterval.I cConstraintInterval.h
+  cConstrainTransformInterval.I cConstrainTransformInterval.h
+  cConstrainPosInterval.I cConstrainPosInterval.h
+  cConstrainHprInterval.I cConstrainHprInterval.h
+  cConstrainPosHprInterval.I cConstrainPosHprInterval.h
+  cLerpInterval.I cLerpInterval.h
+  cLerpNodePathInterval.I cLerpNodePathInterval.h
+  cLerpAnimEffectInterval.I cLerpAnimEffectInterval.h
+  cMetaInterval.I cMetaInterval.h
+  hideInterval.I hideInterval.h
+  lerpblend.h
+  showInterval.I showInterval.h
+  waitInterval.I waitInterval.h
+  lerp_helpers.h
+)
+
+set(P3INTERVAL_SOURCES
+  config_interval.cxx
+  cInterval.cxx
+  cIntervalManager.cxx
+  cConstraintInterval.cxx
+  cConstrainTransformInterval.cxx
+  cConstrainPosInterval.cxx
+  cConstrainHprInterval.cxx
+  cConstrainPosHprInterval.cxx
+  cLerpInterval.cxx
+  cLerpNodePathInterval.cxx
+  cLerpAnimEffectInterval.cxx
+  cMetaInterval.cxx
+  hideInterval.cxx
+  lerpblend.cxx
+  showInterval.cxx
+  waitInterval.cxx
+)
+
+composite_sources(p3interval P3INTERVAL_SOURCES)
+add_component_library(p3interval SYMBOL BUILDING_DIRECT_INTERVAL
+  ${P3INTERVAL_HEADERS} ${P3INTERVAL_SOURCES})
+target_link_libraries(p3interval p3directbase panda)
+target_interrogate(p3interval ALL)
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3interval
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3INTERVAL_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 2 - 5
direct/src/interval/Interval.py

@@ -454,12 +454,9 @@ class Interval(DirectObject):
         """
         """
         # Don't use a regular import, to prevent ModuleFinder from picking
         # Don't use a regular import, to prevent ModuleFinder from picking
         # it up as a dependency when building a .p3d package.
         # it up as a dependency when building a .p3d package.
-        import importlib, sys
+        import importlib
         EntryScale = importlib.import_module('direct.tkwidgets.EntryScale')
         EntryScale = importlib.import_module('direct.tkwidgets.EntryScale')
-        if sys.version_info >= (3, 0):
-            tkinter = importlib.import_module('tkinter')
-        else:
-            tkinter = importlib.import_module('Tkinter')
+        tkinter = importlib.import_module('tkinter')
 
 
         if tl == None:
         if tl == None:
             tl = tkinter.Toplevel()
             tl = tkinter.Toplevel()

+ 24 - 0
direct/src/motiontrail/CMakeLists.txt

@@ -0,0 +1,24 @@
+set(P3MOTIONTRAIL_HEADERS
+  config_motiontrail.h
+  cMotionTrail.h
+)
+
+set(P3MOTIONTRAIL_SOURCES
+  config_motiontrail.cxx
+  cMotionTrail.cxx
+)
+
+add_component_library(p3motiontrail SYMBOL BUILDING_DIRECT_MOTIONTRAIL
+  ${P3MOTIONTRAIL_HEADERS} ${P3MOTIONTRAIL_SOURCES})
+target_link_libraries(p3motiontrail p3directbase panda)
+target_interrogate(p3motiontrail ALL)
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3motiontrail
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3MOTIONTRAIL_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно