nodePath_ext.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /**
  2. * PANDA 3D SOFTWARE
  3. * Copyright (c) Carnegie Mellon University. All rights reserved.
  4. *
  5. * All use of this software is subject to the terms of the revised BSD
  6. * license. You should have received a copy of this license along
  7. * with this source code in a file named "LICENSE."
  8. *
  9. * @file nodePath_ext.cxx
  10. * @author rdb
  11. * @date 2013-12-09
  12. */
  13. #include "nodePath_ext.h"
  14. #include "typedWritable_ext.h"
  15. #include "shaderInput_ext.h"
  16. #include "shaderAttrib.h"
  17. #include "collisionNode.h"
  18. #ifdef HAVE_PYTHON
  19. #ifndef CPPPARSER
  20. extern struct Dtool_PyTypedObject Dtool_BamWriter;
  21. extern struct Dtool_PyTypedObject Dtool_BamReader;
  22. #ifdef STDFLOAT_DOUBLE
  23. extern struct Dtool_PyTypedObject Dtool_LPoint3d;
  24. #else
  25. extern struct Dtool_PyTypedObject Dtool_LPoint3f;
  26. #endif
  27. extern struct Dtool_PyTypedObject Dtool_NodePath;
  28. extern struct Dtool_PyTypedObject Dtool_PandaNode;
  29. #endif // CPPPARSER
  30. /**
  31. * A special Python method that is invoked by copy.copy(node). Unlike the
  32. * NodePath copy constructor, this makes a duplicate copy of the underlying
  33. * PandaNode (but shares children, instead of copying them or omitting them).
  34. */
  35. NodePath Extension<NodePath>::
  36. __copy__() const {
  37. if (_this->is_empty()) {
  38. // Invoke the copy constructor if we have no node.
  39. return *_this;
  40. }
  41. // If we do have a node, duplicate it, and wrap it in a new NodePath.
  42. return NodePath(invoke_extension(_this->node()).__copy__());
  43. }
  44. /**
  45. * A special Python method that is invoked by copy.deepcopy(np). This calls
  46. * copy_to() unless the NodePath is already present in the provided
  47. * dictionary.
  48. */
  49. PyObject *Extension<NodePath>::
  50. __deepcopy__(PyObject *self, PyObject *memo) const {
  51. extern struct Dtool_PyTypedObject Dtool_NodePath;
  52. // Borrowed reference.
  53. PyObject *dupe;
  54. if (PyDict_GetItemRef(memo, self, &dupe) > 0) {
  55. // Already in the memo dictionary.
  56. return dupe;
  57. }
  58. NodePath *np_dupe;
  59. if (_this->is_empty()) {
  60. np_dupe = new NodePath(*_this);
  61. } else {
  62. np_dupe = new NodePath(_this->copy_to(NodePath()));
  63. }
  64. dupe = DTool_CreatePyInstance((void *)np_dupe, Dtool_NodePath,
  65. true, false);
  66. if (PyDict_SetItem(memo, self, dupe) != 0) {
  67. Py_DECREF(dupe);
  68. return nullptr;
  69. }
  70. return dupe;
  71. }
  72. /**
  73. * This special Python method is implement to provide support for the pickle
  74. * module.
  75. *
  76. * This hooks into the native pickle and cPickle modules, but it cannot
  77. * properly handle self-referential BAM objects.
  78. */
  79. PyObject *Extension<NodePath>::
  80. __reduce__(PyObject *self) const {
  81. return __reduce_persist__(self, nullptr);
  82. }
  83. /**
  84. * This special Python method is implement to provide support for the pickle
  85. * module.
  86. *
  87. * This is similar to __reduce__, but it provides additional support for the
  88. * missing persistent-state object needed to properly support self-referential
  89. * BAM objects written to the pickle stream. This hooks into the pickle and
  90. * cPickle modules implemented in direct/src/stdpy.
  91. */
  92. PyObject *Extension<NodePath>::
  93. __reduce_persist__(PyObject *self, PyObject *pickler) const {
  94. // We should return at least a 2-tuple, (Class, (args)): the necessary class
  95. // object whose constructor we should call (e.g. this), and the arguments
  96. // necessary to reconstruct this object.
  97. BamWriter *writer = nullptr;
  98. if (pickler != nullptr) {
  99. PyObject *py_writer = PyObject_GetAttrString(pickler, "bamWriter");
  100. if (py_writer == nullptr) {
  101. // It's OK if there's no bamWriter.
  102. PyErr_Clear();
  103. } else {
  104. DtoolInstance_GetPointer(py_writer, writer, Dtool_BamWriter);
  105. Py_DECREF(py_writer);
  106. }
  107. }
  108. // We have a non-empty NodePath.
  109. vector_uchar bam_stream;
  110. if (!_this->encode_to_bam_stream(bam_stream, writer)) {
  111. std::ostringstream stream;
  112. stream << "Could not bamify " << _this;
  113. std::string message = stream.str();
  114. PyErr_SetString(PyExc_TypeError, message.c_str());
  115. return nullptr;
  116. }
  117. // Start by getting this class object.
  118. PyObject *this_class = (PyObject *)Py_TYPE(self);
  119. if (this_class == nullptr) {
  120. return nullptr;
  121. }
  122. PyObject *func;
  123. if (writer != nullptr) {
  124. // The modified pickle support: call the "persistent" version of this
  125. // function, which receives the unpickler itself as an additional
  126. // parameter.
  127. func = Extension<TypedWritable>::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream_persist");
  128. if (func == nullptr) {
  129. PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream_persist()");
  130. return nullptr;
  131. }
  132. } else {
  133. // The traditional pickle support: call the non-persistent version of this
  134. // function.
  135. func = Extension<TypedWritable>::find_global_decode(this_class, "py_decode_NodePath_from_bam_stream");
  136. if (func == nullptr) {
  137. PyErr_SetString(PyExc_TypeError, "Couldn't find py_decode_NodePath_from_bam_stream()");
  138. return nullptr;
  139. }
  140. }
  141. // PyTuple_SET_ITEM conveniently borrows the reference it is passed.
  142. PyObject *args = PyTuple_New(1);
  143. PyTuple_SET_ITEM(args, 0, Dtool_WrapValue(bam_stream));
  144. PyObject *tuple = PyTuple_New(2);
  145. PyTuple_SET_ITEM(tuple, 0, func);
  146. PyTuple_SET_ITEM(tuple, 1, args);
  147. return tuple;
  148. }
  149. /**
  150. * Returns the associated node's tags.
  151. */
  152. PyObject *Extension<NodePath>::
  153. get_tags() const {
  154. // An empty NodePath returns None
  155. if (_this->is_empty()) {
  156. return Py_NewRef(Py_None);
  157. }
  158. // Just call PandaNode.tags rather than defining a whole new interface.
  159. PT(PandaNode) node = _this->node();
  160. PyObject *py_node = DTool_CreatePyInstanceTyped
  161. ((void *)node.p(), Dtool_PandaNode, true, false, node->get_type_index());
  162. // DTool_CreatePyInstanceTyped() steals a C++ reference.
  163. node.cheat() = nullptr;
  164. PyObject *result = PyObject_GetAttrString(py_node, "tags");
  165. Py_DECREF(py_node);
  166. return result;
  167. }
  168. /**
  169. * Returns the lowest ancestor of this node that contains a tag definition
  170. * with the indicated key, if any, or an empty NodePath if no ancestor of this
  171. * node contains this tag definition. See set_python_tag().
  172. */
  173. NodePath Extension<NodePath>::
  174. find_net_python_tag(PyObject *key) const {
  175. if (_this->is_empty()) {
  176. return NodePath::not_found();
  177. }
  178. if (has_python_tag(key)) {
  179. return *_this;
  180. }
  181. NodePath parent = _this->get_parent();
  182. return invoke_extension(&parent).find_net_python_tag(key);
  183. }
  184. /**
  185. * This wrapper is defined as a global function to suit pickle's needs.
  186. */
  187. NodePath
  188. py_decode_NodePath_from_bam_stream(vector_uchar data) {
  189. return py_decode_NodePath_from_bam_stream_persist(nullptr, std::move(data));
  190. }
  191. /**
  192. * This wrapper is defined as a global function to suit pickle's needs.
  193. */
  194. NodePath
  195. py_decode_NodePath_from_bam_stream_persist(PyObject *unpickler, vector_uchar data) {
  196. BamReader *reader = nullptr;
  197. if (unpickler != nullptr) {
  198. PyObject *py_reader = PyObject_GetAttrString(unpickler, "bamReader");
  199. if (py_reader == nullptr) {
  200. // It's OK if there's no bamReader.
  201. PyErr_Clear();
  202. } else {
  203. DtoolInstance_GetPointer(py_reader, reader, Dtool_BamReader);
  204. Py_DECREF(py_reader);
  205. }
  206. }
  207. return NodePath::decode_from_bam_stream(std::move(data), reader);
  208. }
  209. /**
  210. * Sets a single shader input.
  211. */
  212. void Extension<NodePath>::
  213. set_shader_input(CPT_InternalName name, PyObject *value, int priority) {
  214. PT(PandaNode) node = _this->node();
  215. CPT(RenderAttrib) prev_attrib = node->get_attrib(ShaderAttrib::get_class_slot());
  216. PT(ShaderAttrib) attrib;
  217. if (prev_attrib == nullptr) {
  218. attrib = new ShaderAttrib();
  219. } else {
  220. attrib = new ShaderAttrib(*(const ShaderAttrib *)prev_attrib.p());
  221. }
  222. ShaderInput &input = attrib->_inputs[name];
  223. invoke_extension(&input).__init__(std::move(name), value, priority);
  224. if (!PyErr_Occurred()) {
  225. node->set_attrib(ShaderAttrib::return_new(attrib));
  226. }
  227. }
  228. /**
  229. * Sets multiple shader inputs at the same time. This can be significantly
  230. * more efficient if many inputs need to be set at the same time.
  231. */
  232. void Extension<NodePath>::
  233. set_shader_inputs(PyObject *args, PyObject *kwargs) {
  234. if (PyObject_Size(args) > 0) {
  235. Dtool_Raise_TypeError("NodePath.set_shader_inputs takes only keyword arguments");
  236. return;
  237. }
  238. PT(PandaNode) node = _this->node();
  239. CPT(RenderAttrib) prev_attrib = node->get_attrib(ShaderAttrib::get_class_slot());
  240. PT(ShaderAttrib) attrib;
  241. if (prev_attrib == nullptr) {
  242. attrib = new ShaderAttrib();
  243. } else {
  244. attrib = new ShaderAttrib(*(const ShaderAttrib *)prev_attrib.p());
  245. }
  246. PyObject *key, *value;
  247. Py_ssize_t pos = 0;
  248. Py_BEGIN_CRITICAL_SECTION(kwargs);
  249. while (PyDict_Next(kwargs, &pos, &key, &value)) {
  250. char *buffer;
  251. Py_ssize_t length;
  252. buffer = (char *)PyUnicode_AsUTF8AndSize(key, &length);
  253. if (buffer == nullptr) {
  254. Dtool_Raise_TypeError("NodePath.set_shader_inputs accepts only string keywords");
  255. break;
  256. }
  257. CPT_InternalName name(std::string(buffer, length));
  258. ShaderInput &input = attrib->_inputs[name];
  259. invoke_extension(&input).__init__(std::move(name), value);
  260. }
  261. Py_END_CRITICAL_SECTION();
  262. if (!PyErr_Occurred()) {
  263. node->set_attrib(ShaderAttrib::return_new(attrib));
  264. }
  265. }
  266. /**
  267. * Returns the tight bounds as a 2-tuple of LPoint3 objects. This is a
  268. * convenience function for Python users, among which the use of
  269. * calc_tight_bounds may be confusing.
  270. *
  271. * Returns None if calc_tight_bounds returned false.
  272. */
  273. PyObject *Extension<NodePath>::
  274. get_tight_bounds(const NodePath &other) const {
  275. LPoint3 *min_point = new LPoint3;
  276. LPoint3 *max_point = new LPoint3;
  277. if (_this->calc_tight_bounds(*min_point, *max_point, other)) {
  278. #ifdef STDFLOAT_DOUBLE
  279. PyObject *min_inst = DTool_CreatePyInstance((void*) min_point, Dtool_LPoint3d, true, false);
  280. PyObject *max_inst = DTool_CreatePyInstance((void*) max_point, Dtool_LPoint3d, true, false);
  281. #else
  282. PyObject *min_inst = DTool_CreatePyInstance((void*) min_point, Dtool_LPoint3f, true, false);
  283. PyObject *max_inst = DTool_CreatePyInstance((void*) max_point, Dtool_LPoint3f, true, false);
  284. #endif
  285. return Py_BuildValue("NN", min_inst, max_inst);
  286. } else {
  287. return Py_NewRef(Py_None);
  288. }
  289. }
  290. /**
  291. * Recursively assigns a weak reference to the given owner object to all
  292. * collision nodes at this level and below.
  293. *
  294. * You may pass in None to clear all owners below this level.
  295. *
  296. * Note that there is no corresponding get_collide_owner(), since there may be
  297. * multiple nodes below this level with different owners.
  298. */
  299. void Extension<NodePath>::
  300. set_collide_owner(PyObject *owner) {
  301. if (owner != Py_None) {
  302. PyObject *ref = PyWeakref_NewRef(owner, nullptr);
  303. if (ref != nullptr) {
  304. r_set_collide_owner(_this->node(), ref);
  305. Py_DECREF(ref);
  306. }
  307. } else {
  308. r_clear_collide_owner(_this->node());
  309. }
  310. }
  311. /**
  312. * Recursive implementation of set_collide_owner. weakref must be a weak ref
  313. * object.
  314. */
  315. void Extension<NodePath>::
  316. r_set_collide_owner(PandaNode *node, PyObject *weakref) {
  317. if (node->is_collision_node()) {
  318. CollisionNode *cnode = (CollisionNode *)node;
  319. cnode->set_owner(Py_NewRef(weakref),
  320. [](void *obj) { Py_DECREF((PyObject *)obj); });
  321. }
  322. PandaNode::Children cr = node->get_children();
  323. int num_children = cr.get_num_children();
  324. for (int i = 0; i < num_children; i++) {
  325. r_set_collide_owner(cr.get_child(i), weakref);
  326. }
  327. }
  328. /**
  329. * Recursive implementation of set_collide_owner(None).
  330. */
  331. void Extension<NodePath>::
  332. r_clear_collide_owner(PandaNode *node) {
  333. if (node->is_collision_node()) {
  334. CollisionNode *cnode = (CollisionNode *)node;
  335. cnode->clear_owner();
  336. }
  337. PandaNode::Children cr = node->get_children();
  338. int num_children = cr.get_num_children();
  339. for (int i = 0; i < num_children; i++) {
  340. r_clear_collide_owner(cr.get_child(i));
  341. }
  342. }
  343. #endif // HAVE_PYTHON