314 lines
9.3 KiB
C++
314 lines
9.3 KiB
C++
/**
|
|
* 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 nodePath_ext.cxx
|
|
* @author rdb
|
|
* @date 2013-12-09
|
|
*/
|
|
|
|
#include "nodePath_ext.h"
|
|
#include "typedWritable_ext.h"
|
|
#include "shaderInput_ext.h"
|
|
#include "shaderAttrib.h"
|
|
|
|
#ifdef HAVE_PYTHON
|
|
|
|
#ifndef CPPPARSER
|
|
extern struct Dtool_PyTypedObject Dtool_BamWriter;
|
|
extern struct Dtool_PyTypedObject Dtool_BamReader;
|
|
#ifdef STDFLOAT_DOUBLE
|
|
extern struct Dtool_PyTypedObject Dtool_LPoint3d;
|
|
#else
|
|
extern struct Dtool_PyTypedObject Dtool_LPoint3f;
|
|
#endif
|
|
extern struct Dtool_PyTypedObject Dtool_NodePath;
|
|
#endif // CPPPARSER
|
|
|
|
/**
|
|
* A special Python method that is invoked by copy.copy(node). Unlike the
|
|
* NodePath copy constructor, this makes a duplicate copy of the underlying
|
|
* PandaNode (but shares children, instead of copying them or omitting them).
|
|
*/
|
|
NodePath Extension<NodePath>::
|
|
__copy__() const {
|
|
if (_this->is_empty()) {
|
|
// Invoke the copy constructor if we have no node.
|
|
return *_this;
|
|
}
|
|
|
|
// If we do have a node, duplicate it, and wrap it in a new NodePath.
|
|
return NodePath(invoke_extension(_this->node()).__copy__());
|
|
}
|
|
|
|
/**
|
|
* A special Python method that is invoked by copy.deepcopy(np). This calls
|
|
* copy_to() unless the NodePath is already present in the provided
|
|
* dictionary.
|
|
*/
|
|
PyObject *Extension<NodePath>::
|
|
__deepcopy__(PyObject *self, PyObject *memo) const {
|
|
extern struct Dtool_PyTypedObject Dtool_NodePath;
|
|
|
|
// Borrowed reference.
|
|
PyObject *dupe = PyDict_GetItem(memo, self);
|
|
if (dupe != NULL) {
|
|
// Already in the memo dictionary.
|
|
Py_INCREF(dupe);
|
|
return dupe;
|
|
}
|
|
|
|
NodePath *np_dupe;
|
|
if (_this->is_empty()) {
|
|
np_dupe = new NodePath(*_this);
|
|
} else {
|
|
np_dupe = new NodePath(_this->copy_to(NodePath()));
|
|
}
|
|
|
|
dupe = DTool_CreatePyInstance((void *)np_dupe, Dtool_NodePath,
|
|
true, false);
|
|
if (PyDict_SetItem(memo, self, dupe) != 0) {
|
|
Py_DECREF(dupe);
|
|
return NULL;
|
|
}
|
|
|
|
return dupe;
|
|
}
|
|
|
|
/**
|
|
* This special Python method is implement to provide support for the pickle
|
|
* module.
|
|
*
|
|
* This hooks into the native pickle and cPickle modules, but it cannot
|
|
* properly handle self-referential BAM objects.
|
|
*/
|
|
PyObject *Extension<NodePath>::
|
|
__reduce__(PyObject *self) const {
|
|
return __reduce_persist__(self, NULL);
|
|
}
|
|
|
|
/**
|
|
* This special Python method is implement to provide support for the pickle
|
|
* module.
|
|
*
|
|
* This is similar to __reduce__, but it provides additional support for the
|
|
* missing persistent-state object needed to properly support self-referential
|
|
* BAM objects written to the pickle stream. This hooks into the pickle and
|
|
* cPickle modules implemented in direct/src/stdpy.
|
|
*/
|
|
PyObject *Extension<NodePath>::
|
|
__reduce_persist__(PyObject *self, PyObject *pickler) const {
|
|
// We should return at least a 2-tuple, (Class, (args)): the necessary class
|
|
// object whose constructor we should call (e.g. this), and the arguments
|
|
// necessary to reconstruct this object.
|
|
|
|
BamWriter *writer = NULL;
|
|
if (pickler != NULL) {
|
|
PyObject *py_writer = PyObject_GetAttrString(pickler, "bamWriter");
|
|
if (py_writer == NULL) {
|
|
// It's OK if there's no bamWriter.
|
|
PyErr_Clear();
|
|
} else {
|
|
DTOOL_Call_ExtractThisPointerForType(py_writer, &Dtool_BamWriter, (void **)&writer);
|
|
Py_DECREF(py_writer);
|
|
}
|
|
}
|
|
|
|
// We have a non-empty NodePath.
|
|
|
|
string bam_stream;
|
|
if (!_this->encode_to_bam_stream(bam_stream, writer)) {
|
|
ostringstream stream;
|
|
stream << "Could not bamify " << _this;
|
|
string message = stream.str();
|
|
PyErr_SetString(PyExc_TypeError, message.c_str());
|
|
return NULL;
|
|
}
|
|
|
|
// Start by getting this class object.
|
|
PyObject *this_class = PyObject_Type(self);
|
|
if (this_class == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
PyObject *func;
|
|
if (writer != NULL) {
|
|
// The modified pickle support: call the "persistent" version of this
|
|
// function, which receives the unpickler itself as an additional
|
|
// parameter.
|
|
func = Extension<TypedWritable>::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream_persist");
|
|
if (func == NULL) {
|
|
PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream_persist()");
|
|
Py_DECREF(this_class);
|
|
return NULL;
|
|
}
|
|
|
|
} else {
|
|
// The traditional pickle support: call the non-persistent version of this
|
|
// function.
|
|
|
|
func = Extension<TypedWritable>::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream");
|
|
if (func == NULL) {
|
|
PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream()");
|
|
Py_DECREF(this_class);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *result = Py_BuildValue("(O(y#))", func, bam_stream.data(), (Py_ssize_t) bam_stream.size());
|
|
#else
|
|
PyObject *result = Py_BuildValue("(O(s#))", func, bam_stream.data(), (Py_ssize_t) bam_stream.size());
|
|
#endif
|
|
Py_DECREF(func);
|
|
Py_DECREF(this_class);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the lowest ancestor of this node that contains a tag definition
|
|
* with the indicated key, if any, or an empty NodePath if no ancestor of this
|
|
* node contains this tag definition. See set_python_tag().
|
|
*/
|
|
NodePath Extension<NodePath>::
|
|
find_net_python_tag(PyObject *key) const {
|
|
if (_this->is_empty()) {
|
|
return NodePath::not_found();
|
|
}
|
|
if (has_python_tag(key)) {
|
|
return *_this;
|
|
}
|
|
NodePath parent = _this->get_parent();
|
|
return invoke_extension(&parent).find_net_python_tag(key);
|
|
}
|
|
|
|
/**
|
|
* This wrapper is defined as a global function to suit pickle's needs.
|
|
*/
|
|
NodePath
|
|
py_decode_NodePath_from_bam_stream(const string &data) {
|
|
return py_decode_NodePath_from_bam_stream_persist(NULL, data);
|
|
}
|
|
|
|
/**
|
|
* This wrapper is defined as a global function to suit pickle's needs.
|
|
*/
|
|
NodePath
|
|
py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, const string &data) {
|
|
BamReader *reader = NULL;
|
|
if (unpickler != NULL) {
|
|
PyObject *py_reader = PyObject_GetAttrString(unpickler, "bamReader");
|
|
if (py_reader == NULL) {
|
|
// It's OK if there's no bamReader.
|
|
PyErr_Clear();
|
|
} else {
|
|
DTOOL_Call_ExtractThisPointerForType(py_reader, &Dtool_BamReader, (void **)&reader);
|
|
Py_DECREF(py_reader);
|
|
}
|
|
}
|
|
|
|
return NodePath::decode_from_bam_stream(data, reader);
|
|
}
|
|
|
|
/**
|
|
* Sets a single shader input.
|
|
*/
|
|
void Extension<NodePath>::
|
|
set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
|
|
PT(PandaNode) node = _this->node();
|
|
CPT(RenderAttrib) prev_attrib = node->get_attrib(ShaderAttrib::get_class_slot());
|
|
PT(ShaderAttrib) attrib;
|
|
if (prev_attrib == nullptr) {
|
|
attrib = new ShaderAttrib();
|
|
} else {
|
|
attrib = new ShaderAttrib(*(const ShaderAttrib *)prev_attrib.p());
|
|
}
|
|
|
|
ShaderInput &input = attrib->_inputs[name];
|
|
invoke_extension(&input).__init__(move(name), value);
|
|
|
|
if (!_PyErr_OCCURRED()) {
|
|
node->set_attrib(ShaderAttrib::return_new(attrib));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets multiple shader inputs at the same time. This can be significantly
|
|
* more efficient if many inputs need to be set at the same time.
|
|
*/
|
|
void Extension<NodePath>::
|
|
set_shader_inputs(PyObject *args, PyObject *kwargs) {
|
|
if (PyObject_Size(args) > 0) {
|
|
Dtool_Raise_TypeError("NodePath.set_shader_inputs takes only keyword arguments");
|
|
return;
|
|
}
|
|
|
|
PT(PandaNode) node = _this->node();
|
|
CPT(RenderAttrib) prev_attrib = node->get_attrib(ShaderAttrib::get_class_slot());
|
|
PT(ShaderAttrib) attrib;
|
|
if (prev_attrib == nullptr) {
|
|
attrib = new ShaderAttrib();
|
|
} else {
|
|
attrib = new ShaderAttrib(*(const ShaderAttrib *)prev_attrib.p());
|
|
}
|
|
|
|
PyObject *key, *value;
|
|
Py_ssize_t pos = 0;
|
|
|
|
while (PyDict_Next(kwargs, &pos, &key, &value)) {
|
|
char *buffer;
|
|
Py_ssize_t length;
|
|
#if PY_MAJOR_VERSION >= 3
|
|
buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length);
|
|
if (buffer == nullptr) {
|
|
#else
|
|
if (PyString_AsStringAndSize(key, &buffer, &length) == -1) {
|
|
#endif
|
|
Dtool_Raise_TypeError("NodePath.set_shader_inputs accepts only string keywords");
|
|
return;
|
|
}
|
|
|
|
CPT_InternalName name(string(buffer, length));
|
|
ShaderInput &input = attrib->_inputs[name];
|
|
invoke_extension(&input).__init__(move(name), value);
|
|
}
|
|
|
|
if (!_PyErr_OCCURRED()) {
|
|
node->set_attrib(ShaderAttrib::return_new(attrib));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the tight bounds as a 2-tuple of LPoint3 objects. This is a
|
|
* convenience function for Python users, among which the use of
|
|
* calc_tight_bounds may be confusing.
|
|
*
|
|
* Returns None if calc_tight_bounds returned false.
|
|
*/
|
|
PyObject *Extension<NodePath>::
|
|
get_tight_bounds(const NodePath &other) const {
|
|
LPoint3 *min_point = new LPoint3;
|
|
LPoint3 *max_point = new LPoint3;
|
|
|
|
if (_this->calc_tight_bounds(*min_point, *max_point, other)) {
|
|
#ifdef STDFLOAT_DOUBLE
|
|
PyObject *min_inst = DTool_CreatePyInstance((void*) min_point, Dtool_LPoint3d, true, false);
|
|
PyObject *max_inst = DTool_CreatePyInstance((void*) max_point, Dtool_LPoint3d, true, false);
|
|
#else
|
|
PyObject *min_inst = DTool_CreatePyInstance((void*) min_point, Dtool_LPoint3f, true, false);
|
|
PyObject *max_inst = DTool_CreatePyInstance((void*) max_point, Dtool_LPoint3f, true, false);
|
|
#endif
|
|
return Py_BuildValue("NN", min_inst, max_inst);
|
|
|
|
} else {
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
}
|
|
|
|
#endif // HAVE_PYTHON
|