├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── __init__.py ├── build.py ├── config.ini ├── prefab.gitignore ├── scripts ├── __init__.py ├── common.py ├── finalize.py ├── interrogate.py └── setup.py └── source ├── config_module.cpp └── config_module.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyd 2 | *.pyc 3 | *.so 4 | *.pdb 5 | 6 | # Various output names 7 | win_*/ 8 | linux_*/ 9 | osx_*/ 10 | Source/Interrogate* 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | dist: trusty 4 | compiler: gcc 5 | addons: 6 | apt: 7 | sources: 8 | - sourceline: "deb http://archive.panda3d.org/ubuntu/ trusty-dev main" 9 | packages: 10 | - cmake 11 | - libeigen3-dev 12 | - libfreetype6-dev 13 | - libbullet-dev 14 | - panda3d1.10 15 | 16 | script: 17 | - export PYTHONPATH=${PYTHONPATH}:/usr/lib/python2.7/dist-packages 18 | - export PYTHONPATH=${PYTHONPATH}:/usr/share/panda3d 19 | - echo -e "\nmodule_name=CI_TEST\n" >> config.ini 20 | - python2.7 build.py 21 | - python2.7 -c "import panda3d; import CI_TEST; print(dir(CI_TEST))" 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(PROJECT_NAME CACHE STRING "Module") 3 | 4 | cmake_minimum_required (VERSION 2.6) 5 | project (${PROJECT_NAME}) 6 | 7 | # Project configuration 8 | set(CMAKE_BUILD_TYPE "Release") 9 | 10 | # Libraries and options set by the module builder 11 | set(HAVE_LIB_EIGEN CACHE BOOL TRUE) 12 | set(HAVE_LIB_BULLET CACHE BOOL FALSE) 13 | set(HAVE_LIB_FREETYPE CACHE BOOL FALSE) 14 | set(OPTIMIZE CACHE STRING "3") 15 | set(IGATE_VERBOSE CACHE STRING "0") 16 | set(INTERROGATE_LIB CACHE STRING "p3interrogatedb") 17 | set(PYTHON_EXECUTABLE CACHE STRING "python") 18 | set(THIRDPARTY_WIN_DIR CACHE STRING "") 19 | 20 | 21 | # --- User controllable variables --- 22 | 23 | # Whether to enable SSE. This only has effect on 32 bit. 24 | # Should probably match the setting for the Panda3D build 25 | set(ENABLE_SSE2 CACHE BOOL 1) 26 | set(TOUCHINPUT_ENABLED CACHE BOOL 0) 27 | 28 | 29 | # --- End of user variables -- 30 | 31 | # Reset cmake flags - we user our own 32 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "") 33 | set(CMAKE_CXX_FLAGS_RELEASE "") 34 | set(CMAKE_CXX_FLAGS_MINSIZEREL "") 35 | 36 | 37 | set(LIBRARIES "") 38 | 39 | # Windows - 32 and 64 bit 40 | if (WIN32) 41 | if (CMAKE_CL_64 STREQUAL "1") 42 | message(STATUS "Bitness: 64 bit ('${CMAKE_CL_64}')") 43 | set(IS_64_BIT TRUE) 44 | set(IS_32_BIT FALSE) 45 | else() 46 | message(STATUS "Bitness: 32 bit ('${CMAKE_CL_64}')") 47 | set(IS_64_BIT TRUE) 48 | set(IS_32_BIT FALSE) 49 | endif() 50 | 51 | set(PYTHONVER CACHE STRING "27") 52 | 53 | # Find panda path 54 | execute_process( 55 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/common.py" "--print-sdk-path" 56 | OUTPUT_VARIABLE WIN_PANDA_PATH 57 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 58 | message(STATUS "Detected panda3d installation: ${WIN_PANDA_PATH}") 59 | 60 | 61 | # Determine bitness 62 | set(THIRDPARTY_SUFFIX "-x64") 63 | if (IS_32_BIT) 64 | set(THIRDPARTY_SUFFIX "") 65 | endif() 66 | 67 | 68 | 69 | # Find visual studio version 70 | if (MSVC10) 71 | set(MSVC_VERSION_SHORT "vc10") 72 | elseif (MSVC12) 73 | set(MSVC_VERSION_SHORT "vc12") 74 | elseif(MSVC14) 75 | set(MSVC_VERSION_SHORT "vc14") 76 | else() 77 | message(WARMING "Unable to determine visual studio version, assuming Visual Studio 2015") 78 | set(MSVC_VERSION_SHORT "vc14") 79 | endif() 80 | message(STATUS "Detected visual studio version: ${MSVC_VERSION_SHORT}") 81 | 82 | 83 | set(THIRDPARTY_DIR ${THIRDPARTY_WIN_DIR}) 84 | message(STATUS "Detected thirdparty directory: ${THIRDPARTY_DIR}") 85 | 86 | 87 | # Find lib and include directory 88 | set(PANDA_LIBRARY_DIRS "${WIN_PANDA_PATH}/lib") 89 | set(PANDA_INCLUDE_DIR "${WIN_PANDA_PATH}/include") 90 | 91 | if (NOT (EXISTS "${PANDA_LIBRARY_DIRS}")) 92 | message(WARNING "Panda3D library directory does not exist! Expected it at: ${PANDA_LIBRARY_DIRS}") 93 | endif() 94 | 95 | set(PYTHON_LIBRARIES "${WIN_PANDA_PATH}/python/libs/python${PYTHONVER}.lib") 96 | set(PYTHON_INCLUDE_DIRS "${WIN_PANDA_PATH}/python/include") 97 | 98 | # [LIB] Bullet 99 | if(HAVE_LIB_BULLET) 100 | 101 | if (NOT (EXISTS "${PANDA_INCLUDE_DIR}/bullet_includes.h")) 102 | message(FATAL_ERROR "This module requires the bullet library, but your Panda3D Build was copmiled without bullet support! Use --use-bullet when compiling.") 103 | endif() 104 | 105 | include_directories("${THIRDPARTY_DIR}/bullet/include/") 106 | link_directories("${THIRDPARTY_DIR}/bullet/lib/") 107 | 108 | if(EXISTS "${THIRDPARTY_DIR}/bullet/lib/LinearMath_x64.lib") 109 | set(LIBRARIES "${LIBRARIES};LinearMath_x64;BulletCollision_x64;BulletDynamics_x64;BulletSoftBody_x64") 110 | else() 111 | set(LIBRARIES "${LIBRARIES};LinearMath;BulletCollision;BulletDynamics;BulletSoftBody") 112 | endif() 113 | endif() 114 | 115 | # [LIB] Eigen 3 116 | if (HAVE_LIB_EIGEN) 117 | include_directories("${THIRDPARTY_DIR}/eigen/include") 118 | 119 | # Eigen build flags (sync with Panda) 120 | if (HAVE_LIB_EIGEN) 121 | add_definitions("/DEIGEN_MPL2_ONLY=") 122 | if ((OPTIMIZE STREQUAL "3") OR (OPTIMIZE STREQUAL "4")) 123 | add_definitions("/DEIGEN_NO_DEBUG=") 124 | if (MSVC) 125 | # Squeeze out a bit more performance on MSVC builds... 126 | # Only do this if EIGEN_NO_DEBUG is also set, otherwise it 127 | # will turn them into runtime assertions. 128 | add_definitions("/DEIGEN_NO_STATIC_ASSERT=") 129 | endif() 130 | endif() 131 | endif() 132 | endif() 133 | 134 | # [LIB] Freetype 135 | if (HAVE_LIB_FREETYPE) 136 | include_directories("${THIRDPARTY_DIR}/freetype/include") 137 | include_directories("${THIRDPARTY_DIR}/freetype/include/freetype2") 138 | link_directories("${THIRDPARTY_DIR}/freetype/lib") 139 | set(LIBRARIES "${LIBRARIES};freetype") 140 | endif() 141 | 142 | # Check if all supplied files exist 143 | set(CHECK_PATHS ${PANDA_LIBRARY_DIRS} ${PANDA_INCLUDE_DIR} ${PYTHON_LIBRARIES} ${PYTHON_INCLUDE_DIRS}) 144 | foreach(PATH ${CHECK_PATHS}) 145 | if(NOT (EXISTS "${PATH}")) 146 | message(FATAL_ERROR "The supplied path '${PATH}' could not be found!") 147 | endif() 148 | endforeach() 149 | 150 | # Link panda libraries 151 | set(PANDA_LIBRARIES "libpanda;libpandaexpress;libp3dtool;libp3dtoolconfig;libp3direct;${INTERROGATE_LIB}") 152 | if(HAVE_LIB_BULLET) 153 | set(PANDA_LIBRARIES "libpandabullet;${PANDA_LIBRARIES}") 154 | endif() 155 | link_directories("${PANDA_LIBRARY_DIRS}") 156 | 157 | else() 158 | set(PYTHONVERDOT CACHE STRING "2.7") 159 | set(Python_ADDITIONAL_VERSIONS ${PYTHONVERDOT}) 160 | find_package(PythonLibs REQUIRED) 161 | 162 | # [LIB] Bullet 163 | if (HAVE_LIB_BULLET) 164 | find_package(Bullet REQUIRED) 165 | include_directories(${BULLET_INCLUDE_DIRS}) 166 | set(LIBRARIES "${LIBRARIES};${BULLET_LIBRARIES}") 167 | endif() 168 | 169 | # [LIB] Eigen 3 170 | if (HAVE_LIB_EIGEN) 171 | if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 172 | include_directories(BEFORE "/usr/local/include/eigen3/") 173 | else() 174 | include_directories(BEFORE "/usr/include/eigen3/") 175 | endif() 176 | 177 | # Eigen build flags (sync with Panda) 178 | if (HAVE_LIB_EIGEN) 179 | add_definitions("/DEIGEN_MPL2_ONLY=") 180 | if ((OPTIMIZE STREQUAL "3") OR (OPTIMIZE STREQUAL "4")) 181 | add_definitions("/DEIGEN_NO_DEBUG=") 182 | endif() 183 | endif() 184 | endif() 185 | 186 | # [LIB] Freetype 187 | if(HAVE_LIB_FREETYPE) 188 | find_package(Freetype REQUIRED) 189 | include_directories(${FREETYPE_INCLUDE_DIRS}) 190 | set(LIBRARIES "${LIBRARIES};${FREETYPE_LIBRARIES}") 191 | endif() 192 | 193 | # Locate the Panda3D headers 194 | find_path(PANDA_INCLUDE_DIR dtoolbase.h PATH_SUFFIXES panda3d) 195 | 196 | # Locate the Panda3D libraries 197 | set(REQ_LIBRARIES panda pandaexpress p3dtool p3dtoolconfig p3direct ${INTERROGATE_LIB}) 198 | if(HAVE_LIB_BULLET) 199 | set(REQ_LIBRARIES pandabullet ${REQ_LIBRARIES}) 200 | endif() 201 | 202 | set(PANDA_LIBRARIES "") 203 | foreach (lib ${REQ_LIBRARIES}) 204 | find_library(PANDA_LIBRARY_${lib} ${lib} PATH_SUFFIXES panda3d) 205 | set(PANDA_LIBRARIES "${PANDA_LIBRARIES};${PANDA_LIBRARY_${lib}}") 206 | endforeach() 207 | 208 | find_package_handle_standard_args(Panda DEFAULT_MSG 209 | PANDA_LIBRARIES PANDA_INCLUDE_DIR) 210 | 211 | if (NOT EXISTS ${PANDA_INCLUDE_DIR}) 212 | # Okay, the standard package handling failed. Try finding a local panda3d installation 213 | 214 | # Find panda path 215 | execute_process( 216 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/common.py" "--print-sdk-path" 217 | OUTPUT_VARIABLE LOCAL_PANDA_PATH 218 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 219 | 220 | if (NOT EXISTS "${LOCAL_PANDA_PATH}/include") 221 | message(FATAL_ERROR "Could not find system wide panda3d headers, and no local installation was found!") 222 | endif() 223 | 224 | set(PANDA_INCLUDE_DIR "${LOCAL_PANDA_PATH}/include") 225 | 226 | set(PANDA_LIBRARIES "") 227 | foreach(lib ${REQ_LIBRARIES}) 228 | find_library(PANDA_LIBRARY_${lib} ${lib} PATHS "${LOCAL_PANDA_PATH}/lib/") 229 | set(PANDA_LIBRARIES "${PANDA_LIBRARIES};${PANDA_LIBRARY_${lib}}") 230 | endforeach() 231 | 232 | endif() 233 | 234 | endif() 235 | 236 | # Find core.so/core.pyd path 237 | execute_process( 238 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/common.py" "--print-core-path" 239 | OUTPUT_VARIABLE PANDA_CORE_PATH 240 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 241 | 242 | 243 | # Link panda includes / libraries 244 | include_directories("${PANDA_INCLUDE_DIR}") 245 | include_directories("${PYTHON_INCLUDE_DIRS}") 246 | 247 | # Run interrogate over the files 248 | execute_process( 249 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/interrogate.py" "${PROJECT_NAME}" "${IGATE_VERBOSE}" 250 | OUTPUT_VARIABLE output 251 | ERROR_VARIABLE errors 252 | RESULT_VARIABLE return_code 253 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 254 | 255 | if (NOT ("${IGATE_VERBOSE}" STREQUAL "0" )) 256 | message(STATUS "IGATE stdout = ${output}") 257 | message(STATUS "IGATE stderr = ${errors}") 258 | endif() 259 | 260 | if(NOT ("${return_code}" STREQUAL "0")) 261 | message(FATAL_ERROR "Interrogate failed: ${output} ${errors}") 262 | endif() 263 | 264 | # Set compiler flags 265 | if (MSVC) 266 | 267 | # This matches the makepanda options 268 | 269 | if (IS_64_BIT) 270 | add_definitions("/favor:blend /DWIN64_VC /DWIN64 /bigobj") 271 | endif() 272 | 273 | add_definitions("/wd4996 /wd4275 /wd4267 /wd4101 /wd4273") 274 | 275 | if (TOUCHINPUT_ENABLED) 276 | # If touchinput is enabled 277 | add_definitions("/DWINVER=0x601") 278 | else() 279 | # If touchinput is disabled 280 | # add_definitions("/DWINVER=0x501") 281 | endif() 282 | 283 | if(IS_32_BIT AND ENABLE_SSE2) 284 | # If SSE is enabled 285 | add_definitions("/arch:SSE2") 286 | endif() 287 | 288 | # Different optimization settings 289 | if (OPTIMIZE STREQUAL "1") 290 | add_definitions("/MDd /Zi /RTCs /GS") 291 | elseif(OPTIMIZE STREQUAL "2") 292 | add_definitions("/MDd /Zi") 293 | elseif(OPTIMIZE STREQUAL "3") 294 | add_definitions("/MD /Zi /GS- /O2 /Ob2 /Oi /Ot /fp:fast") 295 | elseif(OPTIMIZE STREQUAL "4") 296 | add_definitions("/MD /Zi /GS- /Ox /Ob2 /Oi /Ot /fp:fast /DFORCE_INLINING /DNDEBUG /GL /LTCG") 297 | add_definitions("/Oy /Zp16") 298 | else() 299 | message(FATAL_ERROR "Invalid optimize value! Was: '${OPTIMIZE}'") 300 | endif() 301 | 302 | add_definitions("/Zm300 /DWIN32_VC /DWIN32") 303 | 304 | # No exceptions 305 | add_definitions("/D_HAS_EXCEPTIONS=0") 306 | 307 | # No RTTI 308 | add_definitions("/GR-") 309 | 310 | # Warning level 3 should be enough 311 | add_definitions("/W3") 312 | 313 | # Special case for 32 bit 314 | if(IS_32_BIT) 315 | add_definitions("/machine:x86") 316 | endif() 317 | 318 | # Use multi-core compilation 319 | add_definitions("/MP") 320 | # add_definitions("/GM-") 321 | 322 | 323 | else() 324 | # Silence CMake warning on macOS 325 | set(CMAKE_MACOSX_RPATH TRUE) 326 | # Assuming GCC here 327 | 328 | add_definitions("-ftemplate-depth-70 -fPIC -c") 329 | 330 | # On mac 331 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 332 | add_definitions("-Wno-deprecated-declarations") 333 | endif() 334 | 335 | add_definitions("-pthread") 336 | add_definitions("-std=c++11") 337 | 338 | # No exceptions 339 | add_definitions("-fno-exceptions") 340 | 341 | # Differnt optimization settings 342 | if (OPTIMIZE STREQUAL "1") 343 | add_definitions("-ggdb -D_DEBUG") 344 | elseif(OPTIMIZE STREQUAL "2") 345 | add_definitions("-O1 -D_DEBUG") 346 | elseif(OPTIMIZE STREQUAL "3") 347 | add_definitions("-O2") 348 | add_definitions("-ffast-math -fno-finite-math-only -fno-unsafe-math-optimizations") 349 | elseif(OPTIMIZE STREQUAL "4") 350 | add_definitions("-O3 -DNDEBUG -ffast-math -fno-unsafe-math-optimizations -fno-rtti") 351 | else() 352 | message(FATAL_ERROR "Invalid optimize value! Was: '${OPTIMIZE}'") 353 | endif() 354 | 355 | endif() 356 | 357 | # Define the module name 358 | add_definitions("/DPB_MODULE=${PROJECT_NAME}") 359 | add_definitions("/DPB_CFG_MODULE=${PROJECT_NAME}") 360 | 361 | # Collect sources for compiling 362 | file(GLOB_RECURSE SOURCES source/*.cpp source/*.cxx source/*.I source/*.hpp source/*.h source/*.cc source/*.c) 363 | include_directories("source/") 364 | set(SOURCES ${SOURCES_H} ${SOURCES}) 365 | 366 | # Collect subdirs for compiling 367 | file(GLOB POSSIBLE_DIRS RELATIVE ${CMAKE_CURRENT_LIST_DIR} source/*) 368 | foreach(PDIR ${POSSIBLE_DIRS}) 369 | if (IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${PDIR}) 370 | include_directories("${PDIR}") 371 | file(GLOB POSSIBLE_SUB_DIRS RELATIVE ${CMAKE_CURRENT_LIST_DIR}/${PDIR} ${PDIR}/*) 372 | foreach(PSUBDIR ${POSSIBLE_SUB_DIRS}) 373 | if (IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${PDIR}/${PSUBDIR}) 374 | include_directories("${PDIR}/${PSUBDIR}") 375 | endif() 376 | endforeach() 377 | endif() 378 | endforeach() 379 | 380 | 381 | if ((EXISTS "${CMAKE_CURRENT_LIST_DIR}/additional_libs.cmake")) 382 | include("${CMAKE_CURRENT_LIST_DIR}/additional_libs.cmake") 383 | endif() 384 | 385 | 386 | # Build library 387 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 388 | # macOS won't let us link a .so with another .so, so make a .dylib 389 | add_library(${PROJECT_NAME} SHARED ${SOURCES}) 390 | # Python doesn't detect .dylibs, so rename it .so 391 | set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") 392 | else() 393 | add_library(${PROJECT_NAME} MODULE ${SOURCES}) 394 | endif() 395 | 396 | # Don't add lib prefix on Linux 397 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") 398 | 399 | # Add the required libraries 400 | target_link_libraries(${PROJECT_NAME} ${PYTHON_LIBRARIES} ${PANDA_LIBRARIES} ${LIBRARIES}) 401 | 402 | if(WIN32) 403 | # # Eventually link core.pyd? 404 | else() 405 | set(CMAKE_MODULE_LINKER_FLAGS ${PANDA_CORE_PATH}) 406 | endif() 407 | 408 | # After building, copy the file to the current directory 409 | add_custom_command( 410 | TARGET ${PROJECT_NAME} 411 | POST_BUILD 412 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "${CMAKE_CURRENT_LIST_DIR}/scripts/finalize.py" "${PROJECT_NAME}" 413 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 414 | 415 | # Make shared library paths absolute on macOS 416 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 417 | execute_process( 418 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/common.py" "--print-lib-path" 419 | OUTPUT_VARIABLE PANDA_LIB_PATH 420 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 421 | execute_process( 422 | COMMAND "${PYTHON_EXECUTABLE}" "-B" "scripts/common.py" "--print-short-version" 423 | OUTPUT_VARIABLE PANDA_SHORT_VERSION 424 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} 425 | ) 426 | 427 | foreach(lib ${REQ_LIBRARIES}) 428 | add_custom_command( 429 | TARGET ${PROJECT_NAME} 430 | POST_BUILD 431 | COMMAND "install_name_tool" "-change" "@loader_path/../lib/lib${lib}.${PANDA_SHORT_VERSION}.dylib" "${PANDA_LIB_PATH}/lib${lib}.dylib" "${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}" 432 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 433 | endforeach() 434 | endif() 435 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 tobspr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/tobspr/P3DModuleBuilder.svg?branch=master)](https://travis-ci.org/tobspr/P3DModuleBuilder) 2 | 3 | # Panda3D Module Builder 4 | 5 | This tool allows you to seamlessly mix your C++ and Python code for the 6 | Panda3D Game Engine. 7 | 8 | It makes compiling your C++ code the matter of a single mouse-click. 9 | 10 | 11 | ## Features 12 | 13 | - Automatic Python bindings using `interrogate` 14 | - Works on Windows, Linux and Mac 15 | 16 | ## Getting started 17 | 18 | 19 | #### 1. Clone this repository 20 | 21 | You can use the download-zip button, or clone this repository. Copy it to a 22 | suitable path in your project. 23 | 24 | #### 2. Write your source code 25 | 26 | You can now start to write your C++ code and store it in the `source/` directory. 27 | Here's a simple example you can start with (save it as `source/example.h` for example): 28 | 29 | ```cpp 30 | #ifndef EXAMPLE_H 31 | #define EXAMPLE_H 32 | 33 | #include "pandabase.h" 34 | 35 | 36 | BEGIN_PUBLISH // This exposes all functions in this block to python 37 | 38 | inline int multiply(int a, int b) { 39 | return a * b; 40 | } 41 | 42 | END_PUBLISH 43 | 44 | 45 | class ExampleClass { 46 | PUBLISHED: // Exposes all functions in this scope, use instead of "public:" 47 | inline int get_answer() { 48 | return 42; 49 | }; 50 | }; 51 | 52 | 53 | #endif EXAMPLE_H 54 | ``` 55 | 56 | #### 3. Compile the module 57 | 58 | After you wrote your C++ code, run `python build.py`. It will ask you for 59 | a module name, for this example we will choose "TestModule". 60 | 61 | When the compilation finished, there should now be a `TestModule.pyd` / `TestModule.so` (depending on your platform) generated. 62 | 63 | #### 4. Use your module 64 | 65 | Using your compiled module is straightforward: 66 | 67 | ```python 68 | import panda3d.core # Make sure you import this first before importing your module 69 | 70 | import TestModule 71 | 72 | print(TestModule.multiply(3, 4)) # prints 12 73 | 74 | example = TestModule.ExampleClass() 75 | print(example.get_answer()) # prints 42 76 | 77 | ``` 78 | 79 | 80 | 81 | #### 82 | 83 | ## Requirements 84 | 85 | - The Panda3D SDK (get it here) 86 | - CMake 2.6 or higher (get it here) 87 | - windows only: The thirdparty folder installed in the Panda3D sdk folder (See here) 88 | 89 | 90 | **For compiling on Windows 32 bit:** 91 | 92 | - Visual Studio 2010/2015 93 | 94 | **For compiling on Windows 64 bit:** 95 | 96 | - Visual Studio 2010/2015 97 | - Windows SDK 7.1 (be sure to tick the VC++ 64 bit compilers option) 98 | 99 | 100 | ## Advanced configuration 101 | 102 | **Please clean up your built directories after changing the configuration! You can 103 | do so with passing `--clean` in the command line.** 104 | 105 | 106 | ### Command Line 107 | Command line options are: 108 | 109 | - `--optimize=N` to override the optimize option. This overrides the option set in the `config.ini` 110 | - `--clean` to force a clean rebuild 111 | 112 | ### config.ini 113 | Further adjustments can be made in the `config.ini` file: 114 | 115 | - You can set `generate_pdb` to `0` or `1` to control whether a `.pdb` file is generated. 116 | - You can set `optimize` to change the optimization. This has to match the `--optimize=` option of your Panda3D Build. 117 | - You can set `require_lib_eigen` to `1` to require the Eigen 3 library 118 | - You can set `require_lib_bullet` to `1` to require the Bullet library 119 | - You can set `require_lib_freetype` to `1` to require the Freetype library 120 | - You can set `verbose_igate` to `1` or `2` to get detailed interrogate output (1 = verbose, 2 = very verbose) 121 | 122 | ### Additional libaries 123 | 124 | If you want to include additional (external) libraries, you can create a 125 | cmake file named `additional_libs.cmake` in the folder of the module builder, 126 | which will then get included during the build. 127 | 128 | If you would like to include the protobuf library for example, your cmake file could look like this: 129 | 130 | ```cmake 131 | find_package(Protobuf REQUIRED) 132 | include_directories(${PROTOBUF_INCLUDE_DIRS}) 133 | set(LIBRARIES "${LIBRARIES};${PROTOBUF_LIBRARIES}") 134 | 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobspr/P3DModuleBuilder/7572305a178fa44e63cbcb2f34926bebf3c8f520/__init__.py -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Important: import panda3d as the very first library - otherwise it crashes 4 | import panda3d.core # noqa 5 | 6 | import sys 7 | import os 8 | import argparse 9 | from os.path import join, realpath, dirname 10 | 11 | # Change into the current directory 12 | os.chdir(dirname(realpath(__file__))) 13 | 14 | from scripts.common import get_ini_conf, write_ini_conf # noqa 15 | from scripts.setup import make_output_dir, run_cmake, run_cmake_build 16 | 17 | if __name__ == "__main__": 18 | 19 | # Arguments 20 | parser = argparse.ArgumentParser(description="P3DModuleBuilder") 21 | parser.add_argument( 22 | '--optimize', type=int, default=None, 23 | help="Optimize level, should match the one used for the Panda3D build",) 24 | parser.add_argument( 25 | "--clean", action="store_true", help="Forces a clean rebuild") 26 | args = parser.parse_args() 27 | 28 | # Python 2 compatibility 29 | if sys.version_info.major > 2: 30 | raw_input = input 31 | 32 | config_file = join(dirname(realpath(__file__)), "config.ini") 33 | config = get_ini_conf(config_file) 34 | 35 | # Find cached module name 36 | if "module_name" not in config or not config["module_name"]: 37 | module_name = str(raw_input("Enter a module name: ")) 38 | config["module_name"] = module_name.strip() 39 | 40 | # Check for outdated parameters 41 | for outdated_param in ["vc_version", "use_lib_eigen", "use_lib_bullet", "use_lib_freetype"]: 42 | if outdated_param in config: 43 | print("WARNING: Removing obsolete parameter '" + outdated_param + "', is now auto-detected.") 44 | del config[outdated_param] 45 | 46 | # Write back config 47 | write_ini_conf(config, config_file) 48 | 49 | # Just execute the build script 50 | make_output_dir(clean=args.clean) 51 | run_cmake(config, args) 52 | run_cmake_build(config, args) 53 | 54 | print("Success!") 55 | sys.exit(0) 56 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | generate_pdb=1 2 | optimize=3 3 | require_lib_bullet=0 4 | require_lib_eigen=0 5 | require_lib_freetype=0 6 | verbose_igate=0 7 | -------------------------------------------------------------------------------- /prefab.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Prefab gitignore which can be used by the user to ignore the files generated 3 | # by the module builder 4 | 5 | *.pyd 6 | *.pyc 7 | *.so 8 | *.pdb 9 | 10 | # Various output names 11 | win_*/ 12 | linux_*/ 13 | source/interrogate* 14 | 15 | # Source and script files 16 | scripts/ 17 | CMakeLists.txt 18 | build.py 19 | LICENSE 20 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobspr/P3DModuleBuilder/7572305a178fa44e63cbcb2f34926bebf3c8f520/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Common functions for the build system 4 | 5 | """ 6 | 7 | from __future__ import print_function 8 | 9 | import locale 10 | import sys 11 | import subprocess 12 | import platform 13 | 14 | from os.path import dirname, realpath, join, isdir, isfile 15 | from os import makedirs 16 | from sys import argv, stdout, stderr, exit 17 | from panda3d.core import PandaSystem, Filename, ExecutionEnvironment 18 | 19 | 20 | class MSVCVersion(object): 21 | def __init__(self, msc_ver, cmake_str, suffix): 22 | self.version = msc_ver 23 | self.cmake_str = cmake_str 24 | self.suffix = suffix 25 | 26 | @property 27 | def compiler_search_string(self): 28 | return "MSC v." + str(self.version) 29 | 30 | MSVC_VERSIONS = [ 31 | MSVCVersion(1400, "Visual Studio 8 2005", "vc80"), 32 | MSVCVersion(1500, "Visual Studio 9 2008", "vc90"), 33 | MSVCVersion(1600, "Visual Studio 10 2010", "vc100"), 34 | MSVCVersion(1700, "Visual Studio 11 2012", "vc110"), 35 | MSVCVersion(1800, "Visual Studio 12 2013", "vc120"), 36 | MSVCVersion(1900, "Visual Studio 14 2015", "vc140"), 37 | 38 | MSVCVersion(1910, "Visual Studio 15 2017", "vc141"), 39 | MSVCVersion(1911, "Visual Studio 15 2017", "vc141"), 40 | MSVCVersion(1912, "Visual Studio 15 2017", "vc141"), 41 | MSVCVersion(1913, "Visual Studio 15 2017", "vc141"), 42 | MSVCVersion(1914, "Visual Studio 15 2017", "vc141"), 43 | MSVCVersion(1915, "Visual Studio 15 2017", "vc141"), 44 | MSVCVersion(1916, "Visual Studio 15 2017", "vc141"), 45 | 46 | MSVCVersion(1920, "Visual Studio 16 2019", "vc142"), 47 | MSVCVersion(1921, "Visual Studio 16 2019", "vc142"), 48 | MSVCVersion(1922, "Visual Studio 16 2019", "vc142"), 49 | MSVCVersion(1923, "Visual Studio 16 2019", "vc142"), 50 | MSVCVersion(1924, "Visual Studio 16 2019", "vc142"), 51 | MSVCVersion(1925, "Visual Studio 16 2019", "vc142"), 52 | MSVCVersion(1926, "Visual Studio 16 2019", "vc142"), 53 | MSVCVersion(1927, "Visual Studio 16 2019", "vc142"), 54 | MSVCVersion(1928, "Visual Studio 16 2019", "vc142"), 55 | MSVCVersion(1929, "Visual Studio 16 2019", "vc142"), 56 | 57 | MSVCVersion(1930, "Visual Studio 17 2022", "vc143"), 58 | MSVCVersion(1931, "Visual Studio 17 2022", "vc143") 59 | ] 60 | 61 | def get_output_name(): 62 | """ Returns the name of the output dir, depending on the system architecture """ 63 | compiler_suffix = "" 64 | if is_windows(): 65 | compiler_suffix = "_" + get_panda_msvc_version().suffix 66 | 67 | version_suffix = "panda" + PandaSystem.get_version_string() 68 | 69 | return PandaSystem.getPlatform().lower() + "_{}_py{}{}{}".format( 70 | version_suffix, sys.version_info.major, 71 | sys.version_info.minor, compiler_suffix) 72 | 73 | 74 | def get_script_dir(): 75 | """ Returns the name of the directory the scripts are located in """ 76 | return dirname(realpath(__file__)) 77 | 78 | 79 | def get_basepath(): 80 | """ Returns the basepath, based on the script dir """ 81 | return join(get_script_dir(), "..") 82 | 83 | 84 | def get_output_dir(): 85 | """ Returns the output directory where CMake generates the build files into """ 86 | return realpath(join(get_basepath(), get_output_name())) 87 | 88 | 89 | def get_python_dir(): 90 | """ Returns the directory of the python installation """ 91 | return dirname(sys.executable) 92 | 93 | 94 | def is_subdirectory(base, subdir): 95 | """ Returns whether subdir is the same or subdirectory of base """ 96 | base_real = realpath(base) 97 | sub_real = realpath(subdir) 98 | return sub_real.startswith(base_real) 99 | 100 | 101 | def is_installed_via_pip(): 102 | """ Returns whether panda3d has been installed via pip and thus has a different path layout """ 103 | import panda3d 104 | python_base = get_python_dir() 105 | p3d_module = dirname(panda3d.__file__) 106 | return is_subdirectory(python_base, p3d_module) 107 | 108 | 109 | def get_panda_sdk_path(): 110 | """ Returns the path of the panda3d sdk, under windows """ 111 | # Import the base panda3d module 112 | import panda3d 113 | 114 | # Path of the module 115 | p3d_module = dirname(panda3d.__file__) 116 | p3d_sdk = join(p3d_module, "..") 117 | 118 | # Convert it to a valid filename 119 | fname = Filename.from_os_specific(p3d_sdk) 120 | fname.make_absolute() 121 | return fname.to_os_specific() 122 | 123 | 124 | def get_panda_core_lib_path(): 125 | """ Returns of the path of the core panda3d module, either core.pyd on windows 126 | or core.so on linux. This is an absolute path """ 127 | import panda3d.core 128 | return panda3d.core.__file__ 129 | 130 | 131 | def find_in_sdk(folder, filename, on_error=""): 132 | """ Finds the required folder in the sdk, requiring that it contains the given filename """ 133 | return first_existing_path([folder], required_file=filename, base_dir=get_panda_sdk_path(), on_error=on_error) 134 | 135 | def get_panda_bin_path(): 136 | """ Returns the path to the panda3d binaries """ 137 | if is_windows(): 138 | return find_in_sdk("bin", "interrogate.exe", on_error="Failed to find binary path") 139 | elif is_linux() or is_freebsd(): 140 | libpath = get_panda_lib_path() 141 | search = [ 142 | join(libpath, "../bin"), 143 | "/usr/bin", 144 | "/usr/local/bin", 145 | ] 146 | return first_existing_path(search, "interrogate") 147 | elif is_macos(): 148 | return find_in_sdk("bin", "interrogate", on_error="Failed to find binary path") 149 | raise NotImplementedError("Unsupported OS") 150 | 151 | 152 | def get_panda_lib_path(): 153 | """ Returns the path to the panda3d libraries """ 154 | if is_windows(): 155 | return find_in_sdk("lib", "libpanda.lib") 156 | elif is_linux() or is_macos() or is_freebsd(): 157 | return dirname(ExecutionEnvironment.get_dtool_name()) 158 | raise NotImplementedError("Unsupported OS") 159 | 160 | 161 | def get_panda_include_path(): 162 | """ Returns the path to the panda3d includes """ 163 | if is_windows() or is_macos(): 164 | return find_in_sdk("include", "dtoolbase.h") 165 | elif is_linux() or is_freebsd(): 166 | libpath = get_panda_lib_path() 167 | search = [ 168 | join(libpath, "../include/"), 169 | "/usr/include/panda3d", 170 | "/usr/local/include/panda3d" 171 | ] 172 | return first_existing_path(search, "dtoolbase.h") 173 | raise NotImplementedError("Unsupported OS") 174 | 175 | 176 | def first_existing_path(paths, required_file=None, base_dir=None, on_error=""): 177 | """ Returns the first path out of a given list of paths which exists. 178 | If required_file is set, the path additionally has to contain the given 179 | filename """ 180 | for pth in paths: 181 | if base_dir: 182 | pth = join(base_dir, pth) 183 | if isdir(pth) and (required_file is None or isfile(join(pth, required_file))): 184 | return realpath(pth) 185 | if on_error: 186 | print_error("\n" + on_error + "\n") 187 | print_error("We tried to find a folder or file on the following paths:") 188 | for pth in paths: 189 | print_error("[-]", realpath(join(base_dir, pth))) 190 | fatal_error("Failed to locate path") 191 | 192 | 193 | def is_64_bit(): 194 | """ Returns whether the build system is 64 bit (=True) or 32 bit (=False) """ 195 | return PandaSystem.get_platform() in ["win_amd64"] 196 | 197 | 198 | def is_windows(): 199 | """ Returns whether the build system is windows """ 200 | return platform.system().lower() == "windows" 201 | 202 | 203 | def is_linux(): 204 | """ Returns wheter the build system is linux """ 205 | return platform.system().lower() == "linux" 206 | 207 | def is_macos(): 208 | """ Returns whether the build system is macos (darwin) """ 209 | return platform.system().lower() == "darwin" 210 | 211 | def is_freebsd(): 212 | """ Returns whether the build system is freebsd """ 213 | return platform.system().lower() == "freebsd" 214 | 215 | 216 | def get_compiler_name(): 217 | """ Returns the name of the used compiler, either 'MSC', 'GCC' or 'CLANG' """ 218 | full_name = PandaSystem.get_compiler() 219 | compiler_name = full_name.split()[0] 220 | return compiler_name.upper() 221 | 222 | def decode_str(s): 223 | if sys.version_info.major >= 3: 224 | if isinstance(s, str): 225 | return s.encode("ascii", "ignore").decode("ascii", "ignore") 226 | else: 227 | return str(s) 228 | else: 229 | return s.encode("ascii", "ignore") 230 | 231 | 232 | def fatal_error(*args): 233 | """ Prints an error to stderr and then exits with a nonzero status code """ 234 | print("\n\n[!] FATAL ERROR:", *[decode_str(i) for i in args], file=stderr) 235 | exit(1) 236 | 237 | 238 | def debug_out(*args): 239 | """ Prints a debug output string """ 240 | print(*[decode_str(i) for i in args]) 241 | 242 | 243 | def print_error(*args): 244 | """ Prints a debug output string """ 245 | print(*[decode_str(i) for i in args], file=sys.stderr) 246 | 247 | 248 | def try_makedir(dirname): 249 | """ Tries to make the specified dir, but in case it fails it does nothing """ 250 | debug_out("Creating directory", dirname) 251 | try: 252 | makedirs(dirname) 253 | except: 254 | pass 255 | 256 | 257 | def try_execute(*args, **kwargs): 258 | """ Tries to execute the given process, if everything wents good, it just 259 | returns, otherwise it prints the output to stderr and exits with a nonzero 260 | status code """ 261 | error_formatter = kwargs.get("error_formatter", None) # Fix for Py < 3 262 | debug_out("Executing command: ", ' '.join(args), "\n") 263 | try: 264 | process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 265 | line = process.stdout.readline() 266 | output = line 267 | while line: 268 | debug_out(line.decode(locale.getpreferredencoding(), errors="ignore").rstrip("\r\n")) 269 | line = process.stdout.readline() 270 | output += line 271 | process.wait() 272 | if process.returncode != 0: 273 | if error_formatter: 274 | error_formatter(decode_str(output)) 275 | raise Exception("Process had non-zero returncode:", process.returncode) 276 | 277 | except subprocess.CalledProcessError as msg: 278 | debug_out("Process error:") 279 | output = msg.output.decode(locale.getpreferredencoding(), errors="ignore") 280 | if error_formatter: 281 | error_formatter(decode_str(output)) 282 | else: 283 | debug_out(output) 284 | fatal_error("Subprocess returned no-zero statuscode!") 285 | 286 | 287 | def join_abs(*args): 288 | """ Behaves like os.path.join, but replaces stuff like '/../' """ 289 | joined = join(*args) 290 | fname = Filename.from_os_specific(joined) 291 | fname.make_absolute() 292 | return fname.to_os_generic() 293 | 294 | 295 | def get_ini_conf(fname): 296 | """ Very simple one-lined .ini file reader, with no error checking """ 297 | with open(fname, "r") as handle: 298 | return {i.split("=")[0].strip(): i.split("=")[-1].strip() for i in handle.readlines() if i.strip()} # noqa 299 | 300 | 301 | def write_ini_conf(config, fname): 302 | """ Very simple .ini file writer, with no error checking """ 303 | with open(fname, "w") as handle: 304 | handle.write(''.join("{}={}\n".format(k, v) for k, v in sorted(config.items()))) 305 | 306 | def get_panda_msvc_version(): 307 | """ Returns the MSVC version panda was built with """ 308 | compiler = PandaSystem.get_compiler() 309 | for msvc_version in MSVC_VERSIONS: 310 | if msvc_version.compiler_search_string in compiler: 311 | return msvc_version 312 | 313 | print("FATAL ERROR: Unable to detect visual studio version of your Panda3D Build!", file=sys.stderr) 314 | print("Unknown compiler string was: '" + compiler + "'", file=sys.stderr) 315 | print("", file=sys.stderr) 316 | print("Known visual studio versions are:", file=sys.stderr) 317 | for msvc_version in MSVC_VERSIONS: 318 | print("-", msvc_version.cmake_str, "(" + msvc_version.compiler_search_string + ")", file=sys.stderr) 319 | print("", file=sys.stderr) 320 | fatal_error("Unable to determine compiler") 321 | 322 | def get_panda_short_version(): 323 | return PandaSystem.getVersionString().replace(".0", "") 324 | 325 | def have_eigen(): 326 | """ Returns whether this panda3d build has eigen support """ 327 | return PandaSystem.get_global_ptr().has_system("eigen") 328 | 329 | def have_bullet(): 330 | """ Returns whether this panda3d build has bullet support """ 331 | try: 332 | import panda3d.bullet 333 | except Exception as msg: 334 | return False 335 | 336 | return PandaSystem.get_global_ptr().has_system("Bullet") 337 | 338 | def have_freetype(): 339 | """ Returns whether this panda3d build has freetype support """ 340 | return PandaSystem.get_global_ptr().has_system("Freetype") 341 | 342 | 343 | def get_win_thirdparty_dir(): 344 | """ Returns the path of the thirdparty directory, windows only """ 345 | msvc_suffix = get_panda_msvc_version().suffix 346 | 347 | # The thirdparty directory is named "vc14" for example instead of "vc140" 348 | if msvc_suffix.endswith("0"): 349 | msvc_suffix = msvc_suffix[:-1] 350 | 351 | bit_suffix = "-x64" if is_64_bit() else "" 352 | full_suffix = "-" + msvc_suffix + bit_suffix 353 | 354 | possible_dirs = [] 355 | 356 | for base_dir in [".", "..", "../..", "thirdparty", "thirdparty" + full_suffix]: 357 | for thirdparty_suffix in ["", full_suffix]: 358 | for folder_suffix in ["", full_suffix]: 359 | possible_dirs.append(join( 360 | base_dir, "thirdparty" + thirdparty_suffix, "win-libs" + folder_suffix)) 361 | 362 | error_msg = ("The thirdparty directory could not be found. You can get it from " 363 | "https://www.panda3d.org/forums/viewtopic.php?f=9&t=18775 by downloading " 364 | "a prebuilt version or by compiling it yourself.") 365 | return first_existing_path(possible_dirs, base_dir=get_panda_sdk_path(), on_error=error_msg) 366 | 367 | 368 | if __name__ == "__main__": 369 | 370 | # Command line scripts 371 | 372 | if len(argv) != 2: 373 | fatal_error("USAGE: ppython common.py