adp001 opened this issue on Aug 14, 2020 ยท 7 posts
adp001 posted Sun, 12 September 2021 at 7:53 AM
And a new one. This time able to translate and rotate morphs!

The "Mover" is a cube generated on the fly. (green if inactive, red if active). Grab that cube and move it around (after you selected an actor and a morph and clicked "Start"). Or use the dials to position more exactly. The morph will follow the movement.
This is not new. But now you can rotate the "mover" and your morph will rotate! Rotation center is the position of the mover. This is why "No Translation" exist: If you check it, you can move the mover to a new position to set a rotation center.
Note: It may last a moment after pressing "Start" until the "mover" appears. Poser has to create that new grouping object first.
I'll post the source here, but to be sure better download it from here, because the editor here may destray parts of the script. And the source code is more readable on my website.
from __future__ import print_function
import math
import sys
import numpy as NP
import wx
try:
import poser
except ImportError:
# Not required while inside Poser Python, but very helpfull for external editors.
# See https://adp.spdns.org
from PoserLibs import POSER_FAKE as poser
if sys.version_info.major > 2:
# Python 3 (Poser 12 and above)
map = lambda a, b: [a(_b) for _b in b]
basestring = str
else:
range = xrange
SCENE = poser.Scene()
X, Y, Z = range(3)
def actor_has_geom(ac):
return isinstance(ac, poser.ActorType)
and hasattr(ac, "Geometry")
and ac.Geometry() is not None
and ac.Geometry().NumVertices() > 0
def is_morphtarget(parm):
return isinstance(parm, poser.ParmType)
and parm.IsMorphTarget()
and parm.NumMorphTargetDeltas() != 0
def _rotate(ar, theta, axis=(0, 0, 0), order="XYZ"):
AX, AY, AZ = axis
def _X(c, s, ar=ar):
return NP.dot(ar, NP.array([
[1.0, AY, AZ],
[AX, c, -s],
[AX, s, c]
]))
def _Y(c, s, ar=ar):
return NP.dot(ar, NP.array([
[c, AY, -s],
[AX, 1.0, AZ],
[s, AY, c]
]))
def _Z(c, s, ar=ar):
return NP.dot(ar, NP.array([
[c, -s, AZ],
[s, c, AZ],
[AX, AY, 1.0],
]))
for entry in order.upper():
idx = "XYZ".index(entry)
ar = locals().get("_" + entry)(math.cos(theta[idx]), math.sin(theta[idx]), ar)
return ar
def rotated_poserVerts(verts, theta, order):
return _rotate(NP.array([[v.X(), v.Y(), v.Z()] for v in verts]), theta, order)
def rotate_actor(actor, theta, order):
if isinstance(actor, basestring):
try:
actor = SCENE.Actor(actor)
except poser.error:
actor = SCENE.ActorByInternalName(actor)
geom = actor.Geometry()
for verts, vv in zip(rotated_poserVerts(geom.Vertices(), theta, order), geom.Vertices()):
vv.SetX(verts[0])
vv.SetY(verts[1])
vv.SetZ(verts[2])
class Mover(object):
"""
This class uses Posers callbacks. Because of this no Poser object
can be directly hold in a variable (actor, morph, etc.).
Internal names are used instead to avoid crashes.
"""
__slots__ = "_saved_active",
"_figure_iname", "_moveactor_iname", "_morphactor_iname", "_morphname",
"morphindices", "morphdata", "morph_origdata", "morphcenter",
"_morph_min", "_morph_max",
"mover_startpos", "last_coords", "use_trans", "use_rot",
"current_rotation"
def __init__(self, morphparameter):
assert is_morphtarget(morphparameter), "Parameter has no Morphdata."
assert actor_has_geom(morphparameter.Actor()), "Actor to morph must have Geometry."
self.last_coords = NP.array([0, 0, 0], NP.float)
self.current_rotation = NP.array([0, 0, 0], NP.float)
self._saved_active = [False, ]
self._figure_iname = None
self._morphname = self._morphactor_iname = None
self._moveactor_iname = None
self.morphindices = self.morphdata = self.morph_origdata = None
self.use_trans = self.use_rot = False
self.createMover()
self.setmorph(morphparameter)
self.morphcenter = self.get_morph_center(False)
self._morph_min, self._morph_max = self.get_morph_minmax(True)
self.mover_startpos = self.mover_defaultpos()
self.move_mover(self.mover_startpos)
self.set_movercolor(0, 255, 0)
SCENE.DrawAll()
@property
def active(self):
return self._saved_active[-1]
@property
def morphactor(self):
try:
return SCENE.ActorByInternalName(self._morphactor_iname)
except poser.error:
print("Actor '%s' does not exist anymore." % self._morphactor_iname)
self.stop()
@morphactor.setter
def morphactor(self, actor):
if actor is None:
self._morphactor_iname = None
else:
assert isinstance(actor, poser.ActorType)
self._morphactor_iname = actor.InternalName()
@property
def moveactor(self):
try:
return SCENE.ActorByInternalName(self._moveactor_iname)
except poser.error:
print("Move actor does not exist anymore.")
self.stop()
@moveactor.setter
def moveactor(self, actor):
if actor is None:
self._moveactor_iname = None
else:
assert isinstance(actor, poser.ActorType)
self._moveactor_iname = actor.InternalName()
@property
def figure(self):
try:
return SCENE.FigureByInternalName(self._figure_iname)
except poser.error:
return None
@figure.setter
def figure(self, figure):
if figure is None:
self._figure_iname = None
else:
assert isinstance(figure, poser.FigureType)
self._figure_iname = figure.InternalName()
@property
def morph(self):
try:
return self.morphactor.Parameter(self._morphname)
except Exception:
print("Morph '%s' does not exist anymore." % self._morphname)
@morph.setter
def morph(self, parameter):
assert isinstance(parameter, poser.ParmType)
self._morphname = parameter.InternalName()
def mover_defaultpos(self):
c = NP.array(self.get_morph_center(True))
c[2] = self._morph_max[2] + 0.02
return c
def push_active(self, v):
self._saved_active.append(bool(v))
return v
def pop_active(self):
return None if not self._saved_active
else self._saved_active.pop()
def start(self):
SCENE.SelectFigure(self.figure)
self._saved_active = [False, ]
self.reset_mover()
self._start_mover()
self.set_movercolor(255, 0, 0)
self._saved_active = [True, ]
SCENE.SelectActor(self.moveactor)
def stop(self):
SCENE.SelectFigure(self.figure)
self._saved_active = [False, ]
self._stop_mover()
self.set_movercolor(0, 255, 0)
def set_movercolor(self, r, g, b):
s = self.moveactor.Material("Preview").ShaderTree()
s.Node(0).Input(0).SetColor(r, g, b)
s.UpdatePreview()
def setmorph(self, morphparm):
self.push_active(False)
if isinstance(morphparm, basestring):
morphparm = SCENE.CurrentActor().Parameter(morphparm)
if is_morphtarget(morphparm):
self.morph = morphparm
self.morphactor = ac = morphparm.Actor()
fig = ac.ItsFigure() if ac.IsBodyPart() else SCENE.Figures()[0]
self.figure = fig if fig else None
numv = ac.Geometry().NumVertices()
self.morph_origdata = NP.array([morphparm.MorphTargetDelta(idx)
for idx in range(numv)])
self.morphindices = NP.array([idx for idx, v in enumerate(self.morph_origdata)
if not all(v == (0, 0, 0))], NP.int32)
self.morphdata = self.morph_origdata[self.morphindices]
else:
print("Choosen Parameter is not a morphtarget with geometry.")
print("Actor:", self._morphactor_iname, "- Parameter:", self._morphname)
self.pop_active()
def rotate_morphdata(self, theta, order):
if not self.use_rot:
self.morphdata = _rotate(self.morphdata, theta, self.morphcenter, order=order)
def update_morph(self):
"""
Called from callbacks if mover was moved or
rotated and morph needs to be updated.
"""
self.last_coords = NP.array(self.moveactor.WorldDisplacement())
diff = [0, 0, 0] if self.use_trans
else (self.last_coords - self.mover_startpos) * .25
ar = self.morphdata + diff
m = self.morph
for idx, (x, y, z) in zip(self.morphindices, ar):
m.SetMorphTargetDelta(idx, x, y, z)
SCENE.DrawAll()
def undo_morph(self):
m = self.morph
if m:
for idx in self.morphindices:
x, y, z = self.morph_origdata[idx]
m.SetMorphTargetDelta(idx, x, y, z)
self.morphdata = self.morph_origdata.copy()
self.mover_startpos = self.moveactor.WorldDisplacement()
SCENE.DrawAll()
def current_morph_permanent(self):
self.push_active(False)
self.update_morph() # make sure last changes are done.
self.setmorph(self.morph) # setting it will take the data currently in morph
center = self.get_morph_center(True)
self.morphcenter = center
self.mover_startpos = self.mover_defaultpos()
self.move_mover(self.mover_startpos)
SCENE.DrawAll()
self.pop_active()
def get_morph_minmax(self, world=False):
geom = self.morph.Actor().Geometry()
verts = NP.array([[v.X(), v.Y(), v.Z()]
for v in (geom.WorldVertices() if world else geom.Vertices())])
verts = verts[self.morphindices] + NP.array([self.morph.MorphTargetDelta(i)
for i in self.morphindices])
return NP.min(verts, axis=0), NP.max(verts, axis=0)
def get_morph_center(self, world=False):
""":rtype: NP.ndarray"""
_min, _max = self.get_morph_minmax(world)
return _min + (_max - _min) / 2.0
def __trans__(self, parm, value):
"""Callback on translation changes."""
if self.active:
idx = [poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN].index(parm.TypeCode())
if self.last_coords[idx] != parm.Actor().WorldDisplacement()[idx]:
self.update_morph()
return value
def __rot__(self, parm, value):
"""Callback on rotation changes."""
if self.active:
idx = [poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT].index(parm.TypeCode())
r = math.radians(value)
if self.current_rotation[idx] != r:
self.current_rotation[idx] = r
self.rotate_morphdata(self.current_rotation, "XYZ"[idx])
self.update_morph()
return value
def createMover(self):
try:
ac = SCENE.Actor("MorphMover")
except poser.error:
ac = SCENE.CreateGrouping()
ac.SetName("MorphMover")
ac.ParameterByCode(poser.kParmCodeASCALE).SetValue(.15)
ac.SetDisplayStyle(poser.kDisplayCodeFLATLINED)
ac.SetVisible(True)
self._moveactor_iname = ac.InternalName()
return ac
def move_mover(self, coords):
ma = self.moveactor
if ma:
self.push_active(False)
ma.ParameterByCode(poser.kParmCodeXTRAN).SetValue(coords[X])
ma.ParameterByCode(poser.kParmCodeYTRAN).SetValue(coords[Y])
ma.ParameterByCode(poser.kParmCodeZTRAN).SetValue(coords[Z])
self.last_coords = NP.array(coords, NP.float)
SCENE.DrawAll()
self.pop_active()
def reset_mover(self):
ma = self.moveactor
if ma:
self.mover_startpos = NP.array(self.moveactor.WorldDisplacement())
self.move_mover(self.mover_startpos)
def _start_mover(self):
ma = self.moveactor
if ma:
for code in (poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT):
p = ma.ParameterByCode(code)
p.SetValue(0)
p.SetUpdateCallback(self.__rot__)
for code in (poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE):
v = ma.ParameterByCode(code).Value()
ma.ParameterByCode(code).SetUpdateCallback(lambda a, b: v)
for code in (poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN):
ma.ParameterByCode(code).SetUpdateCallback(self.__trans__)
def _stop_mover(self):
ma = self.moveactor
if ma:
for code in (poser.kParmCodeXTRAN, poser.kParmCodeYTRAN, poser.kParmCodeZTRAN,
poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT,
poser.kParmCodeASCALE,
poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE):
ma.ParameterByCode(code).ClearUpdateCallback()
for code in (poser.kParmCodeXROT, poser.kParmCodeYROT, poser.kParmCodeZROT):
ma.ParameterByCode(code).SetValue(0)
for code in (poser.kParmCodeXSCALE, poser.kParmCodeYSCALE, poser.kParmCodeZSCALE):
ma.ParameterByCode(code).SetValue(1)
# ----------------------------------------------------------------
# wxPython UI related
# ----------------------------------------------------------------
def collectActornames(figure=None):
if figure is None:
figure = SCENE.CurrentFigure()
return [ac.Name() for ac in (SCENE.Actors() if not figure else figure.Actors())]
def collectMorphnames(actor=None, filter=None):
if filter is None:
filter = lambda n: True
if actor is None:
actor = SCENE.CurrentActor()
return [p.Name() for p in actor.Parameters() if filter(p.Name()) and is_morphtarget(p)]
class MyFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
super(MyFrame, self).__init__(parent, wx.ID_ANY, *args, **kwargs)
self.morph_mover = None # type: None or Mover
self._selected_actor = None
self._selected_parameter = dict()
self.init_ui()
self.Bind(wx.EVT_CLOSE, self.ev_close)
self.Bind(wx.EVT_SIZE, self.ev_size)
self.Bind(wx.EVT_CHECKBOX, self.on_check)
self.Bind(wx.EVT_BUTTON, self.on_button)
self.Bind(wx.EVT_CHOICE, self.on_choice)
@property
def selected_actor(self):
if self._selected_actor:
try:
return SCENE.ActorByInternalName(self._selected_actor)
except poser.error:
pass
return None
@property
def selected_parameter(self):
if self.selected_actor:
return self._selected_parameter.get(self._selected_actor, None)
return None
def startMover(self):
if self.morph_mover is not None:
self.morph_mover.start()
elif self.selected_parameter is not None:
ac = self.selected_actor
if ac:
try:
parm = ac.Parameter(self.selected_parameter)
self.morph_mover = Mover(parm)
self.morph_mover.start()
except poser.error:
print("Can't use parameter '%s' in actor '%s'." % (
self.selected_parameter, self.selected_actor.Name()))
if self.morph_mover:
self.morph_mover.use_trans = self.FindWindowByName("cb_notranslation").IsChecked()
self.morph_mover.use_rot = self.FindWindowByName("cb_norotation").IsChecked()
def ev_close(self, event):
if self.morph_mover is not None:
self.morph_mover.stop()
self.morph_mover.moveactor.Delete()
event.Skip()
def ev_size(self, event):
self.SetSize(event.Size)
self.Refresh()
event.Skip()
def init_ui(self):
panel = wx.Panel(self)
main_sz = wx.BoxSizer(wx.VERTICAL)
sz_standard = (0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8)
sz_choice = (0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8)
sz_check = (1, wx.ALIGN_CENTER | wx.ALL, 8)
btn_style = wx.NO_BORDER
choice_style = wx.CB_SORT | wx.NO_BORDER
spacersize = 12
main_sz.Add(wx.StaticText(panel, wx.ID_ANY, "Select Actor"), *sz_choice)
main_sz.Add(wx.Choice(panel, wx.ID_ANY, style=choice_style, name="choice_actors",
choices=collectActornames()), *sz_choice)
main_sz.AddSpacer(spacersize)
main_sz.Add(wx.StaticText(panel, wx.ID_ANY, "Select Morph"), *sz_choice)
main_sz.Add(
wx.Choice(panel, wx.ID_ANY, style=choice_style, name="choice_parameters",
choices=collectMorphnames()), *sz_choice)
main_sz.AddSpacer(spacersize)
main_sz.AddStretchSpacer(1)
btn_sz = wx.GridSizer(4, 2, vgap=2, hgap=2)
btn_sz.Add(wx.CheckBox(panel, wx.ID_ANY, label="No Translation", name="cb_notranslation"), *sz_check)
btn_sz.Add(wx.CheckBox(panel, wx.ID_ANY, label="No Rotation", name="cb_norotation"), *sz_check)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Start", style=btn_style, name="btn_start"), *sz_standard)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Stop", style=btn_style, name="btn_stop"), *sz_standard)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reload", style=btn_style, name="btn_reload"), *sz_standard)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reset Mover", style=btn_style, name="btn_reset"), *sz_standard)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Reset Morph", style=btn_style, name="btn_undo"), *sz_standard)
btn_sz.Add(wx.Button(panel, wx.ID_ANY, label="Morph Permanent", style=btn_style, name="btn_permanent"),
*sz_standard)
main_sz.Add(btn_sz, 0, wx.ALIGN_CENTER)
main_sz.AddSpacer(spacersize)
main_sz.AddStretchSpacer(1)
main_sz.Layout()
panel.SetSizer(main_sz)
self.reload_actors()
def reload_actors(self):
ac = SCENE.CurrentActor()
self._selected_actor = ac.InternalName()
ac_choice = self.FindWindowByName("choice_actors")
ac_choice.Clear()
map(ac_choice.Append, collectActornames(None if not hasattr(ac, "ItsFigure")
else ac.ItsFigure()))
ac_choice.SetStringSelection(ac.Name())
self.reload_parms()
def reload_parms(self):
ac = self.selected_actor
if ac:
choice = self.FindWindowByName("choice_parameters") # type: wx.Choice
choice.Clear()
for name in collectMorphnames(ac, filter=lambda n: n[0] not in ".-"):
choice.Append(name)
sel = self.selected_parameter
if not sel:
sel = choice.GetString(0)
self._selected_parameter[self._selected_actor] = sel
choice.SetStringSelection(sel)
def on_check(self, event):
obj = event.GetEventObject() # type: wx.CheckBox
name = obj.Name.lower()
if self.morph_mover:
if "notrans" in name:
self.morph_mover.use_trans = obj.IsChecked()
if "norot" in name:
self.morph_mover.use_rot = obj.IsChecked()
self.morph_mover.update_morph()
def on_button(self, event):
obj = event.GetEventObject() # type: wx.Button
name = obj.Name.lower()
if "start" in name:
self.startMover()
elif "stop" in name:
self.morph_mover.stop()
elif "reload" in name:
self.reload_actors()
elif "reset" in name:
self.morph_mover.reset_mover()
elif "undo" in name:
self.morph_mover.undo_morph()
elif "permanent" in name:
self.morph_mover.current_morph_permanent()
def on_choice(self, event):
obj = event.GetEventObject() # type: wx.Choice
name = obj.Name.lower()
sel_str = obj.GetStringSelection()
if "actor" in name:
self._selected_actor = SCENE.Actor(sel_str).InternalName()
self.reload_parms()
elif "parameter" in name:
self._selected_parameter[self._selected_actor] = sel_str
if self.morph_mover is not None:
self.morph_mover.stop()
self.morph_mover = None
self.startMover()
if __name__ == "__main__":
app = poser.WxApp()
aui = poser.WxAuiManager() # type: wx.aui.AuiManager
root = aui.GetManagedWindow() # type: wx.Window
main = MyFrame(root)
main.Show()
By the way: This is not meant for production but to show and test what is possible with Poser Python.