Browse Source

Commit patch by cfsworks to move pgraph stuff to extensions

rdb 11 years ago
parent
commit
566feda34d

+ 2 - 4
makepanda/makepanda.py

@@ -3200,8 +3200,7 @@ if (not RUNTIME):
   TargetAdd('libp3pgraph.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3pgraph.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3pgraph.in', opts=['IMOD:core', 'ILIB:libp3pgraph', 'SRCDIR:panda/src/pgraph'])
   TargetAdd('libp3pgraph.in', opts=['IMOD:core', 'ILIB:libp3pgraph', 'SRCDIR:panda/src/pgraph'])
   TargetAdd('libp3pgraph_igate.obj', input='libp3pgraph.in', opts=["DEPENDENCYONLY","BIGOBJ"])
   TargetAdd('libp3pgraph_igate.obj', input='libp3pgraph.in', opts=["DEPENDENCYONLY","BIGOBJ"])
-  TargetAdd('p3pgraph_nodePath_ext.obj', opts=OPTS, input='nodePath_ext.cxx')
-  TargetAdd('p3pgraph_nodePathCollection_ext.obj', opts=OPTS, input='nodePathCollection_ext.cxx')
+  TargetAdd('p3pgraph_ext_composite.obj', opts=OPTS, input='p3pgraph_ext_composite.cxx')
 
 
 #
 #
 # DIRECTORY: panda/src/cull/
 # DIRECTORY: panda/src/cull/
@@ -3543,8 +3542,7 @@ if (not RUNTIME):
   TargetAdd('libpanda.dll', input='p3putil_typedWritable_ext.obj')
   TargetAdd('libpanda.dll', input='p3putil_typedWritable_ext.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_pfmFile_ext.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_pfmFile_ext.obj')
   TargetAdd('libpanda.dll', input='p3gobj_geomVertexArrayData_ext.obj')
   TargetAdd('libpanda.dll', input='p3gobj_geomVertexArrayData_ext.obj')
-  TargetAdd('libpanda.dll', input='p3pgraph_nodePath_ext.obj')
-  TargetAdd('libpanda.dll', input='p3pgraph_nodePathCollection_ext.obj')
+  TargetAdd('libpanda.dll', input='p3pgraph_ext_composite.obj')
   TargetAdd('libpanda.dll', input='p3display_graphicsStateGuardian_ext.obj')
   TargetAdd('libpanda.dll', input='p3display_graphicsStateGuardian_ext.obj')
 
 
   if PkgSkip("FREETYPE")==0:
   if PkgSkip("FREETYPE")==0:

+ 5 - 1
panda/src/pgraph/Sources.pp

@@ -116,7 +116,8 @@
 
 
   #define COMBINED_SOURCES \
   #define COMBINED_SOURCES \
     $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx \
     $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx \
-    $[TARGET]_composite3.cxx $[TARGET]_composite4.cxx
+    $[TARGET]_composite3.cxx $[TARGET]_composite4.cxx \
+    $[TARGET]_ext_composite.cxx
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
     accumulatedAttribs.cxx \
     accumulatedAttribs.cxx \
     alphaTestAttrib.cxx \  
     alphaTestAttrib.cxx \  
@@ -277,6 +278,7 @@
     occluderEffect.I occluderEffect.h \
     occluderEffect.I occluderEffect.h \
     occluderNode.I occluderNode.h \
     occluderNode.I occluderNode.h \
     pandaNode.I pandaNode.h \
     pandaNode.I pandaNode.h \
+    pandaNode_ext.h pandaNode_ext.cxx \
     pandaNodeChain.I pandaNodeChain.h \
     pandaNodeChain.I pandaNodeChain.h \
     planeNode.I planeNode.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
     polylightEffect.I polylightEffect.h \
@@ -289,6 +291,7 @@
     renderEffects.I renderEffects.h \
     renderEffects.I renderEffects.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderState.I renderState.h \
     renderState.I renderState.h \
+    renderState_ext.h renderState_ext.cxx \
     rescaleNormalAttrib.I rescaleNormalAttrib.h \
     rescaleNormalAttrib.I rescaleNormalAttrib.h \
     sceneGraphReducer.I sceneGraphReducer.h \
     sceneGraphReducer.I sceneGraphReducer.h \
     sceneSetup.I sceneSetup.h \
     sceneSetup.I sceneSetup.h \
@@ -307,6 +310,7 @@
     texGenAttrib.I texGenAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureStageCollection.I textureStageCollection.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
+    transformState_ext.h transformState_ext.cxx \
     transparencyAttrib.I transparencyAttrib.h \
     transparencyAttrib.I transparencyAttrib.h \
     weakNodePath.I weakNodePath.h \
     weakNodePath.I weakNodePath.h \
     workingNodePath.I workingNodePath.h
     workingNodePath.I workingNodePath.h

+ 9 - 7
panda/src/pgraph/nodePath_ext.I

@@ -12,6 +12,8 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
+#include "pandaNode_ext.h"
+
 #ifndef CPPPARSER
 #ifndef CPPPARSER
 #ifdef STDFLOAT_DOUBLE
 #ifdef STDFLOAT_DOUBLE
 IMPORT_THIS struct Dtool_PyTypedObject Dtool_LPoint3d;
 IMPORT_THIS struct Dtool_PyTypedObject Dtool_LPoint3d;
@@ -33,7 +35,7 @@ IMPORT_THIS struct Dtool_PyTypedObject Dtool_LPoint3f;
 INLINE void Extension<NodePath>::
 INLINE void Extension<NodePath>::
 get_python_tag_keys(vector_string &keys) const {
 get_python_tag_keys(vector_string &keys) const {
   nassertv_always(!_this->is_empty());
   nassertv_always(!_this->is_empty());
-  _this->node()->get_python_tag_keys(keys);
+  invoke_extension(_this->node()).get_python_tag_keys(keys);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -49,7 +51,7 @@ get_tag_keys() const {
     Py_INCREF(Py_None);
     Py_INCREF(Py_None);
     return Py_None;
     return Py_None;
   }
   }
-  return _this->node()->get_tag_keys();
+  return invoke_extension(_this->node()).get_tag_keys();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -66,7 +68,7 @@ get_python_tag_keys() const {
     Py_INCREF(Py_None);
     Py_INCREF(Py_None);
     return Py_None;
     return Py_None;
   }
   }
-  return _this->node()->get_python_tag_keys();
+  return invoke_extension(_this->node()).get_python_tag_keys();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -85,7 +87,7 @@ get_python_tag_keys() const {
 INLINE void Extension<NodePath>::
 INLINE void Extension<NodePath>::
 set_python_tag(const string &key, PyObject *value) {
 set_python_tag(const string &key, PyObject *value) {
   nassertv_always(!_this->is_empty());
   nassertv_always(!_this->is_empty());
-  _this->node()->set_python_tag(key, value);
+  invoke_extension(_this->node()).set_python_tag(key, value);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -104,7 +106,7 @@ get_python_tag(const string &key) const {
     Py_INCREF(Py_None);
     Py_INCREF(Py_None);
     return Py_None;
     return Py_None;
   }
   }
-  return _this->node()->get_python_tag(key);
+  return invoke_extension(_this->node()).get_python_tag(key);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -122,7 +124,7 @@ has_python_tag(const string &key) const {
   if (_this->is_empty()) {
   if (_this->is_empty()) {
     return false;
     return false;
   }
   }
-  return _this->node()->has_python_tag(key);
+  return invoke_extension(_this->node()).has_python_tag(key);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -136,7 +138,7 @@ has_python_tag(const string &key) const {
 INLINE void Extension<NodePath>::
 INLINE void Extension<NodePath>::
 clear_python_tag(const string &key) {
 clear_python_tag(const string &key) {
   nassertv_always(!_this->is_empty());
   nassertv_always(!_this->is_empty());
-  _this->node()->clear_python_tag(key);
+  invoke_extension(_this->node()).clear_python_tag(key);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/pgraph/nodePath_ext.cxx

@@ -40,7 +40,7 @@ __copy__() const {
 
 
   // If we do have a node, duplicate it, and wrap it in a new
   // If we do have a node, duplicate it, and wrap it in a new
   // NodePath.
   // NodePath.
-  return NodePath(_this->node()->__copy__());
+  return NodePath(invoke_extension(_this->node()).__copy__());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 5 - 0
panda/src/pgraph/p3pgraph_ext_composite.cxx

@@ -0,0 +1,5 @@
+#include "nodePath_ext.cxx"
+#include "nodePathCollection_ext.cxx"
+#include "pandaNode_ext.cxx"
+#include "renderState_ext.cxx"
+#include "transformState_ext.cxx"

+ 0 - 253
panda/src/pgraph/pandaNode.cxx

@@ -632,73 +632,6 @@ copy_subgraph(Thread *current_thread) const {
   return r_copy_subgraph(inst_map, current_thread);
   return r_copy_subgraph(inst_map, current_thread);
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::__copy__
-//       Access: Published
-//  Description: A special Python method that is invoked by
-//               copy.copy(node).  Unlike the PandaNode copy
-//               constructor, which creates a new node without
-//               children, this shares child pointers (essentially
-//               making every child an instance).  This is intended to
-//               simulate the behavior of copy.copy() for other
-//               objects.
-////////////////////////////////////////////////////////////////////
-PT(PandaNode) PandaNode::
-__copy__() const {
-  Thread *current_thread = Thread::get_current_thread();
-
-  PT(PandaNode) node_dupe = make_copy();
-
-  Children children = get_children(current_thread);
-  int num_children = children.get_num_children();
-
-  for (int i = 0; i < num_children; ++i) {
-    node_dupe->add_child(children.get_child(i), children.get_child_sort(i));
-  }
-
-  return node_dupe;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::__deepcopy__
-//       Access: Published
-//  Description: A special Python method that is invoked by
-//               copy.deepcopy(node).  This calls copy_subgraph()
-//               unless the node is already present in the provided
-//               dictionary.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-__deepcopy__(PyObject *self, PyObject *memo) const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_PandaNode;
-
-  // Borrowed reference.
-  PyObject *dupe = PyDict_GetItem(memo, self);
-  if (dupe != NULL) {
-    // Already in the memo dictionary.
-    Py_INCREF(dupe);
-    return dupe;
-  }
-
-  PT(PandaNode) node_dupe = copy_subgraph();
-
-  // DTool_CreatePyInstanceTyped() steals a C++ reference.
-  node_dupe->ref();
-  dupe = DTool_CreatePyInstanceTyped
-    ((void *)node_dupe.p(), Dtool_PandaNode, true, false, 
-     node_dupe->get_type_index());
-
-  if (PyDict_SetItem(memo, self, dupe) != 0) {
-    Py_DECREF(dupe);
-    return NULL;
-  }
-
-  return dupe;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::count_num_descendants
 //     Function: PandaNode::count_num_descendants
 //       Access: Published
 //       Access: Published
@@ -1509,118 +1442,6 @@ clear_tag(const string &key, Thread *current_thread) {
   mark_bam_modified();
   mark_bam_modified();
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::set_python_tag
-//       Access: Published
-//  Description: Associates an arbitrary Python object with a
-//               user-defined key which is stored on the node.  This
-//               is similar to set_tag(), except it can store any
-//               Python object instead of just a string.  However, the
-//               Python object is not recorded to a bam file.
-//
-//               Each unique key stores a different string value.
-//               There is no effective limit on the number of
-//               different keys that may be stored or on the length of
-//               any one key's value.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-set_python_tag(const string &key, PyObject *value) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  CDWriter cdata(_cycler);
-  Py_XINCREF(value);
-
-  pair<PythonTagData::iterator, bool> result;
-  result = cdata->_python_tag_data.insert(PythonTagData::value_type(key, value));
-
-  if (!result.second) {
-    // The insert was unsuccessful; that means the key was already
-    // present in the map.  In this case, we should decrement the
-    // original value's reference count and replace it with the new
-    // object.
-    PythonTagData::iterator ti = result.first;
-    PyObject *old_value = (*ti).second;
-    Py_XDECREF(old_value);
-    (*ti).second = value;
-  }
-
-  // Even though the python tag isn't recorded in the bam stream?
-  mark_bam_modified();
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::get_python_tag
-//       Access: Published
-//  Description: Retrieves the Python object that was previously
-//               set on this node for the particular key, if any.  If
-//               no value has been previously set, returns None.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_python_tag(const string &key) const {
-  CDReader cdata(_cycler);
-  PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *result = (*ti).second;
-    Py_XINCREF(result);
-    return result;
-  }
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::has_python_tag
-//       Access: Published
-//  Description: Returns true if a Python object has been defined on
-//               this node for the particular key (even if that object
-//               is None), or false if no object has been set.
-////////////////////////////////////////////////////////////////////
-bool PandaNode::
-has_python_tag(const string &key) const {
-  CDReader cdata(_cycler);
-  PythonTagData::const_iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  return (ti != cdata->_python_tag_data.end());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::clear_python_tag
-//       Access: Published
-//  Description: Removes the Python object defined for this key on
-//               this particular node.  After a call to
-//               clear_python_tag(), has_python_tag() will return
-//               false for the indicated key.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-clear_python_tag(const string &key) {
-  Thread *current_thread = Thread::get_current_thread();
-  int pipeline_stage = current_thread->get_pipeline_stage();
-  nassertv(pipeline_stage == 0);
-
-  CDWriter cdata(_cycler, current_thread);
-  PythonTagData::iterator ti;
-  ti = cdata->_python_tag_data.find(key);
-  if (ti != cdata->_python_tag_data.end()) {
-    PyObject *value = (*ti).second;
-    Py_XDECREF(value);
-    cdata->_python_tag_data.erase(ti);
-  }
-
-  // Even though the python tag isn't recorded in the bam stream?
-  mark_bam_modified();
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::copy_tags
 //     Function: PandaNode::copy_tags
 //       Access: Published
 //       Access: Published
@@ -1743,80 +1564,6 @@ get_tag_keys(vector_string &keys) const {
   }
   }
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_python_tag_keys
-//       Access: Published
-//  Description: Fills the given vector up with the
-//               list of Python tags on this PandaNode.
-//
-//               It is the user's responsibility to ensure that the
-//               keys vector is empty before making this call;
-//               otherwise, the new files will be appended to it.
-////////////////////////////////////////////////////////////////////
-void PandaNode::
-get_python_tag_keys(vector_string &keys) const {
-  CDReader cdata(_cycler);
-  if (!cdata->_python_tag_data.empty()) {
-    PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
-    while (ti != cdata->_python_tag_data.end()) {
-      keys.push_back((*ti).first);
-      ++ti;
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_tag_keys
-//       Access: Published
-//  Description: This variant on get_tag_keys returns
-//               a Python list of strings.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_tag_keys() const {
-  vector_string keys;
-  get_tag_keys(keys);
-
-  PyObject *result = PyList_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    const string &tag_name = keys[i];
-#if PY_MAJOR_VERSION >= 3
-    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
-#else
-    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
-#endif
-    PyList_SET_ITEM(result, i, str);
-  }
-
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Filename::get_python_tag_keys
-//       Access: Published
-//  Description: This variant on get_python_tag_keys returns
-//               a Python list of strings.
-////////////////////////////////////////////////////////////////////
-PyObject *PandaNode::
-get_python_tag_keys() const {
-  vector_string keys;
-  get_python_tag_keys(keys);
-
-  PyObject *result = PyList_New(keys.size());
-  for (size_t i = 0; i < keys.size(); ++i) {
-    const string &tag_name = keys[i];
-#if PY_MAJOR_VERSION >= 3
-    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
-#else
-    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
-#endif
-    PyList_SET_ITEM(result, i, str);
-  }
-
-  return result;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::compare_tags
 //     Function: PandaNode::compare_tags
 //       Access: Published
 //       Access: Published

+ 13 - 14
panda/src/pgraph/pandaNode.h

@@ -45,6 +45,7 @@
 #include "copyOnWriteObject.h"
 #include "copyOnWriteObject.h"
 #include "copyOnWritePointer.h"
 #include "copyOnWritePointer.h"
 #include "lightReMutex.h"
 #include "lightReMutex.h"
+#include "extension.h"
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 
 
@@ -118,10 +119,8 @@ PUBLISHED:
   virtual PandaNode *make_copy() const;
   virtual PandaNode *make_copy() const;
   PT(PandaNode) copy_subgraph(Thread *current_thread = Thread::get_current_thread()) const;
   PT(PandaNode) copy_subgraph(Thread *current_thread = Thread::get_current_thread()) const;
 
 
-#ifdef HAVE_PYTHON
-  PT(PandaNode) __copy__() const;
-  PyObject *__deepcopy__(PyObject *self, PyObject *memo) const;
-#endif
+  EXTENSION(PT(PandaNode) __copy__() const);
+  EXTENSION(PyObject *__deepcopy__(PyObject *self, PyObject *memo) const);
 
 
   INLINE int get_num_parents(Thread *current_thread = Thread::get_current_thread()) const;
   INLINE int get_num_parents(Thread *current_thread = Thread::get_current_thread()) const;
   INLINE PandaNode *get_parent(int n, Thread *current_thread = Thread::get_current_thread()) const;
   INLINE PandaNode *get_parent(int n, Thread *current_thread = Thread::get_current_thread()) const;
@@ -202,16 +201,15 @@ PUBLISHED:
   void clear_tag(const string &key,
   void clear_tag(const string &key,
                  Thread *current_thread = Thread::get_current_thread());
                  Thread *current_thread = Thread::get_current_thread());
   void get_tag_keys(vector_string &keys) const;
   void get_tag_keys(vector_string &keys) const;
-#ifdef HAVE_PYTHON
-  PyObject *get_tag_keys() const;
-
-  void set_python_tag(const string &key, PyObject *value);
-  PyObject *get_python_tag(const string &key) const;
-  bool has_python_tag(const string &key) const;
-  void clear_python_tag(const string &key);
-  void get_python_tag_keys(vector_string &keys) const;
-  PyObject *get_python_tag_keys() const;
-#endif  // HAVE_PYTHON
+
+  EXTENSION(PyObject *get_tag_keys() const);
+
+  EXTENSION(void set_python_tag(const string &key, PyObject *value));
+  EXTENSION(PyObject *get_python_tag(const string &key) const);
+  EXTENSION(bool has_python_tag(const string &key) const);
+  EXTENSION(void clear_python_tag(const string &key));
+  EXTENSION(void get_python_tag_keys(vector_string &keys) const);
+  EXTENSION(PyObject *get_python_tag_keys() const);
 
 
   INLINE bool has_tags() const;
   INLINE bool has_tags() const;
   void copy_tags(PandaNode *other);
   void copy_tags(PandaNode *other);
@@ -784,6 +782,7 @@ private:
   friend class WorkingNodePath;
   friend class WorkingNodePath;
   friend class PandaNodePipelineReader;
   friend class PandaNodePipelineReader;
   friend class EggLoader;
   friend class EggLoader;
+  friend class Extension<PandaNode>;
 };
 };
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 259 - 0
panda/src/pgraph/pandaNode_ext.cxx

@@ -0,0 +1,259 @@
+// Filename: pandaNode_ext.cxx
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandaNode_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::__copy__
+//       Access: Published
+//  Description: A special Python method that is invoked by
+//               copy.copy(node).  Unlike the PandaNode copy
+//               constructor, which creates a new node without
+//               children, this shares child pointers (essentially
+//               making every child an instance).  This is intended to
+//               simulate the behavior of copy.copy() for other
+//               objects.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) Extension<PandaNode>::
+__copy__() const {
+  Thread *current_thread = Thread::get_current_thread();
+
+  PT(PandaNode) node_dupe = _this->make_copy();
+
+  PandaNode::Children children = _this->get_children(current_thread);
+  int num_children = children.get_num_children();
+
+  for (int i = 0; i < num_children; ++i) {
+    node_dupe->add_child(children.get_child(i), children.get_child_sort(i));
+  }
+
+  return node_dupe;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::__deepcopy__
+//       Access: Published
+//  Description: A special Python method that is invoked by
+//               copy.deepcopy(node).  This calls copy_subgraph()
+//               unless the node is already present in the provided
+//               dictionary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+__deepcopy__(PyObject *self, PyObject *memo) const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_PandaNode;
+
+  // Borrowed reference.
+  PyObject *dupe = PyDict_GetItem(memo, self);
+  if (dupe != NULL) {
+    // Already in the memo dictionary.
+    Py_INCREF(dupe);
+    return dupe;
+  }
+
+  PT(PandaNode) node_dupe = _this->copy_subgraph();
+
+  // DTool_CreatePyInstanceTyped() steals a C++ reference.
+  node_dupe->ref();
+  dupe = DTool_CreatePyInstanceTyped
+    ((void *)node_dupe.p(), Dtool_PandaNode, true, false, 
+     node_dupe->get_type_index());
+
+  if (PyDict_SetItem(memo, self, dupe) != 0) {
+    Py_DECREF(dupe);
+    return NULL;
+  }
+
+  return dupe;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::set_python_tag
+//       Access: Published
+//  Description: Associates an arbitrary Python object with a
+//               user-defined key which is stored on the node.  This
+//               is similar to set_tag(), except it can store any
+//               Python object instead of just a string.  However, the
+//               Python object is not recorded to a bam file.
+//
+//               Each unique key stores a different string value.
+//               There is no effective limit on the number of
+//               different keys that may be stored or on the length of
+//               any one key's value.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+set_python_tag(const string &key, PyObject *value) {
+  Thread *current_thread = Thread::get_current_thread();
+  int pipeline_stage = current_thread->get_pipeline_stage();
+  nassertv(pipeline_stage == 0);
+
+  PandaNode::CDWriter cdata(_this->_cycler);
+  Py_XINCREF(value);
+
+  pair<PandaNode::PythonTagData::iterator, bool> result;
+  result = cdata->_python_tag_data.insert(PandaNode::PythonTagData::value_type(key, value));
+
+  if (!result.second) {
+    // The insert was unsuccessful; that means the key was already
+    // present in the map.  In this case, we should decrement the
+    // original value's reference count and replace it with the new
+    // object.
+    PandaNode::PythonTagData::iterator ti = result.first;
+    PyObject *old_value = (*ti).second;
+    Py_XDECREF(old_value);
+    (*ti).second = value;
+  }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  _this->mark_bam_modified();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag
+//       Access: Published
+//  Description: Retrieves the Python object that was previously
+//               set on this node for the particular key, if any.  If
+//               no value has been previously set, returns None.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_python_tag(const string &key) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  PandaNode::PythonTagData::const_iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  if (ti != cdata->_python_tag_data.end()) {
+    PyObject *result = (*ti).second;
+    Py_XINCREF(result);
+    return result;
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::has_python_tag
+//       Access: Published
+//  Description: Returns true if a Python object has been defined on
+//               this node for the particular key (even if that object
+//               is None), or false if no object has been set.
+////////////////////////////////////////////////////////////////////
+bool Extension<PandaNode>::
+has_python_tag(const string &key) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  PandaNode::PythonTagData::const_iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  return (ti != cdata->_python_tag_data.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::clear_python_tag
+//       Access: Published
+//  Description: Removes the Python object defined for this key on
+//               this particular node.  After a call to
+//               clear_python_tag(), has_python_tag() will return
+//               false for the indicated key.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+clear_python_tag(const string &key) {
+  Thread *current_thread = Thread::get_current_thread();
+  int pipeline_stage = current_thread->get_pipeline_stage();
+  nassertv(pipeline_stage == 0);
+
+  PandaNode::CDWriter cdata(_this->_cycler, current_thread);
+  PandaNode::PythonTagData::iterator ti;
+  ti = cdata->_python_tag_data.find(key);
+  if (ti != cdata->_python_tag_data.end()) {
+    PyObject *value = (*ti).second;
+    Py_XDECREF(value);
+    cdata->_python_tag_data.erase(ti);
+  }
+
+  // Even though the python tag isn't recorded in the bam stream?
+  _this->mark_bam_modified();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag_keys
+//       Access: Published
+//  Description: Fills the given vector up with the
+//               list of Python tags on this PandaNode.
+//
+//               It is the user's responsibility to ensure that the
+//               keys vector is empty before making this call;
+//               otherwise, the new files will be appended to it.
+////////////////////////////////////////////////////////////////////
+void Extension<PandaNode>::
+get_python_tag_keys(vector_string &keys) const {
+  PandaNode::CDReader cdata(_this->_cycler);
+  if (!cdata->_python_tag_data.empty()) {
+    PandaNode::PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
+    while (ti != cdata->_python_tag_data.end()) {
+      keys.push_back((*ti).first);
+      ++ti;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_tag_keys
+//       Access: Published
+//  Description: This variant on get_tag_keys returns
+//               a Python list of strings.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_tag_keys() const {
+  vector_string keys;
+  _this->get_tag_keys(keys);
+
+  PyObject *result = PyList_New(keys.size());
+  for (size_t i = 0; i < keys.size(); ++i) {
+    const string &tag_name = keys[i];
+#if PY_MAJOR_VERSION >= 3
+    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
+#else
+    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
+#endif
+    PyList_SET_ITEM(result, i, str);
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<PandaNode>::get_python_tag_keys
+//       Access: Published
+//  Description: This variant on get_python_tag_keys returns
+//               a Python list of strings.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<PandaNode>::
+get_python_tag_keys() const {
+  vector_string keys;
+  get_python_tag_keys(keys);
+
+  PyObject *result = PyList_New(keys.size());
+  for (size_t i = 0; i < keys.size(); ++i) {
+    const string &tag_name = keys[i];
+#if PY_MAJOR_VERSION >= 3
+    PyObject *str = PyUnicode_FromStringAndSize(tag_name.data(), tag_name.size());
+#else
+    PyObject *str = PyString_FromStringAndSize(tag_name.data(), tag_name.size());
+#endif
+    PyList_SET_ITEM(result, i, str);
+  }
+
+  return result;
+}
+
+#endif  // HAVE_PYTHON
+

+ 50 - 0
panda/src/pgraph/pandaNode_ext.h

@@ -0,0 +1,50 @@
+// Filename: pandaNode_ext.h
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PANDANODE_EXT_H
+#define PANDANODE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pandaNode.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<PandaNode>
+// Description : This class defines the extension methods for
+//               PandaNode, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<PandaNode> : public ExtensionBase<PandaNode> {
+public:
+  PT(PandaNode) __copy__() const;
+  PyObject *__deepcopy__(PyObject *self, PyObject *memo) const;
+
+  PyObject *get_tag_keys() const;
+
+  void set_python_tag(const string &key, PyObject *value);
+  PyObject *get_python_tag(const string &key) const;
+  bool has_python_tag(const string &key) const;
+  void clear_python_tag(const string &key);
+  void get_python_tag_keys(vector_string &keys) const;
+  PyObject *get_python_tag_keys() const;
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // PANDANODE_EXT_H

+ 0 - 159
panda/src/pgraph/renderState.cxx

@@ -733,126 +733,6 @@ unref() const {
   return false;
   return false;
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               composition cache.  For each tuple in the list, the
-//               first element is the source render, and the second
-//               is the result render.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  LightReMutexHolder holder(*_states_lock);
-  size_t cache_size = _composition_cache.get_size();
-  PyObject *list = PyList_New(cache_size);
-
-  for (size_t i = 0; i < cache_size; ++i) {
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-    if (!_composition_cache.has_element(i)) {
-      a = Py_None;
-      Py_INCREF(a);
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      const RenderState *source = _composition_cache.get_key(i);
-      if (source == (RenderState *)NULL) {
-        a = Py_None;
-        Py_INCREF(a);
-      } else {
-        source->ref();
-        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
-                                        true, true, source->get_type_index());
-      }
-      const RenderState *result = _composition_cache.get_data(i)._result;
-      if (result == (RenderState *)NULL) {
-        b = Py_None;
-        Py_INCREF(b);
-      } else {
-        result->ref();
-        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
-                                        true, true, result->get_type_index());
-      }
-    }
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    PyList_SET_ITEM(list, i, tuple);
-  }
-
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_invert_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               invert_composition cache.  For each tuple in the list, the
-//               first element is the source render, and the second
-//               is the result render.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->invert_compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_invert_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  LightReMutexHolder holder(*_states_lock);
-  size_t cache_size = _invert_composition_cache.get_size();
-  PyObject *list = PyList_New(cache_size);
-
-  for (size_t i = 0; i < cache_size; ++i) {
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-    if (!_invert_composition_cache.has_element(i)) {
-      a = Py_None;
-      Py_INCREF(a);
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      const RenderState *source = _invert_composition_cache.get_key(i);
-      if (source == (RenderState *)NULL) {
-        a = Py_None;
-        Py_INCREF(a);
-      } else {
-        source->ref();
-        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
-                                        true, true, source->get_type_index());
-      }
-      const RenderState *result = _invert_composition_cache.get_data(i)._result;
-      if (result == (RenderState *)NULL) {
-        b = Py_None;
-        Py_INCREF(b);
-      } else {
-        result->ref();
-        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
-                                        true, true, result->get_type_index());
-      }
-    }
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    PyList_SET_ITEM(list, i, tuple);
-  }
-
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::get_auto_shader_state
 //     Function: RenderState::get_auto_shader_state
 //       Access: Published
 //       Access: Published
@@ -2271,45 +2151,6 @@ update_pstats(int old_referenced_bits, int new_referenced_bits) {
 #endif  // DO_PSTATS
 #endif  // DO_PSTATS
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the RenderState objects
-//               in the state cache.  The order of elements in this
-//               cache is arbitrary.
-////////////////////////////////////////////////////////////////////
-PyObject *RenderState::
-get_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _states->get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const RenderState *state = _states->get_key(si);
-    state->ref();
-    PyObject *a = 
-      DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState, 
-                                  true, true, state->get_type_index());
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, a);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::init_states
 //     Function: RenderState::init_states
 //       Access: Public, Static
 //       Access: Public, Static

+ 4 - 7
panda/src/pgraph/renderState.h

@@ -126,10 +126,8 @@ PUBLISHED:
   INLINE int get_invert_composition_cache_size() const;
   INLINE int get_invert_composition_cache_size() const;
   INLINE const RenderState *get_invert_composition_cache_source(int n) const;
   INLINE const RenderState *get_invert_composition_cache_source(int n) const;
   INLINE const RenderState *get_invert_composition_cache_result(int n) const;
   INLINE const RenderState *get_invert_composition_cache_result(int n) const;
-#ifdef HAVE_PYTHON
-  PyObject *get_composition_cache() const;
-  PyObject *get_invert_composition_cache() const;
-#endif  // HAVE_PYTHON
+  EXTENSION(PyObject *get_composition_cache() const);
+  EXTENSION(PyObject *get_invert_composition_cache() const);
 
 
   const RenderState *get_auto_shader_state() const;
   const RenderState *get_auto_shader_state() const;
 
 
@@ -146,9 +144,7 @@ PUBLISHED:
   static void list_cycles(ostream &out);
   static void list_cycles(ostream &out);
   static void list_states(ostream &out);
   static void list_states(ostream &out);
   static bool validate_states();
   static bool validate_states();
-#ifdef HAVE_PYTHON
-  static PyObject *get_states();
-#endif  // HAVE_PYTHON
+  EXTENSION(static PyObject *get_states());
 
 
 PUBLISHED:
 PUBLISHED:
   // These methods are intended for use by low-level code, but they're
   // These methods are intended for use by low-level code, but they're
@@ -363,6 +359,7 @@ private:
 
 
   friend class GraphicsStateGuardian;
   friend class GraphicsStateGuardian;
   friend class RenderAttribRegistry;
   friend class RenderAttribRegistry;
+  friend class Extension<RenderState>;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const RenderState &state) {
 INLINE ostream &operator << (ostream &out, const RenderState &state) {

+ 174 - 0
panda/src/pgraph/renderState_ext.cxx

@@ -0,0 +1,174 @@
+// Filename: renderState_ext.cxx
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "renderState_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               composition cache.  For each tuple in the list, the
+//               first element is the source render, and the second
+//               is the result render.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  LightReMutexHolder holder(*RenderState::_states_lock);
+  size_t cache_size = _this->_composition_cache.get_size();
+  PyObject *list = PyList_New(cache_size);
+
+  for (size_t i = 0; i < cache_size; ++i) {
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+    if (!_this->_composition_cache.has_element(i)) {
+      a = Py_None;
+      Py_INCREF(a);
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      const RenderState *source = _this->_composition_cache.get_key(i);
+      if (source == (RenderState *)NULL) {
+        a = Py_None;
+        Py_INCREF(a);
+      } else {
+        source->ref();
+        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
+                                        true, true, source->get_type_index());
+      }
+      const RenderState *result = _this->_composition_cache.get_data(i)._result;
+      if (result == (RenderState *)NULL) {
+        b = Py_None;
+        Py_INCREF(b);
+      } else {
+        result->ref();
+        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
+                                        true, true, result->get_type_index());
+      }
+    }
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    PyList_SET_ITEM(list, i, tuple);
+  }
+
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_invert_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               invert_composition cache.  For each tuple in the list, the
+//               first element is the source render, and the second
+//               is the result render.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->invert_compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_invert_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  LightReMutexHolder holder(*RenderState::_states_lock);
+  size_t cache_size = _this->_invert_composition_cache.get_size();
+  PyObject *list = PyList_New(cache_size);
+
+  for (size_t i = 0; i < cache_size; ++i) {
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+    if (!_this->_invert_composition_cache.has_element(i)) {
+      a = Py_None;
+      Py_INCREF(a);
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      const RenderState *source = _this->_invert_composition_cache.get_key(i);
+      if (source == (RenderState *)NULL) {
+        a = Py_None;
+        Py_INCREF(a);
+      } else {
+        source->ref();
+        a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState, 
+                                        true, true, source->get_type_index());
+      }
+      const RenderState *result = _this->_invert_composition_cache.get_data(i)._result;
+      if (result == (RenderState *)NULL) {
+        b = Py_None;
+        Py_INCREF(b);
+      } else {
+        result->ref();
+        b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState, 
+                                        true, true, result->get_type_index());
+      }
+    }
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    PyList_SET_ITEM(list, i, tuple);
+  }
+
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<RenderState>::get_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the RenderState objects
+//               in the state cache.  The order of elements in this
+//               cache is arbitrary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<RenderState>::
+get_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_RenderState;
+  if (RenderState::_states == (RenderState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*RenderState::_states_lock);
+
+  size_t num_states = RenderState::_states->get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = RenderState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!RenderState::_states->has_element(si)) {
+      continue;
+    }
+    const RenderState *state = RenderState::_states->get_key(si);
+    state->ref();
+    PyObject *a = 
+      DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState, 
+                                  true, true, state->get_type_index());
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, a);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+
+
+#endif  // HAVE_PYTHON

+ 42 - 0
panda/src/pgraph/renderState_ext.h

@@ -0,0 +1,42 @@
+// Filename: renderState_ext.h
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef RENDERSTATE_EXT_H
+#define RENDERSTATE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "renderState.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<RenderState>
+// Description : This class defines the extension methods for
+//               RenderState, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<RenderState> : public ExtensionBase<RenderState> {
+public:
+  PyObject *get_composition_cache() const;
+  PyObject *get_invert_composition_cache() const;
+  static PyObject *get_states();
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // RENDERSTATE_EXT_H

+ 0 - 203
panda/src/pgraph/transformState.cxx

@@ -818,134 +818,6 @@ validate_composition_cache() const {
   return true;
   return true;
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               composition cache.  For each tuple in the list, the
-//               first element is the source transform, and the second
-//               is the result transform.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _composition_cache.get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _composition_cache.get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_composition_cache.has_element(si)) {
-      continue;
-    }
-
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-
-    const TransformState *source = _composition_cache.get_key(si);
-    if (source == (TransformState *)NULL) {
-      a = Py_None;
-      Py_INCREF(a);
-    } else {
-      source->ref();
-      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
-                                      true, true, source->get_type_index());
-    }
-    const TransformState *result = _composition_cache.get_data(si)._result;
-    if (result == (TransformState *)NULL) {
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      result->ref();
-      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
-                                      true, true, result->get_type_index());
-    }
-
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, tuple);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_invert_composition_cache
-//       Access: Published
-//  Description: Returns a list of 2-tuples that represents the
-//               invert_composition cache.  For each tuple in the list, the
-//               first element is the source transform, and the second
-//               is the result transform.  If both are None, there is
-//               no entry in the cache at that slot.
-//
-//               In general, a->invert_compose(source) == result.
-//
-//               This has no practical value other than for examining
-//               the cache for performance analysis.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_invert_composition_cache() const {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _invert_composition_cache.get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _invert_composition_cache.get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_invert_composition_cache.has_element(si)) {
-      continue;
-    }
-
-    PyObject *tuple = PyTuple_New(2);
-    PyObject *a, *b;
-
-    const TransformState *source = _invert_composition_cache.get_key(si);
-    if (source == (TransformState *)NULL) {
-      a = Py_None;
-      Py_INCREF(a);
-    } else {
-      source->ref();
-      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
-                                      true, true, source->get_type_index());
-    }
-    const TransformState *result = _invert_composition_cache.get_data(si)._result;
-    if (result == (TransformState *)NULL) {
-      b = Py_None;
-      Py_INCREF(b);
-    } else {
-      result->ref();
-      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
-                                      true, true, result->get_type_index());
-    }
-
-    PyTuple_SET_ITEM(tuple, 0, a);
-    PyTuple_SET_ITEM(tuple, 1, b);
-
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, tuple);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::output
 //     Function: TransformState::output
 //       Access: Published
 //       Access: Published
@@ -1529,81 +1401,6 @@ validate_states() {
   return true;
   return true;
 }
 }
 
 
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the TransformState objects
-//               in the state cache.  The order of elements in this
-//               cache is arbitrary.
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  size_t num_states = _states->get_num_entries();
-  PyObject *list = PyList_New(num_states);
-  size_t i = 0;
-
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const TransformState *state = _states->get_key(si);
-    state->ref();
-    PyObject *a = 
-      DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
-                                  true, true, state->get_type_index());
-    nassertr(i < num_states, list);
-    PyList_SET_ITEM(list, i, a);
-    ++i;
-  }
-  nassertr(i == num_states, list);
-  return list;
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-////////////////////////////////////////////////////////////////////
-//     Function: TransformState::get_unused_states
-//       Access: Published, Static
-//  Description: Returns a list of all of the "unused" TransformState
-//               objects in the state cache.  See
-//               get_num_unused_states().
-////////////////////////////////////////////////////////////////////
-PyObject *TransformState::
-get_unused_states() {
-  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
-  if (_states == (States *)NULL) {
-    return PyList_New(0);
-  }
-  LightReMutexHolder holder(*_states_lock);
-
-  PyObject *list = PyList_New(0);
-  int size = _states->get_size();
-  for (int si = 0; si < size; ++si) {
-    if (!_states->has_element(si)) {
-      continue;
-    }
-    const TransformState *state = _states->get_key(si);
-    if (state->get_cache_ref_count() == state->get_ref_count()) {
-      state->ref();
-      PyObject *a = 
-        DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
-                                    true, true, state->get_type_index());
-      PyList_Append(list, a);
-      Py_DECREF(a);
-    }
-  }
-  return list;
-}
-#endif  // HAVE_PYTHON
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::init_states
 //     Function: TransformState::init_states
 //       Access: Public, Static
 //       Access: Public, Static

+ 7 - 8
panda/src/pgraph/transformState.h

@@ -32,6 +32,7 @@
 #include "deletedChain.h"
 #include "deletedChain.h"
 #include "simpleHashMap.h"
 #include "simpleHashMap.h"
 #include "cacheStats.h"
 #include "cacheStats.h"
+#include "extension.h"
 
 
 class GraphicsStateGuardianBase;
 class GraphicsStateGuardianBase;
 class FactoryParams;
 class FactoryParams;
@@ -190,10 +191,8 @@ PUBLISHED:
   INLINE const TransformState *get_invert_composition_cache_source(int n) const;
   INLINE const TransformState *get_invert_composition_cache_source(int n) const;
   INLINE const TransformState *get_invert_composition_cache_result(int n) const;
   INLINE const TransformState *get_invert_composition_cache_result(int n) const;
   bool validate_composition_cache() const;
   bool validate_composition_cache() const;
-#ifdef HAVE_PYTHON
-  PyObject *get_composition_cache() const;
-  PyObject *get_invert_composition_cache() const;
-#endif  // HAVE_PYTHON
+  EXTENSION(PyObject *get_composition_cache() const);
+  EXTENSION(PyObject *get_invert_composition_cache() const);
 
 
   void output(ostream &out) const;
   void output(ostream &out) const;
   void write(ostream &out, int indent_level) const;
   void write(ostream &out, int indent_level) const;
@@ -206,10 +205,8 @@ PUBLISHED:
   static void list_cycles(ostream &out);
   static void list_cycles(ostream &out);
   static void list_states(ostream &out);
   static void list_states(ostream &out);
   static bool validate_states();
   static bool validate_states();
-#ifdef HAVE_PYTHON
-  static PyObject *get_states();
-  static PyObject *get_unused_states();
-#endif  // HAVE_PYTHON
+  EXTENSION(static PyObject *get_states());
+  EXTENSION(static PyObject *get_unused_states());
 
 
 
 
 public:
 public:
@@ -405,6 +402,8 @@ public:
 
 
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
+
+  friend class Extension<TransformState>;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const TransformState &state) {
 INLINE ostream &operator << (ostream &out, const TransformState &state) {

+ 214 - 0
panda/src/pgraph/transformState_ext.cxx

@@ -0,0 +1,214 @@
+// Filename: transformState_ext.cxx
+// Created by:  CFSworks (30Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "transformState_ext.h"
+
+#ifdef HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               composition cache.  For each tuple in the list, the
+//               first element is the source transform, and the second
+//               is the result transform.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  LightReMutexHolder holder(*_this->_states_lock);
+
+  size_t num_states = _this->_composition_cache.get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = _this->_composition_cache.get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!_this->_composition_cache.has_element(si)) {
+      continue;
+    }
+
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+
+    const TransformState *source = _this->_composition_cache.get_key(si);
+    if (source == (TransformState *)NULL) {
+      a = Py_None;
+      Py_INCREF(a);
+    } else {
+      source->ref();
+      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
+                                      true, true, source->get_type_index());
+    }
+    const TransformState *result = _this->_composition_cache.get_data(si)._result;
+    if (result == (TransformState *)NULL) {
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      result->ref();
+      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
+                                      true, true, result->get_type_index());
+    }
+
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, tuple);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_invert_composition_cache
+//       Access: Published
+//  Description: Returns a list of 2-tuples that represents the
+//               invert_composition cache.  For each tuple in the list, the
+//               first element is the source transform, and the second
+//               is the result transform.  If both are None, there is
+//               no entry in the cache at that slot.
+//
+//               In general, a->invert_compose(source) == result.
+//
+//               This has no practical value other than for examining
+//               the cache for performance analysis.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_invert_composition_cache() const {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  LightReMutexHolder holder(*_this->_states_lock);
+
+  size_t num_states = _this->_invert_composition_cache.get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = _this->_invert_composition_cache.get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!_this->_invert_composition_cache.has_element(si)) {
+      continue;
+    }
+
+    PyObject *tuple = PyTuple_New(2);
+    PyObject *a, *b;
+
+    const TransformState *source = _this->_invert_composition_cache.get_key(si);
+    if (source == (TransformState *)NULL) {
+      a = Py_None;
+      Py_INCREF(a);
+    } else {
+      source->ref();
+      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_TransformState, 
+                                      true, true, source->get_type_index());
+    }
+    const TransformState *result = _this->_invert_composition_cache.get_data(si)._result;
+    if (result == (TransformState *)NULL) {
+      b = Py_None;
+      Py_INCREF(b);
+    } else {
+      result->ref();
+      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_TransformState, 
+                                      true, true, result->get_type_index());
+    }
+
+    PyTuple_SET_ITEM(tuple, 0, a);
+    PyTuple_SET_ITEM(tuple, 1, b);
+
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, tuple);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the TransformState objects
+//               in the state cache.  The order of elements in this
+//               cache is arbitrary.
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  if (TransformState::_states == (TransformState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*TransformState::_states_lock);
+
+  size_t num_states = TransformState::_states->get_num_entries();
+  PyObject *list = PyList_New(num_states);
+  size_t i = 0;
+
+  int size = TransformState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!TransformState::_states->has_element(si)) {
+      continue;
+    }
+    const TransformState *state = TransformState::_states->get_key(si);
+    state->ref();
+    PyObject *a = 
+      DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
+                                  true, true, state->get_type_index());
+    nassertr(i < num_states, list);
+    PyList_SET_ITEM(list, i, a);
+    ++i;
+  }
+  nassertr(i == num_states, list);
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<TransformState>::get_unused_states
+//       Access: Published, Static
+//  Description: Returns a list of all of the "unused" TransformState
+//               objects in the state cache.  See
+//               get_num_unused_states().
+////////////////////////////////////////////////////////////////////
+PyObject *Extension<TransformState>::
+get_unused_states() {
+  IMPORT_THIS struct Dtool_PyTypedObject Dtool_TransformState;
+  if (TransformState::_states == (TransformState::States *)NULL) {
+    return PyList_New(0);
+  }
+  LightReMutexHolder holder(*TransformState::_states_lock);
+
+  PyObject *list = PyList_New(0);
+  int size = TransformState::_states->get_size();
+  for (int si = 0; si < size; ++si) {
+    if (!TransformState::_states->has_element(si)) {
+      continue;
+    }
+    const TransformState *state = TransformState::_states->get_key(si);
+    if (state->get_cache_ref_count() == state->get_ref_count()) {
+      state->ref();
+      PyObject *a = 
+        DTool_CreatePyInstanceTyped((void *)state, Dtool_TransformState, 
+                                    true, true, state->get_type_index());
+      PyList_Append(list, a);
+      Py_DECREF(a);
+    }
+  }
+  return list;
+}
+
+#endif  // HAVE_PYTHON

+ 43 - 0
panda/src/pgraph/transformState_ext.h

@@ -0,0 +1,43 @@
+// Filename: transformState_ext.h
+// Created by:  CFSworks (31Mar14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRANSFORMSTATE_EXT_H
+#define TRANSFORMSTATE_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "transformState.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<TransformState>
+// Description : This class defines the extension methods for
+//               TransformState, which are called instead of
+//               any C++ methods with the same prototype.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<TransformState> : public ExtensionBase<TransformState> {
+public:
+  PyObject *get_composition_cache() const;
+  PyObject *get_invert_composition_cache() const;
+  static PyObject *get_states();
+  static PyObject *get_unused_states();
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // TRANSFORMSTATE_EXT_H