DialCollection.py
1    import re
2    from collections import OrderedDict
3    
4    try:
5        import poser
6    except ImportError:
7        from PoserLibs import POSER_FAKE as poser
8    
9    SCENE = poser.Scene()
10   
11   try:
12       range = xrange
13   except Exception:
14       pass
15   
16   __all__ = ["_Link", "DialCollection",
17              "path_to_poser", "poser_to_path",
18              "pa2po", "po2pa"]
19   
20   
21   class _Link(OrderedDict):
22       """ 
23           Class Link 
24    
25           Extended OrderedDict() to support parameter-links. 
26       """
27   
28       def __init__(self, *args, **kwargs):
29           super(_Link, self).__init__(*args, **kwargs)
30   
31       def __getitem__(self, item):
32           """ 
33               Modified to get items via index-number. 
34    
35                   collection = Link() 
36                   ...  
37                   # assume dictionairy is filled 
38                   item = collection.get("test") # standard 
39                   item = collection["test"] # standard 
40                   item = collection[10] # return 10. item 
41           """
42           if isinstance(item, basestring):
43               return super(_Link, self).__getitem__(item)
44           try:
45               entry = self[self.keys()[item]]
46           except IndexError:
47               # No error. Behave like "get".
48               return None
49   
50           return entry
51   
52       def count(self):
53           """Returns number of entries in collection."""
54   
55           def do_count(d):
56               cnt = 0
57               for entry in d.values():
58                   if isinstance(entry, self.__class__):
59                       cnt += do_count(entry)
60                   cnt += len(entry)
61               return cnt
62   
63           return do_count(self)
64   
65   
66   class DialCollection(_Link):
67       def __init__(self, base=None):
68           super(DialCollection, self).__init__()
69           self.base = base \
70               if isinstance(base, (poser.FigureType, poser.ActorType)) \
71               else None
72   
73           if isinstance(self.base, poser.FigureType):
74               self.collect(base.Actors())
75           elif isinstance(self.base, poser.ActorType):
76               self.collect([self.base, ])
77           else:
78               self.collect(base)
79   
80       def collect(self, actorlist=None, ignore_empty=True):
81           """ 
82               Creates a collection of morphs and their "dials" for 
83               all actors in actorlist, using the Link class from above. 
84    
85               The collection is a modified OrderedDict(). 
86    
87               For the following a complete collection of all parameters 
88               from a figure is assumed: 
89    
90                   collection = <DialCollection>.collect(Scene.Actors()) 
91    
92               If <ignore_empty> is True, Parameters with name set to "-" are 
93               ignored (default). If you need those entries, set 
94               <ignore_empty> explicit to False! 
95    
96               First Level of entries are the scenes figures 
97               (most often there is only one). 
98               They are reachable via: 
99                   collection.get(<str>FigureName) # string selection 
100                      or 
101                  collection[<int>Nr] # selection via numeric index 
102   
103              collection[0] returns the collection of actors from the first 
104              added figure (try collection.keys()). 
105              Only those actornames containing masterdials are stored. 
106   
107              Next level are the masterdials (Master Parameter). Parameter 
108              names are also listable: collection[0][0].keys() 
109   
110              These Parameternames are referenced by the entries of the 
111              list one level deeper: 
112              collection[0][0][0] == list() # list of parameternames 
113   
114              Because the collection-structure is pretty complicated, I made 
115              some helper function to find morphs and their masterdials. 
116   
117              Just make sure function 'collect()' is called 
118              once and the result is stored in a save place for 
119              later questioning. 
120   
121              masterdial_collection = <DialCollection>.collect(poser.Scene().Actors()) 
122                  or 
123              masterdial_collection = <DialCollection>.collect(<Figure>.Actors()) 
124                  or 
125              masterdial_collection = <DialCollection>.collect(<list of Poser actors>) 
126   
127                  then 
128   
129              masterdial_collection.find_target("Twist") 
130              will result in a list like this: 
131              [(u'LaFemme 1R1/Right Shoulder/Arm Up-Down', u'LaFemme 1R1/Right Shoulder/Twist'), 
132              (...),...] 
133   
134              List parts: 
135                  [<masterdial name>, <Parameter name searched for>] 
136   
137              All returned values are "pathes" in the form: 
138                  <figurename>/<actorname>/<parametername> 
139   
140              All parameters named "Twist" are listed along with their 
141              masterdial (parameters without masterdials are not in the list). 
142   
143              masterdial, foundparameter = masterdial_collection.find("Twist")[0] 
144              # Note the [0], so only the first parameter from the "Twist-list" is returned 
145   
146              Regular expressions may also be used to find parameters. 
147              If you need to find a parameter/masterdial from a certain actor, use: 
148   
149              masterdial_collection.find("Right Shoulder/Twist", regex=True) 
150   
151              Maybe you want to use Posers Python-Console to have a deeper look into 
152              the returned collection. 
153   
154          """
155          if hasattr(actorlist, "__iter__"):
156              for actor in actorlist:
157                  if isinstance(actor, basestring):
158                      try:
159                          actor = SCENE.Actors(actor)
160                      except poser.error:
161                          try:
162                              actor = SCENE.ActorByInternalName(actor)
163                          except poser.error:
164                              raise NameError("Need a string with an Actors name. "
165                                              "\n'%s' is not an actor." % actor)
166  
167                  if not isinstance(actor, poser.ActorType):
168                      raise TypeError("Not a Poser actor: %s" % str(actor.__class__))
169  
170                  actorname = actor.Name()
171                  fig = actor.ItsFigure()
172                  figname = fig.Name() if fig else "UNIVERSE"
173                  for parm in actor.Parameters():
174                      parmname = parm.Name()
175                      if ignore_empty and parmname == "-":
176                          continue
177                      if parm.NumValueOperations():
178                          for vop in parm.ValueOperations():
179                              s_parm = vop.SourceParameter()
180                              s_actor = s_parm.Actor()
181                              s_fig = s_actor.ItsFigure()
182                              s_figname = s_fig.Name() if s_fig else "UNIVERSE"
183  
184                              self.setdefault(s_figname, _Link()) \
185                                  .setdefault(s_actor.Name(), _Link()) \
186                                  .setdefault(s_parm.Name(), list()) \
187                                  .append("%s/%s/%s" % (figname, actorname, parmname))
188  
189          return self
190  
191      def find_target(self, param_name, regex=False):
192          """ 
193          Returns collection entries matching param_name. 
194   
195          <param_name> may be a regular expression if <regex> is set to True. 
196          if <param_name> contains ".*", <regex> is automatically set to True. 
197          """
198          if ".*" in param_name:
199              regex = True
200          if regex:
201              c = re.compile(".*(" + param_name + ")$")
202  
203              def test(k):
204                  return c.search(k)
205          else:
206              def test(k):
207                  return k.rsplit("/", 1)[-1] == param_name
208  
209          def do_search(d, base=""):
210              result = []
211              for key, entry in d.items():
212                  if test(base + "/" + key if regex else key):
213                      result.append((base + "/" + key, d.get(key)))
214                  if isinstance(entry, dict):
215                      if not base:
216                          result.extend(do_search(entry, key))
217                      else:
218                          result.extend(do_search(entry, base + "/" + key))
219              return result
220  
221          return do_search(self)
222  
223      def has_child(self, morphpath):
224          k = morphpath.split("/")
225          d = self
226          while d and k:
227              d = d.get(k.pop(0), None)
228          return d is not None
229  
230      def get_child(self, obj_path):
231          """ 
232          Get a Figure, Actor or Parameter given it's path. 
233   
234          "//" at the beginning of obj_path means figure[0]. 
235   
236          "//hip/Twist" == Parameter "Twist" from 
237          first figure in collection, at actor "hip" 
238          :param obj_path:  
239          :return:  
240          """
241          if obj_path.startswith("//"):
242              k = obj_path[2:].split("/")
243              k.insert(0, self.keys()[0])
244          else:
245              k = obj_path.split("/")
246          d = self
247          while d and k:
248              d = d.get(k.pop(0), None)
249          return d
250  
251      def find_masterdial(self, morphname, regex=False):
252          """ 
253              Find morph in collection. 
254          """
255          if ".*" in morphname:
256              regex = True
257          if regex:
258              c = re.compile(".*(" + morphname + ")$")
259  
260              def test(k):
261                  return c.search(k)
262          else:
263              def test(k):
264                  return k.rsplit("/", 1)[-1] == morphname
265  
266          def do_search(d, base=""):
267              result = []
268              for key, entry in d.items():
269                  if isinstance(entry, list):
270                      for kw in entry:
271                          if test(kw):
272                              result.append((base + "/" + key, kw))
273                  elif isinstance(entry, dict):
274                      if not base:
275                          result.extend(do_search(entry, key))
276                      else:
277                          result.extend(do_search(entry, base + "/" + key))
278  
279              return result
280  
281          return do_search(self)
282  
283  
284  def path_to_poser(collection):
285      """ 
286      Takes a collection of pathes (class DialCollection) and converts any 
287      entry in this collection permanently from string (pathes) to Poser objects. 
288      """
289  
290      def translate(path):
291          actor_name = parm_name = None
292          cnt = path.count("/")
293          try:
294              if cnt == 2:
295                  figure_name, actor_name, parm_name = path.split("/")
296                  if figure_name == "":
297                      figure_name = SCENE.CurrentFigure().Name()
298                  return SCENE.Figure(figure_name).Actor(actor_name).Parameter(parm_name)
299              elif cnt == 1:
300                  try:
301                      actor_name, parm_name = path.split("/")
302                      if actor_name == "":
303                          actor_name = SCENE.CurrentActor().Name()
304                      return SCENE.Actor(actor_name).Parameter(parm_name)
305                  except poser.error:
306                      return SCENE.Figure(actor_name).Actor(parm_name)
307              else:
308                  return SCENE.Actor(path)
309  
310          except poser.error:
311              return path
312  
313      if isinstance(collection, dict):
314          for key, entry in collection.items():
315              if isinstance(entry, basestring):
316                  collection[key] = translate(entry)
317              elif hasattr(entry, "__iter__"):
318                  collection[key] = path_to_poser(entry)
319  
320      elif hasattr(collection, "__iter__"):
321          collection = list(collection)
322          for idx, entry in enumerate(collection):
323              if isinstance(entry, basestring):
324                  collection[idx] = translate(entry)
325              if hasattr(entry, "__iter__"):
326                  collection[idx] = path_to_poser(entry)
327      elif isinstance(collection, basestring):
328          collection = translate(collection)
329  
330      return collection
331  
332  
333  pa2po = path_to_poser
334  
335  
336  def poser_to_path(obj):
337      """ 
338      Returns a "path" for a Poser object if possible. 
339      Returns obj unchanged if it is an <int>, <float> or <str>. 
340      Returns an empty <str> if obj is None. 
341   
342      Retured pathes follow this rule: 
343      <Figure> / <Actor> / <Parameter> 
344      """
345      if obj is None:
346          return ""
347      elif isinstance(obj, (int, float, basestring)):
348          return obj
349      elif isinstance(obj, (list, tuple)):
350          return [poser_to_path(entry) for entry in obj]
351      elif isinstance(obj, poser.FigureType):
352          return obj.Name()
353      elif hasattr(obj, "Actor"):
354          return poser_to_path(obj.Actor()) + "/" + obj.Name()
355      elif hasattr(obj, "ItsFigure"):
356          return poser_to_path(obj.ItsFigure()) + "/" + obj.Name()
357      try:
358          return "<%s>.%s" % (obj.__class__.__name__, obj.Name())
359      except AttributeError:
360          name = obj.Name() if hasattr(obj, "Name") else "Unknown"
361          s = str(type(obj)).replace("type ", "").replace("'", "")
362          return "%s.%s" % (s, name)
363  
364  
365  po2pa = poser_to_path
366