| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """
3 Provides classes and functions operating on the MayaAPI class database
4
5 :note: This module must not be auto-initialized as it assumes its parent package to
6 be present already
7 :note: The implementation is considered internal and may change any time unless stated
8 otherwise.
9 """
10 __docformat__ = "restructuredtext"
11
12 from mrv.path import make_path
13 from mrv.util import PipeSeparatedFile
14 import mrv.maya.env as env
15 import mrv.maya as mrvmaya
16
17 import maya.cmds as cmds
18 import maya.OpenMaya as api
19
20 import UserDict
21 import inspect
22 import re
23 from cStringIO import StringIO
24 import string
25 import sys
26 import os
27
28 import logging
29 log = logging.getLogger("mrv.maya.mdb")
30
31 __all__ = ("createDagNodeHierarchy", "createTypeNameToMfnClsMap", "apiModules",
32 "mfnDBPath", "cacheFilePath", "writeMfnDBCacheFiles",
33 "extractMFnFunctions", "PythonMFnCodeGenerator", "MMemberMap",
34 "MMethodDescriptor" )
39 """:return: Path to the node hierarchy file of the currently active maya version"""
40 return cacheFilePath( "nodeHierarchy", "hf", use_version = 1 )
41
43 """ Parse the nodes hierarchy file and return a `DAGTree` with its data
44 :return: `DAGTree`"""
45 mfile = nodeHierarchyFile()
46 return mrvmaya.dag_tree_from_tuple_list( mrvmaya.tuple_list_from_file( mfile ) )
47
49 """Parse a file associating node type names with the best compatible MFn function
50 set and return a dictionary with the data
51
52 :return: dict(((nodeTypeNameStr : api.MFnCls), ...)) dictionary with nodetypeName
53 MFn class mapping"""
54 typenameToClsMap = dict()
55
56 cfile = cacheFilePath( "nodeTypeToMfnCls", "map" )
57 fobj = open( cfile, 'r' )
58 pf = PipeSeparatedFile( fobj )
59
60 version = pf.beginReading( ) # don't care about version
61 for nodeTypeName, mfnTypeName in pf.readColumnLine( ):
62 found = False
63 for apimod in apiModules():
64 try:
65 typenameToClsMap[ nodeTypeName ] = getattr( apimod, mfnTypeName )
66 found = True
67 break # it worked, there is only one matching class
68 except AttributeError:
69 pass
70 # END for each api module
71 if not found:
72 log.debug("Couldn't find mfn class named %s" % mfnTypeName)
73 # END for each type/mfnclass pair
74 fobj.close()
75
76 return typenameToClsMap
77
84 """:return: tuple of api modules containing MayaAPI classes
85 :note: This takes a moment to load as it will import many api modules. Delay
86 the call as much as possible"""
87 import maya.OpenMaya as api
88 import maya.OpenMayaAnim as apianim
89 import maya.OpenMayaUI as apiui
90 import maya.OpenMayaRender as apirender
91 import maya.OpenMayaFX as apifx
92
93 return (api, apianim, apiui, apirender, apifx)
94
96 """Generate a path to a database file containing mfn wrapping information"""
97 return make_path(cacheFilePath("mfndb/"+ mfnclsname, '', use_version=False)[:-1]) # cut the '.'
98
100 """
101 :return: Path to file containing the c++ header of the given apiclass' name.
102 The file will not be verified, hence it may be inaccessible
103 :param apiname: string name, like 'MFnBase'
104 :raise ValueError: if MAYA_LOCATION is not set"""
105 p = make_path("$MAYA_LOCATION").expand_or_raise().realpath()
106 if sys.platform == 'darwin':
107 p = p.parent().parent() / "devkit"
108 # END handle platform dependency
109 return p / ("include/maya/%s.h" % apiname)
110
112 """Return path to cache file from which you would initialize data structures
113
114 :param use_version: if true, the maya version will be appended to the filename """
115 mfile = make_path( __file__ ).parent()
116 version = ""
117 if use_version:
118 version = cmds.about( version=1 ).split( " " )[0]
119 # END use version
120 return mfile / ( "cache/%s%s.%s" % ( filename, version, ext ) )
121
123 """Extract callables from mfncls, sorted into static methods and instance methods
124 :return: tuple(list(callable_staticmethod, ...), list(callable_instancemethod, ...))"""
125 mfnfuncs = list()
126 staticmfnfuncs = list()
127 mfnname = mfncls.__name__
128 for fn, f in mfncls.__dict__.iteritems():
129 if fn.startswith('_') or fn.endswith(mfnname) or not inspect.isroutine(f):
130 continue
131 # END skip non-routines
132
133 if isinstance(f, staticmethod):
134 # convert static methods into callable methods by fetching them officially
135 staticmfnfuncs.append(getattr(mfncls, fn))
136 else:
137 mfnfuncs.append(f)
138 # END handle static methods
139 # END for each function in mfncls dict
140
141 return (staticmfnfuncs, mfnfuncs)
142
144 """:return: True if the given mfncls has at least one enumeration"""
145 for n in mfncls.__dict__.keys():
146 if n.startswith('k') and n[1] in string.ascii_uppercase: # a single k would kill us ...
147 return True
148 # END for each dict name
149 return False
150
152 """Create a simple Memberlist of available mfn classes and their members
153 to allow a simple human-editable way of adjusting which methods will be added
154 to the Nodes.
155
156 :note: currently writes information about all known api modules"""
157 for apimod in apiModules():
158 mfnclsnames = [ clsname for clsname in dir( apimod ) if clsname.startswith( "MFn" ) ]
159 for mfnname in mfnclsnames:
160 mfncls = getattr( apimod, mfnname )
161 if not inspect.isclass(mfncls):
162 continue
163 # END assure we don't get methods, like MFnName_deallocateFlag
164 mfnfile = mfnDBPath( mfnname )
165
166
167 mfnfuncs = list()
168 fstatic, finst = extractMFnFunctions(mfncls)
169 mfnfuncs.extend(fstatic)
170 mfnfuncs.extend(finst)
171
172 if not mfnfuncs:
173 continue
174
175 db = MMemberMap()
176 if mfnfile.exists():
177 db = MMemberMap( mfnfile )
178
179 # assure folder exists
180 folder = mfnfile.dirname()
181 if not folder.isdir(): folder.makedirs()
182
183
184 # write data - simple set the keys, use default flags
185 for func in mfnfuncs:
186 # it could be prefixed with the function set name - remove the prefix
187 # This happens in maya2008 + and may introduce plenty of new methods
188 fname = func.__name__
189 if fname.startswith(mfnname):
190 fname = fname[len(mfnname)+1:] # cut MFnName_(function)
191 # END handle prefix
192
193 db.createEntry(fname)
194 # END for each function to add
195
196 # finally write the change db
197 db.writeToFile( mfnfile )
198 # END for each api class
202 """Return tuple(mobject, modifier) for the nodetype or raise RuntimeError
203 doIt has not yet been called on the modifier, hence the mobject is temporary"""
204 try:
205 mod = api.MDGModifier()
206 obj = mod.createNode(nodetype)
207 return (obj, mod)
208 except RuntimeError:
209 mod = api.MDagModifier()
210 tmpparent = mod.createNode("transform")
211 obj = mod.createNode(nodetype, tmpparent)
212 return (obj, mod)
213 # END exception handling
217 """Returns iterator which yield tuple(nodeTypeName, MObject, modifier) triplets
218 of nodeTypes, with an MObjects instance of it, created with the given modifier,
219 one for each node type available to maya.
220
221 :note: skips manipulators as they tend to crash maya on creation ( perhaps its only
222 one which does that, but its not that important )"""
223 for nodetype in sorted(cmds.ls(nodeTypes=1)):
224 # evil crashers
225 if 'Manip' in nodetype or nodetype.startswith('manip'):
226 continue
227 # END skip manipulators
228 try:
229 obj, mod = _createTmpNode(nodetype)
230 yield nodetype, obj, mod
231 except RuntimeError:
232 log.warn("Could not create '%s'" % nodetype)
233 continue
234 # END create dg/dag node exception handling
237 """Generate the node-hierarchy for the current version based on all node types
238 which can be created in maya.
239
240 :return: tuple(DAGTree, typeToMFnClsNameList)
241
242 * DAGTree representing the type hierarchy
243 * list represents typeName to MFnClassName associations
244
245 :note: should only be run as part of the upgrade process to prepare MRV for a
246 new maya release. Otherwise the nodetype tree will be read from a cache"""
247 from mrv.util import DAGTree
248 from mrv.util import uncapitalize, capitalize
249 from mrv.maya.util import MEnumeration
250
251 # init DagTree
252 root = "_root_"
253 depnode = 'dependNode'
254 depnode_list = [depnode]
255 noderoottype = 'node'
256 dagTree = DAGTree()
257 dagTree.add_edge(root, noderoottype)
258 dagTree.add_edge(noderoottype, depnode)
259
260 apiTypeToNodeTypeMap = dict() # apiTypeStr -> nodeTypeName
261 mfnTypes = set() # apiTypeStr of mfns used by NodeTypes
262 sl = list() # string list
263
264
265 mfndep = api.MFnDependencyNode()
266 def getInheritanceAndUndo(obj, modifier):
267 """Takes a prepared modifier ( doIt not yet called ) and the previously created object,
268 returning the inheritance of the obj which was retrieved before undoing
269 its creation"""
270 modifier.doIt()
271 mfndep.setObject(obj)
272 inheritance = cmds.nodeType(mfndep.name(), i=1)
273 modifier.undoIt()
274 return inheritance
275 # END utility
276
277
278 # CREATE ALL NODE TYPES
279 #######################
280 # query the inheritance afterwards
281 for nodetype, obj, mod in _iterAllNodeTypes():
282 inheritance = getInheritanceAndUndo(obj, mod)
283
284 if not inheritance:
285 log.error("Failed on type %s" % nodetype)
286 continue
287 # END handle unusual case
288
289 # filter bases
290 for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
291 dagTree.add_edge(parent, child)
292 # END for each edge to add
293
294 # retrieve all compatible MFnTypes - these refer to apiTypes which are
295 # also used by Nodes. Currently we have only the type of the node, fortunately,
296 # it will match very well with the MFnType, by just prepending MFn.
297 # As some MFn are in other modules, we will search em all ... later
298 apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype
299
300 api.MGlobal.getFunctionSetList(obj, sl)
301 for mfnType in sl:
302 mfnTypes.add(mfnType)
303 # END for each node type
304
305 # INSERT SPECIAL TYPES
306 ######################
307 # used by the type system if it cannot classify a node at all
308 dagTree.add_edge(depnode, 'unknown')
309
310 # can be iterated using the DagIterator, and it should at least be a dag node,
311 # not unknown. The groundPlane is actually a special object that users shouldn't
312 # touch directly
313 dagTree.add_edge('transform', 'groundPlane')
314
315 # although we don't handle manips directly, we must still support them if it
316 # is a plugin manipulator
317 dagTree.add_edge('transform', 'manipContainer')
318
319
320
321 # INSERT PLUGIN TYPES
322 ######################
323 for edge in ( (depnode, 'DependNode'),
324 ('shape', 'Shape'),
325 ('locator', 'LocatorNode'),
326 ('spring', 'SpringNode'),
327 ('transform', 'TransformNode'),
328 ('manipContainer', 'ManipContainer'),
329 ('dynBase', 'EmitterNode'),
330 ('field', 'FieldNode'),
331 ('objectSet', 'ObjectSet'),
332 ('geometryFilter', 'DeformerNode'),
333 (depnode, 'HwShaderNode'),
334 ('ikSolver', 'IkSolver'),
335 (depnode, 'ImagePlaneNode'),
336 (depnode, 'ParticleAttributeMapperNode') ):
337 dagTree.add_edge(edge[0], 'unknownPlugin'+edge[1])
338 # END for each plugin edge to add
339
340
341
342 # BULD TYPE-TO-MFN MAP
343 ######################
344 # Prepare data to be put into a type separated file, it associates
345 # a nodeType or nodeApiType with the respecitve MFnClass name
346 typeToMFn = set() # list((typeName, MFnClsName), ...)
347
348 # add default associations - some are not picked up due to name mismatches
349 typeToMFn.add((noderoottype, 'MFn'))
350 typeToMFn.add((depnode, 'MFnDependencyNode'))
351 typeToMFn.add(('dagContainer', 'MFnContainerNode'))
352
353 abstractMFns = ('MFnBase', ) # MFns which cannot be instantiated ans should be ignored
354 failedMFnTypes = list() # list of types we could not yet associate
355
356 modsapi = apiModules()
357 for mfnApiType in mfnTypes:
358 mfnNodePseudoType = uncapitalize(mfnApiType[1:]) # # kSomething -> something
359 nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType)
360
361 # MFnSets follow their kMFnType names, but when we try to associate it with
362 # the actual nodeType . Sometimes, they follow the actual nodeType, so we
363 # have to use this one as well
364 found = False
365 for nt, mfnNameCandidate in ( (mfnNodePseudoType, "MFn%s" % capitalize(mfnApiType[1:])),
366 (nodeType, "MFn%s" % capitalize(nodeType)) ):
367 # ignore abstract ones
368 if mfnNameCandidate in abstractMFns:
369 continue
370
371 for modapi in modsapi:
372 if hasattr(modapi, mfnNameCandidate):
373 found = True
374
375 # prefer a real nodetype if we have one - it will default
376 # to the pseudotype
377 typeToMFn.add((nodeType, mfnNameCandidate))
378 break
379 # END module with given name exists
380 # END for each api module
381
382 if found:
383 break
384 # END for each nodeType/mfnNamecandidate
385
386 # still not found ? Keep it, but only if there is a nodetype
387 # associated with it
388 if not found and mfnApiType in apiTypeToNodeTypeMap:
389 failedMFnTypes.append(mfnApiType)
390 # END keep a record
391 # END for each mfnType
392
393
394
395 # DATA, COMPONENTS, ATTRIBUTES
396 ###############################
397 # get inheritance of Data, Component and Attribute types
398 def unMFn(name):
399 return uncapitalize(name[3:])
400 # END remove MFn in front of MFnSomething strings
401
402 for mfnsuffix in ("data", "component", "attribute"):
403 mfnsuffixcap = capitalize(mfnsuffix)
404 mfnnames = list()
405 for modapi in modsapi:
406 mfnnames.extend( n for n in dir(modapi) if n.endswith(mfnsuffixcap) )
407 # END for each api module to get information from
408
409 dagTree.add_edge(root, mfnsuffix)
410
411 mfnsuffix_root = [ mfnsuffix ]
412 for mfnname in mfnnames:
413 for modapi in modsapi:
414 try:
415 mfncls = getattr(modapi, mfnname)
416 except AttributeError:
417 continue
418 # END handle multi-modules
419
420 # skip classes which are just named like the super type, but
421 # don't actually inherit it
422 if "MFn%s" % mfnsuffixcap not in ( p.__name__ for p in mfncls.mro() ):
423 continue
424
425 # add type->MFn association
426 typeToMFn.add((unMFn(mfnname), mfnname))
427
428 # cut object and MFnBase
429 # from the names, cut the MFn and uncaptialize it: MFnData -> data
430 pclsnames = [ unMFn(p.__name__) for p in list(reversed(mfncls.mro()))[2:] ]
431 for parent, child in zip(pclsnames[:-1], pclsnames[1:]):
432 dagTree.add_edge(parent, child)
433 # END for each mfn child to add
434
435 break
436 # END for each api module
437 # END for each name
438 # END for each mfnsuffix
439
440
441 # HANDLE FAILED MFN-ASSOCITAIONS
442 ################################
443 # lets take some very special care !
444 if failedMFnTypes:
445 # Here we handle cases which don't follow any naming conventions really
446 # Hence we have to instantiate an object of the failed type, then
447 # we instantiate all the remaining functions sets to see which one fits.
448 # If the required one has the requested type, we have a match.
449 # If we have no type match, its still a valid MFn - If we haven't seen it
450 # yet, its probably a base MFn whose kType string allows deduction of the
451 # actual abtract node type which we will use instead.
452 associatedMFns = ( t[1] for t in typeToMFn )
453 allMFnSetNames = list()
454 for modapi in modsapi:
455 allMFnSetNames.extend( n for n in dir(modapi) if n.startswith('MFn') and
456 not n.endswith('Ptr') and
457 not '_' in n and # skip 'special' ones
458 not 'Manip' in n ) # skip everything about Manipulators
459 # END get all MFn names
460
461 # find MFnClasses for each candidate name
462 candidateMFnNames = (set(allMFnSetNames) - set(associatedMFns)) - set(abstractMFns)
463 candidateMFns = list()
464 for cn in list(candidateMFnNames):
465 for modapi in modsapi:
466 try:
467 mfncls = getattr(modapi, cn)
468 # ignore them if they don't derive from MFnBase
469 if not hasattr(mfncls, "type"):
470 log.debug("Skipped MFn %s as it didn't derive from MFnBase" % mfncls)
471 candidateMFnNames.discard(cn)
472 continue
473 # END skip mfn
474 candidateMFns.append(mfncls)
475 break
476 except AttributeError:
477 continue
478 # END for each api module
479 # END for each candidate name
480
481 succeededMFnNames = set()
482
483
484 # PRUNE REMAINING MFNs
485 # prune out all MFnClasses that can be constructed without an actual object
486 enumMembers = MEnumDescriptor('Type')
487 enumMembers.extend( m for m in dir(api.MFn) if m.startswith('k') )
488 mfntypes = MEnumeration.create(enumMembers, api.MFn)
489
490 for mfncls in candidateMFns[:]:
491 try:
492 mfninst = mfncls()
493 if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
494 continue
495 # END keep actually missing MFns
496 candidateMFns.remove(mfncls)
497 candidateMFnNames.remove(mfncls.__name__)
498 except RuntimeError:
499 continue
500 # END for each possible MFn to prune
501
502 # at this point, we have about 500 api types with no MFn, but only
503 # about 10 function sets,
504 # Now ... we brute-force test our way through all of these to find
505 # matching ones ... argh
506 derivedMatches = list() # keeps tuple(kTypeStr, mfnname) of matches of derived types
507 perfectMatches = list() # keeps mfnnames of perfect matches
508 for failedApiTypeStr in failedMFnTypes:
509 nodeType = apiTypeToNodeTypeMap[failedApiTypeStr]
510 obj, mod = _createTmpNode(nodeType)
511
512 removeThisMFn = None
513 for mfncls in candidateMFns:
514 try:
515 mfninst = mfncls(obj)
516 except RuntimeError:
517 continue
518 # END handle incompatability
519
520 apiTypeStr = mfntypes.nameByValue(mfninst.type())
521
522 if apiTypeStr not in failedMFnTypes:
523 removeThisMFn = mfncls
524 break
525 # END remove MFns that no one wants
526
527 if apiTypeStr == failedApiTypeStr:
528 mfnname = mfncls.__name__
529 typeToMFn.add((nodeType, mfnname))
530 perfectMatches.append(mfnname)
531 removeThisMFn = mfncls
532 break
533 # END perfect match
534
535 # its matching, but its not perfectly suited for our node type
536 # We keep it, and will map it later if we don't find a better match
537 derivedMatches.append((apiTypeStr, mfncls.__name__))
538 # END for each mfncls
539
540 if removeThisMFn is not None:
541 succeededMFnNames.add(removeThisMFn.__name__)
542 candidateMFns.remove(removeThisMFn)
543 # END remove matched MFn
544
545 if not candidateMFns:
546 break
547 # END abort search if there is nothing left
548 # END for each failed type
549
550 # HANDLE DERIVED MFns
551 # prune out all derived mfs which have found a perfect match in the meanwhile
552 # the rest will be added to the list
553 for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches, derivedMatches):
554 typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname))
555 succeededMFnNames.add(mfnname)
556 # END for each apiTypeStr left ot add
557
558
559 # LAST MANUAL WORK
560 ##################
561 # SubDees, if created empty, cannot be attached to their function set
562 # Hence we don't see the match, but ... we know its there, so we add it
563 # ourselves
564 for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
565 typeToMFn.add((nodeType, mfnname))
566 succeededMFnNames.add(mfnname)
567 # END for each manually added type
568
569
570 for mfnname in candidateMFnNames - succeededMFnNames:
571 log.warn("Could not associate MFn: %s" % mfnname)
572 # END provide some info
573 # END special treatment
574 return (dagTree, sorted(typeToMFn, key=lambda t: t[0]))
575
582 """Define the interface and common utility methods to generate a string defining
583 code for a given MFnMethod according to the meta data provided by an `MMethodDescriptor`.
584
585 Once instantiated, it can create any number of methods"""
586 __slots__ = 'module_dict'
590
591 #{ Utilities
593 """:return: None or a function which receives the return value of our actual mfn function"""
594 if not isinstance( funcname, basestring ):
595 return funcname
596 if funcname == 'None': return None
597
598 try:
599 return self.module_dict[funcname]
600 except KeyError:
601 raise ValueError("'%s' does not exist in code generator's dictionary" % funcname )
602 #} END utilities
603
604
605 #{ Interface
606 - def generateMFnClsMethodWrapper(self, source_method_name, target_method_name, mfn_fun_name, method_descriptor, flags=0):
607 """
608 :return: string containing the code for the wrapper method as configured by the
609 method descriptor
610 :param source_method_name: Original name of the method - this is the name under which
611 it was requested.
612 :param target_method_name: Name of the method in the returned code string
613 :param mfn_fun_name: original name of the MFn function
614 :param method_descriptor: instance of `MMethodDescriptor`
615 :param flags: bit flags providing additional information, depending on the actual
616 implementation. Unsupported flags are ignored."""
617 raise NotImplementedError("To be implemented in SubClass")
618 #} END interfacec
622 """Specialization to generate python code
623
624 **Flags**:
625
626 * kDirectCall:
627 If set, the call return the actual mfn method in the best case, which is
628 a call as direct as it gets. A possibly negative side-effect would be that
629 it the MFnMethod caches the function set and actual MObject/MDagPath, which
630 can be dangerous if held too long
631
632 * kIsMObject:
633 If set, the type we create the method for is not derived from Node, but
634 from MObject. This hint is required in order to generate correct calling code.
635
636 * kIsDagNode:
637 If set, the type we create the method for is derived from DagNode
638
639 * kIsStatic:
640 If set, the method to be wrapped is considered static, no self is needed, nor
641 any object.
642 NOTE: This flag is likely to be removed as it should be part of the method_descriptor,
643 for now though it does not provide that information so we pass it in.
644
645 * kWithDocs:
646 If set, a doc string will be generated the method. In future, this information
647 will come from the method descriptor. Please note that docs should only be attaced
648 in interactive modes, otherwise its a waste of memory.
649
650 """
651 # IMPORTANT: If these change, update docs above, and test.maya.test_mdb and test.maya.performance.test_mdb !
652 kDirectCall, \
653 kIsMObject, \
654 kIsDagNode, \
655 kIsStatic, \
656 kWithDocs = [ 1<<i for i in range(5) ]
657
658 - def generateMFnClsMethodWrapper(self, source_method_name, target_method_name, mfn_fun_name, method_descriptor, flags=0):
659 """Generates code as python string which can be used to compile a function. It assumes the following
660 globals to be existing once evaluated: mfncls, mfn_fun, [rvalfunc]
661 Currently supports the following data within method_descriptor:
662
663 * method_descriptor.rvalfunc
664
665 as well as all flags except kIsStatic.
666 :raise ValueError: if flags are incompatible with each other
667 """
668 if flags & self.kIsMObject and flags & self.kIsDagNode:
669 raise ValueError("kIsMObject and kIsDagNode are mutually exclusive")
670 # END handle flags
671
672
673 sio = StringIO()
674
675 rvalfunname = ''
676 if method_descriptor.rvalfunc != 'None':
677 rvalfunname = method_descriptor.rvalfunc
678
679 sio.write("def %s(self, *args, **kwargs):\n" % target_method_name)
680
681
682 # mfn function call
683 mfnset = "mfncls(self"
684 if flags & self.kIsDagNode:
685 mfnset += ".dagPath()"
686 elif not flags & self.kIsMObject:
687 mfnset += ".object()"
688 mfnset += ")"
689
690 if flags & self.kDirectCall:
691 curline = "\tmfninstfunc = %s.%s\n" % (mfnset, mfn_fun_name)
692 sio.write(curline)
693
694 if rvalfunname:
695 sio.write("\tmfninstfunc = lambda *args, **kwargs: rvalfun(mfninstfunc(*args, **kwargs))\n")
696 # END handle rvalfunc name
697 sio.write("\tself.%s = mfninstfunc\n" % source_method_name)
698 sio.write("\treturn mfninstfunc(*args, **kwargs)")
699 else:
700 curline = "mfn_fun(%s, *args, **kwargs)" % mfnset
701 if rvalfunname:
702 curline = "rvalfunc(%s)" % curline
703 sio.write("\treturn %s" % curline)
704 # END handle direct call
705
706 return sio.getvalue()
707
708 #{ Interface
709
710 - def generateMFnClsMethodWrapperMethod(self, source_method_name, target_method_name, mfncls, mfn_fun, method_descriptor, flags=0):
711 """:return: python function suitable to be installed on a class
712 :param mfncls: MFnFunction set class from which the method was retrieved.
713 :param mfn_fun: function as retrieved from the function set's dict. Its a bare function.
714 :note: For all other args, see `MFnCodeGeneratorBase.generateMFnClsMethodWrapper`"""
715 rvalfunc = self._toRvalFunc(method_descriptor.rvalfunc)
716 mfnfuncname = mfn_fun.__name__
717
718 # handle MFnName_function
719 if mfnfuncname.startswith(mfncls.__name__):
720 mfnfuncname = mfnfuncname[len(mfncls.__name__)+1:]
721
722 new_method = None
723 if flags & self.kIsStatic:
724 # use the function directly
725 rvalfun = self._toRvalFunc(method_descriptor.rvalfunc)
726 if rvalfun is None:
727 new_method = mfn_fun
728 else:
729 fun = lambda *args, **kwargs: rvalfun(mfn_fun(*args, **kwargs))
730 fun.__name__ = target_method_name
731 new_method = fun
732 # END
733 else:
734 # get the compiled code
735 codestr = self.generateMFnClsMethodWrapper(source_method_name, target_method_name, mfnfuncname, method_descriptor, flags)
736 code = compile(codestr, "mrv/%s" % (mfncls.__name__+".py"), "exec") # this operation is expensive !
737
738 # get the function into our local dict, globals are our locals
739 eval(code, locals())
740
741 new_method = locals()[target_method_name]
742 # END handle static methods
743
744 if flags & self.kWithDocs:
745 if hasattr(new_method, 'func_doc'):
746 new_method.func_doc = "%s.%s" % (mfncls.__name__, mfnfuncname)
747 # END attach generated doc string
748
749 return new_method
750
751 #} END interface
752
753 #} END code generators
754
755 #{ Parsers
756
757 -class CppHeaderParser(object):
758 """Simplistic regex based parser which will extract information from the file
759 it was initialized with.
760
761 For now its so simple that there is no more than one method"""
762 reEnums = re.compile( r"""^\s+ enum \s+ (?P<name>\w+) \s* \{ # enum EnumName
763 (?P<members>[\(\)/\w\s,\-+="'\.\#!<\*\\]+) # match whitespace or newlines
764 \}[ \t]*;[ \t]*$ # closing brace""",
765 re.MULTILINE|re.VERBOSE)
766
767 reEnumMembers = re.compile( """
768 [\t ]{2,} # assure we don't get something within the comment
769 (k\w+)[ ]* # find kSomething
770 (?:=[ ]*[\w]+[ ]*)? # optionally find initializer = int|other_enum_member
771 """, re.VERBOSE)
772
773 @classmethod
775 """Parse the given header file and return the parsed information
776
777 :param header_filepath: Path pointing to the given header file. Its currently
778 assumed to be 7 bit ascii
779 :param parse_enums: If True, enumerations will be parsed from the file. If
780 False, the enumeration tuple in the return value will be empty.
781 :note: Currently we can only parse non-anonymous enumerations !
782 :return: tuple(tuple(MEnumDescriptor, ...), )"""
783 enum_list = list()
784
785 # ENUMERATIONS
786 ##############
787 # read everything, but skip the license text when matching
788 if parse_enums:
789 read_method = header_filepath.bytes
790 # on windows, we have \r\n newlines, which are automatically
791 # converted to \n by the .text method. This might be a bit slower,
792 # so we only do it on windows
793 if os.name == 'nt':
794 read_method = header_filepath.text
795 # END handle newline sequence
796
797 header = read_method()
798 for enummatch in cls.reEnums.finditer(header, 2188):
799 ed = MEnumDescriptor(enummatch.group('name'))
800
801 # parse all occurrences of kSomething, including the initializer
802 members = enummatch.group('members')
803 assert members
804 for memmatch in cls.reEnumMembers.finditer(members):
805 ed.append(memmatch.group(1))
806 # END for each member to add
807
808 enum_list.append(ed)
809 # END for each match
810 # END if enums should be parsed
811
812 # METHODS
813 #########
814 # TODO:
815
816 return (tuple(enum_list), )
817
824 """Contains meta-information about a given method according to data read from
825 the MFnDatabase"""
826 __slots__ = ("flag", "rvalfunc", "newname")
827
832
835 """Is an ordered list of enumeration names without its values, together
836 with the name of the enumeration type"""
837 __slots__ = "name"
840
843 """Simple accessor for MFnDatabase access
844 Direct access like db[funcname] returns an entry object with all values
845
846 **Globals**:
847 The __globals__ entry in MFn db files allows to pass additional options.
848 Currently there are no supported flags"""
849 __slots__ = ("flags", "enums")
850 kDelete = 'x'
851
853 """intiialize self from a file if not None
854
855 :param parse_enums: if True, enumerations will be parsed. Save time by specifying
856 False in case you know that there are no enumerations"""
857 UserDict.UserDict.__init__( self )
858
859 self._filepath = filepath
860 if filepath:
861 self._initFromFile( filepath )
862
863 # initialize globals
864 self.flags = 0
865 ge = self.get('__global__', None)
866 if ge is not None:
867 # currently we know none
868 pass
869 # END fetch info
870
871 # INITIALIZE PARSED DATA
872 self.enums = tuple()
873 if parse_enums:
874 self.enums, = CppHeaderParser.parseAndExtract(headerPath(filepath.namebase()))
875 # END if enumerations should be parsed
876
879
880
882 """Initialize the database with values from the given file
883
884 :note: the file must have been written using the `writeToFile` method"""
885 self.clear()
886 fobj = open( filepath, 'r' )
887
888 pf = PipeSeparatedFile( fobj )
889 pf.beginReading( )
890
891 # get the entries
892 for tokens in pf.readColumnLine( ):
893 key = tokens[ 1 ]
894 self[ key ] = MMethodDescriptor( flag=tokens[0], rvalfunc=tokens[2], newname=tokens[3] )
895 # END for each token in read column line
896
898 """Write our database contents to the given file"""
899 klist = self.keys()
900 klist.sort()
901
902 fobj = open( filepath, 'w' )
903 pf = PipeSeparatedFile( fobj )
904 pf.beginWriting( ( 4,40,20,40 ) )
905
906 for key in klist: # write entries
907 e = self[ key ]
908 pf.writeTokens( ( e.flag, key,e.rvalfunc, e.newname ) )
909 # end for each key
910
911 fobj.close()
912
914 """
915 :return: Tuple( mfnfuncname, entry )
916 original mfnclass function name paired with the
917 db entry containing more information
918 :raise KeyError: if no such function exists"""
919 try:
920 return ( funcname, self[ funcname ] )
921 except KeyError:
922 for mfnfuncname,entry in self.iteritems():
923 if entry.newname == funcname:
924 return ( mfnfuncname, entry )
925 # END for each item
926
927 raise KeyError( "Function named '%s' did not exist in db" % funcname )
928
930 """ Create an entry for the given function, or return the existing one
931
932 :return: Entry object for funcname"""
933 return self.setdefault( funcname, MMethodDescriptor() )
934
936 """:return: mfn functionname corresponding to the ( possibly renamed ) funcname """
937 return self.methodByName( funcname )[0]
938
939 #} END database
940
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 19 18:00:25 2011 | http://epydoc.sourceforge.net |