MorphMover2.py
1    from __future__ import print_function
2    
3    import math
4    import sys
5    
6    import numpy as NP
7    import wx
8    
9    try:
10       import poser
11   except ImportError:
12       # Not required while inside Poser Python, but very helpfull for external editors.
13       # See https://adp.spdns.org
14       from PoserLibs import POSER_FAKE as poser
15   
16   if sys.version_info.major > 2:
17       # Python 3 (Poser 12 and above)
18       map = lambda a, b: [a(_b) for _b in b]
19       basestring = str
20   else:
21       range = xrange
22   
23   SCENE = poser.Scene()
24   X, Y, Z = range(3)
25   
26   
27   def actor_has_geom(ac):
28       return isinstance(ac, poser.ActorType) \
29              and hasattr(ac, "Geometry") \
30              and ac.Geometry() is not None \
31              and ac.Geometry().NumVertices() > 0
32   
33   
34 def is_morphtarget(parm): 35 return isinstance(parm, poser.ParmType) \ 36 and parm.IsMorphTarget() \ 37 and parm.NumMorphTargetDeltas() != 0 38 39
40 def _rotate(ar, theta, axis=(0, 0, 0), order="XYZ"): 41 AX, AY, AZ = axis 42 43 def _X(c, s, ar=ar): 44 return NP.dot(ar, NP.array([ 45 [1.0, AY, AZ], 46 [AX, c, -s], 47 [AX, s, c] 48 ])) 49 50 def _Y(c, s, ar=ar): 51 return NP.dot(ar, NP.array([ 52 [c, AY, -s], 53 [AX, 1.0, AZ], 54 [s, AY, c] 55 ])) 56 57 def _Z(c, s, ar=ar): 58 return NP.dot(ar, NP.array([ 59 [c, -s, AZ], 60 [s, c, AZ], 61 [AX, AY, 1.0], 62 ])) 63 64 for entry in order.upper(): 65 idx = "XYZ".index(entry) 66 ar = locals().get("_" + entry)(math.cos(theta[idx]), math.sin(theta[idx]), ar) 67 68 return ar 69 70
71 def rotated_poserVerts(verts, theta, order): 72 return _rotate(NP.array([[v.X(), v.Y(), v.Z()] for v in verts]), theta, order) 73 74
75 def rotate_actor(actor, theta, order): 76 if isinstance(actor, basestring): 77 try: 78 actor = SCENE.Actor(actor) 79 except poser.error: 80 actor = SCENE.ActorByInternalName(actor) 81 geom = actor.Geometry() 82 for verts, vv in zip(rotated_poserVerts(geom.Vertices(), theta, order), geom.Vertices()): 83 vv.SetX(verts[0]) 84 vv.SetY(verts[1]) 85 vv.SetZ(verts[2]) 86 87
88 class Mover(object): 89 """ 90 This class uses Posers callbacks. Because of this no Poser object 91 can be directly hold in a variable (actor, morph, etc.). 92 Internal names are used instead to avoid crashes. 93 """ 94 95 __slots__ = "_saved_active", \ 96 "_figure_iname", "_moveactor_iname", "_morphactor_iname", "_morphname", \ 97 "morphindices", "morphdata", "morph_origdata", "morphcenter", \ 98 "_morph_min", "_morph_max", \ 99 "mover_startpos", "last_coords", "use_trans", "use_rot", \ 100 "current_rotation" 101 102 def __init__(self, morphparameter): 103 assert is_morphtarget(morphparameter), "Parameter has no Morphdata." 104 assert actor_has_geom(morphparameter.Actor()), "Actor to morph must have Geometry." 105 106 self.last_coords = NP.array([0, 0, 0], NP.float) 107 self.current_rotation = NP.array([0, 0, 0], NP.float) 108 109 self._saved_active = [False, ] 110 self._figure_iname = None 111 self._morphname = self._morphactor_iname = None 112 self._moveactor_iname = None 113 self.morphindices = self.morphdata = self.morph_origdata = None 114 self.use_trans = self.use_rot = False 115 116 self.createMover() 117 self.setmorph(morphparameter) 118 self.morphcenter = self.get_morph_center(False) 119 120 self._morph_min, self._morph_max = self.get_morph_minmax(True) 121 self.mover_startpos = self.mover_defaultpos() 122 self.move_mover(self.mover_startpos) 123 self.set_movercolor(0, 255, 0) 124 125 SCENE.DrawAll() 126
127 @property 128 def active(self): 129 return self._saved_active[-1] 130
131 @property 132 def morphactor(self): 133 try: 134 return SCENE.ActorByInternalName(self._morphactor_iname) 135 except poser.error: 136 print("Actor '%s' does not exist anymore." % self._morphactor_iname) 137 self.stop() 138
139 @morphactor.setter 140 def morphactor(self, actor): 141 if actor is None: 142 self._morphactor_iname = None 143 else: 144 assert isinstance(actor, poser.ActorType) 145 self._morphactor_iname = actor.InternalName() 146
147 @property 148 def moveactor(self): 149 try: 150 return SCENE.ActorByInternalName(self._moveactor_iname) 151 except poser.error: 152 print("Move actor does not exist anymore.") 153 self.stop() 154 155
156 @moveactor.setter 157 def moveactor(self, actor): 158 if actor is None: 159 self._moveactor_iname = None 160 else: 161 assert isinstance(actor, poser.ActorType) 162 self._moveactor_iname = actor.InternalName() 163
164 @property 165 def figure(self): 166 try: 167 return SCENE.FigureByInternalName(self._figure_iname) 168 except poser.error: 169 return None 170
171 @figure.setter 172 def figure(self, figure): 173 if figure is None: 174 self._figure_iname = None 175 else: 176 assert isinstance(figure, poser.FigureType) 177 self._figure_iname = figure.InternalName() 178
179 @property 180 def morph(self): 181 try: 182 return self.morphactor.Parameter(self._morphname) 183 except Exception: 184 print("Morph '%s' does not exist anymore." % self._morphname) 185
186 @morph.setter 187 def morph(self, parameter): 188 assert isinstance(parameter, poser.ParmType) 189 self._morphname = parameter.InternalName() 190
191 def mover_defaultpos(self): 192 c = NP.array(self.get_morph_center(True)) 193 c[2] = self._morph_max[2] + 0.02 194 return c 195
196 def push_active(self, v): 197 self._saved_active.append(bool(v)) 198 return v 199
200 def pop_active(self): 201 return None if not self._saved_active \ 202 else self._saved_active.pop() 203
204 def start(self): 205 SCENE.SelectFigure(self.figure) 206 self._saved_active = [False, ] 207 self.reset_mover() 208 self._start_mover() 209 self.set_movercolor(255, 0, 0) 210 self._saved_active = [True, ] 211 SCENE.SelectActor(self.moveactor) 212
213 def stop(self): 214 SCENE.SelectFigure(self.figure) 215 self._saved_active = [False, ] 216 self._stop_mover() 217 self.set_movercolor(0, 255, 0) 218
219 def set_movercolor(self, r, g, b): 220 s = self.moveactor.Material("Preview").ShaderTree() 221 s.Node(0).Input(0).SetColor(r, g, b) 222 s.UpdatePreview() 223
224 def setmorph(self, morphparm): 225 self.push_active(False) 226 if isinstance(morphparm, basestring): 227 morphparm = SCENE.CurrentActor().Parameter(morphparm) 228 229 if is_morphtarget(morphparm): 230 self.morph = morphparm 231 self.morphactor = ac = morphparm.Actor() 232 233 fig = ac.ItsFigure() if ac.IsBodyPart() else SCENE.Figures()[0] 234 self.figure = fig if fig else None 235 236 numv = ac.Geometry().NumVertices() 237 self.morph_origdata = NP.array([morphparm.MorphTargetDelta(idx) 238 for idx in range(numv)]) 239 self.morphindices = NP.array([idx for idx, v in enumerate(self.morph_origdata) 240 if not all(v == (0, 0, 0))], NP.int32) 241 self.morphdata = self.morph_origdata[self.morphindices] 242 243 else: 244 print("Choosen Parameter is not a morphtarget with geometry.") 245 print("Actor:", self._morphactor_iname, "- Parameter:", self._morphname) 246 247 self.pop_active() 248
249 def rotate_morphdata(self, theta, order): 250 if not self.use_rot: 251 self.morphdata = _rotate(self.morphdata, theta, self.morphcenter, order=order) 252
253 def update_morph(self): 254 """ 255 Called from callbacks if mover was moved or 256 rotated and morph needs to be updated. 257 """ 258 self.last_coords = NP.array(self.moveactor.WorldDisplacement()) 259 diff = [0, 0, 0] if self.use_trans \ 260 else (self.last_coords - self.mover_startpos) * .25 261 ar = self.morphdata + diff 262 m = self.morph 263 for idx, (x, y, z) in zip(self.morphindices, ar): 264 m.SetMorphTargetDelta(idx, x, y, z) 265 SCENE.DrawAll() 266
267 def undo_morph(self): 268 m = self.morph 269 if m: 270 for idx in self.morphindices: 271 x, y, z = self.morph_origdata[idx] 272 m.SetMorphTargetDelta(idx, x, y, z) 273 self.morphdata = self.morph_origdata.copy() 274 self.mover_startpos = self.moveactor.WorldDisplacement() 275 SCENE.DrawAll() 276
277 def current_morph_permanent(self): 278 self.push_active(False) 279 self.update_morph() # make sure last changes are done. 280 self.setmorph(self.morph) # setting it will take the data currently in morph 281 center = self.get_morph_center(True) 282 self.morphcenter = center 283 self.mover_startpos = self.mover_defaultpos() 284 self.move_mover(self.mover_startpos) 285 SCENE.DrawAll() 286 self.pop_active() 287
288 def get_morph_minmax(self, world=False): 289 geom = self.morph.Actor().Geometry() 290 verts = NP.array([[v.X(), v.Y(), v.Z()] 291 for v in (geom.WorldVertices() if world else geom.Vertices())]) 292 verts = verts[self.morphindices] + NP.array([self.morph.MorphTargetDelta(i) 293 for i in self.morphindices]) 294 return NP.min(verts, axis=0), NP.max(verts, axis=0) 295
296 def get_morph_center(self, world=False): 297 """:rtype: NP.ndarray""" 298 _min, _max = self.get_morph_minmax(world) 299 return _min + (_max - _min) / 2.0 300
301 def __trans__(self, parm, value): 302 """Callback on translation changes.""" 303 if self.active: 304 idx = [poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN].index(parm.TypeCode()) 305 if self.last_coords[idx] != parm.Actor().WorldDisplacement()[idx]: 306 self.update_morph() 307 return value 308
309 def __rot__(self, parm, value): 310 """Callback on rotation changes.""" 311 if self.active: 312 idx = [poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT].index(parm.TypeCode()) 313 r = math.radians(value) 314 if self.current_rotation[idx] != r: 315 self.current_rotation[idx] = r 316 self.rotate_morphdata(self.current_rotation, "XYZ"[idx]) 317 self.update_morph() 318 return value 319
320 def createMover(self): 321 try: 322 ac = SCENE.Actor("MorphMover") 323 except poser.error: 324 ac = SCENE.CreateGrouping() 325 ac.SetName("MorphMover") 326 ac.ParameterByCode(poser.kParmCodeASCALE).SetValue(.15) 327 ac.SetDisplayStyle(poser.kDisplayCodeFLATLINED) 328 ac.SetVisible(True) 329 self._moveactor_iname = ac.InternalName() 330 return ac 331
332 def move_mover(self, coords): 333 ma = self.moveactor 334 if ma: 335 self.push_active(False) 336 ma.ParameterByCode(poser.kParmCodeXTRAN).SetValue(coords[X]) 337 ma.ParameterByCode(poser.kParmCodeYTRAN).SetValue(coords[Y]) 338 ma.ParameterByCode(poser.kParmCodeZTRAN).SetValue(coords[Z]) 339 self.last_coords = NP.array(coords, NP.float) 340 SCENE.DrawAll() 341 self.pop_active() 342
343 def reset_mover(self): 344 ma = self.moveactor 345 if ma: 346 self.mover_startpos = NP.array(self.moveactor.WorldDisplacement()) 347 self.move_mover(self.mover_startpos) 348
349 def _start_mover(self): 350 ma = self.moveactor 351 if ma: 352 for code in (poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT): 353 p = ma.ParameterByCode(code) 354 p.SetValue(0) 355 p.SetUpdateCallback(self.__rot__) 356 357 for code in (poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE): 358 v = ma.ParameterByCode(code).Value() 359 ma.ParameterByCode(code).SetUpdateCallback(lambda a, b: v) 360 361 for code in (poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN): 362 ma.ParameterByCode(code).SetUpdateCallback(self.__trans__) 363
364 def _stop_mover(self): 365 ma = self.moveactor 366 if ma: 367 for code in (poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN, 368 poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT, 369 poser.kParmCodeASCALE, 370 poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE): 371 ma.ParameterByCode(code).ClearUpdateCallback() 372 for code in (poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT): 373 ma.ParameterByCode(code).SetValue(0) 374 for code in (poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE): 375 ma.ParameterByCode(code).SetValue(1) 376 377 378 # ---------------------------------------------------------------- 379 # wxPython UI related 380 # ---------------------------------------------------------------- 381
382 def collectActornames(figure=None): 383 if figure is None: 384 figure = SCENE.CurrentFigure() 385 return [ac.Name() for ac in (SCENE.Actors() if not figure else figure.Actors())] 386 387
388 def collectMorphnames(actor=None, filter=None): 389 if filter is None: 390 filter = lambda n: True 391 if actor is None: 392 actor = SCENE.CurrentActor() 393 return [p.Name() for p in actor.Parameters() if filter(p.Name()) and is_morphtarget(p)] 394 395
396 class MyFrame(wx.Frame): 397 def __init__(self, parent, *args, **kwargs): 398 super(MyFrame, self).__init__(parent, wx.ID_ANY, *args, **kwargs) 399 400 self.morph_mover = None # type: None or Mover 401 self._selected_actor = None 402 self._selected_parameter = dict() 403 self.init_ui() 404 405 self.Bind(wx.EVT_CLOSE, self.ev_close) 406 self.Bind(wx.EVT_SIZE, self.ev_size) 407 self.Bind(wx.EVT_CHECKBOX, self.on_check) 408 self.Bind(wx.EVT_BUTTON, self.on_button) 409 self.Bind(wx.EVT_CHOICE, self.on_choice) 410
411 @property 412 def selected_actor(self): 413 if self._selected_actor: 414 try: 415 return SCENE.ActorByInternalName(self._selected_actor) 416 except poser.error: 417 pass 418 return None 419
420 @property 421 def selected_parameter(self): 422 if self.selected_actor: 423 return self._selected_parameter.get(self._selected_actor, None) 424 return None 425
426 def startMover(self): 427 if self.morph_mover is not None: 428 self.morph_mover.start() 429 elif self.selected_parameter is not None: 430 ac = self.selected_actor 431 if ac: 432 try: 433 parm = ac.Parameter(self.selected_parameter) 434 self.morph_mover = Mover(parm) 435 self.morph_mover.start() 436 except poser.error: 437 print("Can't use parameter '%s' in actor '%s'." % ( 438 self.selected_parameter, self.selected_actor.Name())) 439 if self.morph_mover: 440 self.morph_mover.use_trans = self.FindWindowByName("cb_notranslation").IsChecked() 441 self.morph_mover.use_rot = self.FindWindowByName("cb_norotation").IsChecked() 442
443 def ev_close(self, event): 444 if self.morph_mover is not None: 445 self.morph_mover.stop() 446 self.morph_mover.moveactor.Delete() 447 event.Skip() 448
449 def ev_size(self, event): 450 self.SetSize(event.Size) 451 self.Refresh() 452 event.Skip() 453
454 def init_ui(self): 455 panel = wx.Panel(self) 456 main_sz = wx.BoxSizer(wx.VERTICAL) 457 sz_standard = (0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8) 458 sz_choice = (0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8) 459 sz_check = (1, wx.ALIGN_CENTER | wx.ALL, 8) 460 btn_style = wx.NO_BORDER 461 choice_style = wx.CB_SORT | wx.NO_BORDER 462 spacersize = 12 463 464 main_sz.Add(wx.StaticText(panel, wx.ID_ANY, "Select Actor"), *sz_choice) 465 main_sz.Add(wx.Choice(panel, wx.ID_ANY, style=choice_style, name="choice_actors", 466 choices=collectActornames()), *sz_choice) 467 main_sz.AddSpacer(spacersize) 468 main_sz.Add(wx.StaticText(panel, wx.ID_ANY, "Select Morph"), *sz_choice) 469 main_sz.Add( 470 wx.Choice(panel, wx.ID_ANY, style=choice_style, name="choice_parameters", 471 choices=collectMorphnames()), *sz_choice) 472 main_sz.AddSpacer(spacersize) 473 main_sz.AddStretchSpacer(1) 474 475 btn_sz = wx.GridSizer(4, 2, vgap=2, hgap=2) 476 btn_sz.Add(wx.CheckBox(panel, wx.ID_ANY, label="No Translation", name="cb_notranslation"), *sz_check) 477 btn_sz.Add(wx.CheckBox(panel, wx.ID_ANY, label="No Rotation", name="cb_norotation"), *sz_check) 478 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Start", style=btn_style, name="btn_start"), *sz_standard) 479 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Stop", style=btn_style, name="btn_stop"), *sz_standard) 480 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reload", style=btn_style, name="btn_reload"), *sz_standard) 481 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reset Mover", style=btn_style, name="btn_reset"), *sz_standard) 482 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reset Morph", style=btn_style, name="btn_undo"), *sz_standard) 483 btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Morph Permanent", style=btn_style, name="btn_permanent"), 484 *sz_standard) 485 main_sz.Add(btn_sz, 0, wx.ALIGN_CENTER) 486 main_sz.AddSpacer(spacersize) 487 488 main_sz.AddStretchSpacer(1) 489 main_sz.Layout() 490 panel.SetSizer(main_sz) 491 492 self.reload_actors() 493
494 def reload_actors(self): 495 ac = SCENE.CurrentActor() 496 self._selected_actor = ac.InternalName() 497 ac_choice = self.FindWindowByName("choice_actors") 498 ac_choice.Clear() 499 map(ac_choice.Append, collectActornames(None if not hasattr(ac, "ItsFigure") 500 else ac.ItsFigure())) 501 ac_choice.SetStringSelection(ac.Name()) 502 self.reload_parms() 503
504 def reload_parms(self): 505 ac = self.selected_actor 506 if ac: 507 choice = self.FindWindowByName("choice_parameters") # type: wx.Choice 508 choice.Clear() 509 for name in collectMorphnames(ac, filter=lambda n: n[0] not in ".-"): 510 choice.Append(name) 511 sel = self.selected_parameter 512 if not sel: 513 sel = choice.GetString(0) 514 self._selected_parameter[self._selected_actor] = sel 515 choice.SetStringSelection(sel) 516
517 def on_check(self, event): 518 obj = event.GetEventObject() # type: wx.CheckBox 519 name = obj.Name.lower() 520 if self.morph_mover: 521 if "notrans" in name: 522 self.morph_mover.use_trans = obj.IsChecked() 523 if "norot" in name: 524 self.morph_mover.use_rot = obj.IsChecked() 525 self.morph_mover.update_morph() 526
527 def on_button(self, event): 528 obj = event.GetEventObject() # type: wx.Button 529 name = obj.Name.lower() 530 if "start" in name: 531 self.startMover() 532 elif "stop" in name: 533 self.morph_mover.stop() 534 elif "reload" in name: 535 self.reload_actors() 536 elif "reset" in name: 537 self.morph_mover.reset_mover() 538 elif "undo" in name: 539 self.morph_mover.undo_morph() 540 elif "permanent" in name: 541 self.morph_mover.current_morph_permanent() 542
543 def on_choice(self, event): 544 obj = event.GetEventObject() # type: wx.Choice 545 name = obj.Name.lower() 546 sel_str = obj.GetStringSelection() 547 if "actor" in name: 548 self._selected_actor = SCENE.Actor(sel_str).InternalName() 549 self.reload_parms() 550 elif "parameter" in name: 551 self._selected_parameter[self._selected_actor] = sel_str 552 if self.morph_mover is not None: 553 self.morph_mover.stop() 554 self.morph_mover = None 555 self.startMover() 556 557 558 if __name__ == "__main__": 559 app = poser.WxApp() 560 aui = poser.WxAuiManager() # type: wx.aui.AuiManager 561 root = aui.GetManagedWindow() # type: wx.Window 562 main = MyFrame(root) 563 main.Show() 564