Merge branch 'master' into astron-master

This commit is contained in:
Sam Edwards 2019-09-18 00:34:49 -06:00
commit aa7c1d76bf
923 changed files with 16836 additions and 99198 deletions

2
.github/FUNDING.yml vendored Normal file
View File

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

5
.gitignore vendored
View File

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

View File

@ -44,7 +44,6 @@ install:
- $PYTHONV -m pip install pytest
script:
- $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
- test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
- test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest -v tests
notifications:
irc:
@ -54,3 +53,8 @@ notifications:
on_failure: always
use_notice: true
skip_join: false
webhooks:
urls:
- https://www.panda3d.org/webhooks/travis-ci.php
on_success: change
on_failure: always

35
BACKERS.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,99 +0,0 @@
// Based on Apple sample code at
// http://developer.apple.com/internet/webcontent/examples/detectplugins_source.html
// initialize global variables
var detectableWithVB = false;
var pluginFound = false;
function goURL(daURL) {
// Assume we have Javascript 1.1 functionality.
window.location.replace(daURL);
return;
}
function redirectCheck(pluginFound, redirectURL, redirectIfFound) {
// check for redirection
if( redirectURL && ((pluginFound && redirectIfFound) ||
(!pluginFound && !redirectIfFound)) ) {
// go away
goURL(redirectURL);
return pluginFound;
} else {
// stay here and return result of plugin detection
return pluginFound;
}
}
function canDetectPlugins() {
if( detectableWithVB || (navigator.plugins && navigator.plugins.length > 0) ) {
return true;
} else {
return false;
}
}
function detectPanda3D(redirectURL, redirectIfFound) {
pluginFound = detectPlugin('Panda3D');
// if not found, try to detect with VisualBasic
if(!pluginFound && detectableWithVB) {
pluginFound = detectActiveXControl('P3DACTIVEX.P3DActiveXCtrl.1');
}
// check for redirection
return redirectCheck(pluginFound, redirectURL, redirectIfFound);
}
function detectPlugin() {
// allow for multiple checks in a single pass
var daPlugins = detectPlugin.arguments;
// consider pluginFound to be false until proven true
var pluginFound = false;
// if plugins array is there and not fake
if (navigator.plugins && navigator.plugins.length > 0) {
var pluginsArrayLength = navigator.plugins.length;
// for each plugin...
for (pluginsArrayCounter=0; pluginsArrayCounter < pluginsArrayLength; pluginsArrayCounter++ ) {
// loop through all desired names and check each against the current plugin name
var numFound = 0;
for(namesCounter=0; namesCounter < daPlugins.length; namesCounter++) {
// if desired plugin name is found in either plugin name or description
if( (navigator.plugins[pluginsArrayCounter].name.indexOf(daPlugins[namesCounter]) >= 0) ||
(navigator.plugins[pluginsArrayCounter].description.indexOf(daPlugins[namesCounter]) >= 0) ) {
// this name was found
numFound++;
}
}
// now that we have checked all the required names against this one plugin,
// if the number we found matches the total number provided then we were successful
if(numFound == daPlugins.length) {
pluginFound = true;
// if we've found the plugin, we can stop looking through at the rest of the plugins
break;
}
}
}
return pluginFound;
} // detectPlugin
// Here we write out the VBScript block for MSIE Windows
if ((navigator.userAgent.indexOf('MSIE') != -1) && (navigator.userAgent.indexOf('Win') != -1)) {
document.writeln('<script language="VBscript">');
document.writeln('\'do a one-time test for a version of VBScript that can handle this code');
document.writeln('detectableWithVB = False');
document.writeln('If ScriptEngineMajorVersion >= 2 then');
document.writeln(' detectableWithVB = True');
document.writeln('End If');
document.writeln('\'this next function will detect most plugins');
document.writeln('Function detectActiveXControl(activeXControlName)');
document.writeln(' on error resume next');
document.writeln(' detectActiveXControl = False');
document.writeln(' If detectableWithVB Then');
document.writeln(' detectActiveXControl = IsObject(CreateObject(activeXControlName))');
document.writeln(' End If');
document.writeln('End Function');
document.writeln('</script>');
}

View File

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

View File

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

View File

@ -1,132 +0,0 @@
// This script injects the appropriate syntax into the document to
// embed Panda3D, either for the ActiveX or Mozilla-based plugin.
// It is also possible to write browser-independent code by nesting
// <object> tags, but this causes problems when you need to reference
// the particular object that is actually running (which object is
// it?) for scripting purposes.
// This script writes only a single <object> tag, and it can be
// assigned the id you specify, avoiding this ambiguity.
var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
function P3D_Generateobj(objAttrs, params, embedAttrs, imageAttrs)
{
var str = '';
if (isIE && isWin && !isOpera)
{
str += '<object ';
for (var i in objAttrs)
{
str += i + '="' + objAttrs[i] + '" ';
}
str += '>';
for (var i in params)
{
str += '<param name="' + i + '" value="' + params[i] + '" /> ';
}
}
else
{
str += '<object ';
for (var i in embedAttrs)
{
str += i + '="' + embedAttrs[i] + '" ';
}
str += '> ';
}
if (imageAttrs["src"]) {
if (imageAttrs["href"]) {
str += '<a href="' + imageAttrs["href"] + '">';
}
str += '<img ';
for (var i in imageAttrs) {
if (i != "href") {
str += i + '="' + imageAttrs[i] + '" ';
}
}
str += '>';
if (imageAttrs["href"]) {
str += '</a>';
}
}
str += '</object>';
document.write(str);
}
function P3D_RunContent() {
var ret =
P3D_GetArgs
(arguments, "clsid:924b4927-d3ba-41ea-9f7e-8a89194ab3ac",
"application/x-panda3d");
P3D_Generateobj(ret.objAttrs, ret.params, ret.embedAttrs, ret.imageAttrs);
}
function P3D_GetArgs(args, classid, mimeType){
var ret = new Object();
ret.embedAttrs = new Object();
ret.params = new Object();
ret.objAttrs = new Object();
ret.imageAttrs = new Object();
for (var i = 0; i < args.length; i = i + 2){
var currArg = args[i].toLowerCase();
switch (currArg){
case "src":
case "data":
ret.embedAttrs['data'] = args[i+1];
ret.params['data'] = args[i+1];
break;
case "codebase":
ret.objAttrs['codebase'] = args[i+1];
break;
case "noplugin_img":
ret.imageAttrs["src"] = args[i+1];
ret.imageAttrs["border"] = '0';
break;
case "noplugin_href":
ret.imageAttrs["href"] = args[i+1];
break;
case "splash_img":
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
if (!ret.imageAttrs["src"]) {
ret.imageAttrs["src"] = args[i+1];
}
break;
case "width":
case "height":
ret.imageAttrs[args[i]] = ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
break;
case "id":
case "align":
case "vspace":
case "hspace":
case "class":
case "title":
case "accesskey":
case "name":
case "tabindex":
ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
break;
default:
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
}
}
ret.objAttrs["classid"] = classid;
if (mimeType) ret.embedAttrs["type"] = mimeType;
return ret;
}

View File

@ -273,6 +273,7 @@ def processModule(handle, package):
if "panda3d." + package == module_name:
processType(handle, type)
else:
typename = interrogate_type_name(type)
print("Type %s has no module name" % typename)
for i_func in range(interrogate_number_of_global_functions()):
@ -283,7 +284,8 @@ def processModule(handle, package):
if "panda3d." + package == module_name:
processFunction(handle, func)
else:
print("Type %s has no module name" % typename)
funcname = interrogate_function_name(func)
print("Function %s has no module name" % funcname)
print("}", file=handle)

View File

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

View File

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

View File

@ -210,15 +210,15 @@ class CompilationEnvironment:
if ('MAKEPANDA' in os.environ):
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
self.compileObjDll = self.compileObjExe
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\libs" /out:%(basename)s.exe %(basename)s.obj'
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs" /out:%(basename)s.exe %(basename)s.obj'
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
else:
os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\include" /I"%(MSVC)s\include" %(filename)s'
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\\include" /I"%(MSVC)s\\include" %(filename)s'
self.compileObjDll = self.compileObjExe
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs" /out:%(basename)s.exe %(basename)s.obj'
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs" /out:%(basename)s.exe %(basename)s.obj'
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
elif self.platform.startswith('osx_'):
# OSX
@ -800,29 +800,36 @@ class Freezer:
self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
else:
self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
abi_version = '{0}{1}'.format(*sys.version_info)
abi_flags = ''
if sys.version_info < (3, 8):
abi_flags += 'm'
if 'linux' in self.platform:
self.moduleSuffixes += [
('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
('.cpython-{0}{1}-x86_64-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
('.cpython-{0}{1}-i686-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
('.so', 'rb', 3),
]
elif 'win' in self.platform:
# ABI flags are not appended on Windows.
self.moduleSuffixes += [
('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3),
('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3),
('.cp{0}-win_amd64.pyd'.format(abi_version), 'rb', 3),
('.cp{0}-win32.pyd'.format(abi_version), 'rb', 3),
('.pyd', 'rb', 3),
]
elif 'mac' in self.platform:
self.moduleSuffixes += [
('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3),
('.cpython-{0}{1}-darwin.so'.format(abi_version, abi_flags), 'rb', 3),
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
('.so', 'rb', 3),
]
else: # FreeBSD et al.
self.moduleSuffixes += [
('.cpython-{0}{1}m.so'.format(*sys.version_info), 'rb', 3),
('.abi{0}.so'.format(*sys.version_info), 'rb', 3),
('.cpython-{0}{1}.so'.format(abi_version, abi_flags), 'rb', 3),
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
('.so', 'rb', 3),
]
@ -845,7 +852,7 @@ class Freezer:
allowChildren is true, the children of the indicated module
may still be included."""
assert self.mf == None
assert self.mf is None
self.modules[moduleName] = self.ModuleDef(
moduleName, exclude = True,
@ -879,7 +886,7 @@ class Freezer:
print("couldn't import %s" % (moduleName))
module = None
if module != None:
if module is not None:
for symbol in moduleName.split('.')[1:]:
module = getattr(module, symbol)
if hasattr(module, '__path__'):
@ -894,7 +901,7 @@ class Freezer:
if '.' in baseName:
parentName, baseName = moduleName.rsplit('.', 1)
path = self.getModulePath(parentName)
if path == None:
if path is None:
return None
try:
@ -918,7 +925,7 @@ class Freezer:
print("couldn't import %s" % (moduleName))
module = None
if module != None:
if module is not None:
for symbol in moduleName.split('.')[1:]:
module = getattr(module, symbol)
if hasattr(module, '__all__'):
@ -931,7 +938,7 @@ class Freezer:
if '.' in baseName:
parentName, baseName = moduleName.rsplit('.', 1)
path = self.getModulePath(parentName)
if path == None:
if path is None:
return None
try:
@ -988,9 +995,9 @@ class Freezer:
for parentName, newParentName in parentNames:
modules = self.getModuleStar(parentName)
if modules == None:
if modules is None:
# It's actually a regular module.
mdef[newParentName] = self.ModuleDef(
mdefs[newParentName] = self.ModuleDef(
parentName, implicit = implicit, guess = guess,
fromSource = fromSource, text = text)
@ -1024,7 +1031,7 @@ class Freezer:
directories within a particular directory.
"""
assert self.mf == None
assert self.mf is None
if not newName:
newName = moduleName
@ -1046,7 +1053,7 @@ class Freezer:
to done(), you may not add any more modules until you call
reset(). """
assert self.mf == None
assert self.mf is None
# If we are building an exe, we also need to implicitly
# bring in Python's startup modules.

View File

@ -13,12 +13,15 @@ import stat
import struct
import imp
import string
import time
import tempfile
import setuptools
import distutils.log
from . import FreezeTool
from . import pefile
from .icon import Icon
import panda3d.core as p3d
@ -30,6 +33,15 @@ if sys.version_info < (3, 0):
# Python 3 defines these subtypes of IOError, but Python 2 doesn't.
FileNotFoundError = IOError
# Warn the user. They might be using Python 2 by accident.
print("=================================================================")
print("WARNING: You are using Python 2, which will soon be discontinued.")
print("WARNING: Please use Python 3 for best results and continued")
print("WARNING: support after the EOL date of December 31st, 2019.")
print("=================================================================")
sys.stdout.flush()
time.sleep(4.0)
def _parse_list(input):
if isinstance(input, basestring):
@ -54,6 +66,8 @@ def _parse_dict(input):
def egg2bam(_build_cmd, srcpath, dstpath):
if dstpath.endswith('.gz') or dstpath.endswith('.pz'):
dstpath = dstpath[:-3]
dstpath = dstpath + '.bam'
try:
subprocess.check_call([
@ -214,6 +228,7 @@ class build_apps(setuptools.Command):
self.exclude_patterns = []
self.include_modules = {}
self.exclude_modules = {}
self.icons = {}
self.platforms = [
'manylinux1_x86_64',
'macosx_10_6_x86_64',
@ -229,7 +244,9 @@ class build_apps(setuptools.Command):
self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
self.use_optimized_wheels = True
self.optimized_wheel_index = ''
self.pypi_extra_indexes = []
self.pypi_extra_indexes = [
'https://archive.panda3d.org/thirdparty',
]
self.file_handlers = {}
self.exclude_dependencies = [
# Windows
@ -286,6 +303,7 @@ class build_apps(setuptools.Command):
key: _parse_list(value)
for key, value in _parse_dict(self.exclude_modules).items()
}
self.icons = _parse_dict(self.icons)
self.platforms = _parse_list(self.platforms)
self.plugins = _parse_list(self.plugins)
self.extra_prc_files = _parse_list(self.extra_prc_files)
@ -345,6 +363,18 @@ class build_apps(setuptools.Command):
tmp.update(self.package_data_dirs)
self.package_data_dirs = tmp
self.icon_objects = {}
for app, iconpaths in self.icons.items():
if not isinstance(iconpaths, list) and not isinstance(iconpaths, tuple):
iconpaths = (iconpaths,)
iconobj = Icon()
for iconpath in iconpaths:
iconobj.addImage(iconpath)
iconobj.generateMissingImages()
self.icon_objects[app] = iconobj
def run(self):
self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
@ -421,6 +451,22 @@ class build_apps(setuptools.Command):
return wheelpaths
def update_pe_resources(self, appname, runtime):
"""Update resources (e.g., icons) in windows PE file"""
icon = self.icon_objects.get(
appname,
self.icon_objects.get('*', None),
)
if icon is not None:
pef = pefile.PEFile()
pef.open(runtime, 'r+')
pef.add_icon(icon)
pef.add_resource_section()
pef.write_changes()
pef.close()
def bundle_macos_app(self, builddir):
"""Bundle built runtime into a .app for macOS"""
@ -462,6 +508,15 @@ class build_apps(setuptools.Command):
'CFBundleSignature': '', #TODO
'CFBundleExecutable': self.macos_main_app,
}
icon = self.icon_objects.get(
self.macos_main_app,
self.icon_objects.get('*', None)
)
if icon is not None:
plist['CFBundleIconFile'] = 'iconfile'
icon.makeICNS(os.path.join(resdir, 'iconfile.icns'))
with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
if hasattr(plistlib, 'dump'):
plistlib.dump(plist, f)
@ -606,6 +661,18 @@ class build_apps(setuptools.Command):
stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
stub_file = open(stub_path, 'rb')
# Do we need an icon? On Windows, we need to add this to the stub
# before we add the blob.
if 'win' in platform:
temp_file = tempfile.NamedTemporaryFile(suffix='-icon.exe', delete=False)
temp_file.write(stub_file.read())
stub_file.close()
temp_file.close()
self.update_pe_resources(appname, temp_file.name)
stub_file = open(temp_file.name, 'rb')
else:
temp_file = None
freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
'prc_data': prcexport if self.embed_prc_data else None,
'default_prc_dir': self.default_prc_dir,
@ -621,6 +688,9 @@ class build_apps(setuptools.Command):
}, self.log_append)
stub_file.close()
if temp_file:
os.unlink(temp_file.name)
# Copy the dependencies.
search_path = [builddir]
if use_wheels:
@ -827,6 +897,9 @@ class build_apps(setuptools.Command):
os.makedirs(dst_dir)
ext = os.path.splitext(src)[1]
# If the file ends with .gz/.pz, we strip this off.
if ext in ('.gz', '.pz'):
ext = os.path.splitext(src[:-3])[1]
if not ext:
ext = os.path.basename(src)
@ -1346,8 +1419,9 @@ class bdist_apps(setuptools.Command):
self.run_command('build_apps')
platforms = build_cmd.platforms
build_base = build_cmd.build_base
os.makedirs(self.dist_dir, exist_ok=True)
build_base = os.path.abspath(build_cmd.build_base)
if not os.path.exists(self.dist_dir):
os.makedirs(self.dist_dir)
os.chdir(self.dist_dir)
for platform in platforms:

269
direct/src/dist/icon.py vendored Normal file
View File

@ -0,0 +1,269 @@
from direct.directnotify.DirectNotifyGlobal import *
from panda3d.core import PNMImage, Filename
class Icon:
""" This class is used to create an icon for various platforms. """
notify = directNotify.newCategory("Icon")
def __init__(self):
self.images = {}
def addImage(self, image):
""" Adds an image to the icon. Returns False on failure, True on success.
Only one image per size can be loaded, and the image size must be square. """
if not isinstance(image, PNMImage):
fn = image
if not isinstance(fn, Filename):
fn = Filename.fromOsSpecific(fn)
image = PNMImage()
if not image.read(fn):
Icon.notify.warning("Image '%s' could not be read" % fn.getBasename())
return False
if image.getXSize() != image.getYSize():
Icon.notify.warning("Ignoring image without square size")
return False
self.images[image.getXSize()] = image
return True
def generateMissingImages(self):
""" Generates image sizes that should be present but aren't by scaling
from the next higher size. """
for required_size in (256, 128, 48, 32, 16):
if required_size in self.images:
continue
sizes = sorted(self.images.keys())
if required_size * 2 in sizes:
from_size = required_size * 2
else:
from_size = 0
for from_size in sizes:
if from_size > required_size:
break
if from_size > required_size:
Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size))
image = PNMImage(required_size, required_size)
if self.images[from_size].hasAlpha():
image.addAlpha()
image.quickFilterFrom(self.images[from_size])
self.images[required_size] = image
else:
Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size))
def _write_bitmap(self, fp, image, size, bpp):
""" Writes the bitmap header and data of an .ico file. """
fp.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
# XOR mask
if bpp == 24:
# Align rows to 4-byte boundary
rowalign = b'\0' * (-(size * 3) & 3)
for y in xrange(size):
for x in xrange(size):
r, g, b = image.getXel(x, size - y - 1)
fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
fp.write(rowalign)
elif bpp == 32:
for y in xrange(size):
for x in xrange(size):
r, g, b, a = image.getXelA(x, size - y - 1)
fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
elif bpp == 8:
# We'll have to generate a palette of 256 colors.
hist = PNMImage.Histogram()
image2 = PNMImage(image)
if image2.hasAlpha():
image2.premultiplyAlpha()
image2.removeAlpha()
image2.quantize(256)
image2.make_histogram(hist)
colors = list(hist.get_pixels())
assert len(colors) <= 256
# Write the palette.
i = 0
while i < 256 and i < len(colors):
r, g, b, a = colors[i]
fp.write(struct.pack('<BBBB', b, g, r, 0))
i += 1
if i < 256:
# Fill the rest with zeroes.
fp.write(b'\x00' * (4 * (256 - i)))
# Write indices. Align rows to 4-byte boundary.
rowalign = b'\0' * (-size & 3)
for y in xrange(size):
for x in xrange(size):
pixel = image2.get_pixel(x, size - y - 1)
index = colors.index(pixel)
if index >= 256:
# Find closest pixel instead.
index = closest_indices[index - 256]
fp.write(struct.pack('<B', index))
fp.write(rowalign)
else:
raise ValueError("Invalid bpp %d" % (bpp))
# Create an AND mask, aligned to 4-byte boundary
if image.hasAlpha() and bpp <= 8:
rowalign = b'\0' * (-((size + 7) >> 3) & 3)
for y in xrange(size):
mask = 0
num_bits = 7
for x in xrange(size):
a = image.get_alpha_val(x, size - y - 1)
if a <= 1:
mask |= (1 << num_bits)
num_bits -= 1
if num_bits < 0:
fp.write(struct.pack('<B', mask))
mask = 0
num_bits = 7
if num_bits < 7:
fp.write(struct.pack('<B', mask))
fp.write(rowalign)
else:
andsize = (size + 7) >> 3
if andsize % 4 != 0:
andsize += 4 - (andsize % 4)
fp.write(b'\x00' * (andsize * size))
def makeICO(self, fn):
""" Writes the images to a Windows ICO file. Returns True on success. """
if not isinstance(fn, Filename):
fn = Filename.fromOsSpecific(fn)
fn.setBinary()
# ICO files only support resolutions up to 256x256.
count = 0
for size in self.images.keys():
if size < 256:
count += 1
if size <= 256:
count += 1
dataoffs = 6 + count * 16
ico = open(fn, 'wb')
ico.write(struct.pack('<HHH', 0, 1, count))
# Write 8-bpp image headers for sizes under 256x256.
for size, image in self.images.items():
if size >= 256:
continue
ico.write(struct.pack('<BB', size, size))
# Calculate row sizes
xorsize = size
if xorsize % 4 != 0:
xorsize += 4 - (xorsize % 4)
andsize = (size + 7) >> 3
if andsize % 4 != 0:
andsize += 4 - (andsize % 4)
datasize = 40 + 256 * 4 + (xorsize + andsize) * size
ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs))
dataoffs += datasize
# Write 24/32-bpp image headers.
for size, image in self.images.items():
if size > 256:
continue
elif size == 256:
ico.write(b'\0\0')
else:
ico.write(struct.pack('<BB', size, size))
# Calculate the size so we can write the offset within the file.
if image.hasAlpha():
bpp = 32
xorsize = size * 4
else:
bpp = 24
xorsize = size * 3 + (-(size * 3) & 3)
andsize = (size + 7) >> 3
if andsize % 4 != 0:
andsize += 4 - (andsize % 4)
datasize = 40 + (xorsize + andsize) * size
ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs))
dataoffs += datasize
# Now write the actual icon bitmap data.
for size, image in self.images.items():
if size < 256:
self._write_bitmap(ico, image, size, 8)
for size, image in self.images.items():
if size <= 256:
bpp = 32 if image.hasAlpha() else 24
self._write_bitmap(ico, image, size, bpp)
assert ico.tell() == dataoffs
ico.close()
return True
def makeICNS(self, fn):
""" Writes the images to an Apple ICNS file. Returns True on success. """
if not isinstance(fn, Filename):
fn = Filename.fromOsSpecific(fn)
fn.setBinary()
icns = open(fn, 'wb')
icns.write(b'icns\0\0\0\0')
icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'}
mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'}
png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'}
pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
for size, image in sorted(self.images.items(), key=lambda item:item[0]):
if size in png_types and pngtype is not None:
stream = StringStream()
image.write(stream, "", pngtype)
pngdata = stream.data
icns.write(png_types[size])
icns.write(struct.pack('>I', len(pngdata)))
icns.write(pngdata)
elif size in icon_types:
# If it has an alpha channel, we write out a mask too.
if image.hasAlpha():
icns.write(mask_types[size])
icns.write(struct.pack('>I', size * size + 8))
for y in xrange(size):
for x in xrange(size):
icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
icns.write(icon_types[size])
icns.write(struct.pack('>I', size * size * 4 + 8))
for y in xrange(size):
for x in xrange(size):
r, g, b = image.getXel(x, y)
icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
length = icns.tell()
icns.seek(4)
icns.write(struct.pack('>I', length))
icns.close()
return True

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
__all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
from panda3d.core import *
from direct.showbase import ShowBaseGlobal
from . import DirectGuiGlobals as DGG
from .DirectFrame import *
from .DirectButton import *
@ -207,7 +208,7 @@ class DirectDialog(DirectFrame):
image = None
# Get size of text/geom without image (for state 0)
if image:
image.reparentTo(hidden)
image.reparentTo(ShowBaseGlobal.hidden)
bounds = self.stateNodePath[0].getTightBounds()
if image:
image.reparentTo(self.stateNodePath[0])

View File

@ -4,6 +4,7 @@ text entered using the keyboard."""
__all__ = ['DirectEntry']
from panda3d.core import *
from direct.showbase import ShowBaseGlobal
from . import DirectGuiGlobals as DGG
from .DirectFrame import *
from .OnscreenText import OnscreenText
@ -94,7 +95,7 @@ class DirectEntry(DirectFrame):
self.onscreenText = self.createcomponent(
'text', (), None,
OnscreenText,
(), parent = hidden,
(), parent = ShowBaseGlobal.hidden,
# Pass in empty text to avoid extra work, since its really
# The PGEntry which will use the TextNode to generate geometry
text = '',
@ -215,11 +216,11 @@ class DirectEntry(DirectFrame):
self._autoCapitalize()
def _autoCapitalize(self):
name = self.get().decode('utf-8')
name = self.guiItem.getWtext()
# capitalize each word, allowing for things like McMutton
capName = ''
capName = u''
# track each individual word to detect prefixes like Mc
wordSoFar = ''
wordSoFar = u''
# track whether the previous character was part of a word or not
wasNonWordChar = True
for i, character in enumerate(name):
@ -228,9 +229,9 @@ class DirectEntry(DirectFrame):
# This assumes that string.lower and string.upper will return different
# values for all unicode letters.
# - Don't count apostrophes as a break between words
if character.lower() == character.upper() and character != "'":
if character.lower() == character.upper() and character != u"'":
# we are between words
wordSoFar = ''
wordSoFar = u''
wasNonWordChar = True
else:
capitalize = False
@ -254,7 +255,8 @@ class DirectEntry(DirectFrame):
wordSoFar += character
wasNonWordChar = False
capName += character
self.enterText(capName.encode('utf-8'))
self.guiItem.setWtext(capName)
self.guiItem.setCursorPosition(self.guiItem.getNumCharacters())
def focusOutCommandFunc(self):
if self['focusOutCommand']:

View File

@ -29,19 +29,41 @@ class DirectEntryScroll(DirectFrame):
# frameSize = (-0.006, 3.2, -0.015, 0.036),
# if you need to scale the entry scale it's parent instead
self.entry = entry
self.canvas = NodePath(self.guiItem.getCanvasNode())
self.entry.reparentTo(self.canvas)
self.canvas.setPos(0,0,0)
self.entry.bind(DGG.CURSORMOVE,self.cursorMove)
self.entry = None
if entry is not None:
self.entry = entry
self.entry.reparentTo(self.canvas)
self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
self.canvas.node().setBounds(OmniBoundingVolume())
self.canvas.node().setFinal(1)
self.resetCanvas()
def setEntry(self, entry):
"""
Sets a DirectEntry element for this scroll frame. A DirectEntryScroll
can only hold one entry at a time, so make sure to not call this
function twice or call clearEntry before to make sure no entry
is already set.
"""
assert self.entry is None, "An entry was already set for this DirectEntryScroll element"
self.entry = entry
self.entry.reparentTo(self.canvas)
self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
def clearEntry(self):
"""
detaches and unbinds the entry from the scroll frame and its
events. You'll be responsible for destroying it.
"""
if self.entry is None: return
self.entry.unbind(DGG.CURSORMOVE)
self.entry.detachNode()
self.entry = None
def cursorMove(self, cursorX, cursorY):
cursorX = self.entry.guiItem.getCursorX() * self.entry['text_scale'][0]

View File

@ -634,7 +634,7 @@ class DirectGuiBase(DirectObject.DirectObject):
"""
# Need to tack on gui item specific id
gEvent = event + self.guiId
if ShowBase.config.GetBool('debug-directgui-msgs', False):
if ShowBaseGlobal.config.GetBool('debug-directgui-msgs', False):
from direct.showbase.PythonUtil import StackTrace
print(gEvent)
print(StackTrace())
@ -663,7 +663,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
# Determine the default initial state for inactive (or
# unclickable) components. If we are in edit mode, these are
# actually clickable by default.
guiEdit = ShowBase.config.GetBool('direct-gui-edit', False)
guiEdit = ShowBaseGlobal.config.GetBool('direct-gui-edit', False)
if guiEdit:
inactiveInitState = DGG.NORMAL
else:
@ -729,7 +729,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
guiObjectCollector.addLevel(1)
guiObjectCollector.flushLevel()
# track gui items by guiId for tracking down leaks
if ShowBase.config.GetBool('track-gui-items', False):
if ShowBaseGlobal.config.GetBool('track-gui-items', False):
if not hasattr(ShowBase, 'guiItems'):
ShowBase.guiItems = {}
if self.guiId in ShowBase.guiItems:

View File

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

View File

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

View File

@ -3,6 +3,7 @@
__all__ = ['DirectScrolledListItem', 'DirectScrolledList']
from panda3d.core import *
from direct.showbase import ShowBaseGlobal
from . import DirectGuiGlobals as DGG
from direct.directnotify import DirectNotifyGlobal
from direct.task.Task import Task
@ -369,7 +370,7 @@ class DirectScrolledList(DirectFrame):
del self.currentSelected
self["items"].remove(item)
if type(item) != type(''):
item.reparentTo(hidden)
item.reparentTo(ShowBaseGlobal.hidden)
self.refresh()
return 1
else:
@ -387,7 +388,7 @@ class DirectScrolledList(DirectFrame):
item.destroy()
self["items"].remove(item)
if type(item) != type(''):
item.reparentTo(hidden)
item.reparentTo(ShowBaseGlobal.hidden)
self.refresh()
return 1
else:
@ -410,7 +411,7 @@ class DirectScrolledList(DirectFrame):
self["items"].remove(item)
if type(item) != type(''):
#RAU possible leak here, let's try to do the right thing
#item.reparentTo(hidden)
#item.reparentTo(ShowBaseGlobal.hidden)
item.removeNode()
retval = 1
@ -435,7 +436,7 @@ class DirectScrolledList(DirectFrame):
self["items"].remove(item)
if type(item) != type(''):
#RAU possible leak here, let's try to do the right thing
#item.reparentTo(hidden)
#item.reparentTo(ShowBaseGlobal.hidden)
item.removeNode()
retval = 1
if (refresh):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +0,0 @@
__all__ = ["DWBPackageInstaller"]
from direct.p3d.PackageInstaller import PackageInstaller
from direct.gui.DirectWaitBar import DirectWaitBar
from direct.gui import DirectGuiGlobals as DGG
class DWBPackageInstaller(DirectWaitBar, PackageInstaller):
""" This class presents a PackageInstaller that also inherits from
DirectWaitBar, so it updates its own GUI as it downloads.
Specify perPackage = True to make the progress bar reset for each
package, or False (the default) to show one continuous progress
bar for all packages.
Specify updateText = True (the default) to update the text label
with the name of the package or False to leave it up to you to set
it.
You can specify a callback function with finished = func; this
function will be called, with one boolean parameter, when the
download has completed. The parameter will be true on success, or
false on failure.
"""
def __init__(self, appRunner, parent = None, **kw):
PackageInstaller.__init__(self, appRunner)
optiondefs = (
('borderWidth', (0.01, 0.01), None),
('relief', DGG.SUNKEN, self.setRelief),
('range', 1, self.setRange),
('barBorderWidth', (0.01, 0.01), self.setBarBorderWidth),
('barColor', (0.424, 0.647, 0.878, 1), self.setBarColor),
('barRelief', DGG.RAISED, self.setBarRelief),
('text', 'Starting', self.setText),
('text_pos', (0, -0.025), None),
('text_scale', 0.1, None),
('perPackage', False, None),
('updateText', True, None),
('finished', None, None),
)
self.defineoptions(kw, optiondefs)
DirectWaitBar.__init__(self, parent, **kw)
self.initialiseoptions(DWBPackageInstaller)
self.updateBarStyle()
# Hidden by default until the download begins.
self.hide()
def cleanup(self):
PackageInstaller.cleanup(self)
DirectWaitBar.destroy(self)
def destroy(self):
PackageInstaller.cleanup(self)
DirectWaitBar.destroy(self)
def packageStarted(self, package):
""" This callback is made for each package between
downloadStarted() and downloadFinished() to indicate the start
of a new package. """
if self['updateText']:
self['text'] = 'Installing %s' % (package.getFormattedName())
self.show()
def packageProgress(self, package, progress):
""" This callback is made repeatedly between packageStarted()
and packageFinished() to update the current progress on the
indicated package only. The progress value ranges from 0
(beginning) to 1 (complete). """
if self['perPackage']:
self['value'] = progress * self['range']
def downloadProgress(self, overallProgress):
""" This callback is made repeatedly between downloadStarted()
and downloadFinished() to update the current progress through
all packages. The progress value ranges from 0 (beginning) to
1 (complete). """
if not self['perPackage']:
self['value'] = overallProgress * self['range']
def downloadFinished(self, success):
""" This callback is made when all of the packages have been
downloaded and installed (or there has been some failure). If
all packages where successfully installed, success is True.
If there were no packages that required downloading, this
callback will be made immediately, *without* a corresponding
call to downloadStarted(). """
self.hide()
if self['finished']:
self['finished'](success)

File diff suppressed because it is too large Load Diff

View File

@ -1,246 +0,0 @@
__all__ = ["FileSpec"]
import os
import time
from panda3d.core import Filename, HashVal, VirtualFileSystem
class FileSpec:
""" This class represents a disk file whose hash and size
etc. were read from an xml file. This class provides methods to
verify whether the file on disk matches the version demanded by
the xml. """
def __init__(self):
self.actualFile = None
self.filename = None
self.size = 0
self.timestamp = 0
self.hash = None
def fromFile(self, packageDir, filename, pathname = None, st = None):
""" Reads the file information from the indicated file. If st
is supplied, it is the result of os.stat on the filename. """
vfs = VirtualFileSystem.getGlobalPtr()
filename = Filename(filename)
if pathname is None:
pathname = Filename(packageDir, filename)
self.filename = str(filename)
self.basename = filename.getBasename()
if st is None:
st = os.stat(pathname.toOsSpecific())
self.size = st.st_size
self.timestamp = int(st.st_mtime)
self.readHash(pathname)
def readHash(self, pathname):
""" Reads the hash only from the indicated pathname. """
hv = HashVal()
hv.hashFile(pathname)
self.hash = hv.asHex()
def loadXml(self, xelement):
""" Reads the file information from the indicated XML
element. """
self.filename = xelement.Attribute('filename')
self.basename = None
if self.filename:
self.basename = Filename(self.filename).getBasename()
size = xelement.Attribute('size')
try:
self.size = int(size)
except:
self.size = 0
timestamp = xelement.Attribute('timestamp')
try:
self.timestamp = int(timestamp)
except:
self.timestamp = 0
self.hash = xelement.Attribute('hash')
def storeXml(self, xelement):
""" Adds the file information to the indicated XML
element. """
if self.filename:
xelement.SetAttribute('filename', self.filename)
if self.size:
xelement.SetAttribute('size', str(self.size))
if self.timestamp:
xelement.SetAttribute('timestamp', str(int(self.timestamp)))
if self.hash:
xelement.SetAttribute('hash', self.hash)
def storeMiniXml(self, xelement):
""" Adds the just the "mini" file information--size and
hash--to the indicated XML element. """
if self.size:
xelement.SetAttribute('size', str(self.size))
if self.hash:
xelement.SetAttribute('hash', self.hash)
def quickVerify(self, packageDir = None, pathname = None,
notify = None, correctSelf = False):
""" Performs a quick test to ensure the file has not been
modified. This test is vulnerable to people maliciously
attempting to fool the program (by setting datestamps etc.).
if correctSelf is True, then any discrepency is corrected by
updating the appropriate fields internally, making the
assumption that the file on disk is the authoritative version.
Returns true if it is intact, false if it is incorrect. If
correctSelf is true, raises OSError if the self-update is
impossible (for instance, because the file does not exist)."""
if not pathname:
pathname = Filename(packageDir, self.filename)
try:
st = os.stat(pathname.toOsSpecific())
except OSError:
# If the file is missing, the file fails.
if notify:
notify.debug("file not found: %s" % (pathname))
if correctSelf:
raise
return False
if st.st_size != self.size:
# If the size is wrong, the file fails.
if notify:
notify.debug("size wrong: %s" % (pathname))
if correctSelf:
self.__correctHash(packageDir, pathname, st, notify)
return False
if int(st.st_mtime) == self.timestamp:
# If the size is right and the timestamp is right, the
# file passes.
if notify:
notify.debug("file ok: %s" % (pathname))
return True
if notify:
notify.debug("modification time wrong: %s" % (pathname))
# If the size is right but the timestamp is wrong, the file
# soft-fails. We follow this up with a hash check.
if not self.checkHash(packageDir, pathname, st):
# Hard fail, the hash is wrong.
if notify:
notify.debug("hash check wrong: %s" % (pathname))
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
if correctSelf:
self.__correctHash(packageDir, pathname, st, notify)
return False
if notify:
notify.debug("hash check ok: %s" % (pathname))
# The hash is OK after all. Change the file's timestamp back
# to what we expect it to be, so we can quick-verify it
# successfully next time.
if correctSelf:
# Or update our own timestamp.
self.__correctTimestamp(pathname, st, notify)
return False
else:
self.__updateTimestamp(pathname, st)
return True
def fullVerify(self, packageDir = None, pathname = None, notify = None):
""" Performs a more thorough test to ensure the file has not
been modified. This test is less vulnerable to malicious
attacks, since it reads and verifies the entire file.
Returns true if it is intact, false if it needs to be
redownloaded. """
if not pathname:
pathname = Filename(packageDir, self.filename)
try:
st = os.stat(pathname.toOsSpecific())
except OSError:
# If the file is missing, the file fails.
if notify:
notify.debug("file not found: %s" % (pathname))
return False
if st.st_size != self.size:
# If the size is wrong, the file fails;
if notify:
notify.debug("size wrong: %s" % (pathname))
return False
if not self.checkHash(packageDir, pathname, st):
# Hard fail, the hash is wrong.
if notify:
notify.debug("hash check wrong: %s" % (pathname))
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
return False
if notify:
notify.debug("hash check ok: %s" % (pathname))
# The hash is OK. If the timestamp is wrong, change it back
# to what we expect it to be, so we can quick-verify it
# successfully next time.
if int(st.st_mtime) != self.timestamp:
self.__updateTimestamp(pathname, st)
return True
def __updateTimestamp(self, pathname, st):
# On Windows, we have to change the file to read-write before
# we can successfully update its timestamp.
try:
os.chmod(pathname.toOsSpecific(), 0o755)
os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
os.chmod(pathname.toOsSpecific(), 0o555)
except OSError:
pass
def __correctTimestamp(self, pathname, st, notify):
""" Corrects the internal timestamp to match the one on
disk. """
if notify:
notify.info("Correcting timestamp of %s to %d (%s)" % (
self.filename, st.st_mtime, time.asctime(time.localtime(st.st_mtime))))
self.timestamp = int(st.st_mtime)
def checkHash(self, packageDir, pathname, st):
""" Returns true if the file has the expected md5 hash, false
otherwise. As a side effect, stores a FileSpec corresponding
to the on-disk file in self.actualFile. """
fileSpec = FileSpec()
fileSpec.fromFile(packageDir, self.filename,
pathname = pathname, st = st)
self.actualFile = fileSpec
return (fileSpec.hash == self.hash)
def __correctHash(self, packageDir, pathname, st, notify):
""" Corrects the internal hash to match the one on disk. """
if not self.actualFile:
self.checkHash(packageDir, pathname, st)
if notify:
notify.info("Correcting hash %s to %s" % (
self.filename, self.actualFile.hash))
self.hash = self.actualFile.hash
self.size = self.actualFile.size
self.timestamp = self.actualFile.timestamp

View File

@ -1,751 +0,0 @@
__all__ = ["HostInfo"]
from panda3d.core import HashVal, Filename, PandaSystem, DocumentSpec, Ramfile
from panda3d.core import HTTPChannel, ConfigVariableInt
from panda3d import core
from direct.p3d.PackageInfo import PackageInfo
from direct.p3d.FileSpec import FileSpec
from direct.directnotify.DirectNotifyGlobal import directNotify
import time
class HostInfo:
""" This class represents a particular download host serving up
Panda3D packages. It is the Python equivalent of the P3DHost
class in the core API. """
notify = directNotify.newCategory("HostInfo")
def __init__(self, hostUrl, appRunner = None, hostDir = None,
rootDir = None, asMirror = False, perPlatform = None):
""" You must specify either an appRunner or a hostDir to the
HostInfo constructor.
If you pass asMirror = True, it means that this HostInfo
object is to be used to populate a "mirror" folder, a
duplicate (or subset) of the contents hosted by a server.
This means when you use this HostInfo to download packages, it
will only download the compressed archive file and leave it
there. At the moment, mirror folders do not download old
patch files from the server.
If you pass perPlatform = True, then files are unpacked into a
platform-specific directory, which is appropriate when you
might be downloading multiple platforms. The default is
perPlatform = False, which means all files are unpacked into
the host directory directly, without an intervening
platform-specific directory name. If asMirror is True, then
the default is perPlatform = True.
Note that perPlatform is also restricted by the individual
package's specification. """
self.__setHostUrl(hostUrl)
self.appRunner = appRunner
self.rootDir = rootDir
if rootDir is None and appRunner:
self.rootDir = appRunner.rootDir
if hostDir and not isinstance(hostDir, Filename):
hostDir = Filename.fromOsSpecific(hostDir)
self.hostDir = hostDir
self.asMirror = asMirror
self.perPlatform = perPlatform
if perPlatform is None:
self.perPlatform = asMirror
# Initially false, this is set true when the contents file is
# successfully read.
self.hasContentsFile = False
# This is the time value at which the current contents file is
# no longer valid.
self.contentsExpiration = 0
# Contains the md5 hash of the original contents.xml file.
self.contentsSpec = FileSpec()
# descriptiveName will be filled in later, when the
# contents file is read.
self.descriptiveName = None
# A list of known mirrors for this host, all URL's guaranteed
# to end with a slash.
self.mirrors = []
# A map of keyword -> altHost URL's. An altHost is different
# than a mirror; an altHost is an alternate URL to download a
# different (e.g. testing) version of this host's contents.
# It is rarely used.
self.altHosts = {}
# This is a dictionary of packages by (name, version). It
# will be filled in when the contents file is read.
self.packages = {}
if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCForce:
# Attempt to pre-read the existing contents.xml; maybe it
# will be current enough for our purposes.
self.readContentsFile()
def __setHostUrl(self, hostUrl):
""" Assigns self.hostUrl, and related values. """
self.hostUrl = hostUrl
if not self.hostUrl:
# A special case: the URL will be set later.
self.hostUrlPrefix = None
self.downloadUrlPrefix = None
else:
# hostUrlPrefix is the host URL, but it is guaranteed to end
# with a slash.
self.hostUrlPrefix = hostUrl
if self.hostUrlPrefix[-1] != '/':
self.hostUrlPrefix += '/'
# downloadUrlPrefix is the URL prefix that should be used for
# everything other than the contents.xml file. It might be
# the same as hostUrlPrefix, but in the case of an
# https-protected hostUrl, it will be the cleartext channel.
self.downloadUrlPrefix = self.hostUrlPrefix
def freshenFile(self, http, fileSpec, localPathname):
""" Ensures that the localPathname is the most current version
of the file defined by fileSpec, as offered by host. If not,
it downloads a new version on-the-spot. Returns true on
success, false on failure. """
if fileSpec.quickVerify(pathname = localPathname):
# It's good, keep it.
return True
# It's stale, get a new one.
doc = None
if self.appRunner and self.appRunner.superMirrorUrl:
# Use the "super mirror" first.
url = core.URLSpec(self.appRunner.superMirrorUrl + fileSpec.filename)
self.notify.info("Freshening %s" % (url))
doc = http.getDocument(url)
if not doc or not doc.isValid():
# Failing the super mirror, contact the actual host.
url = core.URLSpec(self.hostUrlPrefix + fileSpec.filename)
self.notify.info("Freshening %s" % (url))
doc = http.getDocument(url)
if not doc.isValid():
return False
file = Filename.temporary('', 'p3d_')
if not doc.downloadToFile(file):
# Failed to download.
file.unlink()
return False
# Successfully downloaded!
localPathname.makeDir()
if not file.renameTo(localPathname):
# Couldn't move it into place.
file.unlink()
return False
if not fileSpec.fullVerify(pathname = localPathname, notify = self.notify):
# No good after download.
self.notify.info("%s is still no good after downloading." % (url))
return False
return True
def downloadContentsFile(self, http, redownload = False,
hashVal = None):
""" Downloads the contents.xml file for this particular host,
synchronously, and then reads it. Returns true on success,
false on failure. If hashVal is not None, it should be a
HashVal object, which will be filled with the hash from the
new contents.xml file."""
if self.hasCurrentContentsFile():
# We've already got one.
return True
if self.appRunner and self.appRunner.verifyContents == self.appRunner.P3DVCNever:
# Not allowed to.
return False
rf = None
if http:
if not redownload and self.appRunner and self.appRunner.superMirrorUrl:
# We start with the "super mirror", if it's defined.
url = self.appRunner.superMirrorUrl + 'contents.xml'
request = DocumentSpec(url)
self.notify.info("Downloading contents file %s" % (request))
rf = Ramfile()
channel = http.makeChannel(False)
channel.getDocument(request)
if not channel.downloadToRam(rf):
self.notify.warning("Unable to download %s" % (url))
rf = None
if not rf:
# Then go to the main host, if our super mirror let us
# down.
url = self.hostUrlPrefix + 'contents.xml'
# Append a uniquifying query string to the URL to force the
# download to go all the way through any caches. We use the
# time in seconds; that's unique enough.
url += '?' + str(int(time.time()))
# We might as well explicitly request the cache to be disabled
# too, since we have an interface for that via HTTPChannel.
request = DocumentSpec(url)
request.setCacheControl(DocumentSpec.CCNoCache)
self.notify.info("Downloading contents file %s" % (request))
statusCode = None
statusString = ''
for attempt in range(int(ConfigVariableInt('contents-xml-dl-attempts', 3))):
if attempt > 0:
self.notify.info("Retrying (%s)..."%(attempt,))
rf = Ramfile()
channel = http.makeChannel(False)
channel.getDocument(request)
if channel.downloadToRam(rf):
self.notify.info("Successfully downloaded %s" % (url,))
break
else:
rf = None
statusCode = channel.getStatusCode()
statusString = channel.getStatusString()
self.notify.warning("Could not contact download server at %s" % (url,))
self.notify.warning("Status code = %s %s" % (statusCode, statusString))
if not rf:
self.notify.warning("Unable to download %s" % (url,))
try:
# Something screwed up.
if statusCode == HTTPChannel.SCDownloadOpenError or \
statusCode == HTTPChannel.SCDownloadWriteError:
launcher.setPandaErrorCode(2)
elif statusCode == 404:
# 404 not found
launcher.setPandaErrorCode(5)
elif statusCode < 100:
# statusCode < 100 implies the connection attempt itself
# failed. This is usually due to firewall software
# interfering. Apparently some firewall software might
# allow the first connection and disallow subsequent
# connections; how strange.
launcher.setPandaErrorCode(4)
else:
# There are other kinds of failures, but these will
# generally have been caught already by the first test; so
# if we get here there may be some bigger problem. Just
# give the generic "big problem" message.
launcher.setPandaErrorCode(6)
except NameError as e:
# no launcher
pass
except AttributeError as e:
self.notify.warning("%s" % (str(e),))
pass
return False
tempFilename = Filename.temporary('', 'p3d_', '.xml')
if rf:
f = open(tempFilename.toOsSpecific(), 'wb')
f.write(rf.getData())
f.close()
if hashVal:
hashVal.hashString(rf.getData())
if not self.readContentsFile(tempFilename, freshDownload = True):
self.notify.warning("Failure reading %s" % (url))
tempFilename.unlink()
return False
tempFilename.unlink()
return True
# Couldn't download the file. Maybe we should look for a
# previously-downloaded copy already on disk?
return False
def redownloadContentsFile(self, http):
""" Downloads a new contents.xml file in case it has changed.
Returns true if the file has indeed changed, false if it has
not. """
assert self.hasContentsFile
if self.appRunner and self.appRunner.verifyContents == self.appRunner.P3DVCNever:
# Not allowed to.
return False
url = self.hostUrlPrefix + 'contents.xml'
self.notify.info("Redownloading %s" % (url))
# Get the hash of the original file.
assert self.hostDir
hv1 = HashVal()
if self.contentsSpec.hash:
hv1.setFromHex(self.contentsSpec.hash)
else:
filename = Filename(self.hostDir, 'contents.xml')
hv1.hashFile(filename)
# Now download it again.
self.hasContentsFile = False
hv2 = HashVal()
if not self.downloadContentsFile(http, redownload = True,
hashVal = hv2):
return False
if hv2 == HashVal():
self.notify.info("%s didn't actually redownload." % (url))
return False
elif hv1 != hv2:
self.notify.info("%s has changed." % (url))
return True
else:
self.notify.info("%s has not changed." % (url))
return False
def hasCurrentContentsFile(self):
""" Returns true if a contents.xml file has been successfully
read for this host and is still current, false otherwise. """
if not self.appRunner \
or self.appRunner.verifyContents == self.appRunner.P3DVCNone \
or self.appRunner.verifyContents == self.appRunner.P3DVCNever:
# If we're not asking to verify contents, then
# contents.xml files never expires.
return self.hasContentsFile
now = int(time.time())
return now < self.contentsExpiration and self.hasContentsFile
def readContentsFile(self, tempFilename = None, freshDownload = False):
""" Reads the contents.xml file for this particular host, once
it has been downloaded into the indicated temporary file.
Returns true on success, false if the contents file is not
already on disk or is unreadable.
If tempFilename is specified, it is the filename read, and it
is copied the file into the standard location if it's not
there already. If tempFilename is not specified, the standard
filename is read if it is known. """
if not hasattr(core, 'TiXmlDocument'):
return False
if not tempFilename:
if self.hostDir:
# If the filename is not specified, we can infer it
# if we already know our hostDir
hostDir = self.hostDir
else:
# Otherwise, we have to guess the hostDir.
hostDir = self.__determineHostDir(None, self.hostUrl)
tempFilename = Filename(hostDir, 'contents.xml')
doc = core.TiXmlDocument(tempFilename.toOsSpecific())
if not doc.LoadFile():
return False
xcontents = doc.FirstChildElement('contents')
if not xcontents:
return False
maxAge = xcontents.Attribute('max_age')
if maxAge:
try:
maxAge = int(maxAge)
except:
maxAge = None
if maxAge is None:
# Default max_age if unspecified (see p3d_plugin.h).
from direct.p3d.AppRunner import AppRunner
maxAge = AppRunner.P3D_CONTENTS_DEFAULT_MAX_AGE
# Get the latest possible expiration time, based on the max_age
# indication. Any expiration time later than this is in error.
now = int(time.time())
self.contentsExpiration = now + maxAge
if freshDownload:
self.contentsSpec.readHash(tempFilename)
# Update the XML with the new download information.
xorig = xcontents.FirstChildElement('orig')
while xorig:
xcontents.RemoveChild(xorig)
xorig = xcontents.FirstChildElement('orig')
xorig = core.TiXmlElement('orig')
self.contentsSpec.storeXml(xorig)
xorig.SetAttribute('expiration', str(self.contentsExpiration))
xcontents.InsertEndChild(xorig)
else:
# Read the download hash and expiration time from the XML.
expiration = None
xorig = xcontents.FirstChildElement('orig')
if xorig:
self.contentsSpec.loadXml(xorig)
expiration = xorig.Attribute('expiration')
if expiration:
try:
expiration = int(expiration)
except:
expiration = None
if not self.contentsSpec.hash:
self.contentsSpec.readHash(tempFilename)
if expiration is not None:
self.contentsExpiration = min(self.contentsExpiration, expiration)
# Look for our own entry in the hosts table.
if self.hostUrl:
self.__findHostXml(xcontents)
else:
assert self.hostDir
self.__findHostXmlForHostDir(xcontents)
if self.rootDir and not self.hostDir:
self.hostDir = self.__determineHostDir(None, self.hostUrl)
# Get the list of packages available for download and/or import.
xpackage = xcontents.FirstChildElement('package')
while xpackage:
name = xpackage.Attribute('name')
platform = xpackage.Attribute('platform')
version = xpackage.Attribute('version')
try:
solo = int(xpackage.Attribute('solo') or '')
except ValueError:
solo = False
try:
perPlatform = int(xpackage.Attribute('per_platform') or '')
except ValueError:
perPlatform = False
package = self.__makePackage(name, platform, version, solo, perPlatform)
package.descFile = FileSpec()
package.descFile.loadXml(xpackage)
package.setupFilenames()
package.importDescFile = None
ximport = xpackage.FirstChildElement('import')
if ximport:
package.importDescFile = FileSpec()
package.importDescFile.loadXml(ximport)
xpackage = xpackage.NextSiblingElement('package')
self.hasContentsFile = True
# Now save the contents.xml file into the standard location.
if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCNever:
assert self.hostDir
filename = Filename(self.hostDir, 'contents.xml')
filename.makeDir()
if freshDownload:
doc.SaveFile(filename.toOsSpecific())
else:
if filename != tempFilename:
tempFilename.copyTo(filename)
return True
def __findHostXml(self, xcontents):
""" Looks for the <host> or <alt_host> entry in the
contents.xml that corresponds to the URL that we actually
downloaded from. """
xhost = xcontents.FirstChildElement('host')
while xhost:
url = xhost.Attribute('url')
if url == self.hostUrl:
self.readHostXml(xhost)
return
xalthost = xhost.FirstChildElement('alt_host')
while xalthost:
url = xalthost.Attribute('url')
if url == self.hostUrl:
self.readHostXml(xalthost)
return
xalthost = xalthost.NextSiblingElement('alt_host')
xhost = xhost.NextSiblingElement('host')
def __findHostXmlForHostDir(self, xcontents):
""" Looks for the <host> or <alt_host> entry in the
contents.xml that corresponds to the host dir that we read the
contents.xml from. This is used when reading a contents.xml
file found on disk, as opposed to downloading it from a
site. """
xhost = xcontents.FirstChildElement('host')
while xhost:
url = xhost.Attribute('url')
hostDirBasename = xhost.Attribute('host_dir')
hostDir = self.__determineHostDir(hostDirBasename, url)
if hostDir == self.hostDir:
self.__setHostUrl(url)
self.readHostXml(xhost)
return
xalthost = xhost.FirstChildElement('alt_host')
while xalthost:
url = xalthost.Attribute('url')
hostDirBasename = xalthost.Attribute('host_dir')
hostDir = self.__determineHostDir(hostDirBasename, url)
if hostDir == self.hostDir:
self.__setHostUrl(url)
self.readHostXml(xalthost)
return
xalthost = xalthost.NextSiblingElement('alt_host')
xhost = xhost.NextSiblingElement('host')
def readHostXml(self, xhost):
""" Reads a <host> or <alt_host> entry and applies the data to
this object. """
descriptiveName = xhost.Attribute('descriptive_name')
if descriptiveName and not self.descriptiveName:
self.descriptiveName = descriptiveName
hostDirBasename = xhost.Attribute('host_dir')
if self.rootDir and not self.hostDir:
self.hostDir = self.__determineHostDir(hostDirBasename, self.hostUrl)
# Get the "download" URL, which is the source from which we
# download everything other than the contents.xml file.
downloadUrl = xhost.Attribute('download_url')
if downloadUrl:
self.downloadUrlPrefix = downloadUrl
if self.downloadUrlPrefix[-1] != '/':
self.downloadUrlPrefix += '/'
else:
self.downloadUrlPrefix = self.hostUrlPrefix
xmirror = xhost.FirstChildElement('mirror')
while xmirror:
url = xmirror.Attribute('url')
if url:
if url[-1] != '/':
url += '/'
if url not in self.mirrors:
self.mirrors.append(url)
xmirror = xmirror.NextSiblingElement('mirror')
xalthost = xhost.FirstChildElement('alt_host')
while xalthost:
keyword = xalthost.Attribute('keyword')
url = xalthost.Attribute('url')
if url and keyword:
self.altHosts[keyword] = url
xalthost = xalthost.NextSiblingElement('alt_host')
def __makePackage(self, name, platform, version, solo, perPlatform):
""" Creates a new PackageInfo entry for the given name,
version, and platform. If there is already a matching
PackageInfo, returns it. """
if not platform:
platform = None
platforms = self.packages.setdefault((name, version or ""), {})
package = platforms.get("", None)
if not package:
package = PackageInfo(self, name, version, platform = platform,
solo = solo, asMirror = self.asMirror,
perPlatform = perPlatform)
platforms[platform or ""] = package
return package
def getPackage(self, name, version, platform = None):
""" Returns a PackageInfo that matches the indicated name and
version and the indicated platform or the current runtime
platform, if one is provided by this host, or None if not. """
assert self.hasContentsFile
platforms = self.packages.get((name, version or ""), {})
if platform:
# In this case, we are looking for a specific platform
# only.
return platforms.get(platform, None)
# We are looking for one matching the current runtime
# platform. First, look for a package matching the current
# platform exactly.
package = platforms.get(PandaSystem.getPlatform(), None)
# If not found, look for one matching no particular platform.
if not package:
package = platforms.get("", None)
return package
def getPackages(self, name = None, platform = None):
""" Returns a list of PackageInfo objects that match the
indicated name and/or platform, with no particular regards to
version. If name is None, all packages are returned. """
assert self.hasContentsFile
packages = []
for (pn, version), platforms in self.packages.items():
if name and pn != name:
continue
if not platform:
for p2 in platforms:
package = self.getPackage(pn, version, platform = p2)
if package:
packages.append(package)
else:
package = self.getPackage(pn, version, platform = platform)
if package:
packages.append(package)
return packages
def getAllPackages(self, includeAllPlatforms = False):
""" Returns a list of all available packages provided by this
host. """
result = []
items = sorted(self.packages.items())
for key, platforms in items:
if self.perPlatform or includeAllPlatforms:
# If we maintain a different answer per platform,
# return all of them.
pitems = sorted(platforms.items())
for pkey, package in pitems:
result.append(package)
else:
# If we maintain a host for the current platform
# only (e.g. a client copy), then return only the
# current platform, or no particular platform.
package = platforms.get(PandaSystem.getPlatform(), None)
if not package:
package = platforms.get("", None)
if package:
result.append(package)
return result
def deletePackages(self, packages):
""" Removes all of the indicated packages from the disk,
uninstalling them and deleting all of their files. The
packages parameter must be a list of one or more PackageInfo
objects, for instance as returned by getPackage(). Returns
the list of packages that were NOT found. """
packages = packages[:]
for key, platforms in list(self.packages.items()):
for platform, package in list(platforms.items()):
if package in packages:
self.__deletePackageFiles(package)
del platforms[platform]
packages.remove(package)
if not platforms:
# If we've removed all the platforms for a given
# package, remove the key from the toplevel map.
del self.packages[key]
return packages
def __deletePackageFiles(self, package):
""" Called by deletePackage(), this actually removes the files
for the indicated package. """
if self.appRunner:
self.notify.info("Deleting package %s: %s" % (package.packageName, package.getPackageDir()))
self.appRunner.rmtree(package.getPackageDir())
self.appRunner.sendRequest('forget_package', self.hostUrl, package.packageName, package.packageVersion or '')
def __determineHostDir(self, hostDirBasename, hostUrl):
""" Hashes the host URL into a (mostly) unique directory
string, which will be the root of the host's install tree.
Returns the resulting path, as a Filename.
This code is duplicated in C++, in
P3DHost::determine_host_dir(). """
if hostDirBasename:
# If the contents.xml specified a host_dir parameter, use
# it.
hostDir = str(self.rootDir) + '/hosts'
for component in hostDirBasename.split('/'):
if component:
if component[0] == '.':
# Forbid ".foo" or "..".
component = 'x' + component
hostDir += '/'
hostDir += component
return Filename(hostDir)
hostDir = 'hosts/'
# Look for a server name in the URL. Including this string in the
# directory name makes it friendlier for people browsing the
# directory.
# We could use URLSpec, but we do it by hand instead, to make
# it more likely that our hash code will exactly match the
# similar logic in P3DHost.
p = hostUrl.find('://')
hostname = ''
if p != -1:
start = p + 3
end = hostUrl.find('/', start)
# Now start .. end is something like "username@host:port".
at = hostUrl.find('@', start)
if at != -1 and at < end:
start = at + 1
colon = hostUrl.find(':', start)
if colon != -1 and colon < end:
end = colon
# Now start .. end is just the hostname.
hostname = hostUrl[start : end]
# Now build a hash string of the whole URL. We'll use MD5 to
# get a pretty good hash, with a minimum chance of collision.
# Even if there is a hash collision, though, it's not the end
# of the world; it just means that both hosts will dump their
# packages into the same directory, and they'll fight over the
# toplevel contents.xml file. Assuming they use different
# version numbers (which should be safe since they have the
# same hostname), there will be minimal redownloading.
hashSize = 16
keepHash = hashSize
if hostname:
hostDir += hostname + '_'
# If we successfully got a hostname, we don't really need the
# full hash. We'll keep half of it.
keepHash = keepHash // 2
md = HashVal()
md.hashString(hostUrl)
hostDir += md.asHex()[:keepHash * 2]
hostDir = Filename(self.rootDir, hostDir)
return hostDir

View File

@ -1,24 +0,0 @@
__all__ = ["InstalledHostData"]
from panda3d.core import URLSpec
class InstalledHostData:
""" A list of instances of this class is returned by
AppRunner.scanInstalledPackages(). Each of these corresponds to a
particular host that has provided packages that have been
installed on the local client. """
def __init__(self, host, dirnode):
self.host = host
self.pathname = dirnode.pathname
self.totalSize = dirnode.getTotalSize()
self.packages = []
if self.host:
self.hostUrl = self.host.hostUrl
self.descriptiveName = self.host.descriptiveName
if not self.descriptiveName:
self.descriptiveName = URLSpec(self.hostUrl).getServer()
else:
self.hostUrl = 'unknown'
self.descriptiveName = 'unknown'

View File

@ -1,30 +0,0 @@
__all__ = ["InstalledPackageData"]
class InstalledPackageData:
""" A list of instances of this class is maintained by
InstalledHostData (which is in turn returned by
AppRunner.scanInstalledPackages()). Each of these corresponds to
a particular package that has been installed on the local
client. """
def __init__(self, package, dirnode):
self.package = package
self.pathname = dirnode.pathname
self.totalSize = dirnode.getTotalSize()
self.lastUse = None
if self.package:
self.displayName = self.package.getFormattedName()
xusage = self.package.getUsage()
if xusage:
lastUse = xusage.Attribute('last_use')
try:
lastUse = int(lastUse or '')
except ValueError:
lastUse = None
self.lastUse = lastUse
else:
self.displayName = dirnode.pathname.getBasename()

View File

@ -1,298 +0,0 @@
""" This module defines some simple classes and instances which are
useful when writing code that integrates with JavaScript, especially
code that runs in a browser via the web plugin. """
__all__ = ["UndefinedObject", "Undefined", "ConcreteStruct", "BrowserObject", "MethodWrapper"]
class UndefinedObject:
""" This is a special object that is returned by the browser to
represent an "undefined" or "void" value, typically the value for
an uninitialized variable or undefined property. It has no
attributes, similar to None, but it is a slightly different
concept in JavaScript. """
def __bool__(self):
return False
__nonzero__ = __bool__ # Python 2
def __str__(self):
return "Undefined"
# In fact, we normally always return this precise instance of the
# UndefinedObject.
Undefined = UndefinedObject()
class ConcreteStruct:
""" Python objects that inherit from this class are passed to
JavaScript as a concrete struct: a mapping from string -> value,
with no methods, passed by value. This can be more optimal than
traditional Python objects which are passed by reference,
especially for small objects which might be repeatedly referenced
on the JavaScript side. """
def __init__(self):
pass
def getConcreteProperties(self):
""" Returns a list of 2-tuples of the (key, value) pairs that
are to be passed to the concrete instance. By default, this
returns all properties of the object. You can override this
to restrict the set of properties that are uploaded. """
return list(self.__dict__.items())
class BrowserObject:
""" This class provides the Python wrapper around some object that
actually exists in the plugin host's namespace, e.g. a JavaScript
or DOM object. """
def __init__(self, runner, objectId):
self.__dict__['_BrowserObject__runner'] = runner
self.__dict__['_BrowserObject__objectId'] = objectId
# This element is filled in by __getattr__; it connects
# the object to its parent.
self.__dict__['_BrowserObject__childObject'] = (None, None)
# This is a cache of method names to MethodWrapper objects in
# the parent object.
self.__dict__['_BrowserObject__methods'] = {}
def __del__(self):
# When the BrowserObject destructs, tell the parent process it
# doesn't need to keep around its corresponding P3D_object any
# more.
self.__runner.dropObject(self.__objectId)
def __cacheMethod(self, methodName):
""" Stores a pointer to the named method on this object, so
that the next time __getattr__ is called, it can retrieve the
method wrapper without having to query the browser. This
cache assumes that callable methods don't generally come and
go on and object.
The return value is the MethodWrapper object. """
method = self.__methods.get(methodName, None)
if method is None:
method = MethodWrapper(self.__runner, self, methodName)
self.__methods[methodName] = method
return method
def __str__(self):
return self.toString()
def __bool__(self):
return True
__nonzero__ = __bool__ # Python 2
def __call__(self, *args, **kw):
needsResponse = True
if 'needsResponse' in kw:
needsResponse = kw['needsResponse']
del kw['needsResponse']
if kw:
raise ArgumentError('Keyword arguments not supported')
try:
parentObj, attribName = self.__childObject
if parentObj:
# Call it as a method.
if parentObj is self.__runner.dom and attribName == 'alert':
# As a special hack, we don't wait for the return
# value from the alert() call, since this is a
# blocking call, and waiting for this could cause
# problems.
needsResponse = False
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
# As another special hack, we make dom.eval() a
# special case, and map it directly into an eval()
# call. If the string begins with 'void ', we further
# assume we're not waiting for a response.
if args[0].startswith('void '):
needsResponse = False
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
else:
# This is a normal method call.
try:
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
except EnvironmentError:
# Problem on the call. Maybe no such method?
raise AttributeError
# Hey, the method call appears to have succeeded.
# Cache the method object on the parent so we won't
# have to look up the method wrapper again next time.
parentObj.__cacheMethod(attribName)
else:
# Call it as a plain function.
result = self.__runner.scriptRequest('call', self, value = args, needsResponse = needsResponse)
except EnvironmentError:
# Some odd problem on the call.
raise TypeError
return result
def __getattr__(self, name):
""" Remaps attempts to query an attribute, as in obj.attr,
into the appropriate calls to query the actual browser object
under the hood. """
# First check to see if there's a cached method wrapper from a
# previous call.
method = self.__methods.get(name, None)
if method:
return method
# No cache. Go query the browser for the desired value.
try:
value = self.__runner.scriptRequest('get_property', self,
propertyName = name)
except EnvironmentError:
# Failed to retrieve the attribute. But maybe there's a
# method instead?
if self.__runner.scriptRequest('has_method', self, propertyName = name):
# Yes, so create a method wrapper for it.
return self.__cacheMethod(name)
raise AttributeError(name)
if isinstance(value, BrowserObject):
# Fill in the parent object association, so __call__ can
# properly call a method. (Javascript needs to know the
# method container at the time of the call, and doesn't
# store it on the function object.)
value.__dict__['_BrowserObject__childObject'] = (self, name)
return value
def __setattr__(self, name, value):
if name in self.__dict__:
self.__dict__[name] = value
return
result = self.__runner.scriptRequest('set_property', self,
propertyName = name,
value = value)
if not result:
raise AttributeError(name)
def __delattr__(self, name):
if name in self.__dict__:
del self.__dict__[name]
return
result = self.__runner.scriptRequest('del_property', self,
propertyName = name)
if not result:
raise AttributeError(name)
def __getitem__(self, key):
""" Remaps attempts to query an attribute, as in obj['attr'],
into the appropriate calls to query the actual browser object
under the hood. Following the JavaScript convention, we treat
obj['attr'] almost the same as obj.attr. """
try:
value = self.__runner.scriptRequest('get_property', self,
propertyName = str(key))
except EnvironmentError:
# Failed to retrieve the property. We return IndexError
# for numeric keys so we can properly support Python's
# iterators, but we return KeyError for string keys to
# emulate mapping objects.
if isinstance(key, str):
raise KeyError(key)
else:
raise IndexError(key)
return value
def __setitem__(self, key, value):
result = self.__runner.scriptRequest('set_property', self,
propertyName = str(key),
value = value)
if not result:
if isinstance(key, str):
raise KeyError(key)
else:
raise IndexError(key)
def __delitem__(self, key):
result = self.__runner.scriptRequest('del_property', self,
propertyName = str(key))
if not result:
if isinstance(key, str):
raise KeyError(key)
else:
raise IndexError(key)
class MethodWrapper:
""" This is a Python wrapper around a property of a BrowserObject
that doesn't appear to be a first-class object in the Python
sense, but is nonetheless a callable method. """
def __init__(self, runner, parentObj, objectId):
self.__dict__['_MethodWrapper__runner'] = runner
self.__dict__['_MethodWrapper__childObject'] = (parentObj, objectId)
def __str__(self):
parentObj, attribName = self.__childObject
return "%s.%s" % (parentObj, attribName)
def __bool__(self):
return True
__nonzero__ = __bool__ # Python 2
def __call__(self, *args, **kw):
needsResponse = True
if 'needsResponse' in kw:
needsResponse = kw['needsResponse']
del kw['needsResponse']
if kw:
raise ArgumentError('Keyword arguments not supported')
try:
parentObj, attribName = self.__childObject
# Call it as a method.
if parentObj is self.__runner.dom and attribName == 'alert':
# As a special hack, we don't wait for the return
# value from the alert() call, since this is a
# blocking call, and waiting for this could cause
# problems.
needsResponse = False
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
# As another special hack, we make dom.eval() a
# special case, and map it directly into an eval()
# call. If the string begins with 'void ', we further
# assume we're not waiting for a response.
if args[0].startswith('void '):
needsResponse = False
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
else:
# This is a normal method call.
try:
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
except EnvironmentError:
# Problem on the call. Maybe no such method?
raise AttributeError
except EnvironmentError:
# Some odd problem on the call.
raise TypeError
return result
def __setattr__(self, name, value):
""" setattr will generally fail on method objects. """
raise AttributeError(name)
def __delattr__(self, name):
""" delattr will generally fail on method objects. """
raise AttributeError(name)

File diff suppressed because it is too large Load Diff

View File

@ -1,640 +0,0 @@
__all__ = ["PackageInstaller"]
from direct.showbase.DirectObject import DirectObject
from direct.stdpy.threading import Lock, RLock
from direct.showbase.MessengerGlobal import messenger
from direct.task.TaskManagerGlobal import taskMgr
from direct.p3d.PackageInfo import PackageInfo
from panda3d.core import TPLow, PStatCollector
from direct.directnotify.DirectNotifyGlobal import directNotify
class PackageInstaller(DirectObject):
""" This class is used in a p3d runtime environment to manage the
asynchronous download and installation of packages. If you just
want to install a package synchronously, see
appRunner.installPackage() for a simpler interface.
To use this class, you should subclass from it and override any of
the six callback methods: downloadStarted(), packageStarted(),
packageProgress(), downloadProgress(), packageFinished(),
downloadFinished().
Also see DWBPackageInstaller, which does exactly this, to add a
DirectWaitBar GUI.
"""
notify = directNotify.newCategory("PackageInstaller")
globalLock = Lock()
nextUniqueId = 1
# This is a chain of state values progressing forward in time.
S_initial = 0 # addPackage() calls are being made
S_ready = 1 # donePackages() has been called
S_started = 2 # download has started
S_done = 3 # download is over
class PendingPackage:
""" This class describes a package added to the installer for
download. """
notify = directNotify.newCategory("PendingPackage")
def __init__(self, packageName, version, host):
self.packageName = packageName
self.version = version
self.host = host
# This will be filled in properly by checkDescFile() or
# getDescFile(); in the meantime, set a placeholder.
self.package = PackageInfo(host, packageName, version)
# Set true when the package has finished downloading,
# either successfully or unsuccessfully.
self.done = False
# Set true or false when self.done has been set.
self.success = False
# Set true when the packageFinished() callback has been
# delivered.
self.notified = False
# These are used to ensure the callbacks only get
# delivered once for a particular package.
self.calledPackageStarted = False
self.calledPackageFinished = False
# This is the amount of stuff we have to process to
# install this package, and the amount of stuff we have
# processed so far. "Stuff" includes bytes downloaded,
# bytes uncompressed, and bytes extracted; and each of
# which is weighted differently into one grand total. So,
# the total doesn't really represent bytes; it's a
# unitless number, which means something only as a ratio
# to other packages. Filled in by checkDescFile() or
# getDescFile().
self.downloadEffort = 0
# Similar, but this is the theoretical effort if the
# package were already downloaded.
self.prevDownloadedEffort = 0
def __cmp__(self, pp):
""" Python comparision function. This makes all
PendingPackages withe same (packageName, version, host)
combination be deemed equivalent. """
return cmp((self.packageName, self.version, self.host),
(pp.packageName, pp.version, pp.host))
def getProgress(self):
""" Returns the download progress of this package in the
range 0..1. """
return self.package.downloadProgress
def checkDescFile(self):
""" Returns true if the desc file is already downloaded
and good, or false if it needs to be downloaded. """
if not self.host.hasCurrentContentsFile():
# If the contents file isn't ready yet, we can't check
# the desc file yet.
return False
# All right, get the package info now.
package = self.host.getPackage(self.packageName, self.version)
if not package:
self.notify.warning("Package %s %s not known on %s" % (
self.packageName, self.version, self.host.hostUrl))
return False
self.package = package
self.package.checkStatus()
if not self.package.hasDescFile:
return False
self.downloadEffort = self.package.getDownloadEffort()
self.prevDownloadEffort = 0
if self.downloadEffort == 0:
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
return True
def getDescFile(self, http):
""" Synchronously downloads the desc files required for
the package. """
if not self.host.downloadContentsFile(http):
return False
# All right, get the package info now.
package = self.host.getPackage(self.packageName, self.version)
if not package:
self.notify.warning("Package %s %s not known on %s" % (
self.packageName, self.version, self.host.hostUrl))
return False
self.package = package
if not self.package.downloadDescFile(http):
return False
self.package.checkStatus()
self.downloadEffort = self.package.getDownloadEffort()
self.prevDownloadEffort = 0
if self.downloadEffort == 0:
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
return True
def __init__(self, appRunner, taskChain = 'default'):
self.globalLock.acquire()
try:
self.uniqueId = PackageInstaller.nextUniqueId
PackageInstaller.nextUniqueId += 1
finally:
self.globalLock.release()
self.appRunner = appRunner
self.taskChain = taskChain
# If we're to be running on an asynchronous task chain, and
# the task chain hasn't yet been set up already, create the
# default parameters now.
if taskChain != 'default' and not taskMgr.hasTaskChain(self.taskChain):
taskMgr.setupTaskChain(self.taskChain, numThreads = 1,
threadPriority = TPLow)
self.callbackLock = Lock()
self.calledDownloadStarted = False
self.calledDownloadFinished = False
# A list of all packages that have been added to the
# installer.
self.packageLock = RLock()
self.packages = []
self.state = self.S_initial
# A list of packages that are waiting for their desc files.
self.needsDescFile = []
self.descFileTask = None
# A list of packages that are waiting to be downloaded and
# installed.
self.needsDownload = []
self.downloadTask = None
# A list of packages that were already done at the time they
# were passed to addPackage().
self.earlyDone = []
# A list of packages that have been successfully installed, or
# packages that have failed.
self.done = []
self.failed = []
# This task is spawned on the default task chain, to update
# the status during the download.
self.progressTask = None
self.accept('PackageInstaller-%s-allHaveDesc' % self.uniqueId,
self.__allHaveDesc)
self.accept('PackageInstaller-%s-packageStarted' % self.uniqueId,
self.__packageStarted)
self.accept('PackageInstaller-%s-packageDone' % self.uniqueId,
self.__packageDone)
def destroy(self):
""" Interrupts all pending downloads. No further callbacks
will be made. """
self.cleanup()
def cleanup(self):
""" Interrupts all pending downloads. No further callbacks
will be made. """
self.packageLock.acquire()
try:
if self.descFileTask:
taskMgr.remove(self.descFileTask)
self.descFileTask = None
if self.downloadTask:
taskMgr.remove(self.downloadTask)
self.downloadTask = None
finally:
self.packageLock.release()
if self.progressTask:
taskMgr.remove(self.progressTask)
self.progressTask = None
self.ignoreAll()
def addPackage(self, packageName, version = None, hostUrl = None):
""" Adds the named package to the list of packages to be
downloaded. Call donePackages() to finish the list. """
if self.state != self.S_initial:
raise ValueError('addPackage called after donePackages')
host = self.appRunner.getHostWithAlt(hostUrl)
pp = self.PendingPackage(packageName, version, host)
self.packageLock.acquire()
try:
self.__internalAddPackage(pp)
finally:
self.packageLock.release()
def __internalAddPackage(self, pp):
""" Adds the indicated "pending package" to the appropriate
list(s) for downloading and installing. Assumes packageLock
is already held."""
if pp in self.packages:
# Already added.
return
self.packages.append(pp)
# We always add the package to needsDescFile, even if we
# already have its desc file; this guarantees that packages
# are downloaded in the order they are added.
self.needsDescFile.append(pp)
if not self.descFileTask:
self.descFileTask = taskMgr.add(
self.__getDescFileTask, 'getDescFile',
taskChain = self.taskChain)
def donePackages(self):
""" After calling addPackage() for each package to be
installed, call donePackages() to mark the end of the list.
This is necessary to determine what the complete set of
packages is (and therefore how large the total download size
is). None of the low-level callbacks will be made before this
call. """
if self.state != self.S_initial:
# We've already been here.
return
# Throw the messages for packages that were already done
# before we started.
for pp in self.earlyDone:
self.__donePackage(pp, True)
self.earlyDone = []
self.packageLock.acquire()
try:
if self.state != self.S_initial:
return
self.state = self.S_ready
if not self.needsDescFile:
# All package desc files are already available; so begin.
self.__prepareToStart()
finally:
self.packageLock.release()
if not self.packages:
# Trivial no-op.
self.__callDownloadFinished(True)
def downloadStarted(self):
""" This callback is made at some point after donePackages()
is called; at the time of this callback, the total download
size is known, and we can sensibly report progress through the
whole. """
self.notify.info("downloadStarted")
def packageStarted(self, package):
""" This callback is made for each package between
downloadStarted() and downloadFinished() to indicate the start
of a new package. """
self.notify.debug("packageStarted: %s" % (package.packageName))
def packageProgress(self, package, progress):
""" This callback is made repeatedly between packageStarted()
and packageFinished() to update the current progress on the
indicated package only. The progress value ranges from 0
(beginning) to 1 (complete). """
self.notify.debug("packageProgress: %s %s" % (package.packageName, progress))
def downloadProgress(self, overallProgress):
""" This callback is made repeatedly between downloadStarted()
and downloadFinished() to update the current progress through
all packages. The progress value ranges from 0 (beginning) to
1 (complete). """
self.notify.debug("downloadProgress: %s" % (overallProgress))
def packageFinished(self, package, success):
""" This callback is made for each package between
downloadStarted() and downloadFinished() to indicate that a
package has finished downloading. If success is true, there
were no problems and the package is now installed.
If this package did not require downloading (because it was
already downloaded), this callback will be made immediately,
*without* a corresponding call to packageStarted(), and may
even be made before downloadStarted(). """
self.notify.info("packageFinished: %s %s" % (package.packageName, success))
def downloadFinished(self, success):
""" This callback is made when all of the packages have been
downloaded and installed (or there has been some failure). If
all packages where successfully installed, success is True.
If there were no packages that required downloading, this
callback will be made immediately, *without* a corresponding
call to downloadStarted(). """
self.notify.info("downloadFinished: %s" % (success))
def __prepareToStart(self):
""" This is called internally when transitioning from S_ready
to S_started. It sets up whatever initial values are
needed. Assumes self.packageLock is held. Returns False if
there were no packages to download, and the state was
therefore transitioned immediately to S_done. """
if not self.needsDownload:
self.state = self.S_done
return False
self.state = self.S_started
assert not self.downloadTask
self.downloadTask = taskMgr.add(
self.__downloadPackageTask, 'downloadPackage',
taskChain = self.taskChain)
assert not self.progressTask
self.progressTask = taskMgr.add(
self.__progressTask, 'packageProgress')
return True
def __allHaveDesc(self):
""" This method is called internally when all of the pending
packages have their desc info. """
working = True
self.packageLock.acquire()
try:
if self.state == self.S_ready:
# We've already called donePackages(), so move on now.
working = self.__prepareToStart()
finally:
self.packageLock.release()
if not working:
self.__callDownloadFinished(True)
def __packageStarted(self, pp):
""" This method is called when a single package is beginning
to download. """
self.__callDownloadStarted()
self.__callPackageStarted(pp)
def __packageDone(self, pp):
""" This method is called when a single package has been
downloaded and installed, or has failed. """
self.__callPackageFinished(pp, pp.success)
pp.notified = True
# See if there are more packages to go.
success = True
allDone = True
self.packageLock.acquire()
try:
for pp in self.packages:
if pp.notified:
success = success and pp.success
else:
allDone = False
finally:
self.packageLock.release()
if allDone:
self.__callDownloadFinished(success)
def __callPackageStarted(self, pp):
""" Calls the packageStarted() callback for a particular
package if it has not already been called, being careful to
avoid race conditions. """
self.callbackLock.acquire()
try:
if not pp.calledPackageStarted:
self.packageStarted(pp.package)
self.packageProgress(pp.package, 0)
pp.calledPackageStarted = True
finally:
self.callbackLock.release()
def __callPackageFinished(self, pp, success):
""" Calls the packageFinished() callback for a paricular
package if it has not already been called, being careful to
avoid race conditions. """
self.callbackLock.acquire()
try:
if not pp.calledPackageFinished:
if success:
self.packageProgress(pp.package, 1)
self.packageFinished(pp.package, success)
pp.calledPackageFinished = True
finally:
self.callbackLock.release()
def __callDownloadStarted(self):
""" Calls the downloadStarted() callback if it has not already
been called, being careful to avoid race conditions. """
self.callbackLock.acquire()
try:
if not self.calledDownloadStarted:
self.downloadStarted()
self.downloadProgress(0)
self.calledDownloadStarted = True
finally:
self.callbackLock.release()
def __callDownloadFinished(self, success):
""" Calls the downloadFinished() callback if it has not
already been called, being careful to avoid race
conditions. """
self.callbackLock.acquire()
try:
if not self.calledDownloadFinished:
if success:
self.downloadProgress(1)
self.downloadFinished(success)
self.calledDownloadFinished = True
finally:
self.callbackLock.release()
def __getDescFileTask(self, task):
""" This task runs on the aysynchronous task chain; each pass,
it extracts one package from self.needsDescFile and downloads
its desc file. On success, it adds the package to
self.needsDownload. """
self.packageLock.acquire()
try:
# If we've finished all of the packages that need desc
# files, stop the task.
if not self.needsDescFile:
self.descFileTask = None
eventName = 'PackageInstaller-%s-allHaveDesc' % self.uniqueId
messenger.send(eventName, taskChain = 'default')
return task.done
pp = self.needsDescFile[0]
del self.needsDescFile[0]
finally:
self.packageLock.release()
# Now serve this one package.
if not pp.checkDescFile():
if not pp.getDescFile(self.appRunner.http):
self.__donePackage(pp, False)
return task.cont
# This package is now ready to be downloaded. We always add
# it to needsDownload, even if it's already downloaded, to
# guarantee ordering of packages.
self.packageLock.acquire()
try:
# Also add any packages required by this one.
for packageName, version, host in pp.package.requires:
pp2 = self.PendingPackage(packageName, version, host)
self.__internalAddPackage(pp2)
self.needsDownload.append(pp)
finally:
self.packageLock.release()
return task.cont
def __downloadPackageTask(self, task):
""" This task runs on the aysynchronous task chain; each pass,
it extracts one package from self.needsDownload and downloads
it. """
while True:
self.packageLock.acquire()
try:
# If we're done downloading, stop the task.
if self.state == self.S_done or not self.needsDownload:
self.downloadTask = None
self.packageLock.release()
yield task.done; return
assert self.state == self.S_started
pp = self.needsDownload[0]
del self.needsDownload[0]
except:
self.packageLock.release()
raise
self.packageLock.release()
# Now serve this one package.
eventName = 'PackageInstaller-%s-packageStarted' % self.uniqueId
messenger.send(eventName, [pp], taskChain = 'default')
if not pp.package.hasPackage:
for token in pp.package.downloadPackageGenerator(self.appRunner.http):
if token == pp.package.stepContinue:
yield task.cont
else:
break
if token != pp.package.stepComplete:
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
pc.start()
self.__donePackage(pp, False)
pc.stop()
yield task.cont
continue
# Successfully downloaded and installed.
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
pc.start()
self.__donePackage(pp, True)
pc.stop()
# Continue the loop without yielding, so we pick up the
# next package within this same frame.
def __donePackage(self, pp, success):
""" Marks the indicated package as done, either successfully
or otherwise. """
assert not pp.done
if success:
pc = PStatCollector(':App:PackageInstaller:install:%s' % (pp.package.packageName))
pc.start()
pp.package.installPackage(self.appRunner)
pc.stop()
self.packageLock.acquire()
try:
pp.done = True
pp.success = success
if success:
self.done.append(pp)
else:
self.failed.append(pp)
finally:
self.packageLock.release()
eventName = 'PackageInstaller-%s-packageDone' % self.uniqueId
messenger.send(eventName, [pp], taskChain = 'default')
def __progressTask(self, task):
self.callbackLock.acquire()
try:
if not self.calledDownloadStarted:
# We haven't yet officially started the download.
return task.cont
if self.calledDownloadFinished:
# We've officially ended the download.
self.progressTask = None
return task.done
downloadEffort = 0
currentDownloadSize = 0
for pp in self.packages:
downloadEffort += pp.downloadEffort + pp.prevDownloadedEffort
packageProgress = pp.getProgress()
currentDownloadSize += pp.downloadEffort * packageProgress + pp.prevDownloadedEffort
if pp.calledPackageStarted and not pp.calledPackageFinished:
self.packageProgress(pp.package, packageProgress)
if not downloadEffort:
progress = 1
else:
progress = float(currentDownloadSize) / float(downloadEffort)
self.downloadProgress(progress)
finally:
self.callbackLock.release()
return task.cont

View File

@ -1,306 +0,0 @@
__all__ = ["PackageMerger", "PackageMergerError"]
from direct.p3d.FileSpec import FileSpec
from direct.p3d.SeqValue import SeqValue
from direct.directnotify.DirectNotifyGlobal import *
from panda3d.core import *
import shutil
import os
class PackageMergerError(Exception):
pass
class PackageMerger:
""" This class will combine two or more separately-built stage
directories, the output of Packager.py or the ppackage tool, into
a single output directory. It assumes that the clocks on all
hosts are in sync, so that the file across all builds with the
most recent timestamp (indicated in the contents.xml file) is
always the most current version of the file. """
notify = directNotify.newCategory("PackageMerger")
class PackageEntry:
""" This corresponds to a <package> entry in the contents.xml
file. """
def __init__(self, xpackage, sourceDir):
self.sourceDir = sourceDir
self.loadXml(xpackage)
def getKey(self):
""" Returns a tuple used for sorting the PackageEntry
objects uniquely per package. """
return (self.packageName, self.platform, self.version)
def isNewer(self, other):
return self.descFile.timestamp > other.descFile.timestamp
def loadXml(self, xpackage):
self.packageName = xpackage.Attribute('name')
self.platform = xpackage.Attribute('platform')
self.version = xpackage.Attribute('version')
solo = xpackage.Attribute('solo')
self.solo = int(solo or '0')
perPlatform = xpackage.Attribute('per_platform')
self.perPlatform = int(perPlatform or '0')
self.descFile = FileSpec()
self.descFile.loadXml(xpackage)
self.validatePackageContents()
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
self.packageSeq = SeqValue()
self.packageSeq.loadXml(xpackage, 'seq')
self.packageSetVer = SeqValue()
self.packageSetVer.loadXml(xpackage, 'set_ver')
self.importDescFile = None
ximport = xpackage.FirstChildElement('import')
if ximport:
self.importDescFile = FileSpec()
self.importDescFile.loadXml(ximport)
self.importDescFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
def makeXml(self):
""" Returns a new TiXmlElement. """
xpackage = TiXmlElement('package')
xpackage.SetAttribute('name', self.packageName)
if self.platform:
xpackage.SetAttribute('platform', self.platform)
if self.version:
xpackage.SetAttribute('version', self.version)
if self.solo:
xpackage.SetAttribute('solo', '1')
if self.perPlatform:
xpackage.SetAttribute('per_platform', '1')
self.descFile.storeXml(xpackage)
self.packageSeq.storeXml(xpackage, 'seq')
self.packageSetVer.storeXml(xpackage, 'set_ver')
if self.importDescFile:
ximport = TiXmlElement('import')
self.importDescFile.storeXml(ximport)
xpackage.InsertEndChild(ximport)
return xpackage
def validatePackageContents(self):
""" Validates the contents of the package directory itself
against the expected hashes and timestamps. Updates
hashes and timestamps where needed. """
if self.solo:
return
needsChange = False
packageDescFullpath = Filename(self.sourceDir, self.descFile.filename)
packageDir = Filename(packageDescFullpath.getDirname())
doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
if not doc.LoadFile():
message = "Could not read XML file: %s" % (self.descFile.filename)
raise OSError(message)
xpackage = doc.FirstChildElement('package')
if not xpackage:
message = "No package definition: %s" % (self.descFile.filename)
raise OSError(message)
xcompressed = xpackage.FirstChildElement('compressed_archive')
if xcompressed:
spec = FileSpec()
spec.loadXml(xcompressed)
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
spec.storeXml(xcompressed)
needsChange = True
xpatch = xpackage.FirstChildElement('patch')
while xpatch:
spec = FileSpec()
spec.loadXml(xpatch)
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
spec.storeXml(xpatch)
needsChange = True
xpatch = xpatch.NextSiblingElement('patch')
if needsChange:
PackageMerger.notify.info("Rewriting %s" % (self.descFile.filename))
doc.SaveFile()
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
# PackageMerger constructor
def __init__(self, installDir):
self.installDir = installDir
self.xhost = None
self.contents = {}
self.maxAge = None
self.contentsSeq = SeqValue()
# We allow the first one to fail quietly.
self.__readContentsFile(self.installDir, None)
def __readContentsFile(self, sourceDir, packageNames):
""" Reads the contents.xml file from the indicated sourceDir,
and updates the internal set of packages appropriately. """
assert sourceDir != None, "No source directory was specified!"
contentsFilename = Filename(sourceDir, 'contents.xml')
doc = TiXmlDocument(contentsFilename.toOsSpecific())
if not doc.LoadFile():
# Couldn't read file.
return False
xcontents = doc.FirstChildElement('contents')
if xcontents:
maxAge = xcontents.Attribute('max_age')
if maxAge:
maxAge = int(maxAge)
if self.maxAge is None:
self.maxAge = maxAge
else:
self.maxAge = min(self.maxAge, maxAge)
contentsSeq = SeqValue()
if contentsSeq.loadXml(xcontents):
self.contentsSeq = max(self.contentsSeq, contentsSeq)
xhost = xcontents.FirstChildElement('host')
if xhost:
self.xhost = xhost.Clone()
xpackage = xcontents.FirstChildElement('package')
while xpackage:
pe = self.PackageEntry(xpackage, sourceDir)
# Filter out any packages not listed in
# packageNames (unless packageNames is None,
# in which case don't filter anything).
if packageNames is None or pe.packageName in packageNames:
other = self.contents.get(pe.getKey(), None)
if not other or pe.isNewer(other):
# Store this package in the resulting output.
self.contents[pe.getKey()] = pe
xpackage = xpackage.NextSiblingElement('package')
self.contentsDoc = doc
return True
def __writeContentsFile(self):
""" Writes the contents.xml file at the end of processing. """
filename = Filename(self.installDir, 'contents.xml')
doc = TiXmlDocument(filename.toOsSpecific())
decl = TiXmlDeclaration("1.0", "utf-8", "")
doc.InsertEndChild(decl)
xcontents = TiXmlElement('contents')
if self.xhost:
xcontents.InsertEndChild(self.xhost)
if self.maxAge is not None:
xcontents.SetAttribute('max_age', str(self.maxAge))
self.contentsSeq.storeXml(xcontents)
contents = list(self.contents.items())
contents.sort()
for key, pe in contents:
xpackage = pe.makeXml()
xcontents.InsertEndChild(xpackage)
doc.InsertEndChild(xcontents)
doc.SaveFile()
def __copySubdirectory(self, pe):
""" Copies the subdirectory referenced in the indicated
PackageEntry object into the installDir, replacing the
contents of any similarly-named subdirectory already
there. """
dirname = Filename(pe.descFile.filename).getDirname()
self.notify.info("copying %s" % (dirname))
sourceDirname = Filename(pe.sourceDir, dirname)
targetDirname = Filename(self.installDir, dirname)
self.__rCopyTree(sourceDirname, targetDirname)
def __rCopyTree(self, sourceFilename, targetFilename):
""" Recursively copies the contents of sourceDirname onto
targetDirname. This behaves like shutil.copytree, but it does
not remove pre-existing subdirectories. """
if targetFilename.exists():
if not targetFilename.isDirectory():
# Delete any regular files in the way.
targetFilename.unlink()
elif not sourceFilename.isDirectory():
# If the source file is a regular file, but the target
# file is a directory, completely remove the target
# file.
shutil.rmtree(targetFilename.toOsSpecific())
else:
# Both the source file and target file are
# directories.
# We have to clean out the target directory first.
# Instead of using shutil.rmtree(), remove the files in
# this directory one at a time, so we don't inadvertently
# clean out subdirectories too.
files = os.listdir(targetFilename.toOsSpecific())
for file in files:
f = Filename(targetFilename, file)
if f.isRegularFile():
f.unlink()
if sourceFilename.isDirectory():
# Recursively copying a directory.
Filename(targetFilename, '').makeDir()
files = os.listdir(sourceFilename.toOsSpecific())
for file in files:
self.__rCopyTree(Filename(sourceFilename, file),
Filename(targetFilename, file))
else:
# Copying a regular file.
sourceFilename.copyTo(targetFilename)
# Also try to copy the timestamp, but don't fuss too much
# if it doesn't work.
try:
st = os.stat(sourceFilename.toOsSpecific())
os.utime(targetFilename.toOsSpecific(), (st.st_atime, st.st_mtime))
except OSError:
pass
def merge(self, sourceDir, packageNames = None):
""" Adds the contents of the indicated source directory into
the current pool. If packageNames is not None, it is a list
of package names that we wish to include from the source;
packages not named in this list will be unchanged. """
if not self.__readContentsFile(sourceDir, packageNames):
message = "Couldn't read %s" % (sourceDir)
raise PackageMergerError(message)
def close(self):
""" Finalizes the results of all of the previous calls to
merge(), writes the new contents.xml file, and copies in all
of the new contents. """
dirname = Filename(self.installDir, '')
dirname.makeDir()
for pe in self.contents.values():
if pe.sourceDir != self.installDir:
# Here's a new subdirectory we have to copy in.
self.__copySubdirectory(pe)
self.contentsSeq += 1
self.__writeContentsFile()

File diff suppressed because it is too large Load Diff

View File

@ -1,814 +0,0 @@
__all__ = ["PatchMaker"]
from direct.p3d.FileSpec import FileSpec
from direct.p3d.SeqValue import SeqValue
from panda3d.core import *
import copy
class PatchMaker:
""" This class will operate on an existing package install
directory, as generated by the Packager, and create patchfiles
between versions as needed. It is also used at runtime, to apply
the downloaded patches. """
class PackageVersion:
""" A specific patch version of a package. This is not just
the package's "version" string; it also corresponds to the
particular patch version, which increments independently of
the "version". """
def __init__(self, packageName, platform, version, hostUrl, file):
self.packageName = packageName
self.platform = platform
self.version = version
self.hostUrl = hostUrl
self.file = file
self.printName = None
# The Package object that produces this version, if this
# is the current, base, or top form, respectively.
self.packageCurrent = None
self.packageBase = None
self.packageTop = None
# A list of patchfiles that can produce this version.
self.fromPatches = []
# A list of patchfiles that can start from this version.
self.toPatches = []
# A temporary file for re-creating the archive file for
# this version.
self.tempFile = None
def cleanup(self):
if self.tempFile:
self.tempFile.unlink()
def getPatchChain(self, startPv, alreadyVisited = []):
""" Returns a list of patches that, when applied in
sequence to the indicated PackageVersion object, will
produce this PackageVersion object. Returns None if no
chain can be found. """
if self is startPv:
# We're already here. A zero-length patch chain is
# therefore the answer.
return []
if self in alreadyVisited:
# We've already been here; this is a loop. Avoid
# infinite recursion.
return None
alreadyVisited = alreadyVisited[:]
alreadyVisited.append(self)
bestPatchChain = None
for patchfile in self.fromPatches:
fromPv = patchfile.fromPv
patchChain = fromPv.getPatchChain(startPv, alreadyVisited)
if patchChain is not None:
# There's a path through this patchfile.
patchChain = patchChain + [patchfile]
if bestPatchChain is None or len(patchChain) < len(bestPatchChain):
bestPatchChain = patchChain
# Return the shortest path found, or None if there were no
# paths found.
return bestPatchChain
def getRecreateFilePlan(self, alreadyVisited = []):
""" Returns the tuple (startFile, startPv, plan),
describing how to recreate the archive file for this
version. startFile and startPv is the Filename and
packageVersion of the file to start with, and plan is a
list of tuples (patchfile, pv), listing the patches to
apply in sequence, and the packageVersion object
associated with each patch. Returns (None, None, None) if
there is no way to recreate this archive file. """
if self.tempFile:
return (self.tempFile, self, [])
if self in alreadyVisited:
# We've already been here; this is a loop. Avoid
# infinite recursion.
return (None, None, None)
alreadyVisited = alreadyVisited[:]
alreadyVisited.append(self)
if self.packageCurrent:
# This PackageVersion instance represents the current
# version of some package.
package = self.packageCurrent
return (Filename(package.packageDir, package.compressedFilename), self, [])
if self.packageBase:
# This PackageVersion instance represents the base
# (oldest) version of some package.
package = self.packageBase
return (Filename(package.packageDir, package.baseFile.filename + '.pz'), self, [])
# We'll need to re-create the file.
bestPlan = None
bestStartFile = None
bestStartPv = None
for patchfile in self.fromPatches:
fromPv = patchfile.fromPv
startFile, startPv, plan = fromPv.getRecreateFilePlan(alreadyVisited)
if plan is not None:
# There's a path through this patchfile.
plan = plan + [(patchfile, self)]
if bestPlan is None or len(plan) < len(bestPlan):
bestPlan = plan
bestStartFile = startFile
bestStartPv = startPv
# Return the shortest path found, or None if there were no
# paths found.
return (bestStartFile, bestStartPv, bestPlan)
def getFile(self):
""" Returns the Filename of the archive file associated
with this version. If the file doesn't actually exist on
disk, a temporary file will be created. Returns None if
the file can't be recreated. """
startFile, startPv, plan = self.getRecreateFilePlan()
if startFile.getExtension() in ('pz', 'gz'):
# If the starting file is compressed, we have to
# decompress it first.
assert startPv.tempFile is None
startPv.tempFile = Filename.temporary('', 'patch_')
if not decompressFile(startFile, startPv.tempFile):
# Failure trying to decompress the file.
return None
startFile = startPv.tempFile
if not plan:
# If plan is a zero-length list, we're already
# here--return startFile. If plan is None, there's no
# solution, and startFile is None. In either case, we
# can return startFile.
return startFile
# If plan is a non-empty list, we have to walk the list to
# apply the patch plan.
prevFile = startFile
for patchfile, pv in plan:
fromPv = patchfile.fromPv
patchFilename = Filename(patchfile.package.packageDir, patchfile.file.filename)
result = self.applyPatch(prevFile, patchFilename)
if not result:
# Failure trying to re-create the file.
return None
pv.tempFile = result
prevFile = result
# Successfully patched.
assert pv is self and prevFile is self.tempFile
return prevFile
def applyPatch(self, origFile, patchFilename):
""" Applies the named patch to the indicated original
file, storing the results in a temporary file, and returns
that temporary Filename. Returns None on failure. """
result = Filename.temporary('', 'patch_')
p = Patchfile()
if not p.apply(patchFilename, origFile, result):
print("Internal patching failed: %s" % (patchFilename))
return None
return result
def getNext(self, package):
""" Gets the next patch in the chain towards this
package. """
for patch in self.toPatches:
if patch.packageName == package.packageName and \
patch.platform == package.platform and \
patch.version == package.version and \
patch.hostUrl == package.hostUrl:
return patch.toPv
return None
class Patchfile:
""" A single patchfile for a package. """
def __init__(self, package):
self.package = package
self.packageName = package.packageName
self.platform = package.platform
self.version = package.version
self.hostUrl = None
# FileSpec for the patchfile itself
self.file = None
# FileSpec for the package file that the patch is applied to
self.sourceFile = None
# FileSpec for the package file that the patch generates
self.targetFile = None
# The PackageVersion corresponding to our sourceFile
self.fromPv = None
# The PackageVersion corresponding to our targetFile
self.toPv = None
def getSourceKey(self):
""" Returns the key for locating the package that this
patchfile can be applied to. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.sourceFile)
def getTargetKey(self):
""" Returns the key for locating the package that this
patchfile will generate. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.targetFile)
def fromFile(self, packageDir, patchFilename, sourceFile, targetFile):
""" Creates the data structures from an existing patchfile
on disk. """
self.file = FileSpec()
self.file.fromFile(packageDir, patchFilename)
self.sourceFile = sourceFile
self.targetFile = targetFile
def loadXml(self, xpatch):
""" Reads the data structures from an xml file. """
self.packageName = xpatch.Attribute('name') or self.packageName
self.platform = xpatch.Attribute('platform') or self.platform
self.version = xpatch.Attribute('version') or self.version
self.hostUrl = xpatch.Attribute('host') or self.hostUrl
self.file = FileSpec()
self.file.loadXml(xpatch)
xsource = xpatch.FirstChildElement('source')
if xsource:
self.sourceFile = FileSpec()
self.sourceFile.loadXml(xsource)
xtarget = xpatch.FirstChildElement('target')
if xtarget:
self.targetFile = FileSpec()
self.targetFile.loadXml(xtarget)
def makeXml(self, package):
xpatch = TiXmlElement('patch')
if self.packageName != package.packageName:
xpatch.SetAttribute('name', self.packageName)
if self.platform != package.platform:
xpatch.SetAttribute('platform', self.platform)
if self.version != package.version:
xpatch.SetAttribute('version', self.version)
if self.hostUrl != package.hostUrl:
xpatch.SetAttribute('host', self.hostUrl)
self.file.storeXml(xpatch)
xsource = TiXmlElement('source')
self.sourceFile.storeMiniXml(xsource)
xpatch.InsertEndChild(xsource)
xtarget = TiXmlElement('target')
self.targetFile.storeMiniXml(xtarget)
xpatch.InsertEndChild(xtarget)
return xpatch
class Package:
""" This is a particular package. This contains all of the
information needed to reconstruct the package's desc file. """
def __init__(self, packageDesc, patchMaker, xpackage = None):
self.packageDir = Filename(patchMaker.installDir, packageDesc.getDirname())
self.packageDesc = packageDesc
self.patchMaker = patchMaker
self.contentsDocPackage = xpackage
self.patchVersion = 1
self.currentPv = None
self.basePv = None
self.topPv = None
self.packageName = None
self.platform = None
self.version = None
self.hostUrl = None
self.currentFile = None
self.baseFile = None
self.doc = None
self.anyChanges = False
self.patches = []
def getCurrentKey(self):
""" Returns the key to locate the current version of this
package. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.currentFile)
def getBaseKey(self):
""" Returns the key to locate the "base" or oldest version
of this package. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile)
def getTopKey(self):
""" Returns the key to locate the "top" or newest version
of this package. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.topFile)
def getGenericKey(self, fileSpec):
""" Returns the key that has the indicated hash. """
return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec)
def readDescFile(self, doProcessing = False):
""" Reads the existing package.xml file and stores it in
this class for later rewriting. if doProcessing is true,
it may massage the file and the directory contents in
preparation for building patches. Returns true on
success, false on failure. """
self.anyChanges = False
packageDescFullpath = Filename(self.patchMaker.installDir, self.packageDesc)
self.doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
if not self.doc.LoadFile():
print("Couldn't read %s" % (packageDescFullpath))
return False
xpackage = self.doc.FirstChildElement('package')
if not xpackage:
return False
self.packageName = xpackage.Attribute('name')
self.platform = xpackage.Attribute('platform')
self.version = xpackage.Attribute('version')
# All packages we defined in-line are assigned to the
# "none" host. TODO: support patching from packages on
# other hosts, which means we'll need to fill in a value
# here for those hosts.
self.hostUrl = None
self.currentFile = None
self.baseFile = None
self.topFile = None
self.compressedFilename = None
compressedFile = None
# Assume there are changes for this version, until we
# discover that there aren't.
isNewVersion = True
# Get the actual current version.
xarchive = xpackage.FirstChildElement('uncompressed_archive')
if xarchive:
self.currentFile = FileSpec()
self.currentFile.loadXml(xarchive)
# Get the top_version--the top (newest) of the patch
# chain.
xarchive = xpackage.FirstChildElement('top_version')
if xarchive:
self.topFile = FileSpec()
self.topFile.loadXml(xarchive)
if self.topFile.hash == self.currentFile.hash:
# No new version this pass.
isNewVersion = False
else:
# There's a new version this pass. Update it.
self.anyChanges = True
else:
# If there isn't a top_version yet, we have to make
# one, by duplicating the currentFile.
self.topFile = copy.copy(self.currentFile)
self.anyChanges = True
# Get the current patch version. If we have a
# patch_version attribute, it refers to this particular
# instance of the file, and that is the current patch
# version number. If we only have a last_patch_version
# attribute, it means a patch has not yet been built for
# this particular instance, and that number is the
# previous version's patch version number.
patchVersion = xpackage.Attribute('patch_version')
if patchVersion:
self.patchVersion = int(patchVersion)
else:
patchVersion = xpackage.Attribute('last_patch_version')
if patchVersion:
self.patchVersion = int(patchVersion)
if isNewVersion:
self.patchVersion += 1
self.anyChanges = True
# Put the patchVersion in the compressed filename, for
# cache-busting. This means when the version changes, its
# URL will also change, guaranteeing that users will
# download the latest version, and not some stale cache
# file.
xcompressed = xpackage.FirstChildElement('compressed_archive')
if xcompressed:
compressedFile = FileSpec()
compressedFile.loadXml(xcompressed)
oldCompressedFilename = compressedFile.filename
self.compressedFilename = oldCompressedFilename
if doProcessing:
newCompressedFilename = '%s.%s.pz' % (self.currentFile.filename, self.patchVersion)
if newCompressedFilename != oldCompressedFilename:
oldCompressedPathname = Filename(self.packageDir, oldCompressedFilename)
newCompressedPathname = Filename(self.packageDir, newCompressedFilename)
if oldCompressedPathname.renameTo(newCompressedPathname):
compressedFile.fromFile(self.packageDir, newCompressedFilename)
compressedFile.storeXml(xcompressed)
self.compressedFilename = newCompressedFilename
self.anyChanges = True
# Get the base_version--the bottom (oldest) of the patch
# chain.
xarchive = xpackage.FirstChildElement('base_version')
if xarchive:
self.baseFile = FileSpec()
self.baseFile.loadXml(xarchive)
else:
# If there isn't a base_version yet, we have to make
# one, by duplicating the currentFile.
self.baseFile = copy.copy(self.currentFile)
# Note that the we only store the compressed version
# of base_filename on disk, but we store the md5 of
# the uncompressed version in the xml file. To
# emphasize this, we name it without the .pz extension
# in the xml file, even though the compressed file on
# disk actually has a .pz extension.
self.baseFile.filename += '.base'
# Also duplicate the (compressed) file itself.
if doProcessing and self.compressedFilename:
fromPathname = Filename(self.packageDir, self.compressedFilename)
toPathname = Filename(self.packageDir, self.baseFile.filename + '.pz')
fromPathname.copyTo(toPathname)
self.anyChanges = True
self.patches = []
xpatch = xpackage.FirstChildElement('patch')
while xpatch:
patchfile = PatchMaker.Patchfile(self)
patchfile.loadXml(xpatch)
self.patches.append(patchfile)
xpatch = xpatch.NextSiblingElement('patch')
return True
def writeDescFile(self):
""" Rewrites the desc file with the new patch
information. """
if not self.anyChanges:
# No need to rewrite.
return
xpackage = self.doc.FirstChildElement('package')
if not xpackage:
return
packageSeq = SeqValue()
packageSeq.loadXml(xpackage, 'seq')
packageSeq += 1
packageSeq.storeXml(xpackage, 'seq')
# Remove all of the old patch entries from the desc file
# we read earlier.
xremove = []
for value in ['base_version', 'top_version', 'patch']:
xpatch = xpackage.FirstChildElement(value)
while xpatch:
xremove.append(xpatch)
xpatch = xpatch.NextSiblingElement(value)
for xelement in xremove:
xpackage.RemoveChild(xelement)
xpackage.RemoveAttribute('last_patch_version')
# Now replace them with the current patch information.
xpackage.SetAttribute('patch_version', str(self.patchVersion))
xarchive = TiXmlElement('base_version')
self.baseFile.storeXml(xarchive)
xpackage.InsertEndChild(xarchive)
# The current version is now the top version.
xarchive = TiXmlElement('top_version')
self.currentFile.storeXml(xarchive)
xpackage.InsertEndChild(xarchive)
for patchfile in self.patches:
xpatch = patchfile.makeXml(self)
xpackage.InsertEndChild(xpatch)
self.doc.SaveFile()
# Also copy the seq to the import desc file, for
# documentation purposes.
importDescFilename = str(self.packageDesc)[:-3] + 'import.xml'
importDescFullpath = Filename(self.patchMaker.installDir, importDescFilename)
doc = TiXmlDocument(importDescFullpath.toOsSpecific())
if doc.LoadFile():
xpackage = doc.FirstChildElement('package')
if xpackage:
packageSeq.storeXml(xpackage, 'seq')
doc.SaveFile()
else:
print("Couldn't read %s" % (importDescFullpath))
if self.contentsDocPackage:
# Now that we've rewritten the xml file, we have to
# change the contents.xml file that references it to
# indicate the new file hash.
fileSpec = FileSpec()
fileSpec.fromFile(self.patchMaker.installDir, self.packageDesc)
fileSpec.storeXml(self.contentsDocPackage)
# Also important to update the import.xml hash.
ximport = self.contentsDocPackage.FirstChildElement('import')
if ximport:
fileSpec = FileSpec()
fileSpec.fromFile(self.patchMaker.installDir, importDescFilename)
fileSpec.storeXml(ximport)
# Also copy the package seq value into the
# contents.xml file, mainly for documentation purposes
# (the authoritative seq value is within the desc
# file).
packageSeq.storeXml(self.contentsDocPackage, 'seq')
# PatchMaker constructor.
def __init__(self, installDir):
self.installDir = installDir
self.packageVersions = {}
self.packages = []
def buildPatches(self, packageNames = None):
""" Makes the patches required in a particular directory
structure on disk. If packageNames is None, this makes
patches for all packages; otherwise, it should be a list of
package name strings, limiting the set of packages that are
processed. """
if not self.readContentsFile():
return False
self.buildPatchChains()
if packageNames is None:
self.processAllPackages()
else:
self.processSomePackages(packageNames)
self.writeContentsFile()
self.cleanup()
return True
def cleanup(self):
""" Should be called on exit to remove temporary files and
such created during processing. """
for pv in self.packageVersions.values():
pv.cleanup()
def getPatchChainToCurrent(self, descFilename, fileSpec):
""" Reads the package defined in the indicated desc file, and
constructs a patch chain from the version represented by
fileSpec to the current version of this package, if possible.
Returns the patch chain if successful, or None otherwise. """
package = self.readPackageDescFile(descFilename)
if not package:
return None
self.buildPatchChains()
fromPv = self.getPackageVersion(package.getGenericKey(fileSpec))
toPv = package.currentPv
patchChain = None
if toPv and fromPv:
patchChain = toPv.getPatchChain(fromPv)
return patchChain
def readPackageDescFile(self, descFilename):
""" Reads a desc file associated with a particular package,
and adds the package to self.packages. Returns the Package
object, or None on failure. """
package = self.Package(Filename(descFilename), self)
if not package.readDescFile(doProcessing = False):
return None
self.packages.append(package)
return package
def readContentsFile(self):
""" Reads the contents.xml file at the beginning of
processing. """
contentsFilename = Filename(self.installDir, 'contents.xml')
doc = TiXmlDocument(contentsFilename.toOsSpecific())
if not doc.LoadFile():
# Couldn't read file.
print("couldn't read %s" % (contentsFilename))
return False
xcontents = doc.FirstChildElement('contents')
if xcontents:
contentsSeq = SeqValue()
contentsSeq.loadXml(xcontents)
contentsSeq += 1
contentsSeq.storeXml(xcontents)
xpackage = xcontents.FirstChildElement('package')
while xpackage:
solo = xpackage.Attribute('solo')
solo = int(solo or '0')
filename = xpackage.Attribute('filename')
if filename and not solo:
filename = Filename(filename)
package = self.Package(filename, self, xpackage)
package.readDescFile(doProcessing = True)
self.packages.append(package)
xpackage = xpackage.NextSiblingElement('package')
self.contentsDoc = doc
return True
def writeContentsFile(self):
""" Writes the contents.xml file at the end of processing. """
# We also have to write the desc file for all packages that
# might need it, because we might have changed some of them on
# read.
for package in self.packages:
package.writeDescFile()
# The above writeDescFile() call should also update each
# package's element within the contents.xml document, so all
# we have to do now is write out the document.
self.contentsDoc.SaveFile()
def getPackageVersion(self, key):
""" Returns a shared PackageVersion object for the indicated
key. """
packageName, platform, version, hostUrl, file = key
# We actually key on the hash, not the FileSpec itself.
k = (packageName, platform, version, hostUrl, file.hash)
pv = self.packageVersions.get(k, None)
if not pv:
pv = self.PackageVersion(*key)
self.packageVersions[k] = pv
return pv
def buildPatchChains(self):
""" Builds up the chains of PackageVersions and the patchfiles
that connect them. """
self.patchFilenames = {}
for package in self.packages:
if not package.baseFile:
# This package doesn't have any versions yet.
continue
currentPv = self.getPackageVersion(package.getCurrentKey())
package.currentPv = currentPv
currentPv.packageCurrent = package
currentPv.printName = package.currentFile.filename
basePv = self.getPackageVersion(package.getBaseKey())
package.basePv = basePv
basePv.packageBase = package
basePv.printName = package.baseFile.filename
topPv = self.getPackageVersion(package.getTopKey())
package.topPv = topPv
topPv.packageTop = package
for patchfile in package.patches:
self.recordPatchfile(patchfile)
def recordPatchfile(self, patchfile):
""" Adds the indicated patchfile to the patch chains. """
self.patchFilenames[patchfile.file.filename] = patchfile
fromPv = self.getPackageVersion(patchfile.getSourceKey())
patchfile.fromPv = fromPv
fromPv.toPatches.append(patchfile)
toPv = self.getPackageVersion(patchfile.getTargetKey())
patchfile.toPv = toPv
toPv.fromPatches.append(patchfile)
toPv.printName = patchfile.file.filename
def processSomePackages(self, packageNames):
""" Builds missing patches only for the named packages. """
remainingNames = packageNames[:]
for package in self.packages:
if package.packageName in packageNames:
self.processPackage(package)
if package.packageName in remainingNames:
remainingNames.remove(package.packageName)
if remainingNames:
print("Unknown packages: %s" % (remainingNames,))
def processAllPackages(self):
""" Walks through the list of packages, and builds missing
patches for each one. """
for package in self.packages:
self.processPackage(package)
def processPackage(self, package):
""" Builds missing patches for the indicated package. """
if not package.baseFile:
# No versions.
return
# What's the current version on the top of the tree?
topPv = package.topPv
currentPv = package.currentPv
if topPv != currentPv:
# They're different, so build a new patch.
filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion))
assert filename not in self.patchFilenames
if not self.buildPatch(topPv, currentPv, package, filename):
raise Exception("Couldn't build patch.")
def buildPatch(self, v1, v2, package, patchFilename):
""" Builds a patch from PackageVersion v1 to PackageVersion
v2, and stores it in patchFilename.pz. Returns true on
success, false on failure."""
pathname = Filename(package.packageDir, patchFilename)
if not self.buildPatchFile(v1.getFile(), v2.getFile(), pathname,
v1.printName, v2.printName):
return False
compressedPathname = Filename(pathname + '.pz')
compressedPathname.unlink()
if not compressFile(pathname, compressedPathname, 9):
raise Exception("Couldn't compress patch.")
pathname.unlink()
patchfile = self.Patchfile(package)
patchfile.fromFile(package.packageDir, patchFilename + '.pz',
v1.file, v2.file)
package.patches.append(patchfile)
package.anyChanges = True
self.recordPatchfile(patchfile)
return True
def buildPatchFile(self, origFilename, newFilename, patchFilename,
printOrigName, printNewName):
""" Creates a patch file from origFilename to newFilename,
storing the result in patchFilename. Returns true on success,
false on failure. """
if not origFilename.exists():
# No original version to patch from.
return False
print("Building patch from %s to %s" % (printOrigName, printNewName))
patchFilename.unlink()
p = Patchfile() # The C++ class
if p.build(origFilename, newFilename, patchFilename):
return True
# Unable to build a patch for some reason.
patchFilename.unlink()
return False

View File

@ -1,95 +0,0 @@
__all__ = ["ScanDirectoryNode"]
from panda3d.core import VirtualFileSystem, VirtualFileMountSystem, Filename, TiXmlDocument
vfs = VirtualFileSystem.getGlobalPtr()
class ScanDirectoryNode:
""" This class is used to scan a list of files on disk. """
def __init__(self, pathname, ignoreUsageXml = False):
self.pathname = pathname
self.filenames = []
self.fileSize = 0
self.nested = []
self.nestedSize = 0
xusage = None
if not ignoreUsageXml:
# Look for a usage.xml file in this directory. If we find
# one, we read it for the file size and then stop here, as
# an optimization.
usageFilename = Filename(pathname, 'usage.xml')
doc = TiXmlDocument(usageFilename.toOsSpecific())
if doc.LoadFile():
xusage = doc.FirstChildElement('usage')
if xusage:
diskSpace = xusage.Attribute('disk_space')
try:
diskSpace = int(diskSpace or '')
except ValueError:
diskSpace = None
if diskSpace is not None:
self.fileSize = diskSpace
return
files = vfs.scanDirectory(self.pathname)
if files is None:
files = []
for vfile in files:
if hasattr(vfile, 'getMount'):
if not isinstance(vfile.getMount(), VirtualFileMountSystem):
# Not a real file; ignore it.
continue
if vfile.isDirectory():
# A nested directory.
subdir = ScanDirectoryNode(vfile.getFilename(), ignoreUsageXml = ignoreUsageXml)
self.nested.append(subdir)
self.nestedSize += subdir.getTotalSize()
elif vfile.isRegularFile():
# A nested file.
self.filenames.append(vfile.getFilename())
self.fileSize += vfile.getFileSize()
else:
# Some other wacky file thing.
self.filenames.append(vfile.getFilename())
if xusage:
# Now update the usage.xml file with the newly-determined
# disk space.
xusage.SetAttribute('disk_space', str(self.getTotalSize()))
tfile = Filename.temporary(str(pathname), '.xml')
if doc.SaveFile(tfile.toOsSpecific()):
tfile.renameTo(usageFilename)
def getTotalSize(self):
return self.nestedSize + self.fileSize
def extractSubdir(self, pathname):
""" Finds the ScanDirectoryNode within this node that
corresponds to the indicated full pathname. If it is found,
removes it from its parent, and returns it. If it is not
found, returns None. """
# We could be a little smarter here, but why bother. Just
# recursively search all children.
for subdir in self.nested:
if subdir.pathname == pathname:
self.nested.remove(subdir)
self.nestedSize -= subdir.getTotalSize()
return subdir
result = subdir.extractSubdir(pathname)
if result:
self.nestedSize -= result.getTotalSize()
if subdir.getTotalSize() == 0:
# No other files in the subdirectory that contains
# this package; remove it too.
self.nested.remove(subdir)
return result
return None

View File

@ -1,90 +0,0 @@
__all__ = ["SeqValue"]
class SeqValue:
""" This represents a sequence value read from a contents.xml
file, either from the <contents> or the <package> section. It's
represented as series of dotted integers in the xml file, and
stored internally as a tuple of integers.
It may be incremented, which increments only the last integer in
the series; or it may be compared with another SeqValue, which
compares all of the integers componentwise. """
def __init__(self, value = None):
self.value = ()
if value is not None:
self.set(value)
def set(self, value):
""" Sets the seq from the indicated value of unspecified
type. """
if isinstance(value, tuple):
self.setFromTuple(value)
elif isinstance(value, str):
self.setFromString(value)
else:
raise TypeError('Invalid sequence type: %s' % (value,))
def setFromTuple(self, value):
""" Sets the seq from the indicated tuple of integers. """
assert isinstance(value, tuple)
self.value = value
def setFromString(self, value):
""" Sets the seq from the indicated string of dot-separated
integers. Raises ValueError on error. """
assert isinstance(value, str)
self.value = ()
if value:
value = value.split('.')
value = map(int, value)
self.value = tuple(value)
def loadXml(self, xelement, attribute = 'seq'):
""" Reads the seq from the indicated XML element. Returns
true if loaded, false if not given or if there was an
error. """
self.value = ()
value = xelement.Attribute(attribute)
if value:
try:
self.setFromString(value)
except ValueError:
return False
return True
return False
def storeXml(self, xelement, attribute = 'seq'):
""" Adds the seq to the indicated XML element. """
if self.value:
value = '.'.join(map(str, self.value))
xelement.SetAttribute(attribute, value)
def __add__(self, inc):
""" Increments the seq value, returning the new value. """
if not self.value:
value = (1,)
else:
value = self.value[:-1] + (self.value[-1] + inc,)
return SeqValue(value)
def __cmp__(self, other):
""" Compares to another seq value. """
return cmp(self.value, other.value)
def __lt__(self, other):
return self.value < other.value
def __gt__(self, other):
return self.value > other.value
def __bool__(self):
return bool(self.value)
def __str__(self):
return 'SeqValue%s' % (repr(self.value))

View File

@ -1,4 +0,0 @@
"""
This package provides the Python interface to functionality relating to
the Panda3D Runtime environment.
"""

View File

@ -1,124 +0,0 @@
from panda3d.core import getModelPath, Filename, ConfigVariableFilename, DSearchPath, ExecutionEnvironment, PandaSystem
# This file defines a number of standard "packages" that correspond to
# a Panda3D distribution. These packages are built by passing this
# file to the ppackage utility, either as a packaged application, or
# as the module direct.p3d.ppackage.
# The packages in this file define the "Core API". This is the second
# installed piece of the three-part plugin system (the plugin, the
# core API, Panda3D).
# These packages are downloaded directly by the plugin, from the host
# specified by the value of PANDA_PACKAGE_HOST_URL compiled into the
# plugin. Thus, these packages are inextricably tied to the
# particular plugin they have been built with. They do not have to be
# present on a server that hosts a version of Panda3D for download,
# just on the server that hosts the plugin itself. These packages do
# not need to be updated with each new version of Panda3D.
# Also see panda3d.pdef.
class coreapi(solo):
# The special "coreapi" package. As a "solo", this is just a
# single .dll (or dylib, or whatever).
setVer(PandaSystem.getP3dCoreapiVersionString())
file('p3d_plugin.dll')
class images(package):
# The default startup images are stored as their own package.
names = ['download', 'failed', 'play_click', 'play_ready', 'play_rollover',
'auth_click', 'auth_ready', 'auth_rollover']
configDict = {}
# Construct a search path to look for the images.
search = DSearchPath()
# First on the path: an explicit $PLUGIN_IMAGES env var.
if ExecutionEnvironment.hasEnvironmentVariable('PLUGIN_IMAGES'):
search.appendDirectory(Filename.expandFrom('$PLUGIN_IMAGES'))
# Next on the path: the models/plugin_images directory within the
# current directory.
search.appendDirectory('models/plugin_images')
# Finally on the path: models/plugin_images within the model
# search path.
for dir in getModelPath().getDirectories():
search.appendDirectory(Filename(dir, 'plugin_images'))
for name in names:
# Look for a png image first.
basename = '%s.png' % (name)
filename = Filename(basename)
found = filename.resolveFilename(search)
if not found:
# Then try a jpeg image.
basename = '%s.jpg' % (name)
filename = Filename(basename)
found = filename.resolveFilename(search)
if found:
# Add the image file to the package
file(filename, newName = basename, extract = True)
# And set the config variable to reference it.
token = '%s_img' % (name)
configDict[token] = basename
else:
print("Could not locate %s" % (filename))
# Also make a few special cases. We use the same default image
# for many of the states.
download = configDict.get('download_img', None)
if download:
configDict['ready_img'] = download
configDict['unauth_img'] = download
configDict['launch_img'] = download
configDict['active_img'] = download
config(**configDict)
class certlist(package):
# This package holds any certificates that should be considered
# pre-approved by the plugin vendor. If you build and host your
# own version of the Panda3D plugin, you can add your own
# certificates to this list. The certificates in this package
# will be assumed to have been approved by the user when he/she
# installed the plugin.
# They should be PEM-encoded and their filenames must end in the
# ".pem" or ".crt" extension, and they should be added with the
# extract = True flag so they will be extracted to disk.
pass
class p3dcert(package):
# This special application, used to pop up a dialog to prompt the
# user to accept or deny unknown applications, is its own package.
config(display_name = "Authorization Dialog")
if platform.startswith('osx'):
# On Mac, we package up a P3DCert.app bundle. This includes
# specifications in the plist file to avoid creating a dock
# icon and stuff.
# Find p3dcert.plist in the direct source tree.
import direct
plist = Filename(direct.__path__[0], 'plugin/p3dcert.plist')
makeBundle('P3DCert.app', plist, executable = 'p3dcert')
else:
# Anywhere else, we just ship the executable file p3dcert.exe.
file('p3dcert.exe', required = True)
class p3dembed(package):
# This contains the p3dembed executable, that is used by
# pdeploy to generate a self-extracting .p3d executable.
config(platform_specific = True)
file('p3dembed.exe', required = True)
if platform.startswith('win'):
file('p3dembedw.exe', required = True)

View File

@ -1,127 +0,0 @@
/* Filename: p3dWrapper.c
* Created by: rdb (16Jan10)
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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."
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* p3dWrapper is a small wrapper executable that locates a .p3d file
in the same directory as this executable file, with the same name
(except .p3d instead of .exe of course). It is only meant to be
used on Windows system, it is not needed on Unix-like systems. */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <assert.h>
#include <malloc.h>
#define BUFFER_SIZE 1024
/* It makes sense to use "App Paths\panda3d.exe". However, Microsoft
decided in their infinite wisdom to disable Redirection for that
key from Windows 7 onward, so we can't rely on it producing a
result appropriate to the right architecture when both the 32-bit
and 64-bit versions of the runtime are installed. Beh. */
#define UNINST_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Panda3D Game Engine"
int main (int argc, char* argv[]) {
int i;
char buffer[BUFFER_SIZE];
char* p3dfile;
char* runtime = NULL;
DWORD size;
STARTUPINFO si;
PROCESS_INFORMATION pi;
char *cmd;
char *newcmd;
HKEY hKey = 0;
char buf[1024] = {0};
DWORD dwType = REG_SZ;
DWORD dwBufSize = sizeof(buf);
size = GetModuleFileName(NULL, buffer, BUFFER_SIZE);
assert (size > 0);
/* Chop off the .exe and replace it by .p3d. */
p3dfile = (char*) _alloca(size + 1);
memcpy(p3dfile, buffer, size);
p3dfile[size] = 0;
memcpy(p3dfile + size - 3, "p3d", 3);
/* Find the location of panda3d.exe using the registry path. */
#ifdef _WIN64
/* If we're on 64-bit Windows, try the 64-bit registry first. */
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) {
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
char *slash = strrchr(buf, '\\');
if (slash != NULL) {
strcpy(slash, "\\panda3d.exe");
runtime = buf;
}
}
RegCloseKey(hKey);
}
#endif
/* On 32-bit Windows, or no 64-bit Runtime installed. Try 32-bit registry. */
if (runtime == NULL) {
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) {
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
char *slash = strrchr(buf, '\\');
if (slash != NULL) {
strcpy(slash, "\\panda3d.exe");
runtime = buf;
}
}
RegCloseKey(hKey);
}
}
/* Backward compatibility: Runtime 1.0.4 and below looked for the below key, even though the
above keys should work fine, but let's just be certain.
Find the Panda3D applet\DefaultIcon key and extract the path to the runtime from there. */
if (runtime == NULL) {
if (RegOpenKey(HKEY_CLASSES_ROOT, "Panda3D applet\\DefaultIcon", &hKey) == ERROR_SUCCESS) {
if (RegQueryValueEx(hKey, 0, 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
char *slash = strrchr(buf, '\\');
if (slash != NULL) {
strcpy(slash, "\\panda3d.exe");
runtime = buf;
}
} else {
fprintf(stderr, "Failed to read registry key. Try reinstalling the Panda3D Runtime.\n");
return 1;
}
RegCloseKey(hKey);
} else {
fprintf(stderr, "The Panda3D Runtime does not appear to be installed!\n");
return 1;
}
}
if (runtime == NULL) {
fprintf(stderr, "Failed to find panda3d.exe in registry. Try reinstalling the Panda3D Runtime.\n");
return 1;
}
/* Build the command-line and run panda3d.exe. */
cmd = GetCommandLine();
newcmd = (char*) _alloca(strlen(runtime) + strlen(p3dfile) + strlen(cmd) - strlen (argv[0]) + 7);
sprintf(newcmd, "\"%s\" \"%s\" %s", runtime, p3dfile, cmd + strlen(argv[0]));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
if (CreateProcess(runtime, newcmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
}
return 0;
}

View File

@ -1,241 +0,0 @@
#! /usr/bin/env python
usageText = """
This command will pack a Panda application, consisting of a directory
tree of .py files and models, into a p3d file for convenient
distribution. The resulting p3d file can be run by the Panda3D
runtime executable, or by the Panda3D web browser plugin.
This command will build p3d files that reference Panda3D %s,
from host %s .
Also see ppackage, a more powerful (but more complex) tool that can
also be used to build p3d applications, using a pdef description file.
Usage:
%s [opts] -o app.p3d
Options:
-o app.p3d
Specify the name of the p3d file to generate. This is required.
-d application_root
Specify the root directory of the application source; this is a
directory tree that contains all of your .py files and models.
If this is omitted, the default is the current directory.
-m main.py
Names the Python file that begins the application. This should
be a file within the root directory. If this is omitted, the
default is a file named "main.py", or if there is only one Python
file present, it is used. If this file contains a function
called main(), that function will be called after importing it
(this is preferable to having the module start itself immediately
upon importing).
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
Signs the resulting p3d with the indicated certificate. You may
specify the signing certificate, the optional authorization
chain, and the private key in three different files, or they may
all be combined in the first file. If the private key is
encrypted, the password will be required to decrypt it.
-e ext
Adds a new extension to be processed as a generic, compressible
file type. Do not include the leading dot. Files matching this
extension found within the root directory will be automatically
added to the p3d file, in compressed form. This option may be
repeated as necessary.
-n ext
Adds a new extension to be processed as a noncompressible file
type. Files matching this extension will be added to the p3d
file, in their original, uncompressed form. You should use this
instead of -e for files that are uncompressible by their nature
(e.g. mpg files). This option may be repeated as necessary.
-x ext
Marks files with the given extensions of needing to be physically
extracted to disk before they can be loaded. This is used for
file types that cannot be loaded via the virtual file system,
such as .ico files on Windows.
This option is currently only implemented when deploying the
application with pdeploy.
-p python_lib_dir
Adds a directory to search for additional Python modules. You
can use this to add your system's Python path, to allow packp3d
to find any system modules not included in the standard Panda3D
release, but your version of Python must match this one (%s).
This option may be repeated to add multiple directories.
-c config=value
Sets the indicated config flag in the application. This option
may be repeated as necessary.
-r package[,version[,hostURL]]
Names an additional package that this application requires at
startup time. The default package is 'panda3d'; you may repeat
this option to indicate dependencies on additional packages.
-s search_dir
Additional directories to search for previously-built packages.
This option may be repeated as necessary. These directories may
also be specified with the pdef-path Config.prc variable.
-D
Sets the allow_python_dev flag in the application. This enables
additional runtime debug operations, particularly the -i option
to the panda3d command, which enables a live Python prompt within
the application's environment. Setting this flag may be useful
to develop an application initially, but should not be set on an
application intended for deployment.
"""
import sys
import os
import getopt
import glob
from direct.p3d import Packager
from panda3d.core import *
# Temp hack for debugging.
#from direct.p3d.AppRunner import dummyAppRunner; dummyAppRunner()
class ArgumentError(Exception):
pass
def makePackedApp(args):
opts, args = getopt.getopt(args, 'o:d:m:S:e:n:x:p:c:r:s:Dh')
packager = Packager.Packager()
appFilename = None
root = Filename('.')
main = None
configFlags = []
requires = []
allowPythonDev = False
for option, value in opts:
if option == '-o':
appFilename = Filename.fromOsSpecific(value)
elif option == '-d':
root = Filename.fromOsSpecific(value)
elif option == '-m':
main = value
elif option == '-S':
tokens = value.split(',')
while len(tokens) < 4:
tokens.append('')
certificate, chain, pkey, password = tokens[:4]
packager.signParams.append((Filename.fromOsSpecific(certificate),
Filename.fromOsSpecific(chain),
Filename.fromOsSpecific(pkey),
Filename.fromOsSpecific(password)))
elif option == '-e':
packager.binaryExtensions.append(value)
elif option == '-n':
packager.uncompressibleExtensions.append(value)
elif option == '-x':
packager.extractExtensions.append(value)
elif option == '-p':
sys.path.append(value)
elif option == '-c':
configFlags.append(value.split('=', 1))
elif option == '-r':
tokens = value.split(',')
while len(tokens) < 3:
tokens.append('')
name, version, host = tokens[:3]
requires.append((name, version, host))
elif option == '-s':
packager.installSearch.append(Filename.fromOsSpecific(value))
elif option == '-D':
allowPythonDev = True
elif option == '-h':
print(usageText % (
PandaSystem.getPackageVersionString(),
PandaSystem.getPackageHostUrl(),
os.path.split(sys.argv[0])[1],
'%s.%s' % (sys.version_info[0], sys.version_info[1])))
sys.exit(0)
if not appFilename:
raise ArgumentError("No target app specified. Use:\n %s -o app.p3d\nUse -h to get more usage information." % (os.path.split(sys.argv[0])[1]))
if args:
raise ArgumentError("Extra arguments on command line.")
if appFilename.getExtension() != 'p3d':
raise ArgumentError('Application filename must end in ".p3d".')
appDir = Filename(appFilename.getDirname())
if not appDir:
appDir = Filename('.')
appBase = appFilename.getBasenameWoExtension()
if main:
main = Filename.fromOsSpecific(main)
main.makeAbsolute(root)
else:
main = Filename(root, 'main.py')
if not main.exists():
main = glob.glob(os.path.join(root.toOsSpecific(), '*.py'))
if len(main) == 0:
raise ArgumentError('No Python files in root directory.')
elif len(main) > 1:
raise ArgumentError('Multiple Python files in root directory; specify the main application with -m "main".')
main = Filename.fromOsSpecific(os.path.split(main[0])[1])
main.makeAbsolute(root)
packager.installDir = appDir
packager.allowPythonDev = allowPythonDev
# Put the root directory on the front of the model-path, so that
# any texture references in egg or bam files that reference
# textures from the top of the root directory will be properly
# resolved.
getModelPath().prependDirectory(root)
try:
packager.setup()
packager.beginPackage(appBase, p3dApplication = True)
# Pre-require panda3d, to give a less-confusing error message
# if one of our requirements pulls in a wrong version of
# panda3d.
if 'panda3d' not in [t[0] for t in requires]:
packager.do_require('panda3d')
for name, version, host in requires:
packager.do_require(name, version = version, host = host)
if configFlags:
packager.do_config(**dict(configFlags))
packager.do_dir(root)
packager.do_main(main)
packager.endPackage()
packager.close()
except Packager.PackagerError:
# Just print the error message and exit gracefully.
inst = sys.exc_info()[1]
print(inst.args[0])
sys.exit(1)
try:
makePackedApp(sys.argv[1:])
except ArgumentError as e:
print(e.args[0])
sys.exit(1)
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)

View File

@ -1,518 +0,0 @@
import sys
from panda3d.core import Filename, PandaSystem, getModelPath
# This file defines a number of standard "packages" that correspond to
# a Panda3D distribution. These packages are built by passing this
# file to the ppackage utility, either as a packaged application, or
# as the module direct.p3d.ppackage.
# The packages in this file define Panda3D itself. This is the third
# installed piece of the three-part plugin system (the plugin, the
# core API, Panda3D).
# When needed, these packages are downloaded by the core API, from the
# host URL specified in a given p3d file, and not from any hardcoded
# URL. Thus, any custom version of Panda3D may be hosted on any
# server in the world, and any version of the plugin can download it.
# Also see coreapi.pdef.
class panda3d(package):
# The main Panda3D package. Contains Python and most of the graphics
# code in Panda3D.
config(display_name = "Panda3D")
# First, add the minimum startup files for a Panda3D package.
# These are files that the Panda3D runtime will explicitly look
# for by name in order to get itself bootstrapped.
setupPanda3D()
# These are Python modules that are needed by most Panda3D
# applications. It doesn't matter too much if we miss one or two
# here, since any module imported by any of this code will
# automatically be included as well, and we end up with a pretty
# complete list that way.
module('direct.directbase.DirectStart',
# Don't want to include all files in direct.p3d, because
# that picks up the runtime scripts too, which are their
# own p3d files below.
'direct.p3d.AppRunner',
'direct.p3d.DWBPackageInstaller',
'direct.actor.*',
'direct.controls.*',
'direct.directdevices.*',
'direct.directnotify.*',
'direct.directtools.*',
'direct.directutil.*',
'direct.distributed.*',
'direct.filter.*',
'direct.fsm.*',
'direct.gui.*',
'direct.interval.*',
'direct.particles.*',
'direct.showbase.*',
'direct.showutil.*',
'direct.stdpy.*',
'direct.task.*')
# Keep these modules for backward compatibility.
module('pandac.PandaModules', 'pandac.extension_native_helpers')
module('panda3d.core',
'panda3d.direct',
'panda3d.fx',
'panda3d.physics')
# Include various standard Python encodings. The rest is in morepy.
module('encodings', 'encodings.aliases', 'encodings.undefined',
'encodings.utf_8', 'encodings.ascii', 'encodings.mbcs',
'encodings.latin_1', 'io')
if sys.version_info < (3, 0):
module('encodings.string_escape')
# pandac.PandaModules pulls in other Panda3D libs automatically.
# Exclude these Panda3D libs; they are big and many applications don't
# use them. We define them as separate, optional packages, below,
# except for skel - this is useless in a shipped game anyways.
excludeModule('panda3d.egg')
excludeModule('panda3d.ode')
excludeModule('panda3d.bullet')
excludeModule('panda3d.vision')
excludeModule('panda3d.skel')
excludeModule('panda3d.physx')
excludeModule('panda3d.ai')
excludeModule('panda3d.vrpn')
excludeModule('libpandaegg')
excludeModule('libpandaode')
excludeModule('libpandabullet')
excludeModule('libp3vision')
excludeModule('libpandaskel')
excludeModule('libpandaphysx')
excludeModule('libpandaai')
excludeModule('libp3vrpn')
# Exclude these GUI toolkits; they're big, and many applications don't
# use them. We define them as separate, optional packages, below.
excludeModule('wx',
'direct.showbase.WxGlobal')
excludeModule('Tkinter', 'tkinter', 'Pmw', 'tkinter.simpledialog',
'direct.showbase.TkGlobal',
'direct.tkpanels', 'direct.tkwidgets',
'tkCommonDialog', 'tkMessageBox', 'tkSimpleDialog')
excludeModule('MySQLdb', 'MySQLdb.connections', 'MySQLdb.constants',
'MySQLdb.converters', '_mysql')
# Some code in distributed conditionally imports these.
excludeModule('otp.ai', 'otp.ai.AIZoneData')
# Most of the core Panda3D DLL's will be included implicitly due to
# being referenced by the above Python code. Here we name a few more
# that are also needed, but aren't referenced by any code. Again,
# note that the .dll extension is automatically replaced with the
# platform-specific extension for an executable.
file('libpandagl.dll', 'libp3tinydisplay.dll')
if platform.startswith('win'):
file('libpandadx9.dll')
# A basic config file is needed to lay some some fundamental runtime
# variables.
if platform.startswith('win'):
auxDisplays = """
aux-display pandagl
aux-display pandadx9
aux-display p3tinydisplay
"""
else:
auxDisplays = """
aux-display pandagl
aux-display p3tinydisplay
"""
file('Config.prc', extract = True, text = """
plugin-path $PANDA3D_ROOT
default-model-extension .bam
cull-bin gui-popup 60 unsorted
""" + auxDisplays)
class morepy(package):
# Additional Python modules provided by the standard Python
# library. Include this package to get the full suite of standard
# Python functionality.
config(display_name = "Python standard library")
require('panda3d')
module('string', 're', 'struct', 'difflib',
'textwrap', 'codecs', 'unicodedata', 'stringprep', 'datetime',
'calendar', 'collections', 'heapq', 'bisect', 'array',
'sched', 'queue', 'weakref', 'types', 'copy', 'pprint',
'numbers', 'math', 'cmath', 'decimal', 'fractions',
'random', 'itertools', 'functools', 'operator', 'os.path',
'fileinput', 'filecmp', 'tempfile', 'glob', 'fnmatch', 'linecache',
'shutil', 'macpath', 'pickle', 'shelve', 'marshal', 'zlib',
'gzip', 'bz2', 'zipfile', 'tarfile', 'csv', 'netrc',
'xdrlib', 'plistlib', 'hashlib', 'hmac', 'os',
'time', 'optparse', 'getopt', 'logging', 'logging.*')
module('getpass', 'curses', 'curses.textpad', 'curses.wrapper',
'curses.ascii', 'curses.panel', 'platform', 'errno',
'ctypes', 'select', 'threading', 'dummy_threading', 'dummy_thread',
'multiprocessing', 'mmap', 'readline', 'rlcompleter', 'subprocess',
'socket', 'ssl', 'signal', 'asyncore', 'asynchat', 'email', 'json',
'mailcap', 'mailbox', 'mimetypes', 'base64', 'binhex', 'binascii',
'quopri', 'uu', 'xml.parsers.expat', 'xml.dom', 'xml.dom.minidom',
'xml.dom.pulldom', 'xml.sax', 'xml.sax.handler',
'xml.sax.saxutils', 'xml.sax.xmlreader',
'xml.etree.ElementTree', 'webbrowser', 'cgi', 'cgitb')
module('wsgiref', 'ftplib', 'poplib', 'imaplib', 'nntplib', 'smtplib',
'smtpd', 'telnetlib', 'uuid', 'audioop', 'aifc', 'sunau',
'wave', 'chunk', 'colorsys', 'imghdr', 'sndhdr',
'ossaudiodev', 'gettext', 'locale', 'cmd', 'shlex',
'pydoc', 'doctest', 'unittest', 'test',
'bdb', 'pdb', 'timeit', 'trace', 'sys')
module('warnings', 'contextlib', 'abc',
'atexit', 'traceback', '__future__', 'gc', 'inspect',
'site', 'fpectl', 'code', 'codeop', 'zipimport', 'pkgutil',
'modulefinder', 'runpy', 'parser', 'symtable', 'symbol',
'token', 'keyword', 'tokenize', 'tabnanny', 'pyclbr',
'py_compile', 'compileall', 'dis', 'pickletools',
'distutils', 'msilib', 'msvcrt', 'winsound', 'posix', 'pwd', 'spwd',
'grp', 'crypt', 'termios', 'tty', 'pty', 'fcntl', 'pipes',
'resource', 'nis', 'syslog', 'ast')
# Handle module name differences between Python 2 and Python 3 (see PEP3108)
if sys.version_info[0] < 3:
# Deprecated modules that were removed in Python 3
module('posixfile', 'rfc822', 'mimetools','MimeWriter', 'mimify',
'multifile', 'sets', 'md5', 'sha', 'imp', 'formatter')
# Mac-specific modules that were removed
module('autoGIL', 'ColorPicker', 'EasyDialogs', 'findertools',
'FrameWork', 'ic', 'MacOS', 'macostools')
# Removed because they were hardly used
module('imputil', 'mutex', 'user', 'new')
# Removed because they were obsolete
module('Bastion', 'rexec', 'commands', 'dircache', 'dl', 'fpformat',
'htmllib', 'imageop', 'mhlib', 'popen2', 'sgmllib', 'stat',
'statvfs', 'thread', 'UserDict', 'UserList', 'UserString',
'future_builtins', 'hotshot', 'bsddb')
# Renamed to fix PEP8 violations
module('_winreg', 'ConfigParser', 'copy_reg', 'SocketServer')
# C and Python implementations of the same interface were merged
module('cPickle', 'cStringIO', 'StringIO')
# Renamed because of poorly chosen names
module('repr', 'test.test_support', '__builtins__')
# Modules that got grouped under packages
module('anydbm', 'whichdb', 'dbm', 'dumbdbm', 'gdbm', 'dbhash')
module('HTMLParser', 'htmlentitydefs')
module('BaseHTTPServer', 'SimpleHTTPServer', 'cookielib', 'Cookie',
'CGIHTTPServer', 'httplib')
module('urllib', 'urllib2', 'urlparse', 'robotparser')
module('xmlrpclib', 'SimpleXMLRPCServer', 'DocXMLRPCServer')
else:
# Renamed to fix PEP8 violations
module('winreg', 'configparser', 'copyreg', 'socketserver')
# Renamed because of poorly chosen names
module('reprlib', 'test.support', 'builtins')
# Modules that got grouped under packages
module('dbm')
module('html')
module('http')
module('urllib')
module('xmlrpc')
# To add the multitude of standard Python string encodings.
module('encodings', 'encodings.*')
class models(package):
# The standard models package. This is the contents of the
# "models" directory that ships with Panda; it includes a few
# default fonts, and some silly little sample models like smiley
# and teapot.
config(display_name = "Standard models")
# We assign it the same version as the panda3d package. This
# would be assigned by default if we had a requirement on panda3d,
# but there's no real reason to declare that requirement.
config(version = PandaSystem.getPackageVersionString())
# Look for cmss12.egg on the model-path. Wherever this is found,
# we assume this is the models directory.
pathname = getModelPath().findFile('cmss12.egg')
if pathname:
dir(pathname.getDirname(), newDir = 'models')
# Some people are used to loading the models without models/ prefix.
file('models.prc', extract = True, text = "model-path $MODELS_ROOT/models")
class fmod(package):
# This package includes the FMod audio library. This is
# full-featured and robust, but it is closed-source and the
# licensing is cost-free only for non-commercial products.
config(display_name = "FMod audio library")
require('panda3d')
file('libp3fmod_audio.dll', required = True)
file('fmod.prc', extract = True, text = """
plugin-path $FMOD_ROOT
audio-library-name p3fmod_audio
""")
class openal(package):
# This package includes the OpenAL audio libraries. This is free
# in both senses, but there are some technical issues, especially
# on certain platforms.
config(display_name = "OpenAL audio library")
require('panda3d')
file('libp3openal_audio.dll', required = True)
file('OpenAL32.dll')
file('soft_oal.dll')
file('wrap_oal.dll')
file('openal.prc', extract = True, text = """
plugin-path $OPENAL_ROOT
audio-library-name p3openal_audio
""")
class audio(package):
# This package includes the best audio library for the given
# platform, assuming a non-commercial application.
require('panda3d')
# OpenAL seems to work pretty well universally now.
require('openal')
class ffmpeg(package):
# This package includes the ffmpeg libraries and integration.
# It's a bit big, and is restricted by patents and the restrictive
# GPLv3 license, which is why it is included as a separate package.
config(display_name = "FFMpeg multimedia decoder")
require('panda3d')
file('libp3ffmpeg.dll', required = True)
file('ffmpeg.prc', extract = True, text = """
plugin-path $FFMPEG_ROOT
load-audio-type * p3ffmpeg
load-video-type * p3ffmpeg
""")
class egg(package):
# This package contains the code for reading and operating on egg
# files and other model files. Since the Packager automatically
# converts egg files to bam files, this is not needed for most
# Panda3D applications.
config(display_name = "Panda3D egg loader")
require('panda3d')
module('panda3d.egg', required = True)
file('libpandaegg.dll', required = True)
file('libp3ptloader.dll', required = True)
file('egg.prc', extract = True, text = """
plugin-path $EGG_ROOT
load-file-type egg pandaegg
load-file-type p3ptloader
# These are excerpted from the default Confauto.prc file.
egg-object-type-portal <Scalar> portal { 1 }
egg-object-type-polylight <Scalar> polylight { 1 }
egg-object-type-seq24 <Switch> { 1 } <Scalar> fps { 24 }
egg-object-type-seq12 <Switch> { 1 } <Scalar> fps { 12 }
egg-object-type-indexed <Scalar> indexed { 1 }
egg-object-type-seq10 <Switch> { 1 } <Scalar> fps { 10 }
egg-object-type-seq8 <Switch> { 1 } <Scalar> fps { 8 }
egg-object-type-seq6 <Switch> { 1 } <Scalar> fps { 6 }
egg-object-type-seq4 <Switch> { 1 } <Scalar> fps { 4 }
egg-object-type-seq2 <Switch> { 1 } <Scalar> fps { 2 }
egg-object-type-binary <Scalar> alpha { binary }
egg-object-type-dual <Scalar> alpha { dual }
egg-object-type-glass <Scalar> alpha { blend_no_occlude }
egg-object-type-model <Model> { 1 }
egg-object-type-dcs <DCS> { 1 }
egg-object-type-notouch <DCS> { no_touch }
egg-object-type-barrier <Collide> { Polyset descend }
egg-object-type-sphere <Collide> { Sphere descend }
egg-object-type-invsphere <Collide> { InvSphere descend }
egg-object-type-tube <Collide> { Tube descend }
egg-object-type-trigger <Collide> { Polyset descend intangible }
egg-object-type-trigger-sphere <Collide> { Sphere descend intangible }
egg-object-type-floor <Collide> { Polyset descend level }
egg-object-type-dupefloor <Collide> { Polyset keep descend level }
egg-object-type-bubble <Collide> { Sphere keep descend }
egg-object-type-ghost <Scalar> collide-mask { 0 }
egg-object-type-glow <Scalar> blend { add }
egg-object-type-direct-widget <Scalar> collide-mask { 0x80000000 } <Collide> { Polyset descend }
""")
class ode(package):
# This package contains the code for the ODE integration.
# As not every application uses the ODE layers, and to cut down
# the download size, it is provided as separate package.
config(display_name = "Panda3D Open Dynamics Engine integration")
require('panda3d')
module('panda3d.ode', required = True)
file('libpandaode.dll', required = True)
class bullet(package):
# This package contains the code for the Bullet integration.
# As not every application uses the Bullet layers, and to cut down
# the download size, it is provided as separate package.
config(display_name = "Panda3D Bullet integration")
require('panda3d')
module('panda3d.bullet', required = True)
file('libpandabullet.dll', required = True)
class physx(package):
# This package contains the code for the NVIDIA PhysX integration.
# As not every application uses the NVIDIA PhysX layers, and to cut down
# the download size, it is provided as separate package.
config(display_name = "Panda3D PhysX integration")
require('panda3d')
module('panda3d.physx', required = True)
file('libpandaphysx.dll', required = True)
file('physxcudart_20.dll')
file('PhysXDevice.dll')
class ai(package):
# This package contains the PandAI library for pathfinding
# and seek/flee behaviors. As some games may not need this
# functionality, it is provided as separate package to save
# on package download size.
config(display_name = "Panda3D AI modules")
require('panda3d')
module('panda3d.ai', required = True)
file('libpandaai.dll', required = True)
class vision(package):
# This package contains the code for webcam support, augmented
# reality and computer vision. As many games will not need this
# functionality, it is provided as separate package to save
# on package download size.
config(display_name = "Panda3D vision modules")
require('panda3d')
module('panda3d.vision', required = True)
file('libp3vision.dll', required = True)
class rocket(package):
# This package contains the code for libRocket integration.
# As not every application uses libRocket GUI, and to cut down
# the download size, it is provided as separate package.
config(display_name = "Panda3D libRocket support")
require('panda3d')
file('rocket.py', extract = True, text = """
from _rocketcore import *
try:
from _rocketcontrols import *
except ImportError:
pass
""")
module('panda3d.rocket', '_rocketcore', required = True)
module('_rocketcontrols')
file('libp3rocket.dll', required = True)
class vrpn(package):
# This package contains the code for VRPN integration.
# Most applications don't use this, which is why it's separate.
config(display_name = "Panda3D VRPN support")
require('panda3d')
module('panda3d.vrpn', required = True)
file('libp3vrpn.dll', required = True)
class packp3d(p3d):
# This application is a command-line convenience for building a p3d
# application out of a directory hierarchy on disk. We build it here
# into its own p3d application, to allow end-users to easily build p3d
# applications using the appropriate version of Python and Panda for
# the targeted runtime.
config(display_name = "Panda3D Application Packer",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d', 'egg')
mainModule('direct.p3d.packp3d')
file('packp3d.prc', extract = True, text = "preload-textures false")
class ppackage(p3d):
# As above, a packaging utility. This is the fully-general ppackage
# utility, which reads pdef files (like this one!) and creates one or
# more packages or p3d applications.
config(display_name = "Panda3D General Package Utility",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d', 'egg')
mainModule('direct.p3d.ppackage')
file('ppackage.prc', extract = True, text = "preload-textures false")
class ppatcher(p3d):
# A handy utility to go along with ppackage. This builds
# patchfiles as needed in the directory structure created by
# ppackage.
config(display_name = "Panda3D Patch Maker",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d')
mainModule('direct.p3d.ppatcher')
class pmerge(p3d):
# Another handy utility to go along with ppackage. This
# merges multiple directory structures as created by
# ppackage into a single one.
config(display_name = "Panda3D Package Merger",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d')
mainModule('direct.p3d.pmerge')
class pdeploy(p3d):
# This utility can distribute a game in the form of
# a standalone executable or a graphical installer.
config(display_name = "Panda3D Deployment Tool",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d', 'morepy')
mainModule('direct.p3d.pdeploy')

View File

@ -1,359 +0,0 @@
#! /usr/bin/env python
usageText = """
This command will help you to distribute your Panda application,
consisting of a .p3d package, into a standalone executable, graphical
installer or an HTML webpage. It will attempt to create packages
for every platform, if possible.
Note that pdeploy requires an internet connection to run.
When used with the 'installer' option, it is strongly advisable
to specify most if not all of the descriptive information that can
be passed on the command-line.
Usage:
%(prog)s [opts] app.p3d standalone|installer|html
Modes:
standalone
A standalone executable will be created that embeds the given
p3d file. The resulting executable will require an
internet connection in order to run properly.
installer
In this mode, installable packages will be created for as many
platforms as possible. To create Windows installers on
non-Windows platforms, you need to have the "makensis" utility
on your system PATH environment variable.
html
An HTML webpage will be generated that can be used to view
the provided p3d file in a browser.
Options:
-n your_app
Short, lowercase name of the application or game. May only
contain alphanumeric characters, underscore or dash. This
name will also define the output file(s) of the process.
If omitted, the basename of the p3d file is used.
-N "Your Application"
Full name of the application or game. This is the
name that will be displayed to the end-user.
The 'display_name' config is used by default. If it
is missing, the short name is used.
-v version_number
This should define the version number of your application
or game. In some deploy modes, this argument is required.
This should only contain alphanumeric characters, dots and
dashes, as otherwise the result of the deployment may be
invalid on some platforms.
-o output_dir
Indicates the directory where the output will be stored.
Within this directory, subdirectories will be created
for every platform, unless -t is provided.
If omitted, the current working directory is assumed.
-t token=value
Defines a web token or parameter to pass to the application.
Use this to configure how the application will be run.
You can pass as many -t options as you need. Some examples of
useful token names are width, height, log_basename, auto_start,
hidden and console_environment.
-P platform
If this option is provided, it should specify a comma-
separated list of platforms that the p3d package will be
deployed for. If omitted, it will be built for all platforms.
This option may be specified multiple times.
Examples of valid platforms are win_i386, linux_amd64 and osx_ppc.
-c
If this option is provided, any -P options are ignored and
the p3d package is only deployed for the current platform.
Furthermore, no per-platform subdirectories will be created
inside the output dirctory.
-s
This option only has effect in 'installer' mode. If it is
provided, the resulting installers will be fully self-contained,
will not require an internet connection to run, and start up
much faster. Note that pdeploy will also take a very long time
to finish when -s is provided.
If it is omitted, pdeploy will finish much quicker, and the
resulting installers will be smaller, but they will require
an internet connection for the first run, and the load time
will be considerably longer.
-l "License Name"
Specifies the name of the software license that the game
or application is licensed under.
Only relevant when generating a graphical installer.
-L licensefile.txt
This should point to a file that contains the full text
describing the software license that the game or application
is licensed under.
Only relevant when generating a graphical installer.
-O
Specify this option when generating a graphical installer to omit
the default checkboxes for "run this program" and "install a
desktop shortcut" on completion.
-a com.your_company
Short identifier of the author of the application. The default
is "org.panda3d", but you will most likely want to change
it to your own name or that of your organization or company.
Only relevant when generating a graphical installer.
-A "Your Company"
Full name of the author of the application. The default is
determined from the GECOS information of the current user if
available; if not, your username is used.
Only relevant when generating a graphical installer.
-e "you@your_company.com"
E-mail address of the maintainer of the application. The default
is username@hostname.
Only relevant when generating a graphical installer.
-i "path/icon32.png" -i "path/icon48.png" -i "path/icon128.png"
This option should be repeated several times with different square
image sizes. These images will then be combined to form an icon
file that will be used to represent the installed application.
To support all platforms, it is recommended to supply images of
the sizes 16x16, 32x32, 48x48, 128x128, 256x256, and 512x512.
The larger icon sizes can safely be omitted if you cannot
provide images in that resolution.
It is recommended to use .png images for correct transparency.
If no images are provided, no icon will be generated.
Only relevant when generating a graphical installer.
-h
Display this help
"""
DEPLOY_MODES = ["standalone", "installer", "html"]
import sys
import os
import getopt
from direct.p3d.DeploymentTools import Standalone, Installer, Icon
from panda3d.core import Filename, PandaSystem
def usage(code, msg = ''):
if not msg:
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
sys.stderr.write(msg + '\n')
sys.exit(code)
shortname = ""
fullname = ""
version = ""
outputDir = Filename("./")
tokens = {}
platforms = []
currentPlatform = False
licensename = ""
licensefile = Filename()
authorid = ""
authorname = ""
authoremail = ""
iconFiles = []
includeRequires = False
omitDefaultCheckboxes = False
try:
opts, args = getopt.getopt(sys.argv[1:], 'n:N:v:o:t:P:csOl:L:a:A:e:i:h')
except getopt.error as msg:
usage(1, msg or 'Invalid option')
for opt, arg in opts:
if opt == '-n':
shortname = arg.strip()
elif opt == '-N':
fullname = arg.strip()
elif opt == '-v':
version = arg.strip()
elif opt == '-o':
outputDir = Filename.fromOsSpecific(arg)
elif opt == '-t':
token = arg.strip().split("=", 1)
tokens[token[0]] = token[1]
elif opt == '-P':
platforms.append(arg)
elif opt == '-c':
currentPlatform = True
elif opt == '-s':
includeRequires = True
elif opt == '-O':
omitDefaultCheckboxes = True
elif opt == '-l':
licensename = arg.strip()
elif opt == '-L':
licensefile = Filename.fromOsSpecific(arg)
elif opt == '-a':
authorid = arg.strip()
elif opt == '-A':
authorname = arg.strip()
elif opt == '-e':
authoremail = arg.strip()
elif opt == '-i':
iconFiles.append(Filename.fromOsSpecific(arg))
elif opt == '-h':
usage(0)
else:
msg = 'illegal option: ' + flag
print(msg)
sys.exit(1, msg)
if not args or len(args) != 2:
msg = 'Wrong number of arguments, received %s, expected 2' % (
len(args or []))
usage(1, msg)
appFilename = Filename.fromOsSpecific(args[0])
if appFilename.getExtension().lower() != 'p3d':
print('Application filename must end in ".p3d".')
sys.exit(1)
deploy_mode = args[1].lower()
if not appFilename.exists():
print('Application filename does not exist!')
sys.exit(1)
if shortname == '':
shortname = appFilename.getBasenameWoExtension()
if shortname.lower() != shortname or ' ' in shortname:
print('\nProvided short name should be lowercase, and may not contain spaces!\n')
if version == '' and deploy_mode == 'installer':
print('\nA version number is required in "installer" mode.\n')
sys.exit(1)
if not outputDir:
print('\nYou must name the output directory with the -o parameter.\n')
sys.exit(1)
if not outputDir.exists():
print('\nThe specified output directory does not exist!\n')
sys.exit(1)
elif not outputDir.isDirectory():
print('\nThe specified output directory is a file!\n')
sys.exit(1)
if deploy_mode == 'standalone':
s = Standalone(appFilename, tokens)
s.basename = shortname
if currentPlatform:
platform = PandaSystem.getPlatform()
if platform.startswith("win"):
s.build(Filename(outputDir, shortname + ".exe"), platform)
else:
s.build(Filename(outputDir, shortname), platform)
elif len(platforms) == 0:
s.buildAll(outputDir)
else:
for platform in platforms:
if platform.startswith("win"):
s.build(Filename(outputDir, platform + "/" + shortname + ".exe"), platform)
else:
s.build(Filename(outputDir, platform + "/" + shortname), platform)
elif deploy_mode == 'installer':
if includeRequires:
tokens["verify_contents"] = "never"
i = Installer(appFilename, shortname, fullname, version, tokens = tokens)
i.includeRequires = includeRequires
if omitDefaultCheckboxes:
i.offerRun = False
i.offerDesktopShortcut = False
i.licensename = licensename
i.licensefile = licensefile
if authorid:
i.authorid = authorid
if authorname:
i.authorname = authorname
if authoremail:
i.authoremail = authoremail
if not authorname or not authoremail or not authorid:
print("Using author \"%s\" <%s> with ID %s" % \
(i.authorname, i.authoremail, i.authorid))
# Add the supplied icon images
if len(iconFiles) > 0:
failed = False
i.icon = Icon()
for iconFile in iconFiles:
if not i.icon.addImage(iconFile):
print('\nFailed to add icon image "%s"!\n' % iconFile)
failed = True
if failed:
sys.exit(1)
# Now build for the requested platforms.
if currentPlatform:
platform = PandaSystem.getPlatform()
if platform.startswith("win"):
i.build(outputDir, platform)
else:
i.build(outputDir, platform)
elif len(platforms) == 0:
i.buildAll(outputDir)
else:
for platform in platforms:
output = Filename(outputDir, platform + "/")
output.makeDir()
i.build(output, platform)
del i
elif deploy_mode == 'html':
w, h = tokens.get("width", 800), tokens.get("height", 600)
if "data" not in tokens:
tokens["data"] = appFilename.getBasename()
print("Creating %s.html..." % shortname)
html = open(Filename(outputDir, shortname + ".html").toOsSpecific(), "w")
html.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
html.write("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
html.write(" <head>\n")
html.write(" <title>%s</title>\n" % fullname)
html.write(" <meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\" />\n")
if authorname:
html.write(" <meta name=\"Author\" content=\"%s\" />\n" % authorname.replace('"', '\\"'))
html.write(" </head>\n")
html.write(" <body>\n")
html.write(" <object")
for key, value in tokens.items():
html.write(" %s=\"%s\"" % (key, value.replace('"', '\\"')))
if "width" not in tokens:
html.write(" width=\"%s\"" % w)
if "height" not in tokens:
html.write(" height=\"%s\"" % h)
html.write(" type=\"application/x-panda3d\">")
html.write(" <object width=\"%s\" height=\"%s\" classid=\"CLSID:924B4927-D3BA-41EA-9F7E-8A89194AB3AC\">\n" % (w, h))
for key, value in tokens.items():
html.write(" <param name=\"%s\" value=\"%s\" />\n" % (key, value.replace('"', '\\"')))
html.write(" </object>\n")
html.write(" </object>\n")
html.write(" </body>\n")
html.write("</html>\n")
html.close()
else:
usage(1, 'Invalid deployment mode!')
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)

View File

@ -1,106 +0,0 @@
#! /usr/bin/env python
usageText = """
This script can be used to merge together the contents of two or more
separately-built stage directories, built independently via ppackage,
or via Packager.py. This script also verifies the hash, file size,
and timestamp values in the stage directory as it runs, so it can be
run on a single standalone directory just to perform this validation.
This script is actually a wrapper around Panda's PackageMerger.py.
Usage:
%(prog)s [opts] [inputdir1 .. inputdirN]
Parameters:
inputdir1 .. inputdirN
Specify the full path to the input directories you wish to merge.
These are the directories specified by -i on the previous
invocations of ppackage. The order is mostly unimportant.
Options:
-i install_dir
The full path to the final install directory. This may also
contain some pre-existing contents; if so, it is merged with all
of the input directories as well. The contents of this directory
are checked for self-consistency with regards to hashes and
timestamps.
-p packageName[,packageName...]
Specifies one or more particular packages by name that are to be
included from the input directories. Any packages not in this
list are left unchanged in the install directory, even if there
is a newer version in one of the input directories. If no
packages are named, all packages are involved. This option may
be repeated.
-h
Display this help
"""
import sys
import getopt
import os
from direct.p3d import PackageMerger
from panda3d.core import Filename
def usage(code, msg = ''):
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
sys.stderr.write(msg + '\n')
sys.exit(code)
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:p:h')
except getopt.error as msg:
usage(1, msg)
installDir = None
packageNames = []
for opt, arg in opts:
if opt == '-i':
installDir = Filename.fromOsSpecific(arg)
elif opt == '-p':
packageNames += arg.split(',')
elif opt == '-h':
usage(0)
else:
print('illegal option: ' + arg)
sys.exit(1)
if not packageNames:
# No package names means allow all packages.
packageNames = None
inputDirs = []
for arg in args:
inputDirs.append(Filename.fromOsSpecific(arg))
# It's now legal to have no input files if you only want to verify
# timestamps and hashes.
## if not inputDirs:
## print "no input directories specified."
## sys.exit(1)
try:
pm = PackageMerger.PackageMerger(installDir)
for dir in inputDirs:
pm.merge(dir, packageNames = packageNames)
pm.close()
except PackageMerger.PackageMergerError:
# Just print the error message and exit gracefully.
inst = sys.exc_info()[1]
print(inst.args[0])
sys.exit(1)
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)

View File

@ -1,267 +0,0 @@
#! /usr/bin/env python
usageText = """
This script can be used to produce a Panda3D downloadable "package",
which may contain arbitrary files--for instance, Python code, bam
files, and/or compiled DLL's--and which may be downloaded by
application code to extend an application at runtime.
In addition to packages, this script can also be used to build
standalone p3d applications, that is, packaged Python code in a p3d
file, for execution by the Panda3D plugin or runtime. (But also see
packp3d, which is designed to be a simpler interface for building
applications.)
This command will build p3d files that reference Panda3D %(version)s,
from host %(host)s .
This script is actually a wrapper around Panda's Packager.py, which
can be invoked directly by Python code that requires a programmatic
interface to building packages.
Usage:
%(prog)s [opts] package.pdef [packageName1 .. packageNameN]
Parameters:
package.pdef
The config file that describes the contents of the package file(s)
to be built, in excruciating detail. See the Panda3D manual for
the syntax of this file.
packageName1 .. packageNameN
Specify the names of the package(s) you wish to build out of the
package.pdef file. This allows you to build only a subset of the
packages defined in this file. If you omit these parameters, all
packages are built, and packages that cannot be built are silently
ignored.
Options:
-i install_dir
The full path to a local directory to copy the
ready-to-be-published files into. This directory structure may
contain multiple different packages from multiple different
invocations of this script. It is the user's responsibility to
copy this directory structure to a server, which will have the
URL specified by the packager.setHost() call appearing within the
pdef file.
-p
Automatically build patches against previous versions after
generating the results. Patches are difference files that users
can download when updating a package, in lieu of redownloading
the whole package; this happens automatically if patches are
present. You should generally build patches when you are
committing to a final, public-facing release. Patches are
usually a good idea, but generating a patch for each internal
test build may needlessly generate a lot of small, inefficient
patch files instead of a few larger ones. You can also generate
patches after the fact, by running ppatcher on the install
directory.
-s search_dir
Additional directories to search for previously-built packages.
This option may be repeated as necessary. These directories may
also be specified with the pdef-path Config.prc variable.
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
Signs any resulting p3d file(s) with the indicated certificate.
You may specify the signing certificate, the optional
authorization chain, and the private key in three different
files, or they may all be combined in the first file. If the
private key is encrypted, the password will be required to
decrypt it.
-D
Sets the allow_python_dev flag in any applications built with
this command. This enables additional runtime debug operations,
particularly the -i option to the panda3d command, which enables
a live Python prompt within the application's environment.
Setting this flag may be useful to develop an application
initially, but should not be set on an application intended for
deployment.
-N
If this option is set, Packager will not try to compile any Python
files to .pyc or .pyo, instead storing the original source files.
-u
On the Mac OSX platform, this means that Panda was built with
universal binaries, and the package should be built that way as
well (that is, a version of the the package should be created for
each supported architecture). On other platforms, this option
does nothing. This is therefore safe to apply in all cases, if
you wish to take advantage of universal binaries. This is
equivalent to "-P osx_i386 -P osx_amd64" on Mac platforms.
-P platform
Specify the platform to masquerade as. The default is whatever
platform Panda has been built for. You can use this on Mac OSX
in order to build packages for an alternate architecture if you
have built Panda with universal binaries; you may repeat this
option for each architecture you wish to support. For other
platforms, it is probably a mistake to set this. However, see
the option -u.
-R sysroot
Specify the sysroot that these files were compiled against. This
will shadow the system shared libraries, so that alternate
versions are used instead of the system versions. If any program
references a library, say /usr/lib/libfoo.so, and
/sysroot/usr/lib/libfoo.so exists instead, that file will be used
instead of the system library. This is particularly useful for
cross-compilation. At the moment, this is supported only on OSX.
-H
Treats a packager.setHost() call in the pdef file as if it were
merely a call to packager.addHost(). This allows producing a
package for an alternate host than its normally configured host,
which is sometimes useful in development.
-a suffix
Appends the given suffix to the p3d filename, before the extension.
This is useful when the same packages are compiled several times
but using different settings, and you want to mark those packages
as such. This only applies to .p3d packages, not to other types
of packages!
-v
Emit a warning for any file not recognized by the dir() command
(indicating there may be a need for addExtensions(...)).
-h
Display this help
"""
import sys
import getopt
import os
from direct.p3d import Packager
from panda3d.core import *
def usage(code, msg = ''):
sys.stderr.write(usageText % {
'version' : PandaSystem.getPackageVersionString(),
'host' : PandaSystem.getPackageHostUrl(),
'prog' : os.path.split(sys.argv[0])[1]
})
sys.stderr.write(msg + '\n')
sys.exit(code)
installDir = None
buildPatches = False
installSearch = []
signParams = []
allowPythonDev = False
storePythonSource = False
universalBinaries = False
systemRoot = None
ignoreSetHost = False
verbosePrint = False
p3dSuffix = ''
platforms = []
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DNuP:R:Ha:hv')
except getopt.error as msg:
usage(1, msg)
for opt, arg in opts:
if opt == '-i':
installDir = Filename.fromOsSpecific(arg)
elif opt == '-p':
buildPatches = True
elif opt == '-s':
installSearch.append(Filename.fromOsSpecific(arg))
elif opt == '-S':
tokens = arg.split(',')
while len(tokens) < 4:
tokens.append('')
certificate, chain, pkey, password = tokens[:4]
signParams.append((Filename.fromOsSpecific(certificate),
Filename.fromOsSpecific(chain),
Filename.fromOsSpecific(pkey),
Filename.fromOsSpecific(password)))
elif opt == '-D':
allowPythonDev = True
elif opt == '-N':
storePythonSource = True
elif opt == '-u':
universalBinaries = True
elif opt == '-P':
platforms.append(arg)
elif opt == '-R':
systemRoot = arg
elif opt == '-H':
ignoreSetHost = True
elif opt == '-a':
p3dSuffix = arg
elif opt == '-v':
verbosePrint = True
elif opt == '-h':
usage(0)
else:
print('illegal option: ' + arg)
sys.exit(1)
if not args:
usage(0)
packageDef = Filename.fromOsSpecific(args[0])
packageNames = None
if len(args) > 1:
packageNames = args[1:]
# Add the directory containing the pdef file itself to sys.path, to
# help the Packager locate modules where a pathname isn't specified.
dirname = packageDef.getDirname()
if dirname:
sys.path.append(Filename(dirname).toOsSpecific())
else:
sys.path.append('.')
if universalBinaries:
if platforms:
print('\nYou may not specify both -u and -P.\n')
sys.exit(1)
if PandaSystem.getPlatform().startswith('osx_'):
platforms = ['osx_i386', 'osx_amd64']
if not platforms:
platforms = [PandaSystem.getPlatform()]
for platform in platforms:
packager = Packager.Packager(platform = platform)
packager.installDir = installDir
packager.installSearch = installSearch + packager.installSearch
if installDir is not None:
packager.installSearch = [installDir] + packager.installSearch
packager.signParams = signParams
packager.allowPythonDev = allowPythonDev
packager.storePythonSource = storePythonSource
packager.systemRoot = systemRoot
packager.ignoreSetHost = ignoreSetHost
packager.verbosePrint = verbosePrint
packager.p3dSuffix = p3dSuffix
try:
packager.setup()
packages = packager.readPackageDef(packageDef, packageNames = packageNames)
packager.close()
if buildPatches:
packager.buildPatches(packages)
except Packager.PackagerError:
# Just print the error message and exit gracefully.
inst = sys.exc_info()[1]
print(inst.args[0])
sys.exit(1)
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)

View File

@ -1,101 +0,0 @@
#! /usr/bin/env python
usageText = """
This script generates the patches required to support incremental
download of Panda3D packages. It can be run as a post-process on a
directory hierarchy created by ppackage; it will examine the directory
hierarchy, and create any patches that appear to be missing.
You may run ppackage on the same directory hierarchy as many times as
you like, without creating patches. You may then download and test
the resulting files--users connecting to the tree without fresh
patches will be forced to download the entire file, instead of making
an incremental download, but the entire process will work otherwise.
When you are satisfied that all of the files are ready to be released,
you may run ppackage on the directory hierarchy to generate the
required patches.
Generating the patches just before final release is a good idea to
limit the number of trivially small patches that are created. Each
time this script is run, a patch is created from the previous version,
and these patches daisy-chain together to define a complete update
sequence. If you run this script on internal releases, you will
generate a long chain of small patches that your users must download;
this is pointless if there is no possibility of anyone having
downloaded one of the intervening versions.
You can also generate patches with the -p option to ppackage, but that
only generates patches for the specific packages built by that
invocation of ppackage. If you use the ppatcher script instead, it
will generate patches for all packages (or the set of packages that
you name specifically).
This script is actually a wrapper around Panda's PatchMaker.py.
Usage:
%(prog)s [opts] [packageName1 .. packageNameN]
Parameters:
packageName1 .. packageNameN
Specify the names of the package(s) you wish to generate patches
for. This allows you to build patches for only a subset of the
packages found in the tree. If you omit these parameters, patches
are built for all packages that require them.
Options:
-i install_dir
The full path to the install directory. This should be the same
directory named by the -i parameter to ppackage.
-h
Display this help
"""
import sys
import getopt
import os
from direct.p3d.PatchMaker import PatchMaker
from panda3d.core import Filename
def usage(code, msg = ''):
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
sys.stderr.write(msg + '\n')
sys.exit(code)
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:h')
except getopt.error as msg:
usage(1, msg)
installDir = None
for opt, arg in opts:
if opt == '-i':
installDir = Filename.fromOsSpecific(arg)
elif opt == '-h':
usage(0)
else:
print('illegal option: ' + arg)
sys.exit(1)
packageNames = args
if not installDir:
installDir = Filename('install')
if not packageNames:
# "None" means all packages.
packageNames = None
pm = PatchMaker(installDir)
pm.buildPatches(packageNames = packageNames)
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)

View File

@ -1,79 +0,0 @@
#! /usr/bin/env python
"""
This tool will invoke the AppRunner to execute a packaged p3d
application. It requires that that the current Panda3D and Python
versions match the version expected by the application.
Normally, you do not need to use this tool; instead, use the provided
standalone panda3d executable to invoke any p3d application. Using
panda3d will guarantee that the correct versions of Panda3D and Python
are used to run the application. However, there may be occasions when
it is useful to use this tool to run the application with the current
build instead of with its advertised version requirements.
Usage:
runp3d.py app.p3d [args]
The command-line arguments following the application name are passed
into the application unchanged.
See pack3d.p3d for an application that generates these p3d files.
"""
import sys
import getopt
from .AppRunner import AppRunner, ArgumentError
from direct.task.TaskManagerGlobal import taskMgr
from panda3d.core import Filename
def parseSysArgs():
""" Handles sys.argv, if there are any local arguments, and
returns a new argv suitable for passing into the
application. """
# We prefix a "+" sign, following the GNU convention, to tell
# getopt not to parse options following the first non-option
# parameter.
opts, args = getopt.getopt(sys.argv[1:], '+h')
for option, value in opts:
if option == '-h':
print(__doc__)
sys.exit(1)
if not args or not args[0]:
raise ArgumentError("No Panda app specified. Use:\nrunp3d.py app.p3d")
arg0 = args[0]
p3dFilename = Filename.fromOsSpecific(arg0)
if p3dFilename.exists():
p3dFilename.makeAbsolute()
arg0 = p3dFilename.toOsSpecific()
return [arg0] + args[1:]
def runPackedApp(pathname):
runner = AppRunner()
runner.gotWindow = True
try:
runner.setP3DFilename(pathname, tokens = [], argv = [],
instanceId = 0, interactiveConsole = False)
except ArgumentError as e:
print(e.args[0])
sys.exit(1)
if __name__ == '__main__':
runner = AppRunner()
runner.gotWindow = True
try:
argv = parseSysArgs()
runner.setP3DFilename(argv[0], tokens = [], argv = argv,
instanceId = 0, interactiveConsole = False)
except ArgumentError as e:
print(e.args[0])
sys.exit(1)
taskMgr.run()

View File

@ -1,167 +0,0 @@
# This file defines a number of standard "packages" that correspond to
# a Panda3D distribution. These packages are built by passing this
# file to the ppackage utility, either as a packaged application, or
# as the module direct.p3d.ppackage.
# The packages in this file define the thirdparty
# packages that could be used by a .p3d archive.
# When needed, these packages are downloaded by the core API, from the
# host URL specified in a given p3d file, and not from any hardcoded
# URL. Thus, any custom version of Panda3D may be hosted on any
# server in the world, and any version of the plugin can download it.
# Also see panda3d.pdef.
from panda3d.core import Filename
import sys
class wx(package):
config(display_name = "wxPython GUI Toolkit")
#config(gui_app = True)
require('panda3d')
module('direct.showbase.WxGlobal')
module('direct.wxwidgets')
module('direct.wxwidgets.*')
module('wx', required = True)
module('wx.*')
class tk(package):
config(display_name = "Tk GUI Toolkit")
#config(gui_app = True)
require('panda3d')
if sys.version_info >= (3, 0):
module('tkinter', '_tkinter', required = True)
module('tkinter.colorchooser', 'tkinter.commondialog',
'tkinter.constants', 'tkinter.dialog', 'tkinter.dnd',
'tkinter.filedialog', 'tkinter.font', 'tkinter.messagebox',
'tkinter.scrolledtext', 'tkinter.simpledialog', 'tkinter.tix',
'tkinter.ttk')
else:
module('Tkinter', '_tkinter', required = True)
module('Tkconstants', 'Tkdnd', 'Tix', 'ScrolledText', 'turtle',
'tkColorChooser', 'tkCommonDialog', 'tkFileDialog',
'tkFont', 'tkMessageBox', 'tkSimpleDialog')
module('direct.showbase.TkGlobal',
'direct.tkpanels',
'direct.tkwidgets')
try:
if sys.version_info >= (3, 0):
from tkinter import Tcl
else:
from Tkinter import Tcl
tcl = Tcl()
dir = Filename.fromOsSpecific(tcl.eval("info library"))
ver = tcl.eval("info tclversion")
file(Filename(dir, "*.tcl"), newDir = 'lib/tcl' + ver, extract = True)
except ImportError:
pass
try:
import Pmw
except ImportError:
Pmw = None
if Pmw is not None:
Pmw = sys.modules['_Pmw']
sys.modules['Pmw'] = Pmw
sys.modules['Pmw'].__name__ = 'Pmw'
module('Pmw', 'Pmw.Pmw_1_3.lib.*')
path = Filename.fromOsSpecific(Pmw.__path__[0])
file(Filename(path, 'Pmw_1_3/lib/Pmw.def'), newDir = 'Pmw/Pmw_1_3/lib')
class sqlite(package):
config(display_name = "SQLite Interface")
require('panda3d')
module('sqlite', 'sqlite3')
class numpy(package):
config(display_name = "NumPy")
require('panda3d')
module('numpy', required = True)
class pygame(package):
config(display_name = "PyGame")
require('panda3d', 'numpy')
module('pygame', required = True)
module('pygame.*', 'pygame._view')
class twisted(package):
config(display_name = "Twisted")
require('panda3d')
module('twisted', 'twisted._version', required = True)
module('twisted.application', 'twisted.conch', 'twisted.cred',
'twisted.enterprise', 'twisted.internet', 'twisted.lore',
'twisted.mail', 'twisted.manhole', 'twisted.names',
'twisted.news', 'twisted.pair', 'twisted.persisted',
'twisted.plugin', 'twisted.plugins', 'twisted.python',
'twisted.runner', 'twisted.scripts', 'twisted.spread',
'twisted.tap', 'twisted.trial', 'twisted.vfs',
'twisted.web', 'twisted.web2', 'twisted.words')
module('twisted.*', 'twisted.*.*')
class pil(package):
config(display_name = "Python Imaging Library")
require('panda3d')
module('Image', required = True)
module('PIL', 'ArgImagePlugin', 'BdfFontFile',
'BmpImagePlugin', 'BufrStubImagePlugin', 'ContainerIO',
'CurImagePlugin', 'DcxImagePlugin', 'EpsImagePlugin',
'ExifTags', 'FitsStubImagePlugin', 'FliImagePlugin',
'FontFile', 'FpxImagePlugin', 'GbrImagePlugin', 'GdImageFile',
'GifImagePlugin', 'GimpGradientFile', 'GimpPaletteFile',
'GribStubImagePlugin', 'Hdf5StubImagePlugin',
'IcnsImagePlugin', 'IcoImagePlugin', 'ImImagePlugin',
'ImageChops', 'ImageCms', 'ImageColor', 'ImageDraw',
'ImageDraw2', 'ImageEnhance', 'ImageFile', 'ImageFileIO',
'ImageFilter', 'ImageFont', 'ImageGL', 'ImageGrab',
'ImageMath', 'ImageMode', 'ImageOps', 'ImagePalette',
'ImagePath', 'ImageQt', 'ImageSequence', 'ImageShow',
'ImageStat', 'ImageTk', 'ImageTransform', 'ImageWin',
'ImtImagePlugin', 'IptcImagePlugin', 'JpegImagePlugin',
'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin',
'MspImagePlugin', 'OleFileIO', 'PSDraw', 'PaletteFile',
'PalmImagePlugin', 'PcdImagePlugin', 'PcfFontFile',
'PcxImagePlugin', 'PdfImagePlugin', 'PixarImagePlugin',
'PngImagePlugin', 'PpmImagePlugin', 'PsdImagePlugin',
'SgiImagePlugin', 'SpiderImagePlugin', 'SunImagePlugin',
'TarIO', 'TgaImagePlugin', 'TiffImagePlugin', 'TiffTags',
'WalImageFile', 'WmfImagePlugin', 'XVThumbImagePlugin',
'XbmImagePlugin', 'XpmImagePlugin', '_imaging',
'_imagingmath', '_imagingtk')
class pyopengl(package):
config(display_name = "PyOpenGL")
require('panda3d', 'numpy')
module('OpenGL', 'OpenGL.GL', required = True)
module('OpenGL.GLU', 'OpenGL.GLUT', 'OpenGL.GLE', 'OpenGL.GLX')
class httplib2(package):
config(display_name = "httplib2", platform_specific = False)
require('panda3d')
module('httplib2', required = True)
class box2d(package):
config(display_name = "Box2D")
require('panda3d')
module('Box2D', required = True)
class pyglet(package):
config(display_name = "pyglet", platform_specific = False)
require('panda3d', 'morepy')
module('pyglet', required = True)

View File

@ -7,6 +7,12 @@ from . import Particles
from . import ForceGroup
from direct.directnotify import DirectNotifyGlobal
import sys
if sys.version_info < (3, 0):
FileNotFoundError = IOError
class ParticleEffect(NodePath):
notify = DirectNotifyGlobal.directNotify.newCategory('ParticleEffect')
@ -163,49 +169,51 @@ class ParticleEffect(NodePath):
def saveConfig(self, filename):
filename = Filename(filename)
f = open(filename.toOsSpecific(), 'wb')
# Add a blank line
f.write('\n')
with open(filename.toOsSpecific(), 'w') as f:
# Add a blank line
f.write('\n')
# Make sure we start with a clean slate
f.write('self.reset()\n')
# Make sure we start with a clean slate
f.write('self.reset()\n')
pos = self.getPos()
hpr = self.getHpr()
scale = self.getScale()
f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
(pos[0], pos[1], pos[2]))
f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
(hpr[0], hpr[1], hpr[2]))
f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
(scale[0], scale[1], scale[2]))
pos = self.getPos()
hpr = self.getHpr()
scale = self.getScale()
f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
(pos[0], pos[1], pos[2]))
f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
(hpr[0], hpr[1], hpr[2]))
f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
(scale[0], scale[1], scale[2]))
# Save all the particles to file
num = 0
for p in list(self.particlesDict.values()):
target = 'p%d' % num
num = num + 1
f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
p.printParams(f, target)
f.write('self.addParticles(%s)\n' % target)
# Save all the particles to file
num = 0
for p in list(self.particlesDict.values()):
target = 'p%d' % num
num = num + 1
f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
p.printParams(f, target)
f.write('self.addParticles(%s)\n' % target)
# Save all the forces to file
num = 0
for fg in list(self.forceGroupDict.values()):
target = 'f%d' % num
num = num + 1
f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
fg.getName())
fg.printParams(f, target)
f.write('self.addForceGroup(%s)\n' % target)
# Close the file
f.close()
# Save all the forces to file
num = 0
for fg in list(self.forceGroupDict.values()):
target = 'f%d' % num
num = num + 1
f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
fg.getName())
fg.printParams(f, target)
f.write('self.addForceGroup(%s)\n' % target)
def loadConfig(self, filename):
data = vfs.readFile(filename, 1)
data = data.replace(b'\r', b'')
fn = Filename(filename)
vfs = VirtualFileSystem.getGlobalPtr()
try:
if not vfs.resolveFilename(fn, getModelPath().value) and not fn.isRegularFile():
raise FileNotFoundError("could not find particle file: %s" % (filename))
data = vfs.readFile(fn, True)
data = data.replace(b'\r', b'')
exec(data)
except:
self.notify.warning('loadConfig: failed to load particle file: '+ repr(filename))

View File

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

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