Browse Source

interrogate: more improvements to seq/map wrappers
Gets rid of properties defined as both MAKE_SEQ_PROPERTY/MAKE_MAP_PROPERTY, which are just a bad idea. Instead, adds a way for map properties to define a separate "keys" interface.

Fixes: #203

rdb 8 years ago
parent
commit
69b3468b2c
62 changed files with 3372 additions and 1870 deletions
  1. 532 526
      dtool/src/cppparser/cppBison.cxx.prebuilt
  2. 92 90
      dtool/src/cppparser/cppBison.h.prebuilt
  3. 78 0
      dtool/src/cppparser/cppBison.yxx
  4. 7 1
      dtool/src/cppparser/cppMakeProperty.cxx
  5. 2 0
      dtool/src/cppparser/cppMakeProperty.h
  6. 1 0
      dtool/src/cppparser/cppPreprocessor.cxx
  7. 2 0
      dtool/src/dtoolbase/dtoolbase.h
  8. 2 0
      dtool/src/interrogate/interfaceMaker.cxx
  9. 2 0
      dtool/src/interrogate/interfaceMaker.h
  10. 150 66
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  11. 78 17
      dtool/src/interrogate/interrogateBuilder.cxx
  12. 1 1
      dtool/src/interrogatedb/interrogateDatabase.cxx
  13. 36 0
      dtool/src/interrogatedb/interrogateElement.I
  14. 8 1
      dtool/src/interrogatedb/interrogateElement.cxx
  15. 8 0
      dtool/src/interrogatedb/interrogateElement.h
  16. 1 4
      dtool/src/interrogatedb/p3interrogatedb_composite2.cxx
  17. 18 955
      dtool/src/interrogatedb/py_panda.cxx
  18. 2 43
      dtool/src/interrogatedb/py_panda.h
  19. 1671 0
      dtool/src/interrogatedb/py_wrappers.cxx
  20. 74 0
      dtool/src/interrogatedb/py_wrappers.h
  21. 10 0
      dtool/src/pystub/pystub.cxx
  22. 0 1
      panda/src/chan/animGroup.h
  23. 0 1
      panda/src/chan/partGroup.h
  24. 22 10
      panda/src/collide/collisionNode.I
  25. 8 7
      panda/src/collide/collisionNode.h
  26. 7 7
      panda/src/egg/eggCompositePrimitive.I
  27. 4 4
      panda/src/egg/eggCompositePrimitive.h
  28. 11 1
      panda/src/egg/eggPrimitive.I
  29. 3 2
      panda/src/egg/eggPrimitive.h
  30. 18 5
      panda/src/gobj/geom.I
  31. 12 8
      panda/src/gobj/geom.cxx
  32. 8 7
      panda/src/gobj/geom.h
  33. 10 10
      panda/src/gobj/geomVertexData.I
  34. 4 4
      panda/src/gobj/geomVertexData.cxx
  35. 9 9
      panda/src/gobj/geomVertexData.h
  36. 19 1
      panda/src/gobj/geomVertexFormat.cxx
  37. 3 2
      panda/src/gobj/geomVertexFormat.h
  38. 1 1
      panda/src/gobj/transformBlend.h
  39. 17 0
      panda/src/gobj/transformTable.cxx
  40. 3 1
      panda/src/gobj/transformTable.h
  41. 9 0
      panda/src/parametrics/parametricCurveCollection.I
  42. 6 6
      panda/src/parametrics/parametricCurveCollection.cxx
  43. 3 2
      panda/src/parametrics/parametricCurveCollection.h
  44. 2 1
      panda/src/pgraph/nodePath.h
  45. 1 0
      panda/src/pgraph/nodePath_ext.I
  46. 25 0
      panda/src/pgraph/nodePath_ext.cxx
  47. 1 0
      panda/src/pgraph/nodePath_ext.h
  48. 31 17
      panda/src/pgraph/pandaNode.I
  49. 31 44
      panda/src/pgraph/pandaNode.cxx
  50. 10 1
      panda/src/pgraph/pandaNode.h
  51. 29 3
      panda/src/pgraph/renderEffects.I
  52. 6 2
      panda/src/pgraph/renderEffects.h
  53. 6 2
      panda/src/pgraph/textureAttrib.h
  54. 15 0
      panda/src/pgraphnodes/computeNode.I
  55. 2 1
      panda/src/pgraphnodes/computeNode.h
  56. 16 2
      panda/src/physics/forceNode.cxx
  57. 2 1
      panda/src/physics/forceNode.h
  58. 15 2
      panda/src/physics/physicalNode.cxx
  59. 3 1
      panda/src/physics/physicalNode.h
  60. 64 0
      panda/src/putil/simpleHashMap.I
  61. 4 0
      panda/src/putil/simpleHashMap.h
  62. 157 0
      tests/interrogate/test_property.py

File diff suppressed because it is too large
+ 532 - 526
dtool/src/cppparser/cppBison.cxx.prebuilt


+ 92 - 90
dtool/src/cppparser/cppBison.h.prebuilt

@@ -143,51 +143,52 @@ extern int cppyydebug;
     KW_IS_TRIVIAL = 353,
     KW_IS_TRIVIAL = 353,
     KW_IS_UNION = 354,
     KW_IS_UNION = 354,
     KW_LONG = 355,
     KW_LONG = 355,
-    KW_MAKE_MAP_PROPERTY = 356,
-    KW_MAKE_PROPERTY = 357,
-    KW_MAKE_PROPERTY2 = 358,
-    KW_MAKE_SEQ = 359,
-    KW_MAKE_SEQ_PROPERTY = 360,
-    KW_MUTABLE = 361,
-    KW_NAMESPACE = 362,
-    KW_NEW = 363,
-    KW_NOEXCEPT = 364,
-    KW_NULLPTR = 365,
-    KW_OPERATOR = 366,
-    KW_OVERRIDE = 367,
-    KW_PRIVATE = 368,
-    KW_PROTECTED = 369,
-    KW_PUBLIC = 370,
-    KW_REGISTER = 371,
-    KW_REINTERPRET_CAST = 372,
-    KW_RETURN = 373,
-    KW_SHORT = 374,
-    KW_SIGNED = 375,
-    KW_SIZEOF = 376,
-    KW_STATIC = 377,
-    KW_STATIC_ASSERT = 378,
-    KW_STATIC_CAST = 379,
-    KW_STRUCT = 380,
-    KW_TEMPLATE = 381,
-    KW_THREAD_LOCAL = 382,
-    KW_THROW = 383,
-    KW_TRUE = 384,
-    KW_TRY = 385,
-    KW_TYPEDEF = 386,
-    KW_TYPEID = 387,
-    KW_TYPENAME = 388,
-    KW_UNDERLYING_TYPE = 389,
-    KW_UNION = 390,
-    KW_UNSIGNED = 391,
-    KW_USING = 392,
-    KW_VIRTUAL = 393,
-    KW_VOID = 394,
-    KW_VOLATILE = 395,
-    KW_WCHAR_T = 396,
-    KW_WHILE = 397,
-    START_CPP = 398,
-    START_CONST_EXPR = 399,
-    START_TYPE = 400
+    KW_MAKE_MAP_KEYS_SEQ = 356,
+    KW_MAKE_MAP_PROPERTY = 357,
+    KW_MAKE_PROPERTY = 358,
+    KW_MAKE_PROPERTY2 = 359,
+    KW_MAKE_SEQ = 360,
+    KW_MAKE_SEQ_PROPERTY = 361,
+    KW_MUTABLE = 362,
+    KW_NAMESPACE = 363,
+    KW_NEW = 364,
+    KW_NOEXCEPT = 365,
+    KW_NULLPTR = 366,
+    KW_OPERATOR = 367,
+    KW_OVERRIDE = 368,
+    KW_PRIVATE = 369,
+    KW_PROTECTED = 370,
+    KW_PUBLIC = 371,
+    KW_REGISTER = 372,
+    KW_REINTERPRET_CAST = 373,
+    KW_RETURN = 374,
+    KW_SHORT = 375,
+    KW_SIGNED = 376,
+    KW_SIZEOF = 377,
+    KW_STATIC = 378,
+    KW_STATIC_ASSERT = 379,
+    KW_STATIC_CAST = 380,
+    KW_STRUCT = 381,
+    KW_TEMPLATE = 382,
+    KW_THREAD_LOCAL = 383,
+    KW_THROW = 384,
+    KW_TRUE = 385,
+    KW_TRY = 386,
+    KW_TYPEDEF = 387,
+    KW_TYPEID = 388,
+    KW_TYPENAME = 389,
+    KW_UNDERLYING_TYPE = 390,
+    KW_UNION = 391,
+    KW_UNSIGNED = 392,
+    KW_USING = 393,
+    KW_VIRTUAL = 394,
+    KW_VOID = 395,
+    KW_VOLATILE = 396,
+    KW_WCHAR_T = 397,
+    KW_WHILE = 398,
+    START_CPP = 399,
+    START_CONST_EXPR = 400,
+    START_TYPE = 401
   };
   };
 #endif
 #endif
 /* Tokens.  */
 /* Tokens.  */
@@ -289,51 +290,52 @@ extern int cppyydebug;
 #define KW_IS_TRIVIAL 353
 #define KW_IS_TRIVIAL 353
 #define KW_IS_UNION 354
 #define KW_IS_UNION 354
 #define KW_LONG 355
 #define KW_LONG 355
-#define KW_MAKE_MAP_PROPERTY 356
-#define KW_MAKE_PROPERTY 357
-#define KW_MAKE_PROPERTY2 358
-#define KW_MAKE_SEQ 359
-#define KW_MAKE_SEQ_PROPERTY 360
-#define KW_MUTABLE 361
-#define KW_NAMESPACE 362
-#define KW_NEW 363
-#define KW_NOEXCEPT 364
-#define KW_NULLPTR 365
-#define KW_OPERATOR 366
-#define KW_OVERRIDE 367
-#define KW_PRIVATE 368
-#define KW_PROTECTED 369
-#define KW_PUBLIC 370
-#define KW_REGISTER 371
-#define KW_REINTERPRET_CAST 372
-#define KW_RETURN 373
-#define KW_SHORT 374
-#define KW_SIGNED 375
-#define KW_SIZEOF 376
-#define KW_STATIC 377
-#define KW_STATIC_ASSERT 378
-#define KW_STATIC_CAST 379
-#define KW_STRUCT 380
-#define KW_TEMPLATE 381
-#define KW_THREAD_LOCAL 382
-#define KW_THROW 383
-#define KW_TRUE 384
-#define KW_TRY 385
-#define KW_TYPEDEF 386
-#define KW_TYPEID 387
-#define KW_TYPENAME 388
-#define KW_UNDERLYING_TYPE 389
-#define KW_UNION 390
-#define KW_UNSIGNED 391
-#define KW_USING 392
-#define KW_VIRTUAL 393
-#define KW_VOID 394
-#define KW_VOLATILE 395
-#define KW_WCHAR_T 396
-#define KW_WHILE 397
-#define START_CPP 398
-#define START_CONST_EXPR 399
-#define START_TYPE 400
+#define KW_MAKE_MAP_KEYS_SEQ 356
+#define KW_MAKE_MAP_PROPERTY 357
+#define KW_MAKE_PROPERTY 358
+#define KW_MAKE_PROPERTY2 359
+#define KW_MAKE_SEQ 360
+#define KW_MAKE_SEQ_PROPERTY 361
+#define KW_MUTABLE 362
+#define KW_NAMESPACE 363
+#define KW_NEW 364
+#define KW_NOEXCEPT 365
+#define KW_NULLPTR 366
+#define KW_OPERATOR 367
+#define KW_OVERRIDE 368
+#define KW_PRIVATE 369
+#define KW_PROTECTED 370
+#define KW_PUBLIC 371
+#define KW_REGISTER 372
+#define KW_REINTERPRET_CAST 373
+#define KW_RETURN 374
+#define KW_SHORT 375
+#define KW_SIGNED 376
+#define KW_SIZEOF 377
+#define KW_STATIC 378
+#define KW_STATIC_ASSERT 379
+#define KW_STATIC_CAST 380
+#define KW_STRUCT 381
+#define KW_TEMPLATE 382
+#define KW_THREAD_LOCAL 383
+#define KW_THROW 384
+#define KW_TRUE 385
+#define KW_TRY 386
+#define KW_TYPEDEF 387
+#define KW_TYPEID 388
+#define KW_TYPENAME 389
+#define KW_UNDERLYING_TYPE 390
+#define KW_UNION 391
+#define KW_UNSIGNED 392
+#define KW_USING 393
+#define KW_VIRTUAL 394
+#define KW_VOID 395
+#define KW_VOLATILE 396
+#define KW_WCHAR_T 397
+#define KW_WHILE 398
+#define START_CPP 399
+#define START_CONST_EXPR 400
+#define START_TYPE 401
 
 
 /* Value type.  */
 /* Value type.  */
 
 

+ 78 - 0
dtool/src/cppparser/cppBison.yxx

@@ -304,6 +304,7 @@ pop_struct() {
 %token KW_IS_TRIVIAL
 %token KW_IS_TRIVIAL
 %token KW_IS_UNION
 %token KW_IS_UNION
 %token KW_LONG
 %token KW_LONG
+%token KW_MAKE_MAP_KEYS_SEQ
 %token KW_MAKE_MAP_PROPERTY
 %token KW_MAKE_MAP_PROPERTY
 %token KW_MAKE_PROPERTY
 %token KW_MAKE_PROPERTY
 %token KW_MAKE_PROPERTY2
 %token KW_MAKE_PROPERTY2
@@ -680,6 +681,49 @@ declaration:
       make_property->_del_function = deleter->as_function_group();
       make_property->_del_function = deleter->as_function_group();
     }
     }
 
 
+    current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
+  }
+}
+        | KW_MAKE_SEQ_PROPERTY '(' name ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
+    length_getter = NULL;
+  }
+
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("Reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
+
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = new CPPMakeProperty($3, CPPMakeProperty::T_sequence, current_scope, @1.file);
+    make_property->_get_function = getter->as_function_group();
+    make_property->_length_function = length_getter->as_function_group();
+
+    CPPDeclaration *setter = $9->find_symbol(current_scope, global_scope, current_lexer);
+    if (setter == nullptr || setter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("Reference to non-existent or invalid setter: " + $9->get_fully_scoped_name(), @9);
+    } else {
+      make_property->_set_function = setter->as_function_group();
+    }
+
+    CPPDeclaration *deleter = $11->find_symbol(current_scope, global_scope, current_lexer);
+    if (deleter == nullptr || deleter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid delete method: " + $11->get_fully_scoped_name(), @11);
+    } else {
+      make_property->_del_function = deleter->as_function_group();
+    }
+
+    CPPDeclaration *inserter = $13->find_symbol(current_scope, global_scope, current_lexer);
+    if (inserter == nullptr || inserter->get_subtype() != CPPDeclaration::ST_function_group) {
+      yyerror("reference to non-existent or invalid append method: " + $13->get_fully_scoped_name(), @13);
+    } else {
+      make_property->_insert_function = inserter->as_function_group();
+    }
+
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
 }
 }
@@ -751,6 +795,40 @@ declaration:
 
 
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
     current_scope->add_declaration(make_property, global_scope, current_lexer, @1);
   }
   }
+}
+        | KW_MAKE_MAP_KEYS_SEQ '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
+{
+  CPPDeclaration *length_getter = $5->find_symbol(current_scope, global_scope, current_lexer);
+  if (length_getter == nullptr || length_getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid length method: " + $5->get_fully_scoped_name(), @5);
+    length_getter = nullptr;
+  }
+
+  CPPDeclaration *getter = $7->find_symbol(current_scope, global_scope, current_lexer);
+  if (getter == nullptr || getter->get_subtype() != CPPDeclaration::ST_function_group) {
+    yyerror("reference to non-existent or invalid getter: " + $7->get_fully_scoped_name(), @7);
+    getter = nullptr;
+  }
+
+  if (getter != nullptr && length_getter != nullptr) {
+    CPPMakeProperty *make_property = nullptr;
+    for (size_t i = 0; i < current_scope->_declarations.size(); ++i) {
+      make_property = current_scope->_declarations[i]->as_make_property();
+      if (make_property != nullptr) {
+        if (make_property->get_fully_scoped_name() == $3->get_fully_scoped_name()) {
+          break;
+        } else {
+          make_property = nullptr;
+        }
+      }
+    }
+    if (make_property != nullptr) {
+      make_property->_get_key_function = getter->as_function_group();
+      make_property->_length_function = length_getter->as_function_group();
+    } else {
+      yyerror("reference to non-existent MAKE_MAP_PROPERTY: " + $3->get_fully_scoped_name(), @3);
+    }
+  }
 }
 }
         | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
         | KW_MAKE_PROPERTY2 '(' name ',' IDENTIFIER ',' IDENTIFIER ')' ';'
 {
 {

+ 7 - 1
dtool/src/cppparser/cppMakeProperty.cxx

@@ -28,7 +28,9 @@ CPPMakeProperty(CPPIdentifier *ident, Type type,
   _get_function(nullptr),
   _get_function(nullptr),
   _set_function(nullptr),
   _set_function(nullptr),
   _clear_function(nullptr),
   _clear_function(nullptr),
-  _del_function(nullptr)
+  _del_function(nullptr),
+  _insert_function(nullptr),
+  _get_key_function(nullptr)
 {
 {
   _ident->_native_scope = current_scope;
   _ident->_native_scope = current_scope;
 }
 }
@@ -96,6 +98,10 @@ output(ostream &out, int indent_level, CPPScope *scope, bool complete) const {
     out << ", " << _del_function->_name;
     out << ", " << _del_function->_name;
   }
   }
 
 
+  if (_insert_function != NULL) {
+    out << ", " << _insert_function->_name;
+  }
+
   out << ");";
   out << ");";
 }
 }
 
 

+ 2 - 0
dtool/src/cppparser/cppMakeProperty.h

@@ -109,6 +109,8 @@ public:
   CPPFunctionGroup *_set_function;
   CPPFunctionGroup *_set_function;
   CPPFunctionGroup *_clear_function;
   CPPFunctionGroup *_clear_function;
   CPPFunctionGroup *_del_function;
   CPPFunctionGroup *_del_function;
+  CPPFunctionGroup *_insert_function;
+  CPPFunctionGroup *_get_key_function;
 };
 };
 
 
 #endif
 #endif

+ 1 - 0
dtool/src/cppparser/cppPreprocessor.cxx

@@ -2641,6 +2641,7 @@ check_keyword(const string &name) {
   if (name == "__is_trivial") return KW_IS_TRIVIAL;
   if (name == "__is_trivial") return KW_IS_TRIVIAL;
   if (name == "__is_union") return KW_IS_UNION;
   if (name == "__is_union") return KW_IS_UNION;
   if (name == "long") return KW_LONG;
   if (name == "long") return KW_LONG;
+  if (name == "__make_map_keys_seq") return KW_MAKE_MAP_KEYS_SEQ;
   if (name == "__make_map_property") return KW_MAKE_MAP_PROPERTY;
   if (name == "__make_map_property") return KW_MAKE_MAP_PROPERTY;
   if (name == "__make_property") return KW_MAKE_PROPERTY;
   if (name == "__make_property") return KW_MAKE_PROPERTY;
   if (name == "__make_property2") return KW_MAKE_PROPERTY2;
   if (name == "__make_property2") return KW_MAKE_PROPERTY2;

+ 2 - 0
dtool/src/dtoolbase/dtoolbase.h

@@ -465,6 +465,7 @@ typedef struct _object PyObject;
 #define MAKE_SEQ(seq_name, num_name, element_name) __make_seq(seq_name, num_name, element_name)
 #define MAKE_SEQ(seq_name, num_name, element_name) __make_seq(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...) __make_seq_property(property_name, __VA_ARGS__)
 #define MAKE_SEQ_PROPERTY(property_name, ...) __make_seq_property(property_name, __VA_ARGS__)
 #define MAKE_MAP_PROPERTY(property_name, ...) __make_map_property(property_name, __VA_ARGS__)
 #define MAKE_MAP_PROPERTY(property_name, ...) __make_map_property(property_name, __VA_ARGS__)
+#define MAKE_MAP_KEYS_SEQ(property_name, ...) __make_map_keys_seq(property_name, __VA_ARGS__)
 #define EXTENSION(x) __extension x
 #define EXTENSION(x) __extension x
 #define EXTEND __extension
 #define EXTEND __extension
 #else
 #else
@@ -476,6 +477,7 @@ typedef struct _object PyObject;
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ(seq_name, num_name, element_name)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
 #define MAKE_SEQ_PROPERTY(property_name, ...)
 #define MAKE_MAP_PROPERTY(property_name, ...)
 #define MAKE_MAP_PROPERTY(property_name, ...)
+#define MAKE_MAP_KEYS_SEQ(property_name, ...)
 #define EXTENSION(x)
 #define EXTENSION(x)
 #define EXTEND
 #define EXTEND
 #endif
 #endif

+ 2 - 0
dtool/src/interrogate/interfaceMaker.cxx

@@ -90,6 +90,8 @@ Property(const InterrogateElement &ielement) :
   _has_function(nullptr),
   _has_function(nullptr),
   _clear_function(nullptr),
   _clear_function(nullptr),
   _deleter(nullptr),
   _deleter(nullptr),
+  _inserter(nullptr),
+  _getkey_function(nullptr),
   _has_this(false)
   _has_this(false)
 {
 {
 }
 }

+ 2 - 0
dtool/src/interrogate/interfaceMaker.h

@@ -132,6 +132,8 @@ public:
     Function *_has_function;
     Function *_has_function;
     Function *_clear_function;
     Function *_clear_function;
     Function *_deleter;
     Function *_deleter;
+    Function *_inserter;
+    Function *_getkey_function;
     bool _has_this;
     bool _has_this;
   };
   };
   typedef vector<Property *> Properties;
   typedef vector<Property *> Properties;

+ 150 - 66
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -6391,6 +6391,8 @@ write_getset(ostream &out, Object *obj, Property *property) {
 
 
   FunctionRemap *len_remap = nullptr;
   FunctionRemap *len_remap = nullptr;
   if (property->_length_function != nullptr) {
   if (property->_length_function != nullptr) {
+    assert(!property->_length_function->_remaps.empty());
+
     // This is actually a sequence.  Wrap this with a special class.
     // This is actually a sequence.  Wrap this with a special class.
     len_remap = property->_length_function->_remaps.front();
     len_remap = property->_length_function->_remaps.front();
 
 
@@ -6416,6 +6418,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
   }
   }
 
 
   if (ielem.is_sequence()) {
   if (ielem.is_sequence()) {
+    assert(len_remap != nullptr);
     out <<
     out <<
       "/**\n"
       "/**\n"
       " * sequence getter for property " << ielem.get_scoped_name() << "\n"
       " * sequence getter for property " << ielem.get_scoped_name() << "\n"
@@ -6543,6 +6546,35 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  return -1;\n";
       out << "  return -1;\n";
       out << "}\n\n";
       out << "}\n\n";
     }
     }
+
+    // Finally, add the inserter, if one exists.
+    if (property->_inserter != nullptr) {
+      out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_insert(PyObject *self, size_t index, PyObject *arg) {\n";
+      if (property->_has_this) {
+        out << "  " << cClassName  << " *local_this = NULL;\n";
+        out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
+            << classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n";
+        out << "    return NULL;\n";
+        out << "  }\n\n";
+      }
+
+      std::set<FunctionRemap*> remaps;
+      remaps.insert(property->_inserter->_remaps.begin(),
+                    property->_inserter->_remaps.end());
+
+      string expected_params;
+      write_function_forset(out, remaps, 2, 2,
+                            expected_params, 2, true, true, AT_single_arg,
+                            RF_pyobject | RF_err_null, false, false, "index");
+
+      out << "  if (!_PyErr_OCCURRED()) {\n";
+      out << "    Dtool_Raise_BadArgumentsError(\n";
+      output_quoted(out, 6, expected_params);
+      out << ");\n";
+      out << "  }\n";
+      out << "  return NULL;\n";
+      out << "}\n\n";
+    }
   }
   }
 
 
 
 
@@ -6674,7 +6706,7 @@ write_getset(ostream &out, Object *obj, Property *property) {
       remaps.insert(property->_setter_remaps.begin(),
       remaps.insert(property->_setter_remaps.begin(),
                     property->_setter_remaps.end());
                     property->_setter_remaps.end());
 
 
-      // We have to create an args tuple only to unpack it alter, ugh.
+      // We have to create an args tuple only to unpack it later, ugh.
       out << "  PyObject *args = PyTuple_New(2);\n"
       out << "  PyObject *args = PyTuple_New(2);\n"
           << "  PyTuple_SET_ITEM(args, 0, key);\n"
           << "  PyTuple_SET_ITEM(args, 0, key);\n"
           << "  PyTuple_SET_ITEM(args, 1, value);\n"
           << "  PyTuple_SET_ITEM(args, 1, value);\n"
@@ -6695,86 +6727,117 @@ write_getset(ostream &out, Object *obj, Property *property) {
       out << "  return -1;\n";
       out << "  return -1;\n";
       out << "}\n\n";
       out << "}\n\n";
     }
     }
+
+    if (property->_getkey_function != nullptr) {
+      out <<
+        "/**\n"
+        " * mapping key-getter for property " << ielem.get_scoped_name() << "\n"
+        " */\n"
+        "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Getkey(PyObject *self, Py_ssize_t index) {\n";
+
+      if (property->_has_this) {
+        out <<
+          "  " << cClassName << " *local_this = NULL;\n"
+          "  if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
+          "    return NULL;\n"
+          "  }\n";
+      }
+
+      // We need to raise IndexError if we're out of bounds.
+      if (len_remap != nullptr) {
+        out << "  if (index < 0 || index >= (Py_ssize_t)"
+            << len_remap->get_call_str("local_this", pexprs) << ") {\n";
+        out << "    PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
+        out << "    return NULL;\n";
+        out << "  }\n";
+      }
+
+      std::set<FunctionRemap*> remaps;
+
+      // Extract only the getters that take one integral argument.
+      Function::Remaps::iterator it;
+      for (it = property->_getkey_function->_remaps.begin();
+            it != property->_getkey_function->_remaps.end();
+            ++it) {
+        FunctionRemap *remap = *it;
+        int min_num_args = remap->get_min_num_args();
+        int max_num_args = remap->get_max_num_args();
+        if (min_num_args <= 1 && max_num_args >= 1 &&
+            TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type())) {
+          remaps.insert(remap);
+        }
+      }
+
+      string expected_params;
+      write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
+                            AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
+
+      out << "  if (!_PyErr_OCCURRED()) {\n";
+      out << "    return Dtool_Raise_BadArgumentsError(\n";
+      output_quoted(out, 6, expected_params);
+      out << ");\n"
+              "  }\n"
+              "}\n\n";
+    }
   }
   }
 
 
   // Now write the actual getter wrapper.  It will be a different wrapper
   // Now write the actual getter wrapper.  It will be a different wrapper
-  // depending on whether it's a mapping, sequence, or both.
-  if (ielem.is_mapping() && ielem.is_sequence()) {
+  // depending on whether it's a mapping or a sequence.
+  if (ielem.is_mapping()) {
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     if (property->_has_this) {
     if (property->_has_this) {
-      out << "  nassertr(self != NULL, NULL);\n"
-             "  Py_INCREF(self);\n";
-    } else {
-      out << "  Py_XINCREF(self);\n";
-    }
-    out << "  Dtool_SeqMapWrapper *wrap = PyObject_New(Dtool_SeqMapWrapper, &Dtool_SeqMapWrapper_Type);\n"
-           "  wrap->_map._base._self = self;\n"
-           "  wrap->_map._base._name = \"" << ClassName << "." << ielem.get_name() << "\";\n"
-           "  wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
-           "  wrap->_seq_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n"
-           "  wrap->_map._getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
-    if (!property->_setter_remaps.empty()) {
-      out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n"
-             "    wrap->_seq_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n"
-             "    wrap->_map._setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n"
-             "  } else {\n"
-             "    wrap->_seq_setitem_func = NULL;\n"
-             "    wrap->_map._setitem_func = NULL;\n"
-             "  }\n";
-    } else {
-      out << "  wrap->_seq_setitem_func = NULL;\n";
-      out << "  wrap->_map._setitem_func = NULL;\n";
+      out << "  nassertr(self != NULL, NULL);\n";
     }
     }
-    out << "  return (PyObject *)wrap;\n"
-            "}\n\n";
-
-  } else if (ielem.is_mapping()) {
-    out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
-    if (property->_has_this) {
-      out << "  nassertr(self != NULL, NULL);\n"
-             "  Py_INCREF(self);\n";
+    if (property->_setter_remaps.empty()) {
+      out << "  Dtool_MappingWrapper *wrap = Dtool_NewMappingWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n";
     } else {
     } else {
-      out << "  Py_XINCREF(self);\n";
+      out << "  Dtool_MappingWrapper *wrap = Dtool_NewMutableMappingWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n";
     }
     }
-    out << "  Dtool_MappingWrapper *wrap = PyObject_New(Dtool_MappingWrapper, &Dtool_MappingWrapper_Type);\n"
-           "  wrap->_base._self = self;\n"
-           "  wrap->_base._name = \"" << ClassName << "." << ielem.get_name() << "\";\n"
-           "  wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
+    out << "  if (wrap != NULL) {\n"
+           "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
     if (!property->_setter_remaps.empty()) {
     if (!property->_setter_remaps.empty()) {
-      out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
-      out << "    wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
-      out << "  } else {\n";
-      out << "    wrap->_setitem_func = NULL;\n";
-      out << "  }\n";
-    } else {
-      out << "  wrap->_setitem_func = NULL;\n";
+      out << "    if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+      out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
+      out << "    }\n";
     }
     }
-    out << "  return (PyObject *)wrap;\n"
-           "}\n\n";
+    if (property->_length_function != nullptr) {
+      out << "    wrap->_keys._len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n";
+      if (property->_getkey_function != nullptr) {
+        out << "    wrap->_keys._getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getkey;\n";
+      }
+    }
+    out << "  }\n"
+           "  return (PyObject *)wrap;\n"
+            "}\n\n";
 
 
   } else if (ielem.is_sequence()) {
   } else if (ielem.is_sequence()) {
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
     if (property->_has_this) {
     if (property->_has_this) {
-      out << "  nassertr(self != NULL, NULL);\n"
-             "  Py_INCREF(self);\n";
-    } else {
-      out << "  Py_XINCREF(self);\n";
+      out << "  nassertr(self != NULL, NULL);\n";
     }
     }
-    out << "  Dtool_SequenceWrapper *wrap = PyObject_New(Dtool_SequenceWrapper, &Dtool_SequenceWrapper_Type);\n"
-           "  wrap->_base._self = self;\n"
-           "  wrap->_base._name = \"" << ClassName << "." << ielem.get_name() << "\";\n"
-           "  wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
-           "  wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
-    if (!property->_setter_remaps.empty()) {
-      out << "  if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
-      out << "    wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
-      out << "  } else {\n";
-      out << "    wrap->_setitem_func = NULL;\n";
-      out << "  }\n";
+    if (property->_setter_remaps.empty()) {
+      out <<
+        "  Dtool_SequenceWrapper *wrap = Dtool_NewSequenceWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n"
+        "  if (wrap != NULL) {\n"
+        "    wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
+        "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
     } else {
     } else {
-      out << "  wrap->_setitem_func = NULL;\n";
+      out <<
+        "  Dtool_MutableSequenceWrapper *wrap = Dtool_NewMutableSequenceWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n"
+        "  if (wrap != NULL) {\n"
+        "    wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
+        "    wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
+      if (!property->_setter_remaps.empty()) {
+        out << "    if (!((Dtool_PyInstDef *)self)->_is_const) {\n";
+        out << "      wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
+        if (property->_inserter != nullptr) {
+          out << "      wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";
+        }
+        out << "    }\n";
+      }
     }
     }
-    out << "  return (PyObject *)wrap;\n"
+    out << "  }\n"
+           "  return (PyObject *)wrap;\n"
             "}\n\n";
             "}\n\n";
 
 
   } else {
   } else {
@@ -7083,9 +7146,30 @@ record_property(const InterrogateType &itype, ElementIndex element_index) {
     }
     }
   }
   }
 
 
-  if (ielement.is_sequence()) {
+  if (ielement.is_sequence() || ielement.is_mapping()) {
     FunctionIndex func_index = ielement.get_length_function();
     FunctionIndex func_index = ielement.get_length_function();
-    property->_length_function = record_function(itype, func_index);
+    if (func_index != 0) {
+      property->_length_function = record_function(itype, func_index);
+    }
+  }
+
+  if (ielement.is_sequence() && ielement.has_insert_function()) {
+    FunctionIndex func_index = ielement.get_insert_function();
+    Function *insert_function = record_function(itype, func_index);
+    if (is_function_legal(insert_function)) {
+      property->_inserter = insert_function;
+      property->_has_this |= insert_function->_has_this;
+    }
+  }
+
+  if (ielement.is_mapping() && ielement.has_getkey_function()) {
+    FunctionIndex func_index = ielement.get_getkey_function();
+    assert(func_index != 0);
+    Function *getkey_function = record_function(itype, func_index);
+    if (is_function_legal(getkey_function)) {
+      property->_getkey_function = getkey_function;
+      property->_has_this |= getkey_function->_has_this;
+    }
   }
   }
 
 
   return property;
   return property;

+ 78 - 17
dtool/src/interrogate/interrogateBuilder.cxx

@@ -1824,20 +1824,17 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   // If we have a length function (ie. this is a sequence property), we should
   // If we have a length function (ie. this is a sequence property), we should
   // find the function that will give us the length.
   // find the function that will give us the length.
   FunctionIndex length_function = 0;
   FunctionIndex length_function = 0;
-  if (make_property->_type & CPPMakeProperty::T_sequence) {
+  CPPFunctionGroup *fgroup = make_property->_length_function;
+  if (fgroup != nullptr) {
     CPPFunctionGroup::Instances::const_iterator fi;
     CPPFunctionGroup::Instances::const_iterator fi;
-    CPPFunctionGroup *fgroup = make_property->_length_function;
-    if (fgroup != NULL) {
-      for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
-        CPPInstance *function = (*fi);
-        CPPFunctionType *ftype =
-          function->_type->as_function_type();
-        if (ftype != NULL) {
-          length_function = get_function(function, "", struct_type,
-                                         struct_type->get_scope(), 0);
-          if (length_function != 0) {
-            break;
-          }
+    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+      CPPInstance *function = (*fi);
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr) {
+        length_function = get_function(function, "", struct_type,
+                                       struct_type->get_scope(), 0);
+        if (length_function != 0) {
+          break;
         }
         }
       }
       }
     }
     }
@@ -1855,7 +1852,7 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   // How many arguments we expect the getter to have.
   // How many arguments we expect the getter to have.
   size_t num_args = (size_t)(make_property->_type != CPPMakeProperty::T_normal);
   size_t num_args = (size_t)(make_property->_type != CPPMakeProperty::T_normal);
 
 
-  CPPFunctionGroup *fgroup = make_property->_get_function;
+  fgroup = make_property->_get_function;
   if (fgroup != NULL) {
   if (fgroup != NULL) {
     CPPFunctionGroup::Instances::const_iterator fi;
     CPPFunctionGroup::Instances::const_iterator fi;
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
@@ -1942,9 +1939,13 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
     for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
       CPPInstance *function = (*fi);
       CPPInstance *function = (*fi);
       CPPFunctionType *ftype = function->_type->as_function_type();
       CPPFunctionType *ftype = function->_type->as_function_type();
-      if (ftype != NULL && ftype->_parameters->_parameters.size() == num_args) {
-        deleter = function;
-        break;
+      if (ftype != nullptr) {
+        const CPPParameterList::Parameters &params = ftype->_parameters->_parameters;
+        if (params.size() == num_args ||
+            (params.size() > num_args && params[num_args]->_initializer != nullptr)) {
+          deleter = function;
+          break;
+        }
       }
       }
     }
     }
 
 
@@ -1955,6 +1956,50 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     }
     }
   }
   }
 
 
+  // And the "inserter".
+  CPPInstance *inserter = nullptr;
+
+  fgroup = make_property->_insert_function;
+  if (fgroup != nullptr) {
+    CPPFunctionGroup::Instances::const_iterator fi;
+    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+      CPPInstance *function = (*fi);
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr && ftype->_parameters->_parameters.size() == 2) {
+        inserter = function;
+        break;
+      }
+    }
+
+    if (inserter == nullptr) {
+      cerr << "No instance of insert-function '"
+           << fgroup->_name << "' is suitable!\n";
+      return 0;
+    }
+  }
+
+  // And the function that returns a key by index.
+  CPPInstance *getkey_function = nullptr;
+
+  fgroup = make_property->_get_key_function;
+  if (fgroup != nullptr) {
+    CPPFunctionGroup::Instances::const_iterator fi;
+    for (fi = fgroup->_instances.begin(); fi != fgroup->_instances.end(); ++fi) {
+      CPPInstance *function = (*fi);
+      CPPFunctionType *ftype = function->_type->as_function_type();
+      if (ftype != nullptr) {
+        getkey_function = function;
+        break;
+      }
+    }
+
+    if (getkey_function == nullptr) {
+      cerr << "No instance of get-key-function '"
+           << fgroup->_name << "' is suitable!\n";
+      return 0;
+    }
+  }
+
   if (index == 0) {
   if (index == 0) {
     // It isn't here, so we'll have to define it.
     // It isn't here, so we'll have to define it.
     index = idb->get_next_index();
     index = idb->get_next_index();
@@ -1980,10 +2025,12 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
   if (make_property->_type & CPPMakeProperty::T_sequence) {
   if (make_property->_type & CPPMakeProperty::T_sequence) {
     iproperty._flags |= InterrogateElement::F_sequence;
     iproperty._flags |= InterrogateElement::F_sequence;
     iproperty._length_function = length_function;
     iproperty._length_function = length_function;
+    assert(length_function != 0);
   }
   }
 
 
   if (make_property->_type & CPPMakeProperty::T_mapping) {
   if (make_property->_type & CPPMakeProperty::T_mapping) {
     iproperty._flags |= InterrogateElement::F_mapping;
     iproperty._flags |= InterrogateElement::F_mapping;
+    iproperty._length_function = length_function;
   }
   }
 
 
   if (make_property->_type == CPPMakeProperty::T_normal) {
   if (make_property->_type == CPPMakeProperty::T_normal) {
@@ -2029,6 +2076,20 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     nassertr(iproperty._del_function, 0);
     nassertr(iproperty._del_function, 0);
   }
   }
 
 
+  if (inserter != NULL) {
+    iproperty._flags |= InterrogateElement::F_has_insert_function;
+    iproperty._insert_function = get_function(inserter, "", struct_type,
+                                              struct_type->get_scope(), 0);
+    nassertr(iproperty._insert_function, 0);
+  }
+
+  if (getkey_function != NULL) {
+    iproperty._flags |= InterrogateElement::F_has_getkey_function;
+    iproperty._getkey_function = get_function(getkey_function, "", struct_type,
+                                              struct_type->get_scope(), 0);
+    nassertr(iproperty._getkey_function, 0);
+  }
+
   // See if there happens to be a comment before the MAKE_PROPERTY macro.
   // See if there happens to be a comment before the MAKE_PROPERTY macro.
   if (make_property->_leading_comment != (CPPCommentBlock *)NULL) {
   if (make_property->_leading_comment != (CPPCommentBlock *)NULL) {
     iproperty._comment = trim_blanks(make_property->_leading_comment->_comment);
     iproperty._comment = trim_blanks(make_property->_leading_comment->_comment);

+ 1 - 1
dtool/src/interrogatedb/interrogateDatabase.cxx

@@ -20,7 +20,7 @@ InterrogateDatabase *InterrogateDatabase::_global_ptr = NULL;
 int InterrogateDatabase::_file_major_version = 0;
 int InterrogateDatabase::_file_major_version = 0;
 int InterrogateDatabase::_file_minor_version = 0;
 int InterrogateDatabase::_file_minor_version = 0;
 int InterrogateDatabase::_current_major_version = 3;
 int InterrogateDatabase::_current_major_version = 3;
-int InterrogateDatabase::_current_minor_version = 2;
+int InterrogateDatabase::_current_minor_version = 3;
 
 
 /**
 /**
  *
  *

+ 36 - 0
dtool/src/interrogatedb/interrogateElement.I

@@ -25,6 +25,8 @@ InterrogateElement(InterrogateModuleDef *def) :
   _has_function = 0;
   _has_function = 0;
   _clear_function = 0;
   _clear_function = 0;
   _del_function = 0;
   _del_function = 0;
+  _insert_function = 0;
+  _getkey_function = 0;
   _length_function = 0;
   _length_function = 0;
   _make_property = nullptr;
   _make_property = nullptr;
 }
 }
@@ -52,6 +54,8 @@ operator = (const InterrogateElement &copy) {
   _has_function = copy._has_function;
   _has_function = copy._has_function;
   _clear_function = copy._clear_function;
   _clear_function = copy._clear_function;
   _del_function = copy._del_function;
   _del_function = copy._del_function;
+  _insert_function = copy._insert_function;
+  _getkey_function = copy._getkey_function;
   _length_function = copy._length_function;
   _length_function = copy._length_function;
   _make_property = copy._make_property;
   _make_property = copy._make_property;
 }
 }
@@ -185,6 +189,38 @@ get_del_function() const {
   return _del_function;
   return _del_function;
 }
 }
 
 
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+has_insert_function() const {
+  return (_flags & F_has_insert_function) != 0;
+}
+
+/**
+ *
+ */
+INLINE FunctionIndex InterrogateElement::
+get_insert_function() const {
+  return _insert_function;
+}
+
+/**
+ *
+ */
+INLINE bool InterrogateElement::
+has_getkey_function() const {
+  return (_flags & F_has_getkey_function) != 0;
+}
+
+/**
+ *
+ */
+INLINE FunctionIndex InterrogateElement::
+get_getkey_function() const {
+  return _getkey_function;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 8 - 1
dtool/src/interrogatedb/interrogateElement.cxx

@@ -29,7 +29,9 @@ output(ostream &out) const {
       << _has_function << " "
       << _has_function << " "
       << _clear_function << " "
       << _clear_function << " "
       << _del_function << " "
       << _del_function << " "
-      << _length_function << " ";
+      << _length_function << " "
+      << _insert_function << " "
+      << _getkey_function << " ";
   idf_output_string(out, _scoped_name);
   idf_output_string(out, _scoped_name);
   idf_output_string(out, _comment, '\n');
   idf_output_string(out, _comment, '\n');
 }
 }
@@ -45,6 +47,9 @@ input(istream &in) {
     in >> _has_function >> _clear_function;
     in >> _has_function >> _clear_function;
     if (InterrogateDatabase::get_file_minor_version() >= 2) {
     if (InterrogateDatabase::get_file_minor_version() >= 2) {
       in >> _del_function >> _length_function;
       in >> _del_function >> _length_function;
+      if (InterrogateDatabase::get_file_minor_version() >= 3) {
+        in >> _insert_function >> _getkey_function;
+      }
     }
     }
   }
   }
   idf_input_string(in, _scoped_name);
   idf_input_string(in, _scoped_name);
@@ -63,5 +68,7 @@ remap_indices(const IndexRemapper &remap) {
   _has_function = remap.map_from(_has_function);
   _has_function = remap.map_from(_has_function);
   _clear_function = remap.map_from(_clear_function);
   _clear_function = remap.map_from(_clear_function);
   _del_function = remap.map_from(_del_function);
   _del_function = remap.map_from(_del_function);
+  _insert_function = remap.map_from(_insert_function);
+  _getkey_function = remap.map_from(_getkey_function);
   _length_function = remap.map_from(_length_function);
   _length_function = remap.map_from(_length_function);
 }
 }

+ 8 - 0
dtool/src/interrogatedb/interrogateElement.h

@@ -50,6 +50,10 @@ public:
   INLINE FunctionIndex get_clear_function() const;
   INLINE FunctionIndex get_clear_function() const;
   INLINE bool has_del_function() const;
   INLINE bool has_del_function() const;
   INLINE FunctionIndex get_del_function() const;
   INLINE FunctionIndex get_del_function() const;
+  INLINE bool has_insert_function() const;
+  INLINE FunctionIndex get_insert_function() const;
+  INLINE bool has_getkey_function() const;
+  INLINE FunctionIndex get_getkey_function() const;
   INLINE bool is_sequence() const;
   INLINE bool is_sequence() const;
   INLINE FunctionIndex get_length_function() const;
   INLINE FunctionIndex get_length_function() const;
   INLINE bool is_mapping() const;
   INLINE bool is_mapping() const;
@@ -69,6 +73,8 @@ private:
     F_has_del_function= 0x0020,
     F_has_del_function= 0x0020,
     F_sequence        = 0x0040,
     F_sequence        = 0x0040,
     F_mapping         = 0x0080,
     F_mapping         = 0x0080,
+    F_has_insert_function= 0x0100,
+    F_has_getkey_function= 0x0200,
   };
   };
 
 
   int _flags;
   int _flags;
@@ -81,6 +87,8 @@ private:
   FunctionIndex _has_function;
   FunctionIndex _has_function;
   FunctionIndex _clear_function;
   FunctionIndex _clear_function;
   FunctionIndex _del_function;
   FunctionIndex _del_function;
+  FunctionIndex _insert_function;
+  FunctionIndex _getkey_function;
 
 
   CPPMakeProperty *_make_property;
   CPPMakeProperty *_make_property;
 
 

+ 1 - 4
dtool/src/interrogatedb/p3interrogatedb_composite2.cxx

@@ -5,7 +5,4 @@
 #include "interrogate_interface.cxx"
 #include "interrogate_interface.cxx"
 #include "interrogate_request.cxx"
 #include "interrogate_request.cxx"
 #include "py_panda.cxx"
 #include "py_panda.cxx"
-
-
-
-
+#include "py_wrappers.cxx"

+ 18 - 955
dtool/src/interrogatedb/py_panda.cxx

@@ -614,12 +614,28 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_SequenceWrapper)");
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_SequenceWrapper)");
     }
     }
 
 
+    if (PyType_Ready(&Dtool_MutableSequenceWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MutableSequenceWrapper)");
+    }
+
     if (PyType_Ready(&Dtool_MappingWrapper_Type) < 0) {
     if (PyType_Ready(&Dtool_MappingWrapper_Type) < 0) {
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper)");
       return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper)");
     }
     }
 
 
-    if (PyType_Ready(&Dtool_SeqMapWrapper_Type) < 0) {
-      return Dtool_Raise_TypeError("PyType_Ready(Dtool_SeqMapWrapper)");
+    if (PyType_Ready(&Dtool_MutableMappingWrapper_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MutableMappingWrapper)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Keys_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Keys)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Values_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Values)");
+    }
+
+    if (PyType_Ready(&Dtool_MappingWrapper_Items_Type) < 0) {
+      return Dtool_Raise_TypeError("PyType_Ready(Dtool_MappingWrapper_Items)");
     }
     }
 
 
     if (PyType_Ready(&Dtool_GeneratorWrapper_Type) < 0) {
     if (PyType_Ready(&Dtool_GeneratorWrapper_Type) < 0) {
@@ -1039,957 +1055,4 @@ bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds)
   return (PyTuple_GET_SIZE(args) == 0);
   return (PyTuple_GET_SIZE(args) == 0);
 }
 }
 
 
-/**
- * These classes are returned from properties that require a subscript
- * interface, ie. something.children[i] = 3.
- */
-static void Dtool_WrapperBase_dealloc(PyObject *self) {
-  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
-  nassertv(wrap);
-  Py_XDECREF(wrap->_self);
-}
-
-static PyObject *Dtool_WrapperBase_repr(PyObject *self) {
-  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
-  nassertr(wrap, nullptr);
-
-  PyObject *repr = PyObject_Repr(wrap->_self);
-  PyObject *result;
-#if PY_MAJOR_VERSION >= 3
-  result = PyUnicode_FromFormat("<%s[] of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
-#else
-  result = PyString_FromFormat("<%s[] of %s>", wrap->_name, PyString_AS_STRING(repr));
-#endif
-  Py_DECREF(repr);
-  return result;
-}
-
-static PyObject *Dtool_SequenceWrapper_repr(PyObject *self) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-
-  Py_ssize_t len = -1;
-  if (wrap->_len_func != nullptr) {
-    len = wrap->_len_func(wrap->_base._self);
-  }
-
-  if (len < 0) {
-    PyErr_Restore(nullptr, nullptr, nullptr);
-    return Dtool_WrapperBase_repr(self);
-  }
-
-  PyObject *repr = PyObject_Repr(wrap->_base._self);
-  PyObject *result;
-#if PY_MAJOR_VERSION >= 3
-  result = PyUnicode_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyUnicode_AsUTF8(repr));
-#else
-  result = PyString_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyString_AS_STRING(repr));
-#endif
-  Py_DECREF(repr);
-  return result;
-}
-
-static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, -1);
-  if (wrap->_len_func != nullptr) {
-    return wrap->_len_func(wrap->_base._self);
-  } else {
-    Dtool_Raise_TypeError("property does not support len()");
-    return -1;
-  }
-}
-
-static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  nassertr(wrap->_getitem_func, nullptr);
-  return wrap->_getitem_func(wrap->_base._self, index);
-}
-
-static int Dtool_SequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, -1);
-  if (wrap->_setitem_func != nullptr) {
-    return wrap->_setitem_func(wrap->_base._self, index, value);
-  } else {
-    Dtool_Raise_TypeError("property does not support item assignment");
-    return -1;
-  }
-}
-
-/**
- * Implementation of property.index(x) which returns the index of the first
- * occurrence of x in the sequence, or raises a ValueError if it isn't found.
- */
-static PyObject *Dtool_SequenceWrapper_index(PyObject *self, PyObject *value) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  Py_ssize_t length = 0;
-  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
-    length = wrap->_len_func(wrap->_base._self);
-  } else {
-    return Dtool_Raise_TypeError("property does not support remove()");
-  }
-
-  // Iterate through the items, invoking the equality function for each, until
-  // we have found the right one.
-  nassertr(wrap->_getitem_func, nullptr);
-  for (Py_ssize_t index = 0; index < length; ++index) {
-    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
-    if (item != nullptr) {
-      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
-      if (cmp > 0) {
-        return Dtool_WrapValue(index);
-      }
-      if (cmp < 0) {
-        return nullptr;
-      }
-    } else {
-      return nullptr;
-    }
-  }
-  // Not found, raise ValueError.
-  return PyErr_Format(PyExc_ValueError, "%s.index() did not find value", wrap->_base._name);
-}
-
-/**
- * Implementation of property.count(x) which returns the number of occurrences
- * of x in the sequence.
- */
-static PyObject *Dtool_SequenceWrapper_count(PyObject *self, PyObject *value) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  Py_ssize_t index = 0;
-  if (wrap->_len_func != nullptr) {
-    index = wrap->_len_func(wrap->_base._self);
-  } else {
-    return Dtool_Raise_TypeError("property does not support count()");
-  }
-  // Iterate through the items, invoking the == operator for each.
-  long count = 0;
-  nassertr(wrap->_getitem_func, nullptr);
-  while (index > 0) {
-    --index;
-    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
-    if (item == nullptr) {
-      return nullptr;
-    }
-    int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
-    if (cmp > 0) {
-      ++count;
-    }
-    if (cmp < 0) {
-      return nullptr;
-    }
-  }
-#if PY_MAJOR_VERSION >= 3
-  return PyLong_FromLong(count);
-#else
-  return PyInt_FromLong(count);
-#endif
-}
-
-/**
- * Implementation of property.clear() which removes all elements in the
- * sequence, starting with the last.
- */
-static PyObject *Dtool_SequenceWrapper_clear(PyObject *self, PyObject *) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  Py_ssize_t index = 0;
-  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
-    index = wrap->_len_func(wrap->_base._self);
-  } else {
-    return Dtool_Raise_TypeError("property does not support clear()");
-  }
-
-  // Iterate through the items, invoking the delete function for each.  We do
-  // this in reverse order, which may be more efficient.
-  while (index > 0) {
-    --index;
-    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
-      return nullptr;
-    }
-  }
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-
-/**
- * Implementation of property.remove(x) which removes the first occurrence of
- * x in the sequence, or raises a ValueError if it isn't found.
- */
-static PyObject *Dtool_SequenceWrapper_remove(PyObject *self, PyObject *value) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  Py_ssize_t length = 0;
-  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
-    length = wrap->_len_func(wrap->_base._self);
-  } else {
-    return Dtool_Raise_TypeError("property does not support remove()");
-  }
-
-  // Iterate through the items, invoking the equality function for each, until
-  // we have found the right one.
-  nassertr(wrap->_getitem_func, nullptr);
-  for (Py_ssize_t index = 0; index < length; ++index) {
-    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
-    if (item != nullptr) {
-      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
-      if (cmp > 0) {
-        if (wrap->_setitem_func(wrap->_base._self, index, nullptr) == 0) {
-          Py_INCREF(Py_None);
-          return Py_None;
-        } else {
-          return nullptr;
-        }
-      }
-      if (cmp < 0) {
-        return nullptr;
-      }
-    } else {
-      return nullptr;
-    }
-  }
-  // Not found, raise ValueError.
-  return PyErr_Format(PyExc_ValueError, "%s.remove() did not find value", wrap->_base._name);
-}
-
-/**
- * Implementation of property.pop([i=-1]) which returns and removes the
- * element at the indicated index in the sequence.  If no index is provided,
- * it removes from the end of the list.
- */
-static PyObject *Dtool_SequenceWrapper_pop(PyObject *self, PyObject *args) {
-  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
-  nassertr(wrap, nullptr);
-  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr ||
-      wrap->_len_func == nullptr) {
-    return Dtool_Raise_TypeError("property does not support pop()");
-  }
-
-  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
-  Py_ssize_t index;
-  switch (PyTuple_GET_SIZE(args)) {
-  case 0:
-    index = length - 1;
-    break;
-  case 1:
-    index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(args, 0), PyExc_IndexError);
-    if (index == -1 && _PyErr_OCCURRED()) {
-      return nullptr;
-    }
-    if (index < 0) {
-      index += length;
-    }
-    break;
-  default:
-    return Dtool_Raise_TypeError("pop([i=-1]) takes 0 or 1 arguments");
-  }
-
-  if (length <= 0) {
-    return PyErr_Format(PyExc_IndexError, "%s.pop() from empty sequence", wrap->_base._name);
-  }
-
-  // Index error will be caught by getitem_func.
-  PyObject *value = wrap->_getitem_func(wrap->_base._self, index);
-  if (value != nullptr) {
-    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
-      return nullptr;
-    }
-    return value;
-  }
-  return nullptr;
-}
-
-static int Dtool_MappingWrapper_contains(PyObject *self, PyObject *key) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, -1);
-  nassertr(wrap->_getitem_func, -1);
-  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
-  if (value != nullptr) {
-    Py_DECREF(value);
-    return 1;
-  } else if (_PyErr_OCCURRED() == PyExc_KeyError ||
-             _PyErr_OCCURRED() == PyExc_TypeError) {
-    PyErr_Restore(nullptr, nullptr, nullptr);
-    return 0;
-  } else {
-    return -1;
-  }
-}
-
-static PyObject *Dtool_MappingWrapper_getitem(PyObject *self, PyObject *key) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, nullptr);
-  nassertr(wrap->_getitem_func, nullptr);
-  return wrap->_getitem_func(wrap->_base._self, key);
-}
-
-static int Dtool_MappingWrapper_setitem(PyObject *self, PyObject *key, PyObject *value) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, -1);
-  if (wrap->_setitem_func != nullptr) {
-    return wrap->_setitem_func(wrap->_base._self, key, value);
-  } else {
-    Dtool_Raise_TypeError("property does not support item assignment");
-    return -1;
-  }
-}
-
-/**
- * Implementation of property.get(key[,def=None]) which returns the value with
- * the given key in the mapping, or the given default value (which defaults to
- * None) if the key isn't found in the mapping.
- */
-static PyObject *Dtool_MappingWrapper_get(PyObject *self, PyObject *args) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, nullptr);
-  nassertr(wrap->_getitem_func, nullptr);
-  Py_ssize_t size = PyTuple_GET_SIZE(args);
-  if (size != 1 && size != 2) {
-    return PyErr_Format(PyExc_TypeError, "%s.get() takes 1 or 2 arguments", wrap->_base._name);
-  }
-  PyObject *defvalue = Py_None;
-  if (size >= 2) {
-    defvalue = PyTuple_GET_ITEM(args, 1);
-  }
-  PyObject *key = PyTuple_GET_ITEM(args, 0);
-  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
-  if (value != nullptr) {
-    return value;
-  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
-    PyErr_Restore(nullptr, nullptr, nullptr);
-    Py_INCREF(defvalue);
-    return defvalue;
-  } else {
-    return nullptr;
-  }
-}
-
-/**
- * Implementation of property.pop(key[,def=None]) which is the same as get()
- * except that it also removes the element from the mapping.
- */
-static PyObject *Dtool_MappingWrapper_pop(PyObject *self, PyObject *args) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, nullptr);
-  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
-    return Dtool_Raise_TypeError("property does not support pop()");
-  }
-
-  Py_ssize_t size = PyTuple_GET_SIZE(args);
-  if (size != 1 && size != 2) {
-    return PyErr_Format(PyExc_TypeError, "%s.pop() takes 1 or 2 arguments", wrap->_base._name);
-  }
-  PyObject *defvalue = Py_None;
-  if (size >= 2) {
-    defvalue = PyTuple_GET_ITEM(args, 1);
-  }
-
-  PyObject *key = PyTuple_GET_ITEM(args, 0);
-  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
-  if (value != nullptr) {
-    // OK, now set unset this value.
-    if (wrap->_setitem_func(wrap->_base._self, key, nullptr) == 0) {
-      return value;
-    } else {
-      Py_DECREF(value);
-      return nullptr;
-    }
-  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
-    PyErr_Restore(nullptr, nullptr, nullptr);
-    Py_INCREF(defvalue);
-    return defvalue;
-  } else {
-    return nullptr;
-  }
-}
-
-/**
- * Implementation of property.setdefault(key[,def=None]) which is the same as
- * get() except that it also writes the default value back to the mapping if
- * the key was not found is missing.
- */
-static PyObject *Dtool_MappingWrapper_setdefault(PyObject *self, PyObject *args) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, nullptr);
-
-  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
-    return Dtool_Raise_TypeError("property does not support setdefault()");
-  }
-
-  Py_ssize_t size = PyTuple_GET_SIZE(args);
-  if (size != 1 && size != 2) {
-    return PyErr_Format(PyExc_TypeError, "%s.setdefault() takes 1 or 2 arguments", wrap->_base._name);
-  }
-  PyObject *defvalue = Py_None;
-  if (size >= 2) {
-    defvalue = PyTuple_GET_ITEM(args, 1);
-  }
-  PyObject *key = PyTuple_GET_ITEM(args, 0);
-  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
-  if (value != nullptr) {
-    return value;
-  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
-    PyErr_Restore(nullptr, nullptr, nullptr);
-    if (wrap->_setitem_func(wrap->_base._self, key, defvalue) == 0) {
-      Py_INCREF(defvalue);
-      return defvalue;
-    }
-  }
-  return nullptr;
-}
-
-/**
- * Implementation of property.update(...) which sets multiple values in one
- * go.  It accepts either a single dictionary or keyword arguments, not both.
- */
-static PyObject *Dtool_MappingWrapper_update(PyObject *self, PyObject *args, PyObject *kwargs) {
-  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
-  nassertr(wrap, nullptr);
-
-  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
-    return Dtool_Raise_TypeError("property does not support update()");
-  }
-
-  // We accept either a dict argument or keyword arguments, but not both.
-  PyObject *dict;
-  switch (PyTuple_GET_SIZE(args)) {
-  case 0:
-    if (kwargs == nullptr) {
-      // This is legal.
-      Py_INCREF(Py_None);
-      return Py_None;
-    }
-    dict = kwargs;
-    break;
-  case 1:
-    if (PyDict_Check(PyTuple_GET_ITEM(args, 0)) && (kwargs == nullptr || Py_SIZE(kwargs) == 0)) {
-      dict = PyTuple_GET_ITEM(args, 0);
-      break;
-    }
-    // Fall through
-  default:
-    return PyErr_Format(PyExc_TypeError, "%s.update() takes either a dict argument or keyword arguments", wrap->_base._name);
-  }
-
-  PyObject *key, *value;
-  Py_ssize_t pos = 0;
-  while (PyDict_Next(dict, &pos, &key, &value)) {
-    if (wrap->_setitem_func(wrap->_base._self, key, value) != 0) {
-      return nullptr;
-    }
-  }
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-
-/**
- * Implementation of len(property) for mapping types.
- */
-static Py_ssize_t Dtool_SeqMapWrapper_length(PyObject *self) {
-  Dtool_SeqMapWrapper *wrap = (Dtool_SeqMapWrapper *)self;
-  nassertr(wrap, -1);
-  if (wrap->_len_func != nullptr) {
-    return wrap->_len_func(wrap->_map._base._self);
-  } else {
-    Dtool_Raise_TypeError("property does not support len()");
-    return -1;
-  }
-}
-
-/**
- * Implementation of property.values() which returns a tuple containing the
- * dictionary values.
- */
-static PyObject *Dtool_SeqMapWrapper_values(PyObject *self, PyObject *) {
-  Dtool_SeqMapWrapper *wrap = (Dtool_SeqMapWrapper *)self;
-  nassertr(wrap, nullptr);
-  nassertr(wrap->_len_func, nullptr);
-  nassertr(wrap->_seq_getitem_func, nullptr);
-
-  Py_ssize_t length = wrap->_len_func(wrap->_map._base._self);
-  PyObject *values = PyTuple_New(length);
-  if (UNLIKELY(values == nullptr)) {
-    return nullptr;
-  }
-
-  for (Py_ssize_t i = 0; i < length; ++i) {
-    PyObject *value = wrap->_seq_getitem_func(wrap->_map._base._self, i);
-    if (value != nullptr) {
-      PyTuple_SET_ITEM(values, i, value);
-    } else {
-      Py_DECREF(values);
-      return nullptr;
-    }
-  }
-
-  return values;
-}
-
-/**
- * This variant defines only a sequence interface.
- */
-static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
-  Dtool_SequenceWrapper_length,
-  0, // sq_concat
-  0, // sq_repeat
-  Dtool_SequenceWrapper_getitem,
-  0, // sq_slice
-  Dtool_SequenceWrapper_setitem,
-  0, // sq_ass_slice
-  0, // sq_contains
-  0, // sq_inplace_concat
-  0, // sq_inplace_repeat
-};
-
-static PyMethodDef Dtool_SequenceWrapper_Methods[] = {
-  {"index", &Dtool_SequenceWrapper_index, METH_O, nullptr},
-  {"count", &Dtool_SequenceWrapper_count, METH_O, nullptr},
-  {"clear", &Dtool_SequenceWrapper_clear, METH_NOARGS, nullptr},
-  {"pop", &Dtool_SequenceWrapper_pop, METH_VARARGS, nullptr},
-  {"remove", &Dtool_SequenceWrapper_remove, METH_O, nullptr},
-  {nullptr, nullptr, 0, nullptr}
-};
-
-PyTypeObject Dtool_SequenceWrapper_Type = {
-  PyVarObject_HEAD_INIT(NULL, 0)
-  "sequence wrapper",
-  sizeof(Dtool_SequenceWrapper),
-  0, // tp_itemsize
-  Dtool_WrapperBase_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-#if PY_MAJOR_VERSION >= 3
-  0, // tp_reserved
-#else
-  0, // tp_compare
-#endif
-  Dtool_SequenceWrapper_repr,
-  0, // tp_as_number
-  &Dtool_SequenceWrapper_SequenceMethods,
-  0, // tp_as_mapping
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  PyObject_GenericSetAttr,
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
-  0, // tp_doc
-  0, // tp_traverse
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  0, // tp_iter
-  0, // tp_iternext
-  Dtool_SequenceWrapper_Methods,
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  0, // tp_descr_get
-  0, // tp_descr_set
-  0, // tp_dictoffset
-  0, // tp_init
-  PyType_GenericAlloc,
-  0, // tp_new
-  PyObject_Del,
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
-/**
- * This variant defines only a mapping interface.
- */
-static PySequenceMethods Dtool_MappingWrapper_SequenceMethods = {
-  0, // sq_length
-  0, // sq_concat
-  0, // sq_repeat
-  0, // sq_item
-  0, // sq_slice
-  0, // sq_ass_item
-  0, // sq_ass_slice
-  Dtool_MappingWrapper_contains,
-  0, // sq_inplace_concat
-  0, // sq_inplace_repeat
-};
-
-static PyMappingMethods Dtool_MappingWrapper_MappingMethods = {
-  0, // mp_length
-  Dtool_MappingWrapper_getitem,
-  Dtool_MappingWrapper_setitem,
-};
-
-static PyMethodDef Dtool_MappingWrapper_Methods[] = {
-  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
-  {"pop", &Dtool_MappingWrapper_pop, METH_VARARGS, nullptr},
-  {"setdefault", &Dtool_MappingWrapper_setdefault, METH_VARARGS, nullptr},
-  {"update", (PyCFunction) &Dtool_MappingWrapper_update, METH_VARARGS | METH_KEYWORDS, nullptr},
-  {nullptr, nullptr, 0, nullptr}
-};
-
-PyTypeObject Dtool_MappingWrapper_Type = {
-  PyVarObject_HEAD_INIT(NULL, 0)
-  "mapping wrapper",
-  sizeof(Dtool_MappingWrapper),
-  0, // tp_itemsize
-  Dtool_WrapperBase_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-#if PY_MAJOR_VERSION >= 3
-  0, // tp_reserved
-#else
-  0, // tp_compare
-#endif
-  Dtool_WrapperBase_repr,
-  0, // tp_as_number
-  &Dtool_MappingWrapper_SequenceMethods,
-  &Dtool_MappingWrapper_MappingMethods,
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  PyObject_GenericSetAttr,
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
-  0, // tp_doc
-  0, // tp_traverse
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  0, // tp_iter
-  0, // tp_iternext
-  Dtool_MappingWrapper_Methods,
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  0, // tp_descr_get
-  0, // tp_descr_set
-  0, // tp_dictoffset
-  0, // tp_init
-  PyType_GenericAlloc,
-  0, // tp_new
-  PyObject_Del,
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
-/**
- * This variant defines both a sequence and mapping interface.
- */
-static PyMappingMethods Dtool_SeqMapWrapper_MappingMethods = {
-  Dtool_SeqMapWrapper_length,
-  Dtool_MappingWrapper_getitem,
-  Dtool_MappingWrapper_setitem,
-};
-
-static PyMethodDef Dtool_SeqMapWrapper_Methods[] = {
-  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
-  {"pop", &Dtool_MappingWrapper_pop, METH_VARARGS, nullptr},
-  {"setdefault", &Dtool_MappingWrapper_setdefault, METH_VARARGS, nullptr},
-  {"update", (PyCFunction) &Dtool_MappingWrapper_update, METH_VARARGS | METH_KEYWORDS, nullptr},
-  {"values", &Dtool_SeqMapWrapper_values, METH_NOARGS, nullptr},
-  {nullptr, nullptr, 0, nullptr}
-};
-
-PyTypeObject Dtool_SeqMapWrapper_Type = {
-  PyVarObject_HEAD_INIT(NULL, 0)
-  "sequence/mapping wrapper",
-  sizeof(Dtool_SeqMapWrapper),
-  0, // tp_itemsize
-  Dtool_WrapperBase_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-#if PY_MAJOR_VERSION >= 3
-  0, // tp_reserved
-#else
-  0, // tp_compare
-#endif
-  Dtool_WrapperBase_repr,
-  0, // tp_as_number
-  0, // tp_as_sequence
-  &Dtool_MappingWrapper_MappingMethods,
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  PyObject_GenericSetAttr,
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
-  0, // tp_doc
-  0, // tp_traverse
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  0, // tp_iter
-  0, // tp_iternext
-  Dtool_SeqMapWrapper_Methods,
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  0, // tp_descr_get
-  0, // tp_descr_set
-  0, // tp_dictoffset
-  0, // tp_init
-  PyType_GenericAlloc,
-  0, // tp_new
-  PyObject_Del,
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
-/**
- * This variant defines only a generator interface.
- */
-static PyObject *Dtool_GeneratorWrapper_iternext(PyObject *self) {
-  Dtool_GeneratorWrapper *wrap = (Dtool_GeneratorWrapper *)self;
-  nassertr(wrap, nullptr);
-  nassertr(wrap->_iternext_func, nullptr);
-  return wrap->_iternext_func(wrap->_base._self);
-}
-
-PyTypeObject Dtool_GeneratorWrapper_Type = {
-  PyVarObject_HEAD_INIT(NULL, 0)
-  "generator wrapper",
-  sizeof(Dtool_GeneratorWrapper),
-  0, // tp_itemsize
-  Dtool_WrapperBase_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-#if PY_MAJOR_VERSION >= 3
-  0, // tp_reserved
-#else
-  0, // tp_compare
-#endif
-  0, // tp_repr
-  0, // tp_as_number
-  0, // tp_as_sequence
-  0, // tp_as_mapping
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  PyObject_GenericSetAttr,
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
-  0, // tp_doc
-  0, // tp_traverse
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  PyObject_SelfIter,
-  Dtool_GeneratorWrapper_iternext,
-  0, // tp_methods
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  0, // tp_descr_get
-  0, // tp_descr_set
-  0, // tp_dictoffset
-  0, // tp_init
-  PyType_GenericAlloc,
-  0, // tp_new
-  PyObject_Del,
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
-/**
- * This is a variant of the Python getset mechanism that permits static
- * properties.
- */
-PyObject *
-Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
-  PyGetSetDescrObject *descr;
-  descr = (PyGetSetDescrObject *)PyType_GenericAlloc(&Dtool_StaticProperty_Type, 0);
-  if (descr != nullptr) {
-    Py_XINCREF(type);
-    descr->d_getset = (PyGetSetDef *)getset;
-#if PY_MAJOR_VERSION >= 3
-    descr->d_common.d_type = type;
-    descr->d_common.d_name = PyUnicode_InternFromString(getset->name);
-#if PY_VERSION_HEX >= 0x03030000
-    descr->d_common.d_qualname = nullptr;
-#endif
-#else
-    descr->d_type = type;
-    descr->d_name = PyString_InternFromString(getset->name);
-#endif
-  }
-  return (PyObject *)descr;
-}
-
-static void
-Dtool_StaticProperty_dealloc(PyDescrObject *descr) {
-  _PyObject_GC_UNTRACK(descr);
-  Py_XDECREF(descr->d_type);
-  Py_XDECREF(descr->d_name);
-//#if PY_MAJOR_VERSION >= 3
-//  Py_XDECREF(descr->d_qualname);
-//#endif
-  PyObject_GC_Del(descr);
-}
-
-static PyObject *
-Dtool_StaticProperty_repr(PyDescrObject *descr, const char *format) {
-#if PY_MAJOR_VERSION >= 3
-  return PyUnicode_FromFormat("<attribute '%s' of '%s'>",
-                              PyUnicode_AsUTF8(descr->d_name),
-                              descr->d_type->tp_name);
-#else
-  return PyString_FromFormat("<attribute '%s' of '%s'>",
-                             PyString_AS_STRING(descr->d_name),
-                             descr->d_type->tp_name);
-#endif
-}
-
-static int
-Dtool_StaticProperty_traverse(PyObject *self, visitproc visit, void *arg) {
-  PyDescrObject *descr = (PyDescrObject *)self;
-  Py_VISIT(descr->d_type);
-  return 0;
-}
-
-static PyObject *
-Dtool_StaticProperty_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) {
-  if (descr->d_getset->get != nullptr) {
-    return descr->d_getset->get(obj, descr->d_getset->closure);
-  } else {
-    return PyErr_Format(PyExc_AttributeError,
-                        "attribute '%s' of type '%.100s' is not readable",
-#if PY_MAJOR_VERSION >= 3
-                        PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
-#else
-                        PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
-#endif
-                        ((PyDescrObject *)descr)->d_type->tp_name);
-  }
-}
-
-static int
-Dtool_StaticProperty_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) {
-  if (descr->d_getset->set != nullptr) {
-    return descr->d_getset->set(obj, value, descr->d_getset->closure);
-  } else {
-    PyErr_Format(PyExc_AttributeError,
-                 "attribute '%s' of type '%.100s' is not writable",
-#if PY_MAJOR_VERSION >= 3
-                 PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
-#else
-                 PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
-#endif
-                 ((PyDescrObject *)descr)->d_type->tp_name);
-    return -1;
-  }
-}
-
-PyTypeObject Dtool_StaticProperty_Type = {
-  PyVarObject_HEAD_INIT(&PyType_Type, 0)
-  "getset_descriptor",
-  sizeof(PyGetSetDescrObject),
-  0, // tp_itemsize
-  (destructor)Dtool_StaticProperty_dealloc,
-  0, // tp_print
-  0, // tp_getattr
-  0, // tp_setattr
-  0, // tp_reserved
-  (reprfunc)Dtool_StaticProperty_repr,
-  0, // tp_as_number
-  0, // tp_as_sequence
-  0, // tp_as_mapping
-  0, // tp_hash
-  0, // tp_call
-  0, // tp_str
-  PyObject_GenericGetAttr,
-  0, // tp_setattro
-  0, // tp_as_buffer
-  Py_TPFLAGS_DEFAULT,
-  0, // tp_doc
-  Dtool_StaticProperty_traverse,
-  0, // tp_clear
-  0, // tp_richcompare
-  0, // tp_weaklistoffset
-  0, // tp_iter
-  0, // tp_iternext
-  0, // tp_methods
-  0, // tp_members
-  0, // tp_getset
-  0, // tp_base
-  0, // tp_dict
-  (descrgetfunc)Dtool_StaticProperty_get,
-  (descrsetfunc)Dtool_StaticProperty_set,
-  0, // tp_dictoffset
-  0, // tp_init
-  0, // tp_alloc
-  0, // tp_new
-  0, // tp_del
-  0, // tp_is_gc
-  0, // tp_bases
-  0, // tp_mro
-  0, // tp_cache
-  0, // tp_subclasses
-  0, // tp_weaklist
-  0, // tp_del
-#if PY_VERSION_HEX >= 0x02060000
-  0, // tp_version_tag
-#endif
-#if PY_VERSION_HEX >= 0x03040000
-  0, // tp_finalize
-#endif
-};
-
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON

+ 2 - 43
dtool/src/interrogatedb/py_panda.h

@@ -468,49 +468,6 @@ copy_from_copy_constructor(PyObject *self, PyObject *noargs);
 EXPCL_INTERROGATEDB PyObject *
 EXPCL_INTERROGATEDB PyObject *
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 map_deepcopy_to_copy(PyObject *self, PyObject *args);
 
 
-/**
- * These classes are returned from properties that require a subscript
- * interface, ie. something.children[i] = 3.
- */
-struct Dtool_WrapperBase {
-  PyObject_HEAD;
-  PyObject *_self;
-  const char *_name;
-};
-
-struct Dtool_SequenceWrapper {
-  Dtool_WrapperBase _base;
-  lenfunc _len_func;
-  ssizeargfunc _getitem_func;
-  ssizeobjargproc _setitem_func;
-};
-
-struct Dtool_MappingWrapper {
-  Dtool_WrapperBase _base;
-  binaryfunc _getitem_func;
-  objobjargproc _setitem_func;
-};
-
-struct Dtool_SeqMapWrapper {
-  Dtool_MappingWrapper _map;
-  lenfunc _len_func;
-  ssizeargfunc _seq_getitem_func;
-  ssizeobjargproc _seq_setitem_func;
-};
-
-struct Dtool_GeneratorWrapper {
-  Dtool_WrapperBase _base;
-  iternextfunc _iternext_func;
-};
-
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_GeneratorWrapper_Type;
-EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
-
-EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
-
 /**
 /**
  * These functions check whether the arguments passed to a function conform to
  * These functions check whether the arguments passed to a function conform to
  * certain expectations.
  * certain expectations.
@@ -566,6 +523,8 @@ EXPCL_INTERROGATEDB extern void Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(PyObjec
 
 
 #include "py_panda.I"
 #include "py_panda.I"
 
 
+#include "py_wrappers.h"
+
 #endif  // HAVE_PYTHON && !CPPPARSER
 #endif  // HAVE_PYTHON && !CPPPARSER
 
 
 #endif // PY_PANDA_H_
 #endif // PY_PANDA_H_

+ 1671 - 0
dtool/src/interrogatedb/py_wrappers.cxx

@@ -0,0 +1,1671 @@
+/**
+ * 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 py_wrappers.cxx
+ * @author rdb
+ * @date 2017-11-26
+ */
+
+#include "py_wrappers.h"
+
+#if PY_VERSION_HEX >= 0x03040000
+#define _COLLECTIONS_ABC "_collections_abc"
+#elif PY_VERSION_HEX >= 0x03030000
+#define _COLLECTIONS_ABC "collections.abc"
+#else
+#define _COLLECTIONS_ABC "_abcoll"
+#endif
+
+static void _register_collection(PyTypeObject *type, const char *abc) {
+  PyObject *sys_modules = PyImport_GetModuleDict();
+  if (sys_modules != nullptr) {
+    PyObject *module = PyDict_GetItemString(sys_modules, _COLLECTIONS_ABC);
+    if (module != nullptr) {
+      PyObject *dict = PyModule_GetDict(module);
+      if (module != nullptr) {
+#if PY_MAJOR_VERSION >= 3
+        static PyObject *register_str = PyUnicode_InternFromString("register");
+#else
+        static PyObject *register_str = PyString_InternFromString("register");
+#endif
+        PyObject *sequence = PyDict_GetItemString(dict, abc);
+        if (sequence != nullptr) {
+          if (PyObject_CallMethodObjArgs(sequence, register_str, (PyObject *)type, nullptr) == nullptr) {
+            PyErr_Print();
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
+ */
+static void Dtool_WrapperBase_dealloc(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertv(wrap);
+  Py_XDECREF(wrap->_self);
+}
+
+static PyObject *Dtool_WrapperBase_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s[] of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s[] of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_SequenceWrapper_repr(PyObject *self) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  Py_ssize_t len = -1;
+  if (wrap->_len_func != nullptr) {
+    len = wrap->_len_func(wrap->_base._self);
+  }
+
+  if (len < 0) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    return Dtool_WrapperBase_repr(self);
+  }
+
+  PyObject *repr = PyObject_Repr(wrap->_base._self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s[%zd] of %s>", wrap->_base._name, len, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static Py_ssize_t Dtool_SequenceWrapper_length(PyObject *self) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_len_func != nullptr) {
+    return wrap->_len_func(wrap->_base._self);
+  } else {
+    Dtool_Raise_TypeError("property does not support len()");
+    return -1;
+  }
+}
+
+static PyObject *Dtool_SequenceWrapper_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, index);
+}
+
+/**
+ * Implementation of (x in property)
+ */
+static int Dtool_SequenceWrapper_contains(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, -1);
+  nassertr(wrap->_len_func, -1);
+  nassertr(wrap->_getitem_func, -1);
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the matching one.
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        return 1;
+      }
+      if (cmp < 0) {
+        return -1;
+      }
+    } else {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/**
+ * Implementation of property.index(x) which returns the index of the first
+ * occurrence of x in the sequence, or raises a ValueError if it isn't found.
+ */
+static PyObject *Dtool_SequenceWrapper_index(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_len_func, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the right one.
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        return Dtool_WrapValue(index);
+      }
+      if (cmp < 0) {
+        return nullptr;
+      }
+    } else {
+      return nullptr;
+    }
+  }
+  // Not found, raise ValueError.
+  return PyErr_Format(PyExc_ValueError, "%s.index() did not find value", wrap->_base._name);
+}
+
+/**
+ * Implementation of property.count(x) which returns the number of occurrences
+ * of x in the sequence.
+ */
+static PyObject *Dtool_SequenceWrapper_count(PyObject *self, PyObject *value) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_len_func != nullptr) {
+    index = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support count()");
+  }
+  // Iterate through the items, invoking the == operator for each.
+  long count = 0;
+  nassertr(wrap->_getitem_func, nullptr);
+  while (index > 0) {
+    --index;
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item == nullptr) {
+      return nullptr;
+    }
+    int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+    if (cmp > 0) {
+      ++count;
+    }
+    if (cmp < 0) {
+      return nullptr;
+    }
+  }
+#if PY_MAJOR_VERSION >= 3
+  return PyLong_FromLong(count);
+#else
+  return PyInt_FromLong(count);
+#endif
+}
+
+/**
+ * Implementation of `property[i] = x`
+ */
+static int Dtool_MutableSequenceWrapper_setitem(PyObject *self, Py_ssize_t index, PyObject *value) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, -1);
+  if (wrap->_setitem_func != nullptr) {
+    return wrap->_setitem_func(wrap->_base._self, index, value);
+  } else {
+    Dtool_Raise_TypeError("property does not support item assignment");
+    return -1;
+  }
+}
+
+/**
+ * Implementation of property.clear() which removes all elements in the
+ * sequence, starting with the last.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_clear(PyObject *self, PyObject *) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
+    index = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support clear()");
+  }
+
+  // Iterate through the items, invoking the delete function for each.  We do
+  // this in reverse order, which may be more efficient.
+  while (index > 0) {
+    --index;
+    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
+      return nullptr;
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of property.remove(x) which removes the first occurrence of
+ * x in the sequence, or raises a ValueError if it isn't found.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_remove(PyObject *self, PyObject *value) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t length = 0;
+  if (wrap->_len_func != nullptr && wrap->_setitem_func != nullptr) {
+    length = wrap->_len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support remove()");
+  }
+
+  // Iterate through the items, invoking the equality function for each, until
+  // we have found the right one.
+  nassertr(wrap->_getitem_func, nullptr);
+  for (Py_ssize_t index = 0; index < length; ++index) {
+    PyObject *item = wrap->_getitem_func(wrap->_base._self, index);
+    if (item != nullptr) {
+      int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
+      if (cmp > 0) {
+        if (wrap->_setitem_func(wrap->_base._self, index, nullptr) == 0) {
+          Py_INCREF(Py_None);
+          return Py_None;
+        } else {
+          return nullptr;
+        }
+      }
+      if (cmp < 0) {
+        return nullptr;
+      }
+    } else {
+      return nullptr;
+    }
+  }
+  // Not found, raise ValueError.
+  return PyErr_Format(PyExc_ValueError, "%s.remove() did not find value", wrap->_base._name);
+}
+
+/**
+ * Implementation of property.pop([i=-1]) which returns and removes the
+ * element at the indicated index in the sequence.  If no index is provided,
+ * it removes from the end of the list.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_pop(PyObject *self, PyObject *args) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr ||
+      wrap->_len_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support pop()");
+  }
+
+  Py_ssize_t length = wrap->_len_func(wrap->_base._self);
+  Py_ssize_t index;
+  switch (PyTuple_GET_SIZE(args)) {
+  case 0:
+    index = length - 1;
+    break;
+  case 1:
+    index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(args, 0), PyExc_IndexError);
+    if (index == -1 && _PyErr_OCCURRED()) {
+      return nullptr;
+    }
+    if (index < 0) {
+      index += length;
+    }
+    break;
+  default:
+    return Dtool_Raise_TypeError("pop([i=-1]) takes 0 or 1 arguments");
+  }
+
+  if (length <= 0) {
+    return PyErr_Format(PyExc_IndexError, "%s.pop() from empty sequence", wrap->_base._name);
+  }
+
+  // Index error will be caught by getitem_func.
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, index);
+  if (value != nullptr) {
+    if (wrap->_setitem_func(wrap->_base._self, index, nullptr) != 0) {
+      return nullptr;
+    }
+    return value;
+  }
+  return nullptr;
+}
+
+/**
+ * Implementation of property.append(x) which is an alias for
+ * property.insert(len(property), x).
+ */
+static PyObject *Dtool_MutableSequenceWrapper_append(PyObject *self, PyObject *arg) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support append()");
+  }
+  return wrap->_insert_func(wrap->_base._self, (size_t)-1, arg);
+}
+
+/**
+ * Implementation of property.insert(i, x) which inserts the given item at the
+ * given position.
+ */
+static PyObject *Dtool_MutableSequenceWrapper_insert(PyObject *self, PyObject *args) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support insert()");
+  }
+  if (PyTuple_GET_SIZE(args) != 2) {
+    return Dtool_Raise_TypeError("insert() takes exactly 2 arguments");
+  }
+  Py_ssize_t index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(args, 0), PyExc_IndexError);
+  if (index == -1 && _PyErr_OCCURRED()) {
+    return nullptr;
+  }
+  if (index < 0) {
+    if (wrap->_len_func != nullptr) {
+      index += wrap->_len_func(wrap->_base._self);
+    } else {
+      return PyErr_Format(PyExc_TypeError, "%s.insert() does not support negative indices", wrap->_base._name);
+    }
+  }
+  return wrap->_insert_func(wrap->_base._self, (ssize_t)max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
+}
+
+/**
+ * Implementation of property.extend(seq) which is equivalent to:
+ * @code
+ * for x in seq:
+ *   property.append(seq)
+ * @endcode
+ */
+static PyObject *Dtool_MutableSequenceWrapper_extend(PyObject *self, PyObject *arg) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_insert_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support extend()");
+  }
+  PyObject *iter = PyObject_GetIter(arg);
+  if (iter == nullptr) {
+    return nullptr;
+  }
+  PyObject *next = PyIter_Next(iter);
+  PyObject *retval = nullptr;
+  while (next != nullptr) {
+    retval = wrap->_insert_func(wrap->_base._self, (size_t)-1, next);
+    Py_DECREF(next);
+    if (retval == nullptr) {
+      Py_DECREF(iter);
+      return nullptr;
+    }
+    Py_DECREF(retval);
+    next = PyIter_Next(iter);
+  }
+
+  Py_DECREF(iter);
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of `x in mapping`.
+ */
+static int Dtool_MappingWrapper_contains(PyObject *self, PyObject *key) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, -1);
+  nassertr(wrap->_getitem_func, -1);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    Py_DECREF(value);
+    return 1;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError ||
+             _PyErr_OCCURRED() == PyExc_TypeError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    return 0;
+  } else {
+    return -1;
+  }
+}
+
+static PyObject *Dtool_MappingWrapper_getitem(PyObject *self, PyObject *key) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  return wrap->_getitem_func(wrap->_base._self, key);
+}
+
+/**
+ * Implementation of iter(property) that returns an iterable over all the
+ * keys.
+ */
+static PyObject *Dtool_MappingWrapper_iter(PyObject *self) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return PyErr_Format(PyExc_TypeError, "%s is not iterable", wrap->_base._name);
+  }
+
+  Dtool_SequenceWrapper *keys = Dtool_NewSequenceWrapper(wrap->_base._self, wrap->_base._name);
+  if (keys != nullptr) {
+    keys->_len_func = wrap->_keys._len_func;
+    keys->_getitem_func = wrap->_keys._getitem_func;
+    return PySeqIter_New((PyObject *)keys);
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.get(key[,def=None]) which returns the value with
+ * the given key in the mapping, or the given default value (which defaults to
+ * None) if the key isn't found in the mapping.
+ */
+static PyObject *Dtool_MappingWrapper_get(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.get() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    return value;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    Py_INCREF(defvalue);
+    return defvalue;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.keys(...) that returns a view of all the keys.
+ */
+static PyObject *Dtool_MappingWrapper_keys(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support keys()");
+  }
+
+  Dtool_MappingWrapper *keys = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (keys == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Keys_Type, "MappingView");
+  }
+
+  PyObject_INIT(keys, &Dtool_MappingWrapper_Keys_Type);
+  Py_XINCREF(wrap->_base._self);
+  keys->_base._self = wrap->_base._self;
+  keys->_base._name = wrap->_base._name;
+  keys->_keys._len_func = wrap->_keys._len_func;
+  keys->_keys._getitem_func = wrap->_keys._getitem_func;
+  keys->_getitem_func = wrap->_getitem_func;
+  keys->_setitem_func = nullptr;
+  return (PyObject *)keys;
+}
+
+/**
+ * Implementation of property.values(...) that returns a view of the values.
+ */
+static PyObject *Dtool_MappingWrapper_values(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support values()");
+  }
+
+  Dtool_MappingWrapper *values = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (values == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Values_Type, "ValuesView");
+  }
+
+  PyObject_INIT(values, &Dtool_MappingWrapper_Values_Type);
+  Py_XINCREF(wrap->_base._self);
+  values->_base._self = wrap->_base._self;
+  values->_base._name = wrap->_base._name;
+  values->_keys._len_func = wrap->_keys._len_func;
+  values->_keys._getitem_func = wrap->_keys._getitem_func;
+  values->_getitem_func = wrap->_getitem_func;
+  values->_setitem_func = nullptr;
+  return (PyObject *)values;
+}
+
+/**
+ * Implementation of property.items(...) that returns an iterable yielding a
+ * `(key, value)` tuple for every item.
+ */
+static PyObject *Dtool_MappingWrapper_items(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_getitem_func, nullptr);
+
+  if (wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support items()");
+  }
+
+  Dtool_MappingWrapper *items = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (items == nullptr) {
+    return PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Items_Type, "MappingView");
+  }
+
+  PyObject_INIT(items, &Dtool_MappingWrapper_Items_Type);
+  Py_XINCREF(wrap->_base._self);
+  items->_base._self = wrap->_base._self;
+  items->_base._name = wrap->_base._name;
+  items->_keys._len_func = wrap->_keys._len_func;
+  items->_keys._getitem_func = wrap->_keys._getitem_func;
+  items->_getitem_func = wrap->_getitem_func;
+  items->_setitem_func = nullptr;
+  return (PyObject *)items;
+}
+
+/**
+ * Implementation of `property[key] = value`
+ */
+static int Dtool_MutableMappingWrapper_setitem(PyObject *self, PyObject *key, PyObject *value) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap->_setitem_func != nullptr, -1);
+  return wrap->_setitem_func(wrap->_base._self, key, value);
+}
+
+/**
+ * Implementation of property.pop(key[,def=None]) which is the same as get()
+ * except that it also removes the element from the mapping.
+ */
+static PyObject *Dtool_MutableMappingWrapper_pop(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support pop()");
+  }
+
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.pop() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    // OK, now set unset this value.
+    if (wrap->_setitem_func(wrap->_base._self, key, nullptr) == 0) {
+      return value;
+    } else {
+      Py_DECREF(value);
+      return nullptr;
+    }
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    Py_INCREF(defvalue);
+    return defvalue;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Implementation of property.popitem() which returns and removes an arbitrary
+ * (key, value) pair from the mapping.  Useful for destructive iteration.
+ */
+static PyObject *Dtool_MutableMappingWrapper_popitem(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr ||
+      wrap->_keys._len_func == nullptr || wrap->_keys._getitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support popitem()");
+  }
+
+  Py_ssize_t length = wrap->_keys._len_func(wrap->_base._self);
+  if (length < 1) {
+    return PyErr_Format(PyExc_KeyError, "%s is empty", wrap->_base._name);
+  }
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, length - 1);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    if (value != nullptr) {
+      // OK, now set unset this value.
+      if (wrap->_setitem_func(wrap->_base._self, key, nullptr) == 0) {
+        PyObject *item = PyTuple_New(2);
+        PyTuple_SET_ITEM(item, 0, key);
+        PyTuple_SET_ITEM(item, 1, value);
+        return item;
+      }
+      Py_DECREF(value);
+    }
+  }
+  return nullptr;
+}
+
+/*
+ * Implementation of property.clear() which removes all elements in the
+ * mapping.
+ */
+static PyObject *Dtool_MutableMappingWrapper_clear(PyObject *self, PyObject *) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  Py_ssize_t index = 0;
+  if (wrap->_keys._len_func != nullptr && wrap->_keys._getitem_func != nullptr &&
+      wrap->_setitem_func != nullptr) {
+    index = wrap->_keys._len_func(wrap->_base._self);
+  } else {
+    return Dtool_Raise_TypeError("property does not support clear()");
+  }
+
+  // Iterate through the items, invoking the delete function for each.  We do
+  // this in reverse order, which may be more efficient.
+  while (index > 0) {
+    --index;
+    PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+    if (key != nullptr) {
+      int result = wrap->_setitem_func(wrap->_base._self, key, nullptr);
+      Py_DECREF(key);
+      if (result != 0) {
+        return nullptr;
+      }
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * Implementation of property.setdefault(key[,def=None]) which is the same as
+ * get() except that it also writes the default value back to the mapping if
+ * the key was not found is missing.
+ */
+static PyObject *Dtool_MutableMappingWrapper_setdefault(PyObject *self, PyObject *args) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support setdefault()");
+  }
+
+  Py_ssize_t size = PyTuple_GET_SIZE(args);
+  if (size != 1 && size != 2) {
+    return PyErr_Format(PyExc_TypeError, "%s.setdefault() takes 1 or 2 arguments", wrap->_base._name);
+  }
+  PyObject *defvalue = Py_None;
+  if (size >= 2) {
+    defvalue = PyTuple_GET_ITEM(args, 1);
+  }
+  PyObject *key = PyTuple_GET_ITEM(args, 0);
+  PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+  if (value != nullptr) {
+    return value;
+  } else if (_PyErr_OCCURRED() == PyExc_KeyError) {
+    PyErr_Restore(nullptr, nullptr, nullptr);
+    if (wrap->_setitem_func(wrap->_base._self, key, defvalue) == 0) {
+      Py_INCREF(defvalue);
+      return defvalue;
+    }
+  }
+  return nullptr;
+}
+
+/**
+ * Implementation of property.update(...) which sets multiple values in one
+ * go.  It accepts either a single dictionary or keyword arguments, not both.
+ */
+static PyObject *Dtool_MutableMappingWrapper_update(PyObject *self, PyObject *args, PyObject *kwargs) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+
+  if (wrap->_getitem_func == nullptr || wrap->_setitem_func == nullptr) {
+    return Dtool_Raise_TypeError("property does not support update()");
+  }
+
+  // We accept either a dict argument or keyword arguments, but not both.
+  PyObject *dict;
+  switch (PyTuple_GET_SIZE(args)) {
+  case 0:
+    if (kwargs == nullptr) {
+      // This is legal.
+      Py_INCREF(Py_None);
+      return Py_None;
+    }
+    dict = kwargs;
+    break;
+  case 1:
+    if (PyDict_Check(PyTuple_GET_ITEM(args, 0)) && (kwargs == nullptr || Py_SIZE(kwargs) == 0)) {
+      dict = PyTuple_GET_ITEM(args, 0);
+      break;
+    }
+    // Fall through
+  default:
+    return PyErr_Format(PyExc_TypeError, "%s.update() takes either a dict argument or keyword arguments", wrap->_base._name);
+  }
+
+  PyObject *key, *value;
+  Py_ssize_t pos = 0;
+  while (PyDict_Next(dict, &pos, &key, &value)) {
+    if (wrap->_setitem_func(wrap->_base._self, key, value) != 0) {
+      return nullptr;
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/**
+ * This variant defines only a sequence interface.
+ */
+static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_SequenceWrapper_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_SequenceWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+static PyMethodDef Dtool_SequenceWrapper_Methods[] = {
+  {"index", &Dtool_SequenceWrapper_index, METH_O, nullptr},
+  {"count", &Dtool_SequenceWrapper_count, METH_O, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_SequenceWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_SequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_SequenceWrapper_repr,
+  0, // tp_as_number
+  &Dtool_SequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  Dtool_SequenceWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is a variant on SequenceWrapper that also has an insert() method.
+ */
+static PySequenceMethods Dtool_MutableSequenceWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_SequenceWrapper_getitem,
+  0, // sq_slice
+  Dtool_MutableSequenceWrapper_setitem,
+  0, // sq_ass_slice
+  Dtool_SequenceWrapper_contains,
+  Dtool_MutableSequenceWrapper_extend,
+  0, // sq_inplace_repeat
+};
+
+static PyMethodDef Dtool_MutableSequenceWrapper_Methods[] = {
+  {"index", &Dtool_SequenceWrapper_index, METH_O, nullptr},
+  {"count", &Dtool_SequenceWrapper_count, METH_O, nullptr},
+  {"clear", &Dtool_MutableSequenceWrapper_clear, METH_NOARGS, nullptr},
+  {"pop", &Dtool_MutableSequenceWrapper_pop, METH_VARARGS, nullptr},
+  {"remove", &Dtool_MutableSequenceWrapper_remove, METH_O, nullptr},
+  {"append", &Dtool_MutableSequenceWrapper_append, METH_O, nullptr},
+  {"insert", &Dtool_MutableSequenceWrapper_insert, METH_VARARGS, nullptr},
+  {"extend", &Dtool_MutableSequenceWrapper_extend, METH_O, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MutableSequenceWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MutableSequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_SequenceWrapper_repr,
+  0, // tp_as_number
+  &Dtool_MutableSequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  Dtool_MutableSequenceWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a mapping interface.
+ */
+static PySequenceMethods Dtool_MappingWrapper_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  0, // sq_item
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+static PyMappingMethods Dtool_MappingWrapper_MappingMethods = {
+  Dtool_SequenceWrapper_length,
+  Dtool_MappingWrapper_getitem,
+  0, // mp_ass_subscript
+};
+
+static PyMethodDef Dtool_MappingWrapper_Methods[] = {
+  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
+  {"keys", &Dtool_MappingWrapper_keys, METH_NOARGS, nullptr},
+  {"values", &Dtool_MappingWrapper_values, METH_NOARGS, nullptr},
+  {"items", &Dtool_MappingWrapper_items, METH_NOARGS, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MappingWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "mapping wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_WrapperBase_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_SequenceMethods,
+  &Dtool_MappingWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  Dtool_MappingWrapper_iter,
+  0, // tp_iternext
+  Dtool_MappingWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a mutable mapping interface.
+ */
+static PyMappingMethods Dtool_MutableMappingWrapper_MappingMethods = {
+  Dtool_SequenceWrapper_length,
+  Dtool_MappingWrapper_getitem,
+  Dtool_MutableMappingWrapper_setitem,
+};
+
+static PyMethodDef Dtool_MutableMappingWrapper_Methods[] = {
+  {"get", &Dtool_MappingWrapper_get, METH_VARARGS, nullptr},
+  {"pop", &Dtool_MutableMappingWrapper_pop, METH_VARARGS, nullptr},
+  {"popitem", &Dtool_MutableMappingWrapper_popitem, METH_NOARGS, nullptr},
+  {"clear", &Dtool_MutableMappingWrapper_clear, METH_VARARGS, nullptr},
+  {"setdefault", &Dtool_MutableMappingWrapper_setdefault, METH_VARARGS, nullptr},
+  {"update", (PyCFunction) &Dtool_MutableMappingWrapper_update, METH_VARARGS | METH_KEYWORDS, nullptr},
+  {"keys", &Dtool_MappingWrapper_keys, METH_NOARGS, nullptr},
+  {"values", &Dtool_MappingWrapper_values, METH_NOARGS, nullptr},
+  {"items", &Dtool_MappingWrapper_items, METH_NOARGS, nullptr},
+  {nullptr, nullptr, 0, nullptr}
+};
+
+PyTypeObject Dtool_MutableMappingWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "mapping wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_WrapperBase_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_SequenceMethods,
+  &Dtool_MutableMappingWrapper_MappingMethods,
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  Dtool_MappingWrapper_iter,
+  0, // tp_iternext
+  Dtool_MutableMappingWrapper_Methods,
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.items().
+ */
+static PyObject *Dtool_MappingWrapper_Items_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.items() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.items() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_MappingWrapper_Items_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_keys._getitem_func, nullptr);
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    if (value != nullptr) {
+      // PyTuple_SET_ITEM steals the reference.
+      PyObject *item = PyTuple_New(2);
+      PyTuple_SET_ITEM(item, 0, key);
+      PyTuple_SET_ITEM(item, 1, value);
+      return item;
+    } else {
+      Py_DECREF(key);
+    }
+  }
+  return nullptr;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Items_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Items_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Items_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Items_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_Items_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.keys().
+ */
+static PyObject *Dtool_MappingWrapper_Keys_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.keys() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.keys() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Keys_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Items_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Keys_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_SequenceWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Keys_repr,
+  0, // tp_as_number
+  &Dtool_SequenceWrapper_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is returned by mapping.values().
+ */
+static PyObject *Dtool_MappingWrapper_Values_repr(PyObject *self) {
+  Dtool_WrapperBase *wrap = (Dtool_WrapperBase *)self;
+  nassertr(wrap, nullptr);
+
+  PyObject *repr = PyObject_Repr(wrap->_self);
+  PyObject *result;
+#if PY_MAJOR_VERSION >= 3
+  result = PyUnicode_FromFormat("<%s.values() of %s>", wrap->_name, PyUnicode_AsUTF8(repr));
+#else
+  result = PyString_FromFormat("<%s.values() of %s>", wrap->_name, PyString_AS_STRING(repr));
+#endif
+  Py_DECREF(repr);
+  return result;
+}
+
+static PyObject *Dtool_MappingWrapper_Values_getitem(PyObject *self, Py_ssize_t index) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_keys._getitem_func, nullptr);
+
+  PyObject *key = wrap->_keys._getitem_func(wrap->_base._self, index);
+  if (key != nullptr) {
+    PyObject *value = wrap->_getitem_func(wrap->_base._self, key);
+    Py_DECREF(key);
+    return value;
+  }
+  return nullptr;
+}
+
+static PySequenceMethods Dtool_MappingWrapper_Values_SequenceMethods = {
+  Dtool_SequenceWrapper_length,
+  0, // sq_concat
+  0, // sq_repeat
+  Dtool_MappingWrapper_Values_getitem,
+  0, // sq_slice
+  0, // sq_ass_item
+  0, // sq_ass_slice
+  Dtool_MappingWrapper_contains,
+  0, // sq_inplace_concat
+  0, // sq_inplace_repeat
+};
+
+PyTypeObject Dtool_MappingWrapper_Values_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "sequence wrapper",
+  sizeof(Dtool_MappingWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  Dtool_MappingWrapper_Values_repr,
+  0, // tp_as_number
+  &Dtool_MappingWrapper_Values_SequenceMethods,
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PySeqIter_New,
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This variant defines only a generator interface.
+ */
+static PyObject *Dtool_GeneratorWrapper_iternext(PyObject *self) {
+  Dtool_GeneratorWrapper *wrap = (Dtool_GeneratorWrapper *)self;
+  nassertr(wrap, nullptr);
+  nassertr(wrap->_iternext_func, nullptr);
+  return wrap->_iternext_func(wrap->_base._self);
+}
+
+PyTypeObject Dtool_GeneratorWrapper_Type = {
+  PyVarObject_HEAD_INIT(nullptr, 0)
+  "generator wrapper",
+  sizeof(Dtool_GeneratorWrapper),
+  0, // tp_itemsize
+  Dtool_WrapperBase_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_compare
+  0, // tp_repr
+  0, // tp_as_number
+  0, // tp_as_sequence
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  PyObject_GenericSetAttr,
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
+  0, // tp_doc
+  0, // tp_traverse
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  PyObject_SelfIter,
+  Dtool_GeneratorWrapper_iternext,
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  0, // tp_descr_get
+  0, // tp_descr_set
+  0, // tp_dictoffset
+  0, // tp_init
+  PyType_GenericAlloc,
+  0, // tp_new
+  PyObject_Del,
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This is a variant of the Python getset mechanism that permits static
+ * properties.
+ */
+static void
+Dtool_StaticProperty_dealloc(PyDescrObject *descr) {
+  _PyObject_GC_UNTRACK(descr);
+  Py_XDECREF(descr->d_type);
+  Py_XDECREF(descr->d_name);
+//#if PY_MAJOR_VERSION >= 3
+//  Py_XDECREF(descr->d_qualname);
+//#endif
+  PyObject_GC_Del(descr);
+}
+
+static PyObject *
+Dtool_StaticProperty_repr(PyDescrObject *descr, const char *format) {
+#if PY_MAJOR_VERSION >= 3
+  return PyUnicode_FromFormat("<attribute '%s' of '%s'>",
+                              PyUnicode_AsUTF8(descr->d_name),
+                              descr->d_type->tp_name);
+#else
+  return PyString_FromFormat("<attribute '%s' of '%s'>",
+                             PyString_AS_STRING(descr->d_name),
+                             descr->d_type->tp_name);
+#endif
+}
+
+static int
+Dtool_StaticProperty_traverse(PyObject *self, visitproc visit, void *arg) {
+  PyDescrObject *descr = (PyDescrObject *)self;
+  Py_VISIT(descr->d_type);
+  return 0;
+}
+
+static PyObject *
+Dtool_StaticProperty_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) {
+  if (descr->d_getset->get != nullptr) {
+    return descr->d_getset->get(obj, descr->d_getset->closure);
+  } else {
+    return PyErr_Format(PyExc_AttributeError,
+                        "attribute '%s' of type '%.100s' is not readable",
+#if PY_MAJOR_VERSION >= 3
+                        PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
+#else
+                        PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
+#endif
+                        ((PyDescrObject *)descr)->d_type->tp_name);
+  }
+}
+
+static int
+Dtool_StaticProperty_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) {
+  if (descr->d_getset->set != nullptr) {
+    return descr->d_getset->set(obj, value, descr->d_getset->closure);
+  } else {
+    PyErr_Format(PyExc_AttributeError,
+                 "attribute '%s' of type '%.100s' is not writable",
+#if PY_MAJOR_VERSION >= 3
+                 PyUnicode_AsUTF8(((PyDescrObject *)descr)->d_name),
+#else
+                 PyString_AS_STRING(((PyDescrObject *)descr)->d_name),
+#endif
+                 ((PyDescrObject *)descr)->d_type->tp_name);
+    return -1;
+  }
+}
+
+PyTypeObject Dtool_StaticProperty_Type = {
+  PyVarObject_HEAD_INIT(&PyType_Type, 0)
+  "getset_descriptor",
+  sizeof(PyGetSetDescrObject),
+  0, // tp_itemsize
+  (destructor)Dtool_StaticProperty_dealloc,
+  0, // tp_print
+  0, // tp_getattr
+  0, // tp_setattr
+  0, // tp_reserved
+  (reprfunc)Dtool_StaticProperty_repr,
+  0, // tp_as_number
+  0, // tp_as_sequence
+  0, // tp_as_mapping
+  0, // tp_hash
+  0, // tp_call
+  0, // tp_str
+  PyObject_GenericGetAttr,
+  0, // tp_setattro
+  0, // tp_as_buffer
+  Py_TPFLAGS_DEFAULT,
+  0, // tp_doc
+  Dtool_StaticProperty_traverse,
+  0, // tp_clear
+  0, // tp_richcompare
+  0, // tp_weaklistoffset
+  0, // tp_iter
+  0, // tp_iternext
+  0, // tp_methods
+  0, // tp_members
+  0, // tp_getset
+  0, // tp_base
+  0, // tp_dict
+  (descrgetfunc)Dtool_StaticProperty_get,
+  (descrsetfunc)Dtool_StaticProperty_set,
+  0, // tp_dictoffset
+  0, // tp_init
+  0, // tp_alloc
+  0, // tp_new
+  0, // tp_del
+  0, // tp_is_gc
+  0, // tp_bases
+  0, // tp_mro
+  0, // tp_cache
+  0, // tp_subclasses
+  0, // tp_weaklist
+  0, // tp_del
+};
+
+/**
+ * This wraps around a property that exposes a sequence interface.
+ */
+Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name) {
+  Dtool_SequenceWrapper *wrap = (Dtool_SequenceWrapper *)PyObject_MALLOC(sizeof(Dtool_SequenceWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_SequenceWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableSequenceWrapper_Type, "Sequence");
+  }
+
+  PyObject_INIT(wrap, &Dtool_SequenceWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_len_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a property that exposes a mutable sequence interface.
+ */
+Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name) {
+  Dtool_MutableSequenceWrapper *wrap = (Dtool_MutableSequenceWrapper *)PyObject_MALLOC(sizeof(Dtool_MutableSequenceWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MutableSequenceWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableSequenceWrapper_Type, "MutableSequence");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MutableSequenceWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_len_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  wrap->_insert_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a mapping interface, with getitem function.
+ */
+Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MappingWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MappingWrapper_Type, "Mapping");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MappingWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_keys._len_func = nullptr;
+  wrap->_keys._getitem_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This wraps around a mapping interface, with getitem/setitem functions.
+ */
+Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name) {
+  Dtool_MappingWrapper *wrap = (Dtool_MappingWrapper *)PyObject_MALLOC(sizeof(Dtool_MappingWrapper));
+  if (wrap == nullptr) {
+    return (Dtool_MappingWrapper *)PyErr_NoMemory();
+  }
+
+  // If the collections.abc module is loaded, register this as a subclass.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    _register_collection((PyTypeObject *)&Dtool_MutableMappingWrapper_Type, "MutableMapping");
+  }
+
+  PyObject_INIT(wrap, &Dtool_MutableMappingWrapper_Type);
+  Py_XINCREF(self);
+  wrap->_base._self = self;
+  wrap->_base._name = name;
+  wrap->_keys._len_func = nullptr;
+  wrap->_keys._getitem_func = nullptr;
+  wrap->_getitem_func = nullptr;
+  wrap->_setitem_func = nullptr;
+  return wrap;
+}
+
+/**
+ * This is a variant of the Python getset mechanism that permits static
+ * properties.
+ */
+PyObject *
+Dtool_NewStaticProperty(PyTypeObject *type, const PyGetSetDef *getset) {
+  PyGetSetDescrObject *descr;
+  descr = (PyGetSetDescrObject *)PyType_GenericAlloc(&Dtool_StaticProperty_Type, 0);
+  if (descr != nullptr) {
+    Py_XINCREF(type);
+    descr->d_getset = (PyGetSetDef *)getset;
+#if PY_MAJOR_VERSION >= 3
+    descr->d_common.d_type = type;
+    descr->d_common.d_name = PyUnicode_InternFromString(getset->name);
+#if PY_VERSION_HEX >= 0x03030000
+    descr->d_common.d_qualname = nullptr;
+#endif
+#else
+    descr->d_type = type;
+    descr->d_name = PyString_InternFromString(getset->name);
+#endif
+  }
+  return (PyObject *)descr;
+}

+ 74 - 0
dtool/src/interrogatedb/py_wrappers.h

@@ -0,0 +1,74 @@
+/**
+ * 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 py_wrappers.h
+ * @author rdb
+ * @date 2017-11-26
+ */
+
+#ifndef PY_WRAPPERS_H
+#define PY_WRAPPERS_H
+
+#include "py_panda.h"
+
+/**
+ * These classes are returned from properties that require a subscript
+ * interface, ie. something.children[i] = 3.
+ */
+struct Dtool_WrapperBase {
+  PyObject_HEAD;
+  PyObject *_self;
+  const char *_name;
+};
+
+struct Dtool_SequenceWrapper {
+  Dtool_WrapperBase _base;
+  lenfunc _len_func;
+  ssizeargfunc _getitem_func;
+};
+
+struct Dtool_MutableSequenceWrapper {
+  Dtool_WrapperBase _base;
+  lenfunc _len_func;
+  ssizeargfunc _getitem_func;
+  ssizeobjargproc _setitem_func;
+  PyObject *(*_insert_func)(PyObject *, size_t, PyObject *);
+};
+
+struct Dtool_MappingWrapper {
+  union {
+    Dtool_WrapperBase _base;
+    Dtool_SequenceWrapper _keys;
+  };
+  binaryfunc _getitem_func;
+  objobjargproc _setitem_func;
+};
+
+struct Dtool_GeneratorWrapper {
+  Dtool_WrapperBase _base;
+  iternextfunc _iternext_func;
+};
+
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MutableSequenceWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MutableMappingWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Items_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Keys_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Values_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_GeneratorWrapper_Type;
+EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
+
+EXPCL_INTERROGATEDB Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name);
+EXPCL_INTERROGATEDB PyObject *Dtool_NewGenerator(PyObject *self, const char *name, iternextfunc func);
+EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
+
+#endif

+ 10 - 0
dtool/src/pystub/pystub.cxx

@@ -66,6 +66,7 @@ extern "C" {
   EXPCL_PYSTUB int PyInt_FromLong(...);
   EXPCL_PYSTUB int PyInt_FromLong(...);
   EXPCL_PYSTUB int PyInt_FromSize_t(...);
   EXPCL_PYSTUB int PyInt_FromSize_t(...);
   EXPCL_PYSTUB int PyInt_Type(...);
   EXPCL_PYSTUB int PyInt_Type(...);
+  EXPCL_PYSTUB int PyIter_Next(...);
   EXPCL_PYSTUB int PyList_Append(...);
   EXPCL_PYSTUB int PyList_Append(...);
   EXPCL_PYSTUB int PyList_AsTuple(...);
   EXPCL_PYSTUB int PyList_AsTuple(...);
   EXPCL_PYSTUB int PyList_GetItem(...);
   EXPCL_PYSTUB int PyList_GetItem(...);
@@ -91,6 +92,7 @@ extern "C" {
   EXPCL_PYSTUB int PyModule_AddStringConstant(...);
   EXPCL_PYSTUB int PyModule_AddStringConstant(...);
   EXPCL_PYSTUB int PyModule_Create2(...);
   EXPCL_PYSTUB int PyModule_Create2(...);
   EXPCL_PYSTUB int PyModule_Create2TraceRefs(...);
   EXPCL_PYSTUB int PyModule_Create2TraceRefs(...);
+  EXPCL_PYSTUB int PyModule_GetDict(...);
   EXPCL_PYSTUB int PyNumber_AsSsize_t(...);
   EXPCL_PYSTUB int PyNumber_AsSsize_t(...);
   EXPCL_PYSTUB int PyNumber_Check(...);
   EXPCL_PYSTUB int PyNumber_Check(...);
   EXPCL_PYSTUB int PyNumber_Float(...);
   EXPCL_PYSTUB int PyNumber_Float(...);
@@ -111,15 +113,18 @@ extern "C" {
   EXPCL_PYSTUB int PyObject_GenericSetAttr(...);
   EXPCL_PYSTUB int PyObject_GenericSetAttr(...);
   EXPCL_PYSTUB int PyObject_GetAttrString(...);
   EXPCL_PYSTUB int PyObject_GetAttrString(...);
   EXPCL_PYSTUB int PyObject_GetBuffer(...);
   EXPCL_PYSTUB int PyObject_GetBuffer(...);
+  EXPCL_PYSTUB int PyObject_GetIter(...);
   EXPCL_PYSTUB int PyObject_HasAttrString(...);
   EXPCL_PYSTUB int PyObject_HasAttrString(...);
   EXPCL_PYSTUB int PyObject_IsInstance(...);
   EXPCL_PYSTUB int PyObject_IsInstance(...);
   EXPCL_PYSTUB int PyObject_IsTrue(...);
   EXPCL_PYSTUB int PyObject_IsTrue(...);
+  EXPCL_PYSTUB int PyObject_Malloc(...);
   EXPCL_PYSTUB int PyObject_Repr(...);
   EXPCL_PYSTUB int PyObject_Repr(...);
   EXPCL_PYSTUB int PyObject_RichCompareBool(...);
   EXPCL_PYSTUB int PyObject_RichCompareBool(...);
   EXPCL_PYSTUB int PyObject_SelfIter(...);
   EXPCL_PYSTUB int PyObject_SelfIter(...);
   EXPCL_PYSTUB int PyObject_SetAttrString(...);
   EXPCL_PYSTUB int PyObject_SetAttrString(...);
   EXPCL_PYSTUB int PyObject_Str(...);
   EXPCL_PYSTUB int PyObject_Str(...);
   EXPCL_PYSTUB int PyObject_Type(...);
   EXPCL_PYSTUB int PyObject_Type(...);
+  EXPCL_PYSTUB int PySeqIter_New(...);
   EXPCL_PYSTUB int PySequence_Check(...);
   EXPCL_PYSTUB int PySequence_Check(...);
   EXPCL_PYSTUB int PySequence_Fast(...);
   EXPCL_PYSTUB int PySequence_Fast(...);
   EXPCL_PYSTUB int PySequence_GetItem(...);
   EXPCL_PYSTUB int PySequence_GetItem(...);
@@ -290,6 +295,7 @@ int PyInt_AsSsize_t(...) { return 0; }
 int PyInt_FromLong(...) { return 0; }
 int PyInt_FromLong(...) { return 0; }
 int PyInt_FromSize_t(...) { return 0; }
 int PyInt_FromSize_t(...) { return 0; }
 int PyInt_Type(...) { return 0; }
 int PyInt_Type(...) { return 0; }
+int PyIter_Next(...) { return 0; }
 int PyList_Append(...) { return 0; }
 int PyList_Append(...) { return 0; }
 int PyList_AsTuple(...) { return 0; }
 int PyList_AsTuple(...) { return 0; }
 int PyList_GetItem(...) { return 0; }
 int PyList_GetItem(...) { return 0; }
@@ -315,6 +321,7 @@ int PyModule_AddObject(...) { return 0; };
 int PyModule_AddStringConstant(...) { return 0; };
 int PyModule_AddStringConstant(...) { return 0; };
 int PyModule_Create2(...) { return 0; };
 int PyModule_Create2(...) { return 0; };
 int PyModule_Create2TraceRefs(...) { return 0; };
 int PyModule_Create2TraceRefs(...) { return 0; };
+int PyModule_GetDict(...) { return 0; };
 int PyNumber_AsSsize_t(...) { return 0; }
 int PyNumber_AsSsize_t(...) { return 0; }
 int PyNumber_Check(...) { return 0; }
 int PyNumber_Check(...) { return 0; }
 int PyNumber_Float(...) { return 0; }
 int PyNumber_Float(...) { return 0; }
@@ -335,15 +342,18 @@ int PyObject_GenericGetAttr(...) { return 0; };
 int PyObject_GenericSetAttr(...) { return 0; };
 int PyObject_GenericSetAttr(...) { return 0; };
 int PyObject_GetAttrString(...) { return 0; }
 int PyObject_GetAttrString(...) { return 0; }
 int PyObject_GetBuffer(...) { return 0; }
 int PyObject_GetBuffer(...) { return 0; }
+int PyObject_GetIter(...) { return 0; }
 int PyObject_HasAttrString(...) { return 0; }
 int PyObject_HasAttrString(...) { return 0; }
 int PyObject_IsInstance(...) { return 0; }
 int PyObject_IsInstance(...) { return 0; }
 int PyObject_IsTrue(...) { return 0; }
 int PyObject_IsTrue(...) { return 0; }
+int PyObject_Malloc(...) { return 0; }
 int PyObject_Repr(...) { return 0; }
 int PyObject_Repr(...) { return 0; }
 int PyObject_RichCompareBool(...) { return 0; }
 int PyObject_RichCompareBool(...) { return 0; }
 int PyObject_SelfIter(...) { return 0; }
 int PyObject_SelfIter(...) { return 0; }
 int PyObject_SetAttrString(...) { return 0; }
 int PyObject_SetAttrString(...) { return 0; }
 int PyObject_Str(...) { return 0; }
 int PyObject_Str(...) { return 0; }
 int PyObject_Type(...) { return 0; }
 int PyObject_Type(...) { return 0; }
+int PySeqIter_New(...) { return 0; }
 int PySequence_Check(...) { return 0; }
 int PySequence_Check(...) { return 0; }
 int PySequence_Fast(...) { return 0; }
 int PySequence_Fast(...) { return 0; }
 int PySequence_GetItem(...) { return 0; }
 int PySequence_GetItem(...) { return 0; }

+ 0 - 1
panda/src/chan/animGroup.h

@@ -49,7 +49,6 @@ PUBLISHED:
   void sort_descendants();
   void sort_descendants();
 
 
   MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
   MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
-  MAKE_MAP_PROPERTY(children, get_child_named, get_child_named);
 
 
 public:
 public:
   virtual TypeHandle get_value_type() const;
   virtual TypeHandle get_value_type() const;

+ 0 - 1
panda/src/chan/partGroup.h

@@ -76,7 +76,6 @@ PUBLISHED:
   void sort_descendants();
   void sort_descendants();
 
 
   MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
   MAKE_SEQ_PROPERTY(children, get_num_children, get_child);
-  MAKE_MAP_PROPERTY(children, get_child_named, get_child_named);
 
 
   bool apply_freeze(const TransformState *transform);
   bool apply_freeze(const TransformState *transform);
   virtual bool apply_freeze_matrix(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale);
   virtual bool apply_freeze_matrix(const LVecBase3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale);

+ 22 - 10
panda/src/collide/collisionNode.I

@@ -65,7 +65,7 @@ clear_solids() {
 /**
 /**
  *
  *
  */
  */
-INLINE int CollisionNode::
+INLINE size_t CollisionNode::
 get_num_solids() const {
 get_num_solids() const {
   return _solids.size();
   return _solids.size();
 }
 }
@@ -74,8 +74,8 @@ get_num_solids() const {
  *
  *
  */
  */
 INLINE CPT(CollisionSolid) CollisionNode::
 INLINE CPT(CollisionSolid) CollisionNode::
-get_solid(int n) const {
-  nassertr(n >= 0 && n < get_num_solids(), NULL);
+get_solid(size_t n) const {
+  nassertr(n < get_num_solids(), nullptr);
   return _solids[n].get_read_pointer();
   return _solids[n].get_read_pointer();
 }
 }
 
 
@@ -83,8 +83,8 @@ get_solid(int n) const {
  *
  *
  */
  */
 INLINE PT(CollisionSolid) CollisionNode::
 INLINE PT(CollisionSolid) CollisionNode::
-modify_solid(int n) {
-  nassertr(n >= 0 && n < get_num_solids(), NULL);
+modify_solid(size_t n) {
+  nassertr(n < get_num_solids(), nullptr);
   mark_internal_bounds_stale();
   mark_internal_bounds_stale();
   return _solids[n].get_write_pointer();
   return _solids[n].get_write_pointer();
 }
 }
@@ -93,19 +93,31 @@ modify_solid(int n) {
  * Replaces the solid with the indicated index.
  * Replaces the solid with the indicated index.
  */
  */
 INLINE void CollisionNode::
 INLINE void CollisionNode::
-set_solid(int n, CollisionSolid *solid) {
-  nassertv(n >= 0 && n < get_num_solids());
+set_solid(size_t n, CollisionSolid *solid) {
+  nassertv(n < get_num_solids());
   _solids[n] = solid;
   _solids[n] = solid;
   mark_internal_bounds_stale();
   mark_internal_bounds_stale();
 }
 }
 
 
+/**
+ * Inserts the indicated solid to the node at the indicated position.
+ */
+INLINE void CollisionNode::
+insert_solid(size_t n, const CollisionSolid *solid) {
+  if (n > _solids.size()) {
+    n = _solids.size();
+  }
+  _solids.insert(_solids.begin() + n, (CollisionSolid *)solid);
+  mark_internal_bounds_stale();
+}
+
 /**
 /**
  * Removes the solid with the indicated index.  This will shift all subsequent
  * Removes the solid with the indicated index.  This will shift all subsequent
  * indices down by one.
  * indices down by one.
  */
  */
 INLINE void CollisionNode::
 INLINE void CollisionNode::
-remove_solid(int n) {
-  nassertv(n >= 0 && n < get_num_solids());
+remove_solid(size_t n) {
+  nassertv(n < get_num_solids());
   _solids.erase(_solids.begin() + n);
   _solids.erase(_solids.begin() + n);
   mark_internal_bounds_stale();
   mark_internal_bounds_stale();
 }
 }
@@ -114,7 +126,7 @@ remove_solid(int n) {
  * Adds the indicated solid to the node.  Returns the index of the new solid
  * Adds the indicated solid to the node.  Returns the index of the new solid
  * within the node's list of solids.
  * within the node's list of solids.
  */
  */
-INLINE int CollisionNode::
+INLINE size_t CollisionNode::
 add_solid(const CollisionSolid *solid) {
 add_solid(const CollisionSolid *solid) {
   _solids.push_back((CollisionSolid *)solid);
   _solids.push_back((CollisionSolid *)solid);
   mark_internal_bounds_stale();
   mark_internal_bounds_stale();

+ 8 - 7
panda/src/collide/collisionNode.h

@@ -61,14 +61,15 @@ PUBLISHED:
                                    set_into_collide_mask);
                                    set_into_collide_mask);
 
 
   INLINE void clear_solids();
   INLINE void clear_solids();
-  INLINE int get_num_solids() const;
-  INLINE CPT(CollisionSolid) get_solid(int n) const;
+  INLINE size_t get_num_solids() const;
+  INLINE CPT(CollisionSolid) get_solid(size_t n) const;
   MAKE_SEQ(get_solids, get_num_solids, get_solid);
   MAKE_SEQ(get_solids, get_num_solids, get_solid);
-  INLINE PT(CollisionSolid) modify_solid(int n);
-  INLINE void set_solid(int n, CollisionSolid *solid);
-  INLINE void remove_solid(int n);
-  INLINE int add_solid(const CollisionSolid *solid);
-  MAKE_SEQ_PROPERTY(solids, get_num_solids, get_solid, set_solid, remove_solid);
+  INLINE PT(CollisionSolid) modify_solid(size_t n);
+  INLINE void set_solid(size_t n, CollisionSolid *solid);
+  INLINE void insert_solid(size_t n, const CollisionSolid *solid);
+  INLINE void remove_solid(size_t n);
+  INLINE size_t add_solid(const CollisionSolid *solid);
+  MAKE_SEQ_PROPERTY(solids, get_num_solids, get_solid, set_solid, remove_solid, insert_solid);
 
 
   INLINE int get_collider_sort() const;
   INLINE int get_collider_sort() const;
   INLINE void set_collider_sort(int sort);
   INLINE void set_collider_sort(int sort);

+ 7 - 7
panda/src/egg/eggCompositePrimitive.I

@@ -38,7 +38,7 @@ operator = (const EggCompositePrimitive &copy) {
  * Returns the number of individual component triangles within the composite.
  * Returns the number of individual component triangles within the composite.
  * Each one of these might have a different set of attributes.
  * Each one of these might have a different set of attributes.
  */
  */
-INLINE int EggCompositePrimitive::
+INLINE size_t EggCompositePrimitive::
 get_num_components() const {
 get_num_components() const {
   return _components.size();
   return _components.size();
 }
 }
@@ -47,8 +47,8 @@ get_num_components() const {
  * Returns the attributes for the nth component triangle.
  * Returns the attributes for the nth component triangle.
  */
  */
 INLINE const EggAttributes *EggCompositePrimitive::
 INLINE const EggAttributes *EggCompositePrimitive::
-get_component(int i) const {
-  nassertr(i >= 0 && i < (int)_components.size(), NULL);
+get_component(size_t i) const {
+  nassertr(i < _components.size(), nullptr);
   return _components[i];
   return _components[i];
 }
 }
 
 
@@ -56,8 +56,8 @@ get_component(int i) const {
  * Returns the attributes for the nth component triangle.
  * Returns the attributes for the nth component triangle.
  */
  */
 INLINE EggAttributes *EggCompositePrimitive::
 INLINE EggAttributes *EggCompositePrimitive::
-get_component(int i) {
-  nassertr(i >= 0 && i < (int)_components.size(), NULL);
+get_component(size_t i) {
+  nassertr(i < _components.size(), nullptr);
   return _components[i];
   return _components[i];
 }
 }
 
 
@@ -65,8 +65,8 @@ get_component(int i) {
  * Changes the attributes for the nth component triangle.
  * Changes the attributes for the nth component triangle.
  */
  */
 INLINE void EggCompositePrimitive::
 INLINE void EggCompositePrimitive::
-set_component(int i, const EggAttributes *attrib) {
-  nassertv(i >= 0 && i < (int)_components.size());
+set_component(size_t i, const EggAttributes *attrib) {
+  nassertv(i < _components.size());
   _components[i] = new EggAttributes(*attrib);
   _components[i] = new EggAttributes(*attrib);
 }
 }
 
 

+ 4 - 4
panda/src/egg/eggCompositePrimitive.h

@@ -32,11 +32,11 @@ PUBLISHED:
 
 
   virtual Shading get_shading() const;
   virtual Shading get_shading() const;
 
 
-  INLINE int get_num_components() const;
-  INLINE const EggAttributes *get_component(int i) const;
-  INLINE EggAttributes *get_component(int i);
+  INLINE size_t get_num_components() const;
+  INLINE const EggAttributes *get_component(size_t i) const;
+  INLINE EggAttributes *get_component(size_t i);
   MAKE_SEQ(get_components, get_num_components, get_component);
   MAKE_SEQ(get_components, get_num_components, get_component);
-  INLINE void set_component(int i, const EggAttributes *attrib);
+  INLINE void set_component(size_t i, const EggAttributes *attrib);
 
 
   MAKE_SEQ_PROPERTY(components, get_num_components, get_component, set_component);
   MAKE_SEQ_PROPERTY(components, get_num_components, get_component, set_component);
 
 

+ 11 - 1
panda/src/egg/eggPrimitive.I

@@ -372,6 +372,17 @@ set_vertex(size_t index, EggVertex *vertex) {
   replace(begin() + index, vertex);
   replace(begin() + index, vertex);
 }
 }
 
 
+/**
+ * Inserts a vertex at the given position.
+ */
+INLINE void EggPrimitive::
+insert_vertex(size_t index, EggVertex *vertex) {
+  if (index > _vertices.size()) {
+    index = _vertices.size();
+  }
+  _vertices.insert(_vertices.begin() + index, vertex);
+}
+
 /**
 /**
  * Returns a particular index based on its index number.
  * Returns a particular index based on its index number.
  */
  */
@@ -381,7 +392,6 @@ get_vertex(size_t index) const {
   return *(begin() + index);
   return *(begin() + index);
 }
 }
 
 
-
 /**
 /**
  * Returns the vertex pool associated with the vertices of the primitive, or
  * Returns the vertex pool associated with the vertices of the primitive, or
  * NULL if the primitive has no vertices.
  * NULL if the primitive has no vertices.

+ 3 - 2
panda/src/egg/eggPrimitive.h

@@ -181,13 +181,14 @@ PUBLISHED:
 
 
   // These are shorthands if you don't want to use the iterators.
   // These are shorthands if you don't want to use the iterators.
   INLINE size_t get_num_vertices() const;
   INLINE size_t get_num_vertices() const;
-  INLINE void set_vertex(size_t index, EggVertex *vertex);
   INLINE EggVertex *get_vertex(size_t index) const;
   INLINE EggVertex *get_vertex(size_t index) const;
+  INLINE void set_vertex(size_t index, EggVertex *vertex);
+  INLINE void insert_vertex(size_t index, EggVertex *vertex);
   MAKE_SEQ(get_vertices, get_num_vertices, get_vertex);
   MAKE_SEQ(get_vertices, get_num_vertices, get_vertex);
 
 
   INLINE EggVertexPool *get_pool() const;
   INLINE EggVertexPool *get_pool() const;
 
 
-  MAKE_SEQ_PROPERTY(vertices, get_num_vertices, get_vertex, set_vertex, remove_vertex);
+  MAKE_SEQ_PROPERTY(vertices, get_num_vertices, get_vertex, set_vertex, remove_vertex, insert_vertex);
   MAKE_PROPERTY(pool, get_pool);
   MAKE_PROPERTY(pool, get_pool);
 
 
   virtual void write(ostream &out, int indent_level) const=0;
   virtual void write(ostream &out, int indent_level) const=0;

+ 18 - 5
panda/src/gobj/geom.I

@@ -68,7 +68,7 @@ is_empty() const {
  * Returns the number of GeomPrimitive objects stored within the Geom, each of
  * Returns the number of GeomPrimitive objects stored within the Geom, each of
  * which represents a number of primitives of a particular type.
  * which represents a number of primitives of a particular type.
  */
  */
-INLINE int Geom::
+INLINE size_t Geom::
 get_num_primitives() const {
 get_num_primitives() const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
   return cdata->_primitives.size();
   return cdata->_primitives.size();
@@ -80,9 +80,9 @@ get_num_primitives() const {
  * or set_primitive() if you want to modify it.
  * or set_primitive() if you want to modify it.
  */
  */
 INLINE CPT(GeomPrimitive) Geom::
 INLINE CPT(GeomPrimitive) Geom::
-get_primitive(int i) const {
+get_primitive(size_t i) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  nassertr(i >= 0 && i < (int)cdata->_primitives.size(), NULL);
+  nassertr(i < cdata->_primitives.size(), nullptr);
   return cdata->_primitives[i].get_read_pointer();
   return cdata->_primitives[i].get_read_pointer();
 }
 }
 
 
@@ -95,15 +95,28 @@ get_primitive(int i) const {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 INLINE PT(GeomPrimitive) Geom::
 INLINE PT(GeomPrimitive) Geom::
-modify_primitive(int i) {
+modify_primitive(size_t i) {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
-  nassertr(i >= 0 && i < (int)cdata->_primitives.size(), NULL);
+  nassertr(i < cdata->_primitives.size(), NULL);
   cdata->_modified = Geom::get_next_modified();
   cdata->_modified = Geom::get_next_modified();
   clear_cache_stage(current_thread);
   clear_cache_stage(current_thread);
   return cdata->_primitives[i].get_write_pointer();
   return cdata->_primitives[i].get_write_pointer();
 }
 }
 
 
+/**
+ * Inserts a new GeomPrimitive structure to the Geom object.  This specifies a
+ * particular subset of vertices that are used to define geometric primitives
+ * of the indicated type.
+ *
+ * Don't call this in a downstream thread unless you don't mind it blowing
+ * away other changes you might have recently made in an upstream thread.
+ */
+INLINE void Geom::
+add_primitive(const GeomPrimitive *primitive) {
+  insert_primitive((size_t)-1, primitive);
+}
+
 /**
 /**
  * Decomposes all of the primitives within this Geom, returning the result.
  * Decomposes all of the primitives within this Geom, returning the result.
  * See GeomPrimitive::decompose().
  * See GeomPrimitive::decompose().

+ 12 - 8
panda/src/gobj/geom.cxx

@@ -294,10 +294,10 @@ make_nonindexed(bool composite_only) {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 void Geom::
 void Geom::
-set_primitive(int i, const GeomPrimitive *primitive) {
+set_primitive(size_t i, const GeomPrimitive *primitive) {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
-  nassertv(i >= 0 && i < (int)cdata->_primitives.size());
+  nassertv(i < cdata->_primitives.size());
   nassertv(primitive->check_valid(cdata->_data.get_read_pointer(current_thread)));
   nassertv(primitive->check_valid(cdata->_data.get_read_pointer(current_thread)));
 
 
   // All primitives within a particular Geom must have the same fundamental
   // All primitives within a particular Geom must have the same fundamental
@@ -327,7 +327,7 @@ set_primitive(int i, const GeomPrimitive *primitive) {
 }
 }
 
 
 /**
 /**
- * Adds a new GeomPrimitive structure to the Geom object.  This specifies a
+ * Inserts a new GeomPrimitive structure to the Geom object.  This specifies a
  * particular subset of vertices that are used to define geometric primitives
  * particular subset of vertices that are used to define geometric primitives
  * of the indicated type.
  * of the indicated type.
  *
  *
@@ -335,7 +335,7 @@ set_primitive(int i, const GeomPrimitive *primitive) {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 void Geom::
 void Geom::
-add_primitive(const GeomPrimitive *primitive) {
+insert_primitive(size_t i, const GeomPrimitive *primitive) {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
 
 
@@ -348,9 +348,13 @@ add_primitive(const GeomPrimitive *primitive) {
 
 
   // They also should have a compatible shade model.
   // They also should have a compatible shade model.
   CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model);
   CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model);
-  nassertv_always(compat != (GeomPrimitive *)NULL);
+  nassertv_always(compat != nullptr);
 
 
-  cdata->_primitives.push_back((GeomPrimitive *)compat.p());
+  if (i >= cdata->_primitives.size()) {
+    cdata->_primitives.push_back((GeomPrimitive *)compat.p());
+  } else {
+    cdata->_primitives.insert(cdata->_primitives.begin() + i, (GeomPrimitive *)compat.p());
+  }
   PrimitiveType new_primitive_type = compat->get_primitive_type();
   PrimitiveType new_primitive_type = compat->get_primitive_type();
   if (new_primitive_type != cdata->_primitive_type) {
   if (new_primitive_type != cdata->_primitive_type) {
     cdata->_primitive_type = new_primitive_type;
     cdata->_primitive_type = new_primitive_type;
@@ -374,10 +378,10 @@ add_primitive(const GeomPrimitive *primitive) {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 void Geom::
 void Geom::
-remove_primitive(int i) {
+remove_primitive(size_t i) {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   CDWriter cdata(_cycler, true, current_thread);
   CDWriter cdata(_cycler, true, current_thread);
-  nassertv(i >= 0 && i < (int)cdata->_primitives.size());
+  nassertv(i < cdata->_primitives.size());
   cdata->_primitives.erase(cdata->_primitives.begin() + i);
   cdata->_primitives.erase(cdata->_primitives.begin() + i);
   if (cdata->_primitives.empty()) {
   if (cdata->_primitives.empty()) {
     cdata->_primitive_type = PT_none;
     cdata->_primitive_type = PT_none;

+ 8 - 7
panda/src/gobj/geom.h

@@ -87,15 +87,16 @@ PUBLISHED:
 
 
   INLINE bool is_empty() const;
   INLINE bool is_empty() const;
 
 
-  INLINE int get_num_primitives() const;
-  INLINE CPT(GeomPrimitive) get_primitive(int i) const;
+  INLINE size_t get_num_primitives() const;
+  INLINE CPT(GeomPrimitive) get_primitive(size_t i) const;
   MAKE_SEQ(get_primitives, get_num_primitives, get_primitive);
   MAKE_SEQ(get_primitives, get_num_primitives, get_primitive);
-  INLINE PT(GeomPrimitive) modify_primitive(int i);
-  void set_primitive(int i, const GeomPrimitive *primitive);
-  void add_primitive(const GeomPrimitive *primitive);
-  void remove_primitive(int i);
+  INLINE PT(GeomPrimitive) modify_primitive(size_t i);
+  void set_primitive(size_t i, const GeomPrimitive *primitive);
+  void insert_primitive(size_t i, const GeomPrimitive *primitive);
+  INLINE void add_primitive(const GeomPrimitive *primitive);
+  void remove_primitive(size_t i);
   void clear_primitives();
   void clear_primitives();
-  MAKE_SEQ_PROPERTY(primitives, get_num_primitives, get_primitive, set_primitive, remove_primitive);
+  MAKE_SEQ_PROPERTY(primitives, get_num_primitives, get_primitive, set_primitive, remove_primitive, insert_primitive);
 
 
   INLINE PT(Geom) decompose() const;
   INLINE PT(Geom) decompose() const;
   INLINE PT(Geom) doubleside() const;
   INLINE PT(Geom) doubleside() const;

+ 10 - 10
panda/src/gobj/geomVertexData.I

@@ -129,7 +129,7 @@ reserve_num_rows(int n) {
  * Returns the number of individual arrays stored within the data.  This must
  * Returns the number of individual arrays stored within the data.  This must
  * match get_format()->get_num_arrays().
  * match get_format()->get_num_arrays().
  */
  */
-INLINE int GeomVertexData::
+INLINE size_t GeomVertexData::
 get_num_arrays() const {
 get_num_arrays() const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
   return cdata->_arrays.size();
   return cdata->_arrays.size();
@@ -141,9 +141,9 @@ get_num_arrays() const {
  * data.
  * data.
  */
  */
 INLINE CPT(GeomVertexArrayData) GeomVertexData::
 INLINE CPT(GeomVertexArrayData) GeomVertexData::
-get_array(int i) const {
+get_array(size_t i) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  nassertr(i >= 0 && i < (int)cdata->_arrays.size(), NULL);
+  nassertr(i < cdata->_arrays.size(), nullptr);
   return cdata->_arrays[i].get_read_pointer();
   return cdata->_arrays[i].get_read_pointer();
 }
 }
 
 
@@ -151,10 +151,10 @@ get_array(int i) const {
  * Equivalent to get_array(i).get_handle().
  * Equivalent to get_array(i).get_handle().
  */
  */
 INLINE CPT(GeomVertexArrayDataHandle) GeomVertexData::
 INLINE CPT(GeomVertexArrayDataHandle) GeomVertexData::
-get_array_handle(int i) const {
+get_array_handle(size_t i) const {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   CDReader cdata(_cycler, current_thread);
   CDReader cdata(_cycler, current_thread);
-  nassertr(i >= 0 && i < (int)cdata->_arrays.size(), NULL);
+  nassertr(i < cdata->_arrays.size(), nullptr);
   return new GeomVertexArrayDataHandle(cdata->_arrays[i].get_read_pointer(), current_thread);
   return new GeomVertexArrayDataHandle(cdata->_arrays[i].get_read_pointer(), current_thread);
 }
 }
 
 
@@ -168,7 +168,7 @@ get_array_handle(int i) const {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 INLINE PT(GeomVertexArrayData) GeomVertexData::
 INLINE PT(GeomVertexArrayData) GeomVertexData::
-modify_array(int i) {
+modify_array(size_t i) {
   GeomVertexDataPipelineWriter writer(this, true, Thread::get_current_thread());
   GeomVertexDataPipelineWriter writer(this, true, Thread::get_current_thread());
   return writer.modify_array(i);
   return writer.modify_array(i);
 }
 }
@@ -177,7 +177,7 @@ modify_array(int i) {
  * Equivalent to modify_array(i).modify_handle().
  * Equivalent to modify_array(i).modify_handle().
  */
  */
 INLINE PT(GeomVertexArrayDataHandle) GeomVertexData::
 INLINE PT(GeomVertexArrayDataHandle) GeomVertexData::
-modify_array_handle(int i) {
+modify_array_handle(size_t i) {
   Thread *current_thread = Thread::get_current_thread();
   Thread *current_thread = Thread::get_current_thread();
   GeomVertexDataPipelineWriter writer(this, true, current_thread);
   GeomVertexDataPipelineWriter writer(this, true, current_thread);
   return new GeomVertexArrayDataHandle(writer.modify_array(i), current_thread);
   return new GeomVertexArrayDataHandle(writer.modify_array(i), current_thread);
@@ -192,7 +192,7 @@ modify_array_handle(int i) {
  * away other changes you might have recently made in an upstream thread.
  * away other changes you might have recently made in an upstream thread.
  */
  */
 INLINE void GeomVertexData::
 INLINE void GeomVertexData::
-set_array(int i, const GeomVertexArrayData *array) {
+set_array(size_t i, const GeomVertexArrayData *array) {
   GeomVertexDataPipelineWriter writer(this, true, Thread::get_current_thread());
   GeomVertexDataPipelineWriter writer(this, true, Thread::get_current_thread());
   writer.set_array(i, array);
   writer.set_array(i, array);
 }
 }
@@ -888,9 +888,9 @@ check_array_writers() const {
  *
  *
  */
  */
 INLINE GeomVertexArrayDataHandle *GeomVertexDataPipelineWriter::
 INLINE GeomVertexArrayDataHandle *GeomVertexDataPipelineWriter::
-get_array_writer(int i) const {
+get_array_writer(size_t i) const {
   nassertr(_got_array_writers, NULL);
   nassertr(_got_array_writers, NULL);
-  nassertr(i >= 0 && i < (int)_array_writers.size(), NULL);
+  nassertr(i < _array_writers.size(), nullptr);
   return _array_writers[i];
   return _array_writers[i];
 }
 }
 
 

+ 4 - 4
panda/src/gobj/geomVertexData.cxx

@@ -2565,8 +2565,8 @@ reserve_num_rows(int n) {
  *
  *
  */
  */
 PT(GeomVertexArrayData) GeomVertexDataPipelineWriter::
 PT(GeomVertexArrayData) GeomVertexDataPipelineWriter::
-modify_array(int i) {
-  nassertr(i >= 0 && i < (int)_cdata->_arrays.size(), NULL);
+modify_array(size_t i) {
+  nassertr(i < _cdata->_arrays.size(), nullptr);
 
 
   PT(GeomVertexArrayData) new_data;
   PT(GeomVertexArrayData) new_data;
   if (_got_array_writers) {
   if (_got_array_writers) {
@@ -2586,8 +2586,8 @@ modify_array(int i) {
  *
  *
  */
  */
 void GeomVertexDataPipelineWriter::
 void GeomVertexDataPipelineWriter::
-set_array(int i, const GeomVertexArrayData *array) {
-  nassertv(i >= 0 && i < (int)_cdata->_arrays.size());
+set_array(size_t i, const GeomVertexArrayData *array) {
+  nassertv(i < _cdata->_arrays.size());
   _cdata->_arrays[i] = (GeomVertexArrayData *)array;
   _cdata->_arrays[i] = (GeomVertexArrayData *)array;
   _object->clear_cache_stage();
   _object->clear_cache_stage();
   _cdata->_modified = Geom::get_next_modified();
   _cdata->_modified = Geom::get_next_modified();

+ 9 - 9
panda/src/gobj/geomVertexData.h

@@ -105,13 +105,13 @@ PUBLISHED:
   INLINE bool reserve_num_rows(int n);
   INLINE bool reserve_num_rows(int n);
   void clear_rows();
   void clear_rows();
 
 
-  INLINE int get_num_arrays() const;
-  INLINE CPT(GeomVertexArrayData) get_array(int i) const;
-  INLINE CPT(GeomVertexArrayDataHandle) get_array_handle(int i) const;
+  INLINE size_t get_num_arrays() const;
+  INLINE CPT(GeomVertexArrayData) get_array(size_t i) const;
+  INLINE CPT(GeomVertexArrayDataHandle) get_array_handle(size_t i) const;
   MAKE_SEQ(get_arrays, get_num_arrays, get_array);
   MAKE_SEQ(get_arrays, get_num_arrays, get_array);
-  INLINE PT(GeomVertexArrayData) modify_array(int i);
-  INLINE PT(GeomVertexArrayDataHandle) modify_array_handle(int i);
-  INLINE void set_array(int i, const GeomVertexArrayData *array);
+  INLINE PT(GeomVertexArrayData) modify_array(size_t i);
+  INLINE PT(GeomVertexArrayDataHandle) modify_array_handle(size_t i);
+  INLINE void set_array(size_t i, const GeomVertexArrayData *array);
   MAKE_SEQ_PROPERTY(arrays, get_num_arrays, get_array, set_array);
   MAKE_SEQ_PROPERTY(arrays, get_num_arrays, get_array, set_array);
 
 
   INLINE const TransformTable *get_transform_table() const;
   INLINE const TransformTable *get_transform_table() const;
@@ -520,10 +520,10 @@ public:
   INLINE GeomVertexData *get_object() const;
   INLINE GeomVertexData *get_object() const;
 
 
   INLINE void check_array_writers() const;
   INLINE void check_array_writers() const;
-  INLINE GeomVertexArrayDataHandle *get_array_writer(int i) const;
+  INLINE GeomVertexArrayDataHandle *get_array_writer(size_t i) const;
 
 
-  PT(GeomVertexArrayData) modify_array(int i);
-  void set_array(int i, const GeomVertexArrayData *array);
+  PT(GeomVertexArrayData) modify_array(size_t i);
+  void set_array(size_t i, const GeomVertexArrayData *array);
 
 
   int get_num_rows() const;
   int get_num_rows() const;
   bool set_num_rows(int n);
   bool set_num_rows(int n);

+ 19 - 1
panda/src/gobj/geomVertexFormat.cxx

@@ -310,7 +310,9 @@ add_array(const GeomVertexArrayFormat *array_format) {
 void GeomVertexFormat::
 void GeomVertexFormat::
 insert_array(size_t array, const GeomVertexArrayFormat *array_format) {
 insert_array(size_t array, const GeomVertexArrayFormat *array_format) {
   nassertv(!is_registered());
   nassertv(!is_registered());
-  nassertv(array <= _arrays.size());
+  if (array > _arrays.size()) {
+    array = _arrays.size();
+  }
 
 
   _arrays.insert(_arrays.begin() + array, (GeomVertexArrayFormat *)array_format);
   _arrays.insert(_arrays.begin() + array, (GeomVertexArrayFormat *)array_format);
 }
 }
@@ -377,6 +379,22 @@ get_column(size_t i) const {
   return NULL;
   return NULL;
 }
 }
 
 
+/**
+ * Returns the name of the ith column, across all arrays.
+ */
+const InternalName *GeomVertexFormat::
+get_column_name(size_t i) const {
+  Arrays::const_iterator ai;
+  for (ai = _arrays.begin(); ai != _arrays.end(); ++ai) {
+    if (i < (size_t)(*ai)->get_num_columns()) {
+      return (*ai)->get_column(i)->get_name();
+    }
+    i -= (*ai)->get_num_columns();
+  }
+
+  return nullptr;
+}
+
 /**
 /**
  * Returns the index number of the array with the ith column.
  * Returns the index number of the array with the ith column.
  *
  *

+ 3 - 2
panda/src/gobj/geomVertexFormat.h

@@ -92,6 +92,7 @@ PUBLISHED:
   int get_array_with(const InternalName *name) const;
   int get_array_with(const InternalName *name) const;
   const GeomVertexColumn *get_column(const InternalName *name) const;
   const GeomVertexColumn *get_column(const InternalName *name) const;
   INLINE bool has_column(const InternalName *name) const;
   INLINE bool has_column(const InternalName *name) const;
+  const InternalName *get_column_name(size_t i) const;
 
 
   MAKE_SEQ(get_columns, get_num_columns, get_column);
   MAKE_SEQ(get_columns, get_num_columns, get_column);
 
 
@@ -120,13 +121,13 @@ PUBLISHED:
   MAKE_SEQ(get_morph_bases, get_num_morphs, get_morph_base);
   MAKE_SEQ(get_morph_bases, get_num_morphs, get_morph_base);
   MAKE_SEQ(get_morph_deltas, get_num_morphs, get_morph_delta);
   MAKE_SEQ(get_morph_deltas, get_num_morphs, get_morph_delta);
 
 
-  MAKE_SEQ_PROPERTY(arrays, get_num_arrays, get_array, set_array, remove_array);
-  MAKE_SEQ_PROPERTY(columns, get_num_columns, get_column);
+  MAKE_SEQ_PROPERTY(arrays, get_num_arrays, get_array, set_array, remove_array, insert_array);
   MAKE_SEQ_PROPERTY(points, get_num_points, get_point);
   MAKE_SEQ_PROPERTY(points, get_num_points, get_point);
   MAKE_SEQ_PROPERTY(vectors, get_num_vectors, get_vector);
   MAKE_SEQ_PROPERTY(vectors, get_num_vectors, get_vector);
 
 
   // We also define this as a mapping interface, for lookups by name.
   // We also define this as a mapping interface, for lookups by name.
   MAKE_MAP_PROPERTY(columns, has_column, get_column);
   MAKE_MAP_PROPERTY(columns, has_column, get_column);
+  MAKE_MAP_KEYS_SEQ(columns, get_num_columns, get_column_name);
 
 
   void output(ostream &out) const;
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
   void write(ostream &out, int indent_level = 0) const;

+ 1 - 1
panda/src/gobj/transformBlend.h

@@ -68,8 +68,8 @@ PUBLISHED:
 
 
   MAKE_SEQ_PROPERTY(transforms, get_num_transforms, get_transform,
   MAKE_SEQ_PROPERTY(transforms, get_num_transforms, get_transform,
                     set_transform, remove_transform);
                     set_transform, remove_transform);
-  MAKE_SEQ_PROPERTY(weights, get_num_transforms, get_weight, set_weight);
   MAKE_MAP_PROPERTY(weights, has_transform, get_weight);
   MAKE_MAP_PROPERTY(weights, has_transform, get_weight);
+  MAKE_MAP_KEYS_SEQ(weights, get_num_transforms, get_transform);
 
 
   INLINE void update_blend(Thread *current_thread) const;
   INLINE void update_blend(Thread *current_thread) const;
 
 

+ 17 - 0
panda/src/gobj/transformTable.cxx

@@ -65,6 +65,23 @@ set_transform(size_t n, const VertexTransform *transform) {
   _transforms[n] = transform;
   _transforms[n] = transform;
 }
 }
 
 
+/**
+ * Inserts a new transform to the table at the given index position.  If the
+ * index is beyond the end of the table, appends it to the end.  Only valid
+ * for unregistered tables.
+ *
+ * This does not automatically uniquify the pointer; if the transform is
+ * already present in the table, it will be added twice.
+ */
+void TransformTable::
+insert_transform(size_t n, const VertexTransform *transform) {
+  nassertv(!_is_registered);
+  if (n > _transforms.size()) {
+    n = _transforms.size();
+  }
+  _transforms.insert(_transforms.begin() + n, transform);
+}
+
 /**
 /**
  * Removes the nth transform.  Only valid for unregistered tables.
  * Removes the nth transform.  Only valid for unregistered tables.
  */
  */

+ 3 - 1
panda/src/gobj/transformTable.h

@@ -51,6 +51,7 @@ PUBLISHED:
   INLINE UpdateSeq get_modified(Thread *current_thread = Thread::get_current_thread()) const;
   INLINE UpdateSeq get_modified(Thread *current_thread = Thread::get_current_thread()) const;
 
 
   void set_transform(size_t n, const VertexTransform *transform);
   void set_transform(size_t n, const VertexTransform *transform);
+  void insert_transform(size_t n, const VertexTransform *transform);
   void remove_transform(size_t n);
   void remove_transform(size_t n);
   size_t add_transform(const VertexTransform *transform);
   size_t add_transform(const VertexTransform *transform);
 
 
@@ -58,7 +59,8 @@ PUBLISHED:
 
 
   MAKE_PROPERTY(registered, is_registered);
   MAKE_PROPERTY(registered, is_registered);
   MAKE_PROPERTY(modified, get_modified);
   MAKE_PROPERTY(modified, get_modified);
-  MAKE_SEQ_PROPERTY(transforms, get_num_transforms, get_transform, set_transform, remove_transform);
+  MAKE_SEQ_PROPERTY(transforms, get_num_transforms, get_transform, set_transform,
+                                remove_transform, insert_transform);
 
 
 private:
 private:
   void do_register();
   void do_register();

+ 9 - 0
panda/src/parametrics/parametricCurveCollection.I

@@ -36,6 +36,15 @@ get_curve(int index) const {
   return _curves[index];
   return _curves[index];
 }
 }
 
 
+/**
+ * Adds a new ParametricCurve to the collection at the indicated index.
+ * @deprecated Use insert_curve(index, curve) instead.
+ */
+INLINE void ParametricCurveCollection::
+add_curve(ParametricCurve *curve, int index) {
+  insert_curve(max(index, 0), curve);
+}
+
 /**
 /**
  * Returns the maximum T value associated with the *last* curve in the
  * Returns the maximum T value associated with the *last* curve in the
  * collection.  Normally, this will be either the XYZ or HPR curve, or a
  * collection.  Normally, this will be either the XYZ or HPR curve, or a

+ 6 - 6
panda/src/parametrics/parametricCurveCollection.cxx

@@ -42,9 +42,9 @@ add_curve(ParametricCurve *curve) {
  * Adds a new ParametricCurve to the collection at the indicated index.
  * Adds a new ParametricCurve to the collection at the indicated index.
  */
  */
 void ParametricCurveCollection::
 void ParametricCurveCollection::
-add_curve(ParametricCurve *curve, int index) {
+insert_curve(size_t index, ParametricCurve *curve) {
   prepare_add_curve(curve);
   prepare_add_curve(curve);
-  index = max(min(index, (int)_curves.size()), 0);
+  index = min(index, _curves.size());
   _curves.insert(_curves.begin() + index, curve);
   _curves.insert(_curves.begin() + index, curve);
   redraw();
   redraw();
 }
 }
@@ -93,8 +93,8 @@ remove_curve(ParametricCurve *curve) {
  * number.
  * number.
  */
  */
 void ParametricCurveCollection::
 void ParametricCurveCollection::
-remove_curve(int index) {
-  nassertv(index >= 0 && index < (int)_curves.size());
+remove_curve(size_t index) {
+  nassertv(index < _curves.size());
   PT(ParametricCurve) curve = _curves[index];
   PT(ParametricCurve) curve = _curves[index];
   prepare_remove_curve(curve);
   prepare_remove_curve(curve);
   _curves.erase(_curves.begin() + index);
   _curves.erase(_curves.begin() + index);
@@ -107,8 +107,8 @@ remove_curve(int index) {
  * number.
  * number.
  */
  */
 void ParametricCurveCollection::
 void ParametricCurveCollection::
-set_curve(int index, ParametricCurve *curve) {
-  nassertv(index >= 0 && index < (int)_curves.size());
+set_curve(size_t index, ParametricCurve *curve) {
+  nassertv(index < _curves.size());
   prepare_remove_curve(_curves[index]);
   prepare_remove_curve(_curves[index]);
   prepare_add_curve(curve);
   prepare_add_curve(curve);
   _curves[index] = curve;
   _curves[index] = curve;

+ 3 - 2
panda/src/parametrics/parametricCurveCollection.h

@@ -41,10 +41,11 @@ PUBLISHED:
 
 
   void add_curve(ParametricCurve *curve);
   void add_curve(ParametricCurve *curve);
   void add_curve(ParametricCurve *curve, int index);
   void add_curve(ParametricCurve *curve, int index);
+  void insert_curve(size_t index, ParametricCurve *curve);
   int add_curves(PandaNode *node);
   int add_curves(PandaNode *node);
   bool remove_curve(ParametricCurve *curve);
   bool remove_curve(ParametricCurve *curve);
-  void remove_curve(int index);
-  void set_curve(int index, ParametricCurve *curve);
+  void remove_curve(size_t index);
+  void set_curve(size_t index, ParametricCurve *curve);
   bool has_curve(ParametricCurve *curve) const;
   bool has_curve(ParametricCurve *curve) const;
   void clear();
   void clear();
   void clear_timewarps();
   void clear_timewarps();

+ 2 - 1
panda/src/pgraph/nodePath.h

@@ -913,10 +913,11 @@ PUBLISHED:
   INLINE bool has_net_tag(const string &key) const;
   INLINE bool has_net_tag(const string &key) const;
   NodePath find_net_tag(const string &key) const;
   NodePath find_net_tag(const string &key) const;
 
 
-  MAKE_MAP_PROPERTY(tags, has_tag, get_tag, set_tag, clear_tag);
   MAKE_MAP_PROPERTY(net_tags, has_net_tag, get_net_tag);
   MAKE_MAP_PROPERTY(net_tags, has_net_tag, get_net_tag);
 
 
+  EXTENSION(INLINE PyObject *get_tags() const);
   EXTENSION(INLINE PyObject *get_tag_keys() const);
   EXTENSION(INLINE PyObject *get_tag_keys() const);
+  MAKE_PROPERTY(tags, get_tags);
 
 
   EXTENSION(PyObject *get_python_tags());
   EXTENSION(PyObject *get_python_tags());
   EXTENSION(INLINE void set_python_tag(PyObject *keys, PyObject *value));
   EXTENSION(INLINE void set_python_tag(PyObject *keys, PyObject *value));

+ 1 - 0
panda/src/pgraph/nodePath_ext.I

@@ -32,6 +32,7 @@ get_python_tags() {
 /**
 /**
  * This variant on get_tag_keys returns a Python list of strings.  Returns
  * This variant on get_tag_keys returns a Python list of strings.  Returns
  * None if the NodePath is empty.
  * None if the NodePath is empty.
+ * @deprecated use `np.tags.keys()` instead.
  */
  */
 INLINE PyObject *Extension<NodePath>::
 INLINE PyObject *Extension<NodePath>::
 get_tag_keys() const {
 get_tag_keys() const {

+ 25 - 0
panda/src/pgraph/nodePath_ext.cxx

@@ -27,6 +27,7 @@ extern struct Dtool_PyTypedObject Dtool_LPoint3d;
 extern struct Dtool_PyTypedObject Dtool_LPoint3f;
 extern struct Dtool_PyTypedObject Dtool_LPoint3f;
 #endif
 #endif
 extern struct Dtool_PyTypedObject Dtool_NodePath;
 extern struct Dtool_PyTypedObject Dtool_NodePath;
+extern struct Dtool_PyTypedObject Dtool_PandaNode;
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
 /**
 /**
@@ -166,6 +167,30 @@ __reduce_persist__(PyObject *self, PyObject *pickler) const {
   return tuple;
   return tuple;
 }
 }
 
 
+/**
+ * Returns the associated node's tags.
+ */
+PyObject *Extension<NodePath>::
+get_tags() const {
+  // An empty NodePath returns None
+  if (_this->is_empty()) {
+    Py_INCREF(Py_None);
+    return 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
  * 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
  * with the indicated key, if any, or an empty NodePath if no ancestor of this

+ 1 - 0
panda/src/pgraph/nodePath_ext.h

@@ -34,6 +34,7 @@ public:
   PyObject *__reduce__(PyObject *self) const;
   PyObject *__reduce__(PyObject *self) const;
   PyObject *__reduce_persist__(PyObject *self, PyObject *pickler) const;
   PyObject *__reduce_persist__(PyObject *self, PyObject *pickler) const;
 
 
+  PyObject *get_tags() const;
   INLINE PyObject *get_tag_keys() const;
   INLINE PyObject *get_tag_keys() const;
 
 
   INLINE PyObject *get_python_tags();
   INLINE PyObject *get_python_tags();

+ 31 - 17
panda/src/pgraph/pandaNode.I

@@ -348,12 +348,12 @@ has_dirty_prev_transform() const {
 INLINE string PandaNode::
 INLINE string PandaNode::
 get_tag(const string &key, Thread *current_thread) const {
 get_tag(const string &key, Thread *current_thread) const {
   CDReader cdata(_cycler, current_thread);
   CDReader cdata(_cycler, current_thread);
-  TagData::const_iterator ti;
-  ti = cdata->_tag_data.find(key);
-  if (ti != cdata->_tag_data.end()) {
-    return (*ti).second;
+  int index = cdata->_tag_data.find(key);
+  if (index >= 0) {
+    return cdata->_tag_data.get_data((size_t)index);
+  } else {
+    return string();
   }
   }
-  return string();
 }
 }
 
 
 /**
 /**
@@ -364,9 +364,25 @@ get_tag(const string &key, Thread *current_thread) const {
 INLINE bool PandaNode::
 INLINE bool PandaNode::
 has_tag(const string &key, Thread *current_thread) const {
 has_tag(const string &key, Thread *current_thread) const {
   CDReader cdata(_cycler, current_thread);
   CDReader cdata(_cycler, current_thread);
-  TagData::const_iterator ti;
-  ti = cdata->_tag_data.find(key);
-  return (ti != cdata->_tag_data.end());
+  return cdata->_tag_data.find(key) >= 0;
+}
+
+/**
+ * Returns the number of tags applied to this node.
+ */
+INLINE size_t PandaNode::
+get_num_tags() const {
+  CDReader cdata(_cycler);
+  return cdata->_tag_data.size();
+}
+
+/**
+ * Returns the key of the nth tag applied to this node.
+ */
+INLINE string PandaNode::
+get_tag_key(size_t i) const {
+  CDReader cdata(_cycler);
+  return cdata->_tag_data.get_key(i);
 }
 }
 
 
 /**
 /**
@@ -376,7 +392,7 @@ has_tag(const string &key, Thread *current_thread) const {
 INLINE bool PandaNode::
 INLINE bool PandaNode::
 has_tags() const {
 has_tags() const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  if (!cdata->_tag_data.empty()) {
+  if (!cdata->_tag_data.is_empty()) {
     return true;
     return true;
   }
   }
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
@@ -1448,12 +1464,12 @@ get_prev_transform() const {
  */
  */
 INLINE string PandaNodePipelineReader::
 INLINE string PandaNodePipelineReader::
 get_tag(const string &key) const {
 get_tag(const string &key) const {
-  PandaNode::TagData::const_iterator ti;
-  ti = _cdata->_tag_data.find(key);
-  if (ti != _cdata->_tag_data.end()) {
-    return (*ti).second;
+  int index = _cdata->_tag_data.find(key);
+  if (index >= 0) {
+    return _cdata->_tag_data.get_data((size_t)index);
+  } else {
+    return string();
   }
   }
-  return string();
 }
 }
 
 
 /**
 /**
@@ -1463,9 +1479,7 @@ get_tag(const string &key) const {
  */
  */
 INLINE bool PandaNodePipelineReader::
 INLINE bool PandaNodePipelineReader::
 has_tag(const string &key) const {
 has_tag(const string &key) const {
-  PandaNode::TagData::const_iterator ti;
-  ti = _cdata->_tag_data.find(key);
-  return (ti != _cdata->_tag_data.end());
+  return _cdata->_tag_data.find(key) >= 0;
 }
 }
 
 
 /**
 /**

+ 31 - 44
panda/src/pgraph/pandaNode.cxx

@@ -1216,7 +1216,7 @@ set_tag(const string &key, const string &value, Thread *current_thread) {
   // stages.
   // stages.
   OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
   OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
     CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
     CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
-    cdata->_tag_data[key] = value;
+    cdata->_tag_data.store(key, value);
     cdata->set_fancy_bit(FB_tag, true);
     cdata->set_fancy_bit(FB_tag, true);
   }
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
@@ -1231,8 +1231,8 @@ void PandaNode::
 clear_tag(const string &key, Thread *current_thread) {
 clear_tag(const string &key, Thread *current_thread) {
   OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
   OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
     CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
     CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
-    cdata->_tag_data.erase(key);
-    cdata->set_fancy_bit(FB_tag, !cdata->_tag_data.empty());
+    cdata->_tag_data.remove(key);
+    cdata->set_fancy_bit(FB_tag, !cdata->_tag_data.is_empty());
   }
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   mark_bam_modified();
   mark_bam_modified();
@@ -1257,13 +1257,10 @@ copy_tags(PandaNode *other) {
     CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
     CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
     CDStageReader cdatar(other->_cycler, pipeline_stage, current_thread);
     CDStageReader cdatar(other->_cycler, pipeline_stage, current_thread);
 
 
-    TagData::const_iterator ti;
-    for (ti = cdatar->_tag_data.begin();
-         ti != cdatar->_tag_data.end();
-         ++ti) {
-      cdataw->_tag_data[(*ti).first] = (*ti).second;
+    for (size_t n = 0; n < cdatar->_tag_data.size(); ++n) {
+      cdataw->_tag_data.store(cdatar->_tag_data.get_key(n), cdatar->_tag_data.get_data(n));
     }
     }
-    cdataw->set_fancy_bit(FB_tag, !cdataw->_tag_data.empty());
+    cdataw->set_fancy_bit(FB_tag, !cdataw->_tag_data.is_empty());
   }
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
 
 
@@ -1286,14 +1283,11 @@ copy_tags(PandaNode *other) {
 void PandaNode::
 void PandaNode::
 list_tags(ostream &out, const string &separator) const {
 list_tags(ostream &out, const string &separator) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  if (!cdata->_tag_data.empty()) {
-    TagData::const_iterator ti = cdata->_tag_data.begin();
-    out << (*ti).first;
-    ++ti;
-    while (ti != cdata->_tag_data.end()) {
-      out << separator << (*ti).first;
-      ++ti;
+  for (size_t n = 0; n < cdata->_tag_data.size(); ++n) {
+    if (n > 0) {
+      out << separator;
     }
     }
+    out << cdata->_tag_data.get_key(n);
   }
   }
 
 
   // We used to list the Python tags here.  That's a bit awkward, though,
   // We used to list the Python tags here.  That's a bit awkward, though,
@@ -1310,12 +1304,8 @@ list_tags(ostream &out, const string &separator) const {
 void PandaNode::
 void PandaNode::
 get_tag_keys(vector_string &keys) const {
 get_tag_keys(vector_string &keys) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  if (!cdata->_tag_data.empty()) {
-    TagData::const_iterator ti = cdata->_tag_data.begin();
-    while (ti != cdata->_tag_data.end()) {
-      keys.push_back((*ti).first);
-      ++ti;
-    }
+  for (size_t n = 0; n < cdata->_tag_data.size(); ++n) {
+    keys.push_back(cdata->_tag_data.get_key(n));
   }
   }
 }
 }
 
 
@@ -1331,28 +1321,30 @@ compare_tags(const PandaNode *other) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
   CDReader cdata_other(other->_cycler);
   CDReader cdata_other(other->_cycler);
 
 
-  TagData::const_iterator ati = cdata->_tag_data.begin();
-  TagData::const_iterator bti = cdata_other->_tag_data.begin();
-  while (ati != cdata->_tag_data.end() &&
-         bti != cdata_other->_tag_data.end()) {
-    int cmp = strcmp((*ati).first.c_str(), (*bti).first.c_str());
+  const TagData &a_data = cdata->_tag_data;
+  const TagData &b_data = cdata_other->_tag_data;
+
+  size_t ai = 0;
+  size_t bi = 0;
+  while (ai < a_data.size() && bi < b_data.size()) {
+    int cmp = strcmp(a_data.get_key(ai).c_str(), b_data.get_key(bi).c_str());
     if (cmp != 0) {
     if (cmp != 0) {
       return cmp;
       return cmp;
     }
     }
 
 
-    cmp = strcmp((*ati).second.c_str(), (*bti).second.c_str());
+    cmp = strcmp(a_data.get_key(ai).c_str(), b_data.get_key(bi).c_str());
     if (cmp != 0) {
     if (cmp != 0) {
       return cmp;
       return cmp;
     }
     }
 
 
-    ++ati;
-    ++bti;
+    ++ai;
+    ++bi;
   }
   }
-  if (ati != cdata->_tag_data.end()) {
+  if (ai < a_data.size()) {
     // list A is longer.
     // list A is longer.
     return 1;
     return 1;
   }
   }
-  if (bti != cdata_other->_tag_data.end()) {
+  if (bi < b_data.size()) {
     // list B is longer.
     // list B is longer.
     return -1;
     return -1;
   }
   }
@@ -1412,11 +1404,8 @@ copy_all_properties(PandaNode *other) {
     // to preserve properties such as the default GeomNode bitmask.
     // to preserve properties such as the default GeomNode bitmask.
     cdataw->_into_collide_mask |= cdatar->_into_collide_mask;
     cdataw->_into_collide_mask |= cdatar->_into_collide_mask;
 
 
-    TagData::const_iterator ti;
-    for (ti = cdatar->_tag_data.begin();
-         ti != cdatar->_tag_data.end();
-         ++ti) {
-      cdataw->_tag_data[(*ti).first] = (*ti).second;
+    for (size_t n = 0; n < cdatar->_tag_data.size(); ++n) {
+      cdataw->_tag_data.store(cdatar->_tag_data.get_key(n), cdatar->_tag_data.get_data(n));
     }
     }
 
 
     static const int change_bits = (FB_transform | FB_state | FB_effects |
     static const int change_bits = (FB_transform | FB_state | FB_effects |
@@ -3775,13 +3764,11 @@ write_datagram(BamWriter *manager, Datagram &dg) const {
   dg.add_uint8(_bounds_type);
   dg.add_uint8(_bounds_type);
 
 
   dg.add_uint32(_tag_data.size());
   dg.add_uint32(_tag_data.size());
-  TagData::const_iterator ti;
-  for (ti = _tag_data.begin(); ti != _tag_data.end(); ++ti) {
-    dg.add_string((*ti).first);
-    dg.add_string((*ti).second);
+  for (size_t n = 0; n < _tag_data.size(); ++n) {
+    dg.add_string(_tag_data.get_key(n));
+    dg.add_string(_tag_data.get_data(n));
   }
   }
 
 
-
   write_up_list(*get_up(), manager, dg);
   write_up_list(*get_up(), manager, dg);
   write_down_list(*get_down(), manager, dg);
   write_down_list(*get_down(), manager, dg);
   write_down_list(*get_stashed(), manager, dg);
   write_down_list(*get_stashed(), manager, dg);
@@ -3855,7 +3842,7 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
   set_fancy_bit(FB_transform, !_transform->is_identity());
   set_fancy_bit(FB_transform, !_transform->is_identity());
   set_fancy_bit(FB_state, !_state->is_empty());
   set_fancy_bit(FB_state, !_state->is_empty());
   set_fancy_bit(FB_effects, !_effects->is_empty());
   set_fancy_bit(FB_effects, !_effects->is_empty());
-  set_fancy_bit(FB_tag, !_tag_data.empty());
+  set_fancy_bit(FB_tag, !_tag_data.is_empty());
 
 
   // Mark the bounds stale.
   // Mark the bounds stale.
   ++_next_update;
   ++_next_update;
@@ -3914,7 +3901,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   for (int i = 0; i < num_tags; i++) {
   for (int i = 0; i < num_tags; i++) {
     string key = scan.get_string();
     string key = scan.get_string();
     string value = scan.get_string();
     string value = scan.get_string();
-    _tag_data[key] = value;
+    _tag_data.store(key, value);
   }
   }
 
 
 
 

+ 10 - 1
panda/src/pgraph/pandaNode.h

@@ -45,6 +45,7 @@
 #include "copyOnWritePointer.h"
 #include "copyOnWritePointer.h"
 #include "lightReMutex.h"
 #include "lightReMutex.h"
 #include "extension.h"
 #include "extension.h"
+#include "simpleHashMap.h"
 
 
 class NodePathComponent;
 class NodePathComponent;
 class CullTraverser;
 class CullTraverser;
@@ -196,7 +197,15 @@ PUBLISHED:
                       Thread *current_thread = Thread::get_current_thread()) const;
                       Thread *current_thread = Thread::get_current_thread()) const;
   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());
+
+public:
   void get_tag_keys(vector_string &keys) const;
   void get_tag_keys(vector_string &keys) const;
+  INLINE size_t get_num_tags() const;
+  INLINE string get_tag_key(size_t i) const;
+
+PUBLISHED:
+  MAKE_MAP_PROPERTY(tags, has_tag, get_tag, set_tag, clear_tag);
+  MAKE_MAP_KEYS_SEQ(tags, get_num_tags, get_tag_key);
 
 
   EXTENSION(PyObject *get_tag_keys() const);
   EXTENSION(PyObject *get_tag_keys() const);
 
 
@@ -511,7 +520,7 @@ private:
 
 
   // This is used to maintain a table of keyed data on each node, for the
   // This is used to maintain a table of keyed data on each node, for the
   // user's purposes.
   // user's purposes.
-  typedef phash_map<string, string, string_hash> TagData;
+  typedef SimpleHashMap<string, string, string_hash> TagData;
 
 
   // This is actually implemented in pandaNode_ext.h, but defined here so
   // This is actually implemented in pandaNode_ext.h, but defined here so
   // that we can destruct it from the C++ side.  Note that it isn't cycled,
   // that we can destruct it from the C++ side.  Note that it isn't cycled,

+ 29 - 3
panda/src/pgraph/renderEffects.I

@@ -97,8 +97,9 @@ is_empty() const {
 
 
 /**
 /**
  * Returns the number of separate effects indicated in the state.
  * Returns the number of separate effects indicated in the state.
+ * @deprecated in Python, use len(effects) instead, or effects.size() in C++.
  */
  */
-INLINE int RenderEffects::
+INLINE size_t RenderEffects::
 get_num_effects() const {
 get_num_effects() const {
   return _effects.size();
   return _effects.size();
 }
 }
@@ -107,11 +108,36 @@ get_num_effects() const {
  * Returns the nth effect in the state.
  * Returns the nth effect in the state.
  */
  */
 INLINE const RenderEffect *RenderEffects::
 INLINE const RenderEffect *RenderEffects::
-get_effect(int n) const {
-  nassertr(n >= 0 && n < (int)_effects.size(), NULL);
+get_effect(size_t n) const {
+  nassertr(n < _effects.size(), nullptr);
   return _effects[n]._effect;
   return _effects[n]._effect;
 }
 }
 
 
+/**
+ * Returns the number of separate effects indicated in the state.
+ */
+INLINE size_t RenderEffects::
+size() const {
+  return _effects.size();
+}
+
+/**
+ * Returns the nth effect in the state.
+ */
+INLINE const RenderEffect *RenderEffects::
+operator [](size_t n) const {
+  nassertr(n < _effects.size(), nullptr);
+  return _effects[n]._effect;
+}
+
+/**
+ * Returns the effect in the state with the given type.
+ */
+INLINE const RenderEffect *RenderEffects::
+operator [](TypeHandle type) const {
+  return get_effect(type);
+}
+
 /**
 /**
  * This function is provided as an optimization, to speed up the render-time
  * This function is provided as an optimization, to speed up the render-time
  * checking for the existance of a DecalEffect on this state.  It returns true
  * checking for the existance of a DecalEffect on this state.  It returns true

+ 6 - 2
panda/src/pgraph/renderEffects.h

@@ -58,8 +58,12 @@ PUBLISHED:
   bool operator < (const RenderEffects &other) const;
   bool operator < (const RenderEffects &other) const;
 
 
   INLINE bool is_empty() const;
   INLINE bool is_empty() const;
-  INLINE int get_num_effects() const;
-  INLINE const RenderEffect *get_effect(int n) const;
+  INLINE size_t get_num_effects() const;
+  INLINE const RenderEffect *get_effect(size_t n) const;
+
+  INLINE size_t size() const;
+  INLINE const RenderEffect *operator [] (size_t n) const;
+  INLINE const RenderEffect *operator [] (TypeHandle type) const;
 
 
   int find_effect(TypeHandle type) const;
   int find_effect(TypeHandle type) const;
 
 

+ 6 - 2
panda/src/pgraph/textureAttrib.h

@@ -65,7 +65,12 @@ PUBLISHED:
   int find_on_stage(const TextureStage *stage) const;
   int find_on_stage(const TextureStage *stage) const;
 
 
   MAKE_SEQ_PROPERTY(on_stages, get_num_on_stages, get_on_stage);
   MAKE_SEQ_PROPERTY(on_stages, get_num_on_stages, get_on_stage);
-  MAKE_MAP_PROPERTY(on_stages, find_on_stage, get_on_stage);
+
+  MAKE_MAP_PROPERTY(textures, has_on_stage, get_on_texture);
+  MAKE_MAP_KEYS_SEQ(textures, get_num_on_stages, get_on_stage);
+
+  MAKE_MAP_PROPERTY(samplers, has_on_stage, get_on_sampler);
+  MAKE_MAP_KEYS_SEQ(samplers, get_num_on_stages, get_on_stage);
 
 
   INLINE int get_num_off_stages() const;
   INLINE int get_num_off_stages() const;
   INLINE TextureStage *get_off_stage(int n) const;
   INLINE TextureStage *get_off_stage(int n) const;
@@ -74,7 +79,6 @@ PUBLISHED:
   INLINE bool has_all_off() const;
   INLINE bool has_all_off() const;
 
 
   MAKE_SEQ_PROPERTY(off_stages, get_num_off_stages, get_off_stage);
   MAKE_SEQ_PROPERTY(off_stages, get_num_off_stages, get_off_stage);
-  MAKE_MAP_PROPERTY(off_stages, has_off_stage, get_off_stage);
 
 
   INLINE bool is_identity() const;
   INLINE bool is_identity() const;
 
 

+ 15 - 0
panda/src/pgraphnodes/computeNode.I

@@ -71,6 +71,21 @@ set_dispatch(size_t n, const LVecBase3i &dispatch) {
   cdata->_dispatches[n] = dispatch;
   cdata->_dispatches[n] = dispatch;
 }
 }
 
 
+/**
+ * Inserts a dispatch command with the given number of work groups in the X,
+ * Y, and Z dimensions at the given position in the list of dispatch commands.
+ * Any of these values may be set to 1 if the respective dimension should not
+ * be used.
+ */
+INLINE void ComputeNode::
+insert_dispatch(size_t n, const LVecBase3i &dispatch) {
+  Dispatcher::CDWriter cdata(_dispatcher->_cycler);
+  if (n > cdata->_dispatches.size()) {
+    n = cdata->_dispatches.size();
+  }
+  cdata->_dispatches.insert(cdata->_dispatches.begin(), dispatch);
+}
+
 /**
 /**
  * Erases the given dispatch index from the list.
  * Erases the given dispatch index from the list.
  */
  */

+ 2 - 1
panda/src/pgraphnodes/computeNode.h

@@ -34,11 +34,12 @@ PUBLISHED:
   INLINE size_t get_num_dispatches() const;
   INLINE size_t get_num_dispatches() const;
   INLINE const LVecBase3i &get_dispatch(size_t i) const;
   INLINE const LVecBase3i &get_dispatch(size_t i) const;
   INLINE void set_dispatch(size_t i, const LVecBase3i &num_groups);
   INLINE void set_dispatch(size_t i, const LVecBase3i &num_groups);
+  INLINE void insert_dispatch(size_t i, const LVecBase3i &num_groups);
   INLINE void remove_dispatch(size_t i);
   INLINE void remove_dispatch(size_t i);
   INLINE void clear_dispatches();
   INLINE void clear_dispatches();
 
 
   MAKE_SEQ(get_dispatches, get_num_dispatches, get_dispatch);
   MAKE_SEQ(get_dispatches, get_num_dispatches, get_dispatch);
-  MAKE_SEQ_PROPERTY(dispatches, get_num_dispatches, get_dispatch, set_dispatch, remove_dispatch);
+  MAKE_SEQ_PROPERTY(dispatches, get_num_dispatches, get_dispatch, set_dispatch, remove_dispatch, insert_dispatch);
 
 
 public:
 public:
   ComputeNode(const ComputeNode &copy);
   ComputeNode(const ComputeNode &copy);

+ 16 - 2
panda/src/physics/forceNode.cxx

@@ -69,15 +69,29 @@ add_forces_from(const ForceNode &other) {
  */
  */
 void ForceNode::
 void ForceNode::
 set_force(size_t index, BaseForce *force) {
 set_force(size_t index, BaseForce *force) {
-  nassertv(index <= _forces.size());
+  nassertv(index < _forces.size());
 
 
-  _forces[index]->_force_node = (ForceNode *)NULL;
+  _forces[index]->_force_node = nullptr;
   _forces[index]->_force_node_path.clear();
   _forces[index]->_force_node_path.clear();
   _forces[index] = force;
   _forces[index] = force;
   force->_force_node = this;
   force->_force_node = this;
   force->_force_node_path = NodePath(this);
   force->_force_node_path = NodePath(this);
 }
 }
 
 
+/**
+ * insert operation
+ */
+void ForceNode::
+insert_force(size_t index, BaseForce *force) {
+  if (index > _forces.size()) {
+    index = _forces.size();
+  }
+
+  _forces.insert(_forces.begin() + index, force);
+  force->_force_node = this;
+  force->_force_node_path = NodePath(this);
+}
+
 /**
 /**
  * remove operation
  * remove operation
  */
  */

+ 2 - 1
panda/src/physics/forceNode.h

@@ -35,10 +35,11 @@ PUBLISHED:
 
 
   void add_forces_from(const ForceNode &other);
   void add_forces_from(const ForceNode &other);
   void set_force(size_t index, BaseForce *force);
   void set_force(size_t index, BaseForce *force);
+  void insert_force(size_t index, BaseForce *force);
   void remove_force(BaseForce *force);
   void remove_force(BaseForce *force);
   void remove_force(size_t index);
   void remove_force(size_t index);
 
 
-  MAKE_SEQ_PROPERTY(forces, get_num_forces, get_force, set_force, remove_force);
+  MAKE_SEQ_PROPERTY(forces, get_num_forces, get_force, set_force, remove_force, insert_force);
 
 
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write_forces(ostream &out, unsigned int indent=0) const;
   virtual void write_forces(ostream &out, unsigned int indent=0) const;

+ 15 - 2
panda/src/physics/physicalNode.cxx

@@ -77,13 +77,26 @@ add_physicals_from(const PhysicalNode &other) {
  */
  */
 void PhysicalNode::
 void PhysicalNode::
 set_physical(size_t index, Physical *physical) {
 set_physical(size_t index, Physical *physical) {
-  nassertv(index <= _physicals.size());
+  nassertv(index < _physicals.size());
 
 
-  _physicals[index]->_physical_node = (PhysicalNode *) NULL;
+  _physicals[index]->_physical_node = nullptr;
   _physicals[index] = physical;
   _physicals[index] = physical;
   physical->_physical_node = this;
   physical->_physical_node = this;
 }
 }
 
 
+/**
+ * insert operation
+ */
+void PhysicalNode::
+insert_physical(size_t index, Physical *physical) {
+  if (index > _physicals.size()) {
+    index = _physicals.size();
+  }
+
+  _physicals.insert(_physicals.begin() + index, physical);
+  physical->_physical_node = this;
+}
+
 /**
 /**
  * remove operation
  * remove operation
  */
  */

+ 3 - 1
panda/src/physics/physicalNode.h

@@ -36,10 +36,12 @@ PUBLISHED:
 
 
   void add_physicals_from(const PhysicalNode &other);
   void add_physicals_from(const PhysicalNode &other);
   void set_physical(size_t index, Physical *physical);
   void set_physical(size_t index, Physical *physical);
+  void insert_physical(size_t index, Physical *physical);
   void remove_physical(Physical *physical);
   void remove_physical(Physical *physical);
   void remove_physical(size_t index);
   void remove_physical(size_t index);
 
 
-  MAKE_SEQ_PROPERTY(physicals, get_num_physicals, get_physical, set_physical, remove_physical);
+  MAKE_SEQ_PROPERTY(physicals, get_num_physicals, get_physical, set_physical,
+                    remove_physical, insert_physical);
 
 
   virtual void write(ostream &out, unsigned int indent=0) const;
   virtual void write(ostream &out, unsigned int indent=0) const;
 
 

+ 64 - 0
panda/src/putil/simpleHashMap.I

@@ -25,6 +25,25 @@ SimpleHashMap(const Compare &comp) :
 {
 {
 }
 }
 
 
+/**
+ *
+ */
+template<class Key, class Value, class Compare>
+INLINE SimpleHashMap<Key, Value, Compare>::
+SimpleHashMap(const SimpleHashMap &copy) :
+  _table_size(copy._table_size),
+  _num_entries(copy._num_entries),
+  _comp(copy._comp) {
+
+  // We allocate enough bytes for _table_size elements of TableEntry, plus
+  // _table_size * 4 more ints at the end (for the index array).
+  size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity);
+
+  _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
+  _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
+  memcpy(_table, copy._table, alloc_size);
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -37,7 +56,10 @@ SimpleHashMap(SimpleHashMap &&from) NOEXCEPT :
   _num_entries(from._num_entries),
   _num_entries(from._num_entries),
   _comp(move(from._comp))
   _comp(move(from._comp))
 {
 {
+  from._table = nullptr;
+  from._deleted_chain = nullptr;
   from._table_size = 0;
   from._table_size = 0;
+  from._num_entries = 0;
 }
 }
 
 
 /**
 /**
@@ -49,6 +71,48 @@ INLINE SimpleHashMap<Key, Value, Compare>::
   clear();
   clear();
 }
 }
 
 
+/**
+ *
+ */
+template<class Key, class Value, class Compare>
+INLINE SimpleHashMap<Key, Value, Compare> &SimpleHashMap<Key, Value, Compare>::
+operator = (const SimpleHashMap<Key, Value, Compare> &copy) {
+  if (this != &copy) {
+    _table_size = copy._table_size;
+    _num_entries = copy._num_entries;
+    _comp = copy._comp;
+
+    // We allocate enough bytes for _table_size elements of TableEntry, plus
+    // _table_size * 4 more ints at the end (for the index array).
+    size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity);
+
+    _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
+    _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
+    memcpy(_table, copy._table, alloc_size);
+  }
+  return *this;
+}
+
+/**
+ *
+ */
+template<class Key, class Value, class Compare>
+INLINE SimpleHashMap<Key, Value, Compare> &SimpleHashMap<Key, Value, Compare>::
+operator = (SimpleHashMap<Key, Value, Compare> &&from) NOEXCEPT {
+  if (this != &from) {
+    _table = from._table;
+    _deleted_chain = from._deleted_chain;
+    _table_size = from._table_size;
+    _num_entries = from._num_entries;
+    _comp = move(from._comp);
+
+    from._table = nullptr;
+    from._deleted_chain = nullptr;
+    from._table_size = 0;
+    from._num_entries = 0;
+  }
+}
+
 /**
 /**
  * Quickly exchanges the contents of this map and the other map.
  * Quickly exchanges the contents of this map and the other map.
  */
  */

+ 4 - 0
panda/src/putil/simpleHashMap.h

@@ -86,9 +86,13 @@ class SimpleHashMap {
 public:
 public:
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   CONSTEXPR SimpleHashMap(const Compare &comp = Compare());
   CONSTEXPR SimpleHashMap(const Compare &comp = Compare());
+  INLINE SimpleHashMap(const SimpleHashMap &copy);
   INLINE SimpleHashMap(SimpleHashMap &&from) NOEXCEPT;
   INLINE SimpleHashMap(SimpleHashMap &&from) NOEXCEPT;
   INLINE ~SimpleHashMap();
   INLINE ~SimpleHashMap();
 
 
+  INLINE SimpleHashMap &operator = (const SimpleHashMap &copy);
+  INLINE SimpleHashMap &operator = (SimpleHashMap &&from) NOEXCEPT;
+
   INLINE void swap(SimpleHashMap &other);
   INLINE void swap(SimpleHashMap &other);
 
 
   int find(const Key &key) const;
   int find(const Key &key) const;

+ 157 - 0
tests/interrogate/test_property.py

@@ -2,6 +2,7 @@ import sys
 import pytest
 import pytest
 from panda3d import core
 from panda3d import core
 from contextlib import contextmanager
 from contextlib import contextmanager
+import collections
 
 
 
 
 @contextmanager
 @contextmanager
@@ -70,6 +71,15 @@ item_b = core.CollisionSphere((0, 0, 0), 2)
 item_c = core.CollisionSphere((0, 0, 0), 3)
 item_c = core.CollisionSphere((0, 0, 0), 3)
 
 
 
 
+def test_seq_property_abc():
+    prop = seq_property()
+    assert isinstance(prop, collections.Container)
+    assert isinstance(prop, collections.Sized)
+    assert isinstance(prop, collections.Iterable)
+    assert isinstance(prop, collections.MutableSequence)
+    assert isinstance(prop, collections.Sequence)
+
+
 def test_seq_property_empty():
 def test_seq_property_empty():
     prop = seq_property()
     prop = seq_property()
     assert not prop
     assert not prop
@@ -92,6 +102,11 @@ def test_seq_property_iter():
     assert None not in prop
     assert None not in prop
 
 
 
 
+def test_seq_property_reversed():
+    prop = seq_property(item_a, item_b, item_b)
+    assert tuple(reversed(prop)) == tuple(reversed(tuple(prop)))
+
+
 def test_seq_property_getitem():
 def test_seq_property_getitem():
     prop = seq_property(item_a, item_b, item_b)
     prop = seq_property(item_a, item_b, item_b)
 
 
@@ -313,6 +328,88 @@ def test_seq_property_remove():
         prop.remove("nonsense")
         prop.remove("nonsense")
 
 
 
 
+def test_seq_property_append():
+    prop = seq_property(item_a, item_b)
+
+    with constant_refcount(item_c):
+        prop.append(item_c)
+
+    assert tuple(prop) == (item_a, item_b, item_c)
+
+    with pytest.raises(TypeError):
+        prop.append(None)
+    with pytest.raises(TypeError):
+        prop.append("nonsense")
+
+
+def test_seq_property_insert():
+    # Adding at the beginning
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(0, item_b)
+
+    assert tuple(prop) == (item_b, item_a, item_a, item_a)
+
+    # Adding in the middle
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(2, item_b)
+
+    assert tuple(prop) == (item_a, item_a, item_b, item_a)
+
+    # Adding at the end
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(len(prop), item_b)
+
+    assert tuple(prop) == (item_a, item_a, item_a, item_b)
+
+    # Adding with negative index
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(-2, item_b)
+
+    assert tuple(prop) == (item_a, item_b, item_a, item_a)
+
+    # Adding at the end with overflowing index
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(2345, item_b)
+
+    assert tuple(prop) == (item_a, item_a, item_a, item_b)
+
+    # Adding at the beginning with negative overflowing index
+    prop = seq_property(item_a, item_a, item_a)
+    with constant_refcount(item_b):
+        prop.insert(-2345, item_b)
+
+    assert tuple(prop) == (item_b, item_a, item_a, item_a)
+
+
+def test_seq_property_extend():
+    prop = seq_property(item_a)
+
+    with constant_refcount(item_b):
+        prop.extend((item_b, item_c))
+
+    assert tuple(prop) == (item_a, item_b, item_c)
+
+    with pytest.raises(TypeError):
+        prop.extend(None)
+    with pytest.raises(TypeError):
+        prop.extend("nonsense")
+    with pytest.raises(TypeError):
+        prop.extend(item_a)
+    with pytest.raises(TypeError):
+        prop.extend(item_a, item_b)
+    with pytest.raises(TypeError):
+        prop.extend()
+    with pytest.raises(TypeError):
+        prop.extend((item_a, None))
+    with pytest.raises(TypeError):
+        prop.extend(["nonsense"])
+
+
 # The next tests are for MAKE_MAP_PROPERTY.
 # The next tests are for MAKE_MAP_PROPERTY.
 @pytest.fixture
 @pytest.fixture
 def map_property(**items):
 def map_property(**items):
@@ -326,6 +423,27 @@ def map_property(**items):
     return np.tags
     return np.tags
 
 
 
 
+def test_map_property_abc():
+    prop = map_property()
+    assert isinstance(prop, collections.Container)
+    assert isinstance(prop, collections.Sized)
+    assert isinstance(prop, collections.Iterable)
+    assert isinstance(prop, collections.MutableMapping)
+    assert isinstance(prop, collections.Mapping)
+
+
+def test_map_property_empty():
+    prop = map_property()
+    assert not prop
+    assert len(prop) == 0
+
+    with pytest.raises(KeyError):
+        prop.popitem()
+
+    with pytest.raises(KeyError):
+        prop['nonsense']
+
+
 def test_map_property_getitem():
 def test_map_property_getitem():
     key = 'key'
     key = 'key'
     value = 'value'
     value = 'value'
@@ -430,6 +548,24 @@ def test_map_property_pop():
     assert 'key' not in prop
     assert 'key' not in prop
 
 
 
 
+def test_map_property_popitem():
+    key = 'key'
+    value = 'value'
+    prop = map_property(**{key: value})
+
+    assert prop.popitem() == (key, value)
+
+    with pytest.raises(KeyError):
+        assert prop.popitem()
+
+
+def test_map_property_clear():
+    prop = map_property(key='value', key2='value2')
+
+    prop.clear()
+    assert len(prop) == 0
+
+
 def test_map_property_setdefault():
 def test_map_property_setdefault():
     prop = map_property(key='value')
     prop = map_property(key='value')
 
 
@@ -466,3 +602,24 @@ def test_map_property_update():
 
 
     assert prop['key'] == 'value'
     assert prop['key'] == 'value'
     assert prop['key2'] == 'value2'
     assert prop['key2'] == 'value2'
+
+
+def test_map_property_keys():
+    prop = map_property(key='value', key2='value2')
+
+    assert isinstance(prop.keys(), collections.MappingView)
+    assert frozenset(prop.keys()) == frozenset(('key', 'key2'))
+
+
+def test_map_property_values():
+    prop = map_property(key='value', key2='value2')
+
+    assert isinstance(prop.values(), collections.ValuesView)
+    assert frozenset(prop.values()) == frozenset(('value', 'value2'))
+
+
+def test_map_property_items():
+    prop = map_property(key='value', key2='value2')
+
+    assert isinstance(prop.items(), collections.MappingView)
+    assert frozenset(prop.items()) == frozenset((('key', 'value'), ('key2', 'value2')))

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