/** * 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" #include "collisionNode.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; extern struct Dtool_PyTypedObject Dtool_PandaNode; #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:: __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:: __deepcopy__(PyObject *self, PyObject *memo) const { extern struct Dtool_PyTypedObject Dtool_NodePath; // Borrowed reference. PyObject *dupe; if (PyDict_GetItemRef(memo, self, &dupe) > 0) { // Already in the memo dictionary. 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 nullptr; } 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:: __reduce__(PyObject *self) const { return __reduce_persist__(self, nullptr); } /** * 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:: __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 = nullptr; if (pickler != nullptr) { PyObject *py_writer = PyObject_GetAttrString(pickler, "bamWriter"); if (py_writer == nullptr) { // It's OK if there's no bamWriter. PyErr_Clear(); } else { DtoolInstance_GetPointer(py_writer, writer, Dtool_BamWriter); Py_DECREF(py_writer); } } // We have a non-empty NodePath. vector_uchar bam_stream; if (!_this->encode_to_bam_stream(bam_stream, writer)) { std::ostringstream stream; stream << "Could not bamify " << _this; std::string message = stream.str(); PyErr_SetString(PyExc_TypeError, message.c_str()); return nullptr; } // Start by getting this class object. PyObject *this_class = (PyObject *)Py_TYPE(self); if (this_class == nullptr) { return nullptr; } PyObject *func; if (writer != nullptr) { // The modified pickle support: call the "persistent" version of this // function, which receives the unpickler itself as an additional // parameter. func = Extension::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream_persist"); if (func == nullptr) { PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream_persist()"); return nullptr; } } else { // The traditional pickle support: call the non-persistent version of this // function. func = Extension::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream"); if (func == nullptr) { PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream()"); return nullptr; } } // PyTuple_SET_ITEM conveniently borrows the reference it is passed. PyObject *args = PyTuple_New(1); PyTuple_SET_ITEM(args, 0, Dtool_WrapValue(bam_stream)); PyObject *tuple = PyTuple_New(2); PyTuple_SET_ITEM(tuple, 0, func); PyTuple_SET_ITEM(tuple, 1, args); return tuple; } /** * Returns the associated node's tags. */ PyObject *Extension:: get_tags() const { // An empty NodePath returns None if (_this->is_empty()) { return Py_NewRef(Py_None); } // Just call PandaNode.tags rather than defining a whole new interface. PT(PandaNode) node = _this->node(); PyObject *py_node = DTool_CreatePyInstanceTyped ((void *)node.p(), Dtool_PandaNode, true, false, node->get_type_index()); // DTool_CreatePyInstanceTyped() steals a C++ reference. node.cheat() = nullptr; PyObject *result = PyObject_GetAttrString(py_node, "tags"); Py_DECREF(py_node); 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:: 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(vector_uchar data) { return py_decode_NodePath_from_bam_stream_persist(nullptr, std::move(data)); } /** * This wrapper is defined as a global function to suit pickle's needs. */ NodePath py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, vector_uchar data) { BamReader *reader = nullptr; if (unpickler != nullptr) { PyObject *py_reader = PyObject_GetAttrString(unpickler, "bamReader"); if (py_reader == nullptr) { // It's OK if there's no bamReader. PyErr_Clear(); } else { DtoolInstance_GetPointer(py_reader, reader, Dtool_BamReader); Py_DECREF(py_reader); } } return NodePath::decode_from_bam_stream(std::move(data), reader); } /** * Sets a single shader input. */ void Extension:: 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__(std::move(name), value, priority); 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:: 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; Py_BEGIN_CRITICAL_SECTION(kwargs); while (PyDict_Next(kwargs, &pos, &key, &value)) { char *buffer; Py_ssize_t length; buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length); if (buffer == nullptr) { Dtool_Raise_TypeError("NodePath.set_shader_inputs accepts only string keywords"); break; } CPT_InternalName name(std::string(buffer, length)); ShaderInput &input = attrib->_inputs[name]; invoke_extension(&input).__init__(std::move(name), value); } Py_END_CRITICAL_SECTION(); 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:: 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 { return Py_NewRef(Py_None); } } /** * Recursively assigns a weak reference to the given owner object to all * collision nodes at this level and below. * * You may pass in None to clear all owners below this level. * * Note that there is no corresponding get_collide_owner(), since there may be * multiple nodes below this level with different owners. */ void Extension:: set_collide_owner(PyObject *owner) { if (owner != Py_None) { PyObject *ref = PyWeakref_NewRef(owner, nullptr); if (ref != nullptr) { r_set_collide_owner(_this->node(), ref); Py_DECREF(ref); } } else { r_clear_collide_owner(_this->node()); } } /** * Recursive implementation of set_collide_owner. weakref must be a weak ref * object. */ void Extension:: r_set_collide_owner(PandaNode *node, PyObject *weakref) { if (node->is_collision_node()) { CollisionNode *cnode = (CollisionNode *)node; cnode->set_owner(Py_NewRef(weakref), [](void *obj) { Py_DECREF((PyObject *)obj); }); } PandaNode::Children cr = node->get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { r_set_collide_owner(cr.get_child(i), weakref); } } /** * Recursive implementation of set_collide_owner(None). */ void Extension:: r_clear_collide_owner(PandaNode *node) { if (node->is_collision_node()) { CollisionNode *cnode = (CollisionNode *)node; cnode->clear_owner(); } PandaNode::Children cr = node->get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { r_clear_collide_owner(cr.get_child(i)); } } #endif // HAVE_PYTHON