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