Forum Moderators: Staff
Poser Python Scripting F.A.Q (Last Updated: 2024 Sep 18 2:50 am)
from __future__ import print_function
try:
import poser
except ImportError:
raise RuntimeError("Script must run in Poser.")
import wx
import wx.aui
import sys
import os
import time
import json
import numpy as NP
CONFIG = dict()
BASEPATH = os.path.abspath(os.path.dirname(sys.argv[0]))
SCRIPT_FILENAME = os.path.basename(sys.argv[0])
CONFIG_FILENAME = SCRIPT_FILENAME.rsplit(".")[0] + ".cfg"
# Forced floatingpoint precision. Mainly to help avoiding floatingpoint
# errors while reading files from external modelers.
PRECISION = 8
NP_PRECISION = NP.float32
def ErrDialog(err, msg=None):
dlg = wx.MessageDialog(None, err, style=wx.ICON_ERROR)
if msg:
dlg.SetMessage(msg)
dlg.ShowModal()
dlg.Close()
def read_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
if os.path.isfile(fname):
try:
with open(fname, "r") as fh:
CONFIG = json.load(fh, encoding="utf-8")
except IOError:
pass
def write_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
try:
with open(fname, "w") as fh:
json.dump(CONFIG, fh)
except IOError:
ErrDialog("File Error.", "Can't write configfile '{}.".format(fname))
def write_matfile(filename, materials):
"""
Write out a simple material-file.
"""
try:
open(filename, "w")
except IOError:
return ErrDialog("File Error.", "Can't create or write to file '{}'.n"
"Make sure directory '{}' exist and is writable.".
format(filename, os.path.dirname(filename)))
with open(filename, "w") as mfh:
for mat in materials:
print("newmtl", mat.Name(), file=mfh)
print("Ns", mat.Ns(), file=mfh)
print("Ka", "0 0 0", file=mfh)
print("Kd", " ".join(map(str, mat.DiffuseColor())), file=mfh)
print("Ks", "0 0 0", file=mfh)
if mat.TextureMapFileName():
print("map_Kd", mat.TextureMapFileName(), file=mfh)
if mat.BumpMapFileName():
print("map_Bump", mat.BumpMapFileName(), file=mfh)
def collect_geometry(figure):
np_vertex = lambda v: NP.array((v.X(), v.Y(), v.Z()), NP_PRECISION)
geom, actorlist, actor_indices = figure.UnimeshInfo()
verts = NP.zeros((geom.NumVertices(), 3), NP_PRECISION)
for actor_idx, actor in enumerate(actorlist):
world_verts = actor.Geometry().WorldVertices()
for i, vertex_idx in enumerate(actor_indices[actor_idx]):
verts[vertex_idx] = np_vertex(world_verts[i])
return dict(vertices=verts,
geom=geom,
actorlist=actorlist,
actor_indices=actor_indices)
def read_vertices(filename):
"""
Read Wavefront obj-file saved to file. Typically a figure exported
from Poser and modified with an external modeller (Blender etc).
"""
try:
open(filename, "r")
except IOError:
raise RuntimeError("File '{}' does not exist or is not accessible.".
format(filename))
vertices = list()
with open(filename, "r") as fh:
for line in fh:
if not line:
break
c, _, v = line.strip().partition(" ")
if c == "v":
vertices.append(map(float, v.split()))
# Remove following line if vertices are not in one block,
# so the whole file is processed.
elif c in ("vt", "vn", "f"):
break
return NP.array(vertices, NP_PRECISION)
def do_export(figure):
"""
Export figure to Wavefront obj file.
"""
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
parms = collect_geometry(figure)
vertices = parms["vertices"]
geom = parms["geom"]
use_material = CONFIG.get("CTRL_ExportTexture", True)
use_groups = CONFIG.get("CTRL_ExportGroups", False)
morphname = CONFIG["CTRL_MorphName"].strip()
vertices *= int(CONFIG.get("Scale", 100))
if CONFIG.get("CTRL_SingleSelectFile", True):
with wx.FileDialog(None, "Export Wavefront file",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR,
defaultDir=CONFIG.get("ExportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ExportPath"] = dlg.GetPath()
filename = dlg.GetFilename()
else:
filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
try:
open(filename, "w")
except IOError:
ErrDialog("Can't create or write to file '{}'.",
"Maybe you have to select another directory first.")
return
with open(filename, "w") as fh:
print("### Date : %s" % time.asctime(), file=fh)
print("### Figure : %s" % figurename, file=fh)
print("### Vertices: %s" % len(vertices), file=fh)
if use_material and geom.Materials():
matfile = filename.rsplit(".", 1)[0] + ".mtl"
write_matfile(matfile, geom.Materials())
print("mtllib ./" + os.path.basename(matfile), file=fh)
for vertex in vertices:
print("v {} {} {}".format(*vertex), file=fh)
if use_material:
for tvert in geom.TexVertices():
print("vt {} {}".format(tvert.U(), tvert.V()), file=fh)
current_groups = list()
current_mat = list()
if not use_groups:
print("g", figurename, file=fh)
polys = geom.Polygons()
tpolys = geom.TexPolygons()
sets = geom.Sets()
tsets = geom.TexSets()
for index, poly in enumerate(polys):
if use_groups:
if poly.Groups() != current_groups:
current_groups = poly.Groups()
print("g", ", ".join(current_groups), file=fh)
if use_material:
if poly.MaterialName() != current_mat:
current_mat = poly.MaterialName()
print("usemtl", current_mat, file=fh)
line = [str(sets[idx + poly.Start()] + 1) for idx in range(poly.NumVertices())]
if use_material:
tpoly = tpolys[index]
for tidx, v in enumerate((tsets[idx + tpoly.Start()] + 1) for idx in range(tpoly.NumTexVertices())):
line[tidx] += "/%d" % v
print("f", " ".join(map(str, line)), file=fh)
CONFIG["LastExported"] = filename
return filename
def do_import(figure, morphname):
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
geom, actorlist, actor_indices = figure.UnimeshInfo()
if CONFIG.get("CTRL_SingleSelectFile", True):
old_filename = CONFIG.get("LastExported", None)
if not old_filename:
with wx.FileDialog(None, "Import Original exported Wavefront file",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
old_filename = dlg.GetFilename()
with wx.FileDialog(None, "Import Wavefront file as morph",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}_mod.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ExportPath"] = dlg.GetPath()
new_filename = dlg.GetFilename()
else:
old_filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
new_filename = os.path.join(CONFIG["ImportPath"], "{}-{}_mod.obj".format(figurename, morphname))
verts_new = read_vertices(new_filename)
verts_old = read_vertices(old_filename)
if len(verts_old) != len(verts_new):
ErrDialog("Vertices mismatch.", "Old number of vertices: {}."
"New number of vertices: {}.".
format(len(verts_old), len(verts_new)))
return
vertices = (verts_new - verts_old) / int(CONFIG.get("Scale", 100))
del verts_new
del verts_old
body = figure.ParentActor()
masterdial = body.Parameter(morphname)
if masterdial is None:
body.CreateValueParameter(morphname)
masterdial = body.Parameter(morphname)
if masterdial is None:
return ErrDialog("Morph Error.", "Can't find or create morph in body actor.")
for actor_idx, actor in enumerate(actorlist):
morph = list()
for i, v_idx in enumerate(actor_indices[actor_idx]):
x, y, z = map(lambda a: round(a, PRECISION), vertices[v_idx])
if x != 0 or y != 0 or z != 0:
morph.append((i, x, y, z))
if len(morph) == 0:
continue
morphparm = actor.Parameter(morphname)
if morphparm is None:
actor.SpawnTarget(morphname)
morphparm = actor.Parameter(morphname)
if morphparm is None:
return ErrDialog("Morph Error", "Can't create Morphtarget.")
if not morphparm.IsMorphTarget():
return ErrDialog("Morph error.", "Morph Parametername ('%s')n"
"already exist but is not a morph"
% morphname)
for i in range(actor.Geometry().NumVertices()):
morphparm.SetMorphTargetDelta(i, 0, 0, 0)
for i, x, y, z in morph:
morphparm.SetMorphTargetDelta(i, x, y, z)
while morphparm.NumValueOperations():
morphparm.DeleteValueOperation(0)
morphparm.AddValueOperation(poser.kValueOpTypeCodeKEY, masterdial)
vop = morphparm.ValueOperations()[0]
vop.InsertKey(0, 0)
vop.InsertKey(1, 1)
masterdial.SetMinValue(-.5)
masterdial.SetMaxValue(1.0)
APP_NAME = "Figure Importer/Exporter"
STD_COLOR = 120, 120, 120
LBL_COLOR = 80, 80, 80
BG_COLOR = 75, 75, 75
HL_COLOR = 100, 100, 150
FG_COLOR = 200, 200, 200
TX_COLOR = 0xfe, 0xfe, 0xfe
STOPP_UPDATE_UI = False
class GridBagManager(object):
__slots__ = "parent", "sizer", "flag", "border", "current_row", "font"
def __init__(self, *args, **kwargs):
for varname in self.__slots__:
setattr(self, varname, kwargs.get(varname, None))
for idx, entry in enumerate(args):
if self.__slots__[idx] not in kwargs:
setattr(self, self.__slots__[idx], entry)
self.current_row = 0
assert isinstance(self.parent, wx.Panel)
assert isinstance(self.sizer, wx.GridBagSizer)
assert isinstance(self.flag, int)
assert isinstance(self.border, int)
def addrow(self, *args, **kwargs):
row = int(kwargs.get("row", self.current_row))
col = int(kwargs.get("startcol", 0))
flag = kwargs.get("flag", self.flag)
font = kwargs.pop("font", self.font)
widgets = []
for idx, widget in enumerate(args):
if font: widget.SetFont(font)
self.sizer.Add(widget, pos=(row, col + idx),
flag=flag,
border=self.border)
widgets.append(widget)
self.current_row += 1
return widgets
def setTooltip(ctrl, text):
if text and ctrl:
t = wx.ToolTip(text)
t.SetAutoPop(5000)
ctrl.SetToolTip(t)
return ctrl
def ColoredCtrl(ctrl, **kwargs):
ctrl.SetBackgroundColour(kwargs.pop("bgcolor", BG_COLOR))
ctrl.SetForegroundColour(kwargs.pop("fgcolor", FG_COLOR))
return setTooltip(ctrl, kwargs.pop("tooltip", None))
def LabelCtrl(parent, **kwargs):
bg_color = kwargs.pop("bgcolor", BG_COLOR)
fg_color = kwargs.pop("fgcolor", FG_COLOR)
font = kwargs.pop("font", SYS_FONT)
tooltip = kwargs.pop("tooltip", None)
ctrl = wx.StaticText(parent, **kwargs)
ctrl.SetFont(font)
ctrl.SetBackgroundColour(bg_color)
ctrl.SetForegroundColour(fg_color)
return setTooltip(ctrl, tooltip)
def PrepCtrl(parent, ctrl, **kwargs):
bind = kwargs.pop("bind", [])
fgcolor = kwargs.pop("fgcolor", FG_COLOR)
bgcolor = kwargs.pop("bgcolor", BG_COLOR)
font = kwargs.pop("font", None)
value = kwargs.pop("value", None)
tooltip = kwargs.pop("tooltip", None)
ctrl = ctrl(parent, **kwargs)
if bind:
if isinstance(bind[0], (tuple, list)):
for bind_entry in bind:
assert len(bind_entry) == 2
ctrl.Bind(bind_entry[0], bind_entry[1])
else:
assert len(bind) == 2
ctrl.Bind(bind[0], bind[1])
ctrl.SetForegroundColour(fgcolor)
ctrl.SetBackgroundColour(bgcolor)
if font:
ctrl.SetFont(font)
if value:
ctrl.SetValue(value)
return setTooltip(ctrl, tooltip)
class AppPanel(wx.Panel):
_CONFIGCHANGED = False
def __init__(self, parent,
name="Fullbody Morphs",
style=wx.DEFAULT,
size=(270, 400),
**kwargs):
super(self.__class__, self).__init__(parent=parent,
id=wx.ID_ANY,
name=name,
style=style,
size=size,
**kwargs)
read_config()
self.stdfont = SYS_FONT
self.gb_manager = GridBagManager(parent=self,
sizer=wx.GridBagSizer(0, 0),
flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
border=2
)
self.figurelist = PrepCtrl(self, wx.Choice, choices=[],
style=wx.CB_SORT | wx.NO_BORDER,
size=(80, 20), name="figurelist",
font=self.stdfont.SetPointSize(8),
tooltip="Right click to reload Figurelist.",
bind=((wx.EVT_RIGHT_UP, self.updateFigurelist),
(wx.EVT_CHOICE, self.onChoice))
)
self.updateFigurelist()
self.initUI(self.gb_manager)
self.Bind(wx.EVT_WINDOW_DESTROY, self.onClose)
self.Bind(wx.EVT_MOUSE_EVENTS, lambda ev: ev.StopPropagation())
self.Bind(wx.EVT_SIZE, self.onSize)
self.updateUI()
self.Layout()
def initUI(self, gbm):
assert isinstance(gbm, GridBagManager)
self.SetBackgroundColour(BG_COLOR)
self.SetForegroundColour(FG_COLOR)
self.SetFont(self.stdfont)
btn_size = (80, 15)
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Figure",
name="BTN_ExportFigure",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Export currently selected figure.",
bind=((wx.EVT_LEFT_UP, self.onExportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Morph",
name="BTN_ImportMorph",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Import modified figure as morph.",
bind=((wx.EVT_LEFT_UP, self.onImportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Figure",
fgcolor=STD_COLOR, name="LBL_FigureList"),
self.figurelist)
gbm.addrow(LabelCtrl(self, label="Morphname",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, value=CONFIG.get("CTRL_MorphName", "Morph"),
name="CTRL_MorphName", style=wx.BORDER_SIMPLE),
bgcolor=BG_COLOR, fgcolor=TX_COLOR))
gbm.addrow(LabelCtrl(self, label="Increment Morphnon each import",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, value=CONFIG.get("CTRL_RenumberMorph", False),
name="CTRL_RenumberMorph", style=wx.NO_BORDER,
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path to export current figure to.",
bind=((wx.EVT_LEFT_UP, self.onExportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path where exported figures are stored.",
bind=((wx.EVT_LEFT_UP, self.onImportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Use Fileselection",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_SingleSelectFile", True),
name="CTRL_SingleSelectFile",
tooltip="Select file on each Import/Export",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export groups",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportGroups", False),
name="CTRL_ExportGroups",
tooltip="Export actor groups",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export UV",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportTexture", True),
name="CTRL_ExportTexture",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="LBL_Scale",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, name="CTRL_Scale",
value=str(CONFIG.get("Scale", 1)),
style=wx.SIMPLE_BORDER | wx.TE_RIGHT),
fgcolor=FG_COLOR, bgcolor=BG_COLOR))
gbm.sizer.AddGrowableCol(0)
gbm.sizer.AddGrowableCol(1)
sz = wx.BoxSizer(wx.VERTICAL)
sz.AddMany([
(gbm.sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 10),
])
sz.SetSizeHints(self)
self.SetSizer(sz)
wx.CallAfter(self.updateUI)
def onSize(self, ev):
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
self._CONFIGCHANGED = True
ev.Skip()
def updateUI(self):
obj = self.FindWindowByName("LBL_FigureList")
if obj:
c = (0xff, 0, 0) if self.figurelist.GetCount() == 0 else STD_COLOR
obj.SetForegroundColour(c)
if self._CONFIGCHANGED:
write_config()
self._CONFIGCHANGED = False
if not STOPP_UPDATE_UI:
wx.CallLater(300, self.updateUI)
def onChoice(self, ev):
choice = ev.GetEventObject()
if choice.name == "figurelist": # type: wx.Choice
idx = choice.GetCurrentSelection()
if idx == -1:
CONFIG["figure"] = ""
else:
CONFIG["figure"] = choice.GetString(idx)
def onRadioBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onCheckBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onExportModel(self, ev):
""":type ev: wx.Event"""
try:
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
obj = wx.FindWindowByName("CTRL_MorphName")
CONFIG["CTRL_MorphName"] = obj.GetValue()
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
do_export(figure)
obj.SetBackgroundColour(STD_COLOR)
obj.Update()
except Exception:
raise
finally:
ev.Skip()
def onImportModel(self, ev):
try:
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
obj = wx.FindWindowByName("CTRL_MorphName")
morphname = obj.GetValue().strip() if obj else "MORPH"
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
do_import(figure, morphname)
obj.SetBackgroundColour(STD_COLOR)
check = wx.FindWindowByName("CTRL_RenumberMorph")
if check and check.GetValue():
morphname, _, nr = morphname.partition(" ")
morphname += " " + str(int(nr or 0) + 1)
obj.SetValue(morphname)
except Exception:
raise
finally:
ev.Skip()
def onExportPath(self, ev):
try:
defaultpath = CONFIG.get("ExportPath",
CONFIG.get("ImportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(self,
"Choose Export Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ExportPath"] = dlg.GetPath()
self._CONFIGCHANGED = True
except Exception:
raise
def onImportPath(self, ev):
try:
defaultpath = CONFIG.get("ImportPath",
CONFIG.get("ExportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(None,
"Choose Import Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ImportPath"] = dlg.GetPath()
except Exception:
raise
finally:
ev.Skip()
def onEnter_button(self, ev):
obj = ev.GetEventObject()
obj.SetBackgroundColour(HL_COLOR)
ev.Skip()
def onLeave_button(self, event):
obj = event.GetEventObject()
obj.SetBackgroundColour(STD_COLOR)
event.Skip()
def onClose(self, event):
global STOPP_UPDATE_UI
STOPP_UPDATE_UI = True
CONFIG["CTRL_MorphName"] = wx.FindWindowByName("CTRL_MorphName").GetValue()
CONFIG["Scale"] = wx.FindWindowByName("CTRL_Scale").GetValue()
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
write_config()
def updateFigurelist(self, *ev):
if poser:
while self.figurelist.GetCount():
self.figurelist.Delete(0)
for idx, fig in enumerate(poser.Scene().Figures()):
self.figurelist.Append(fig.Name())
if fig == poser.Scene().CurrentFigure():
self.figurelist.SetSelection(idx)
if __name__ == "__main__":
read_config()
aui = poser.WxAuiManager()
root = aui.GetManagedWindow()
SYS_FONT = root.GetFont()
paneInfo = aui.GetPane(APP_NAME)
if paneInfo.IsOk():
aui.ClosePane(paneInfo)
paneInfo = wx.aui.AuiPaneInfo()
paneInfo.name = APP_NAME
paneInfo.Resizable(True).FloatingSize(wx.Size(250, 280)).BestSize(wx.Size(200, 230))
paneInfo.Movable(True).DestroyOnClose(True)
paneInfo.Name(APP_NAME).Caption("+++ NEED A NAME FOR THIS +++")
savedinfo = CONFIG.get("Perspective", None)
if savedinfo is not None:
aui.LoadPaneInfo(savedinfo, paneInfo)
MainPanel = AppPanel(parent=root, name=APP_NAME)
aui.AddPane(MainPanel, paneInfo)
paneInfo.Show()
aui.Update()
This can become very interesting. If I use Marvelous Designer as my external modeller, let my figure in Poser and the corresponding avatar in MD both do the dance along the JCM poses (a fixed list) and compare the fully draped garment geometry in MD with a conformed and elementary rigged version in Poser, the result should be the (post transform) JCM's for the clothing, right?
I have scripts to let MD jackhammer along a series of avatar poses and produce garment obj files for any of them. As long as I keep the vertex order when rigging in Poser ( combine .obj file with skeleton .cr2), I can load them as a morph.
Production cycle for a garment would then become:
Make garment geometry, UV mapping and texture in MD,
Export a zero copy and the posed versions,
In Poser make an elementary conformer: (group the zero version and generate a .cr2 with bones definitions, copy the joint setup and initial morphs), conform, and then
run a difference script like this to make the final garment. Need to convert post transform into pre-transform of course.
Sure there will be plenty hurdles and rivers to cross, but after creation of the garment in MD the only steps needing user intervention are those where joint info and morphs are copied. Both essentially are just button press and are needed only once for a garment.
Following answer in other thread: fully understood. MD just happens to be my generation tool of choice, because it is top line and after using it for 10 or so years I feel no urge to change (as long as they keep up the Python API. ;-)) Same function could be performed by VWD, Poser cloth room, or Blender cloth simulation once that comes to steam. The idea of the post was to illustrate the potential.
Never taken as an offence and fully understood. Price is pretty steep (but not as steep as you indicate).
Until December 31st. it is US$350.- now for 'perpetual' step-in, and then US$ 200.- each 3-4 years if you are happy with the version you have (max. 3 back from current), or (from the top of my head US$75.- (or US$125.-?) each year for update from previous version. Version step-up is every year, normally in October-November.
Prices are ex. VAT.
Had I not been in the position where I am in now, I would surely be looking for alternatives
.
adp001 posted at 7:57PM Sat, 28 December 2019 - #4374716
The script is still WIP. Please report errors or problems.
Feel free to make suggestions.
Well then, here goes:
Could it be there is an error if the selected figure is not the first one in the list:
I have two figures in the scene: An LF figure and a conformer. They show up nicely in the drop-down, but if I select any of the figures the above error is thrown.
Also noted that figures added when the script is already running do not show up in the drop-down list.
Thanks for testing. Choice in this Poser wx-version needs "GetName". Fixed.
Also fixed some other hickups. The linefeed-problem is also fixed.
There is no scanning for new figures in this scriptversion. You have to right-click on the dropdown list. In a later version I'll make the whole thing an Addon. An addon gets info directly from Poser if changes where made (like a figure loaded/deleted).
New script follows.
from __future__ import print_function
try:
import poser
except ImportError:
raise RuntimeError("Script must run in Poser.")
import wx
import wx.aui
import sys
import os
import time
import json
import numpy as NP
CONFIG = dict()
BASEPATH = os.path.abspath(os.path.dirname(sys.argv[0]))
SCRIPT_FILENAME = os.path.basename(sys.argv[0])
CONFIG_FILENAME = SCRIPT_FILENAME.rsplit(".")[0] + ".cfg"
# Forced floatingpoint precision. Mainly to help avoiding floatingpoint
# errors while reading files from external modelers.
PRECISION = 8
NP_PRECISION = NP.float32
_LF = chr(10)
def ErrDialog(err, msg=None):
dlg = wx.MessageDialog(None, caption=err, message=msg, style=wx.ICON_ERROR)
dlg.ShowModal()
dlg.Close()
_INFODISPLAY = None
def InfoDisplay(parent, msg, time2display=1000):
global _INFODISPLAY
if _INFODISPLAY is None:
_INFODISPLAY = wx.Dialog(parent,
style=wx.STAY_ON_TOP | wx.DIALOG_NO_PARENT)
_INFODISPLAY.Center()
def read_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
if os.path.isfile(fname):
try:
with open(fname, "r") as fh:
CONFIG = json.load(fh, encoding="utf-8")
except IOError:
pass
def write_config():
global CONFIG
fname = os.path.join(BASEPATH, CONFIG_FILENAME)
try:
with open(fname, "w") as fh:
json.dump(CONFIG, fh)
except IOError:
ErrDialog("File Error.", "Can't write configfile '{}.".format(fname))
def write_matfile(filename, materials):
"""
Write out a simple material-file.
"""
try:
open(filename, "w")
except IOError:
return ErrDialog("File Error.", "Can't create or write to file '{}'." + _LF +
"Make sure directory '{}' exist and is writable.".
format(filename, os.path.dirname(filename)))
with open(filename, "w") as mfh:
for mat in materials:
print("newmtl", mat.Name(), file=mfh)
print("Ns", mat.Ns(), file=mfh)
print("Ka", "0 0 0", file=mfh)
print("Kd", " ".join(map(str, mat.DiffuseColor())), file=mfh)
print("Ks", "0 0 0", file=mfh)
if mat.TextureMapFileName():
print("map_Kd", mat.TextureMapFileName(), file=mfh)
if mat.BumpMapFileName():
print("map_Bump", mat.BumpMapFileName(), file=mfh)
def collect_geometry(figure):
if figure is None:
return None
np_vertex = lambda v: NP.array((v.X(), v.Y(), v.Z()), NP_PRECISION)
geom, actorlist, actor_indices = figure.UnimeshInfo()
verts = NP.zeros((geom.NumVertices(), 3), NP_PRECISION)
for actor_idx, actor in enumerate(actorlist):
world_verts = actor.Geometry().WorldVertices()
for i, vertex_idx in enumerate(actor_indices[actor_idx]):
verts[vertex_idx] = np_vertex(world_verts[i])
return dict(vertices=verts,
geom=geom,
actorlist=actorlist,
actor_indices=actor_indices)
def read_vertices(filename):
"""
Read Wavefront obj-file saved to file. Typically a figure exported
from Poser and modified with an external modeller (Blender etc).
"""
vertices = list()
try:
with open(filename, "r") as fh:
for line in fh:
if not line:
break
c, _, v = line.strip().partition(" ")
if c == "v":
vertices.append(map(float, v.split()))
# Remove following line if vertices are not in one block,
# so the whole file is processed.
elif c in ("vt", "vn", "f"):
break
except IndexError:
return ErrDialog("Vertex Error.",
"Vertice in file '%filename' corrupted.")
except IOError:
return ErrDialog("File Error.",
"File '{}' does not exist or is not accessible.".
format(filename))
return NP.array(vertices, NP_PRECISION)
def do_export(figure):
"""
Export figure to Wavefront obj file.
"""
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
parms = collect_geometry(figure)
vertices = parms["vertices"]
geom = parms["geom"]
use_material = CONFIG.get("CTRL_ExportTexture", True)
use_groups = CONFIG.get("CTRL_ExportGroups", False)
morphname = CONFIG["CTRL_MorphName"].strip()
vertices *= int(CONFIG.get("Scale", 100))
if CONFIG.get("CTRL_SingleSelectFile", True):
with wx.FileDialog(None, "Export Wavefront file",
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR,
defaultDir=CONFIG.get("ExportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ExportPath"] = dlg.GetPath()
filename = dlg.GetFilename()
else:
filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
try:
open(filename, "w")
except IOError:
ErrDialog("Can't create or write to file '{}'.",
"Maybe you have to select another directory first.")
return
with open(filename, "w") as fh:
print("### Date : %s" % time.asctime(), file=fh)
print("### Figure : %s" % figurename, file=fh)
print("### Vertices: %s" % len(vertices), file=fh)
if use_material and geom.Materials():
matfile = filename.rsplit(".", 1)[0] + ".mtl"
write_matfile(matfile, geom.Materials())
print("mtllib ./" + os.path.basename(matfile), file=fh)
for vertex in vertices:
print("v {} {} {}".format(*vertex), file=fh)
if use_material:
for tvert in geom.TexVertices():
print("vt {} {}".format(tvert.U(), tvert.V()), file=fh)
current_groups = list()
current_mat = list()
if not use_groups:
print("g", figurename, file=fh)
polys = geom.Polygons()
tpolys = geom.TexPolygons()
sets = geom.Sets()
tsets = geom.TexSets()
for index, poly in enumerate(polys):
if use_groups:
if poly.Groups() != current_groups:
current_groups = poly.Groups()
print("g", ", ".join(current_groups), file=fh)
if use_material:
if poly.MaterialName() != current_mat:
current_mat = poly.MaterialName()
print("usemtl", current_mat, file=fh)
line = [str(sets[idx + poly.Start()] + 1) for idx in range(poly.NumVertices())]
if use_material:
tpoly = tpolys[index]
for tidx, v in enumerate((tsets[idx + tpoly.Start()] + 1) for idx in range(tpoly.NumTexVertices())):
line[tidx] += "/%d" % v
print("f", " ".join(map(str, line)), file=fh)
CONFIG["LastExported"] = filename
return filename
def do_import(figure, morphname):
assert isinstance(figure, poser.FigureType)
figurename = figure.Name()
geom, actorlist, actor_indices = figure.UnimeshInfo()
if CONFIG.get("CTRL_SingleSelectFile", True):
old_filename = CONFIG.get("LastExported", None)
if not old_filename:
with wx.FileDialog(None, "Import Original exported Wavefront file",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
old_filename = dlg.GetFilename()
with wx.FileDialog(None, "Import Wavefront file as morph",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST,
defaultDir=CONFIG.get("ImportPath", BASEPATH),
defaultFile="{}-{}_mod.obj".format(figurename, morphname)
) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
CONFIG["ImportPath"] = dlg.GetPath()
new_filename = dlg.GetFilename()
else:
old_filename = os.path.join(CONFIG["ExportPath"], "{}-{}.obj".format(figurename, morphname))
new_filename = os.path.join(CONFIG["ImportPath"], "{}-{}_mod.obj".format(figurename, morphname))
verts_new = read_vertices(new_filename)
verts_old = read_vertices(old_filename)
if len(verts_old) != len(verts_new):
ErrDialog("Vertices mismatch.", "Old number of vertices: {}."
"New number of vertices: {}.".
format(len(verts_old), len(verts_new)))
return
vertices = (verts_new - verts_old) / int(CONFIG.get("Scale", 100))
del verts_new
del verts_old
body = figure.ParentActor()
masterdial = body.Parameter(morphname)
if masterdial is None:
body.CreateValueParameter(morphname)
masterdial = body.Parameter(morphname)
if masterdial is None:
return ErrDialog("Morph Error.", "Can't find or create morph in body actor.")
for actor_idx, actor in enumerate(actorlist):
morph = list()
for i, v_idx in enumerate(actor_indices[actor_idx]):
x, y, z = map(lambda a: round(a, PRECISION), vertices[v_idx])
if x != 0 or y != 0 or z != 0:
morph.append((i, x, y, z))
if len(morph) == 0:
continue
morphparm = actor.Parameter(morphname)
if morphparm is None:
actor.SpawnTarget(morphname)
morphparm = actor.Parameter(morphname)
if morphparm is None:
return ErrDialog("Morph Error", "Can't create Morphtarget.")
if not morphparm.IsMorphTarget():
return ErrDialog("Morph error.", "Morph Parametername ('%s')" + _LF +
"already exist but is not a morph"
% morphname)
for i in range(actor.Geometry().NumVertices()):
morphparm.SetMorphTargetDelta(i, 0, 0, 0)
for i, x, y, z in morph:
morphparm.SetMorphTargetDelta(i, x, y, z)
while morphparm.NumValueOperations():
morphparm.DeleteValueOperation(0)
morphparm.AddValueOperation(poser.kValueOpTypeCodeKEY, masterdial)
vop = morphparm.ValueOperations()[0]
vop.InsertKey(0, 0)
vop.InsertKey(1, 1)
masterdial.SetMinValue(-.5)
masterdial.SetMaxValue(1.0)
APP_NAME = "Figure Importer/Exporter"
STD_COLOR = 120, 120, 120
LBL_COLOR = 80, 80, 80
BG_COLOR = 75, 75, 75
HL_COLOR = 100, 100, 150
FG_COLOR = 200, 200, 200
TX_COLOR = 0xfe, 0xfe, 0xfe
STOPP_UPDATE_UI = False
class GridBagManager(object):
__slots__ = "parent", "sizer", "flag", "border", "current_row", "font"
def __init__(self, *args, **kwargs):
for varname in self.__slots__:
setattr(self, varname, kwargs.get(varname, None))
for idx, entry in enumerate(args):
if self.__slots__[idx] not in kwargs:
setattr(self, self.__slots__[idx], entry)
self.current_row = 0
assert isinstance(self.parent, wx.Panel)
assert isinstance(self.sizer, wx.GridBagSizer)
assert isinstance(self.flag, int)
assert isinstance(self.border, int)
def addrow(self, *args, **kwargs):
row = int(kwargs.get("row", self.current_row))
col = int(kwargs.get("startcol", 0))
flag = kwargs.get("flag", self.flag)
font = kwargs.pop("font", self.font)
widgets = []
for idx, widget in enumerate(args):
if font: widget.SetFont(font)
self.sizer.Add(widget, pos=(row, col + idx),
flag=flag,
border=self.border)
widgets.append(widget)
self.current_row += 1
return widgets
def setTooltip(ctrl, text):
if text and ctrl:
t = wx.ToolTip(text)
t.SetAutoPop(5000)
ctrl.SetToolTip(t)
return ctrl
def ColoredCtrl(ctrl, **kwargs):
ctrl.SetBackgroundColour(kwargs.pop("bgcolor", BG_COLOR))
ctrl.SetForegroundColour(kwargs.pop("fgcolor", FG_COLOR))
return setTooltip(ctrl, kwargs.pop("tooltip", None))
def LabelCtrl(parent, **kwargs):
bg_color = kwargs.pop("bgcolor", BG_COLOR)
fg_color = kwargs.pop("fgcolor", FG_COLOR)
font = kwargs.pop("font", SYS_FONT)
tooltip = kwargs.pop("tooltip", None)
ctrl = wx.StaticText(parent, **kwargs)
ctrl.SetFont(font)
ctrl.SetBackgroundColour(bg_color)
ctrl.SetForegroundColour(fg_color)
return setTooltip(ctrl, tooltip)
def PrepCtrl(parent, ctrl, **kwargs):
bind = kwargs.pop("bind", [])
fgcolor = kwargs.pop("fgcolor", FG_COLOR)
bgcolor = kwargs.pop("bgcolor", BG_COLOR)
font = kwargs.pop("font", None)
value = kwargs.pop("value", None)
tooltip = kwargs.pop("tooltip", None)
ctrl = ctrl(parent, **kwargs)
if bind:
if isinstance(bind[0], (tuple, list)):
for bind_entry in bind:
assert len(bind_entry) == 2
ctrl.Bind(bind_entry[0], bind_entry[1])
else:
assert len(bind) == 2
ctrl.Bind(bind[0], bind[1])
ctrl.SetForegroundColour(fgcolor)
ctrl.SetBackgroundColour(bgcolor)
if font:
ctrl.SetFont(font)
if value:
ctrl.SetValue(value)
return setTooltip(ctrl, tooltip)
class AppPanel(wx.Panel):
_CONFIGCHANGED = False
def __init__(self, parent,
name="Fullbody Morphs",
style=wx.DEFAULT,
size=(270, 400),
**kwargs):
super(self.__class__, self).__init__(parent=parent,
id=wx.ID_ANY,
name=name,
style=style,
size=size,
**kwargs)
read_config()
self.stdfont = SYS_FONT
self.gb_manager = GridBagManager(parent=self,
sizer=wx.GridBagSizer(0, 0),
flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
border=2
)
self.figurelist = PrepCtrl(self, wx.Choice, choices=[],
style=wx.CB_SORT | wx.NO_BORDER,
size=(80, 20), name="figurelist",
font=self.stdfont.SetPointSize(8),
tooltip="Right click to reload Figurelist.",
bind=((wx.EVT_RIGHT_UP, self.updateFigurelist),
(wx.EVT_CHOICE, self.onChoice))
)
self.updateFigurelist()
self.initUI(self.gb_manager)
self.Bind(wx.EVT_WINDOW_DESTROY, self.onClose)
self.Bind(wx.EVT_MOUSE_EVENTS, lambda ev: ev.StopPropagation())
self.Bind(wx.EVT_SIZE, self.onSize)
self.updateUI()
self.Layout()
def initUI(self, gbm):
assert isinstance(gbm, GridBagManager)
self.SetBackgroundColour(BG_COLOR)
self.SetForegroundColour(FG_COLOR)
self.SetFont(self.stdfont)
btn_size = (80, 15)
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Figure",
name="BTN_ExportFigure",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Export currently selected figure.",
bind=((wx.EVT_LEFT_UP, self.onExportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Morph",
name="BTN_ImportMorph",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=(50, 30),
bgcolor=STD_COLOR, fgcolor=(0xD0, 0xC0, 0x30),
tooltip="Import modified figure as morph.",
bind=((wx.EVT_LEFT_UP, self.onImportModel),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Figure",
fgcolor=STD_COLOR, name="LBL_FigureList"),
self.figurelist)
gbm.addrow(LabelCtrl(self, label="Morphname",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, value=CONFIG.get("CTRL_MorphName", "Morph"),
name="CTRL_MorphName", style=wx.BORDER_SIMPLE),
bgcolor=BG_COLOR, fgcolor=TX_COLOR))
gbm.addrow(LabelCtrl(self, label="Increment Morph" + _LF + "on each import",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, value=CONFIG.get("CTRL_RenumberMorph", False),
name="CTRL_RenumberMorph", style=wx.NO_BORDER,
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(PrepCtrl(self, wx.Button, label="Export Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path to export current figure to.",
bind=((wx.EVT_LEFT_UP, self.onExportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
),
PrepCtrl(self, wx.Button, label="Import Path",
style=wx.BORDER_NONE | wx.BU_EXACTFIT, size=btn_size,
bgcolor=STD_COLOR, fgcolor=FG_COLOR,
tooltip="Path where exported figures are stored.",
bind=((wx.EVT_LEFT_UP, self.onImportPath),
(wx.EVT_ENTER_WINDOW, self.onEnter_button),
(wx.EVT_LEAVE_WINDOW, self.onLeave_button))
))
gbm.addrow(LabelCtrl(self, label="Use Fileselection",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_SingleSelectFile", True),
name="CTRL_SingleSelectFile",
tooltip="Select file on each Import/Export",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export groups",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportGroups", False),
name="CTRL_ExportGroups",
tooltip="Export actor groups",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="Export UV",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
PrepCtrl(self, wx.CheckBox, style=wx.NO_BORDER,
value=CONFIG.get("CTRL_ExportTexture", True),
name="CTRL_ExportTexture",
bind=((wx.EVT_CHECKBOX, self.onCheckBox),)))
gbm.addrow(LabelCtrl(self, label="LBL_Scale",
fgcolor=STD_COLOR, bgcolor=BG_COLOR),
ColoredCtrl(wx.TextCtrl(self, name="CTRL_Scale",
value=str(CONFIG.get("Scale", 1)),
style=wx.SIMPLE_BORDER | wx.TE_RIGHT),
fgcolor=FG_COLOR, bgcolor=BG_COLOR))
gbm.sizer.AddGrowableCol(0)
gbm.sizer.AddGrowableCol(1)
sz = wx.BoxSizer(wx.VERTICAL)
sz.AddMany([
(gbm.sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 10),
])
sz.SetSizeHints(self)
self.SetSizer(sz)
wx.CallAfter(self.updateUI)
def onSize(self, ev):
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
self._CONFIGCHANGED = True
ev.Skip()
def updateUI(self):
obj = self.FindWindowByName("LBL_FigureList")
if obj:
c = (0xff, 0, 0) if self.figurelist.GetCount() == 0 else STD_COLOR
obj.SetForegroundColour(c)
if self._CONFIGCHANGED:
write_config()
self._CONFIGCHANGED = False
if not STOPP_UPDATE_UI:
wx.CallLater(300, self.updateUI)
def onChoice(self, ev):
choice = ev.GetEventObject()
if choice.GetName() == "figurelist": # type: wx.Choice
idx = choice.GetCurrentSelection()
if idx == -1:
CONFIG["figure"] = ""
else:
CONFIG["figure"] = choice.GetString(idx)
def onRadioBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onCheckBox(self, ev):
try:
obj = ev.GetEventObject()
CONFIG[obj.GetName()] = obj.GetValue()
except Exception:
raise
finally:
ev.Skip()
def onExportModel(self, ev):
""":type ev: wx.Event"""
try:
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
obj = wx.FindWindowByName("CTRL_MorphName")
CONFIG["CTRL_MorphName"] = obj.GetValue()
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
do_export(figure)
obj.SetBackgroundColour(STD_COLOR)
obj.Update()
except Exception:
raise
finally:
ev.Skip()
def onImportModel(self, ev):
try:
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
obj = wx.FindWindowByName("CTRL_MorphName")
morphname = obj.GetValue().strip() if obj else "MORPH"
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
do_import(figure, morphname)
obj.SetBackgroundColour(STD_COLOR)
check = wx.FindWindowByName("CTRL_RenumberMorph")
if check and check.GetValue():
morphname, _, nr = morphname.partition(" ")
morphname += " " + str(int(nr or 0) + 1)
obj.SetValue(morphname)
except Exception:
raise
finally:
ev.Skip()
def onExportPath(self, ev):
try:
defaultpath = CONFIG.get("ExportPath",
CONFIG.get("ImportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(self,
"Choose Export Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ExportPath"] = dlg.GetPath()
self._CONFIGCHANGED = True
except Exception:
raise
def onImportPath(self, ev):
try:
defaultpath = CONFIG.get("ImportPath",
CONFIG.get("ExportPath",
CONFIG.get("BASEPATH", BASEPATH)))
with wx.DirDialog(None,
"Choose Import Directory", defaultpath,
style=wx.DD_DEFAULT_STYLE
) as dlg:
if dlg.ShowModal() == wx.ID_OK:
CONFIG["ImportPath"] = dlg.GetPath()
except Exception:
raise
finally:
ev.Skip()
def onEnter_button(self, ev):
obj = ev.GetEventObject()
obj.SetBackgroundColour(HL_COLOR)
ev.Skip()
def onLeave_button(self, event):
obj = event.GetEventObject()
obj.SetBackgroundColour(STD_COLOR)
event.Skip()
def onClose(self, event):
global STOPP_UPDATE_UI
STOPP_UPDATE_UI = True
CONFIG["CTRL_MorphName"] = wx.FindWindowByName("CTRL_MorphName").GetValue()
CONFIG["Scale"] = wx.FindWindowByName("CTRL_Scale").GetValue()
CONFIG["Perspective"] = aui.SavePaneInfo(paneInfo)
write_config()
def updateFigurelist(self, *ev):
if poser:
while self.figurelist.GetCount():
self.figurelist.Delete(0)
for idx, fig in enumerate(poser.Scene().Figures()):
self.figurelist.Append(fig.Name())
if fig == poser.Scene().CurrentFigure():
self.figurelist.SetSelection(idx)
if __name__ == "__main__":
read_config()
aui = poser.WxAuiManager()
root = aui.GetManagedWindow()
SYS_FONT = root.GetFont()
paneInfo = aui.GetPane(APP_NAME)
if paneInfo.IsOk():
aui.ClosePane(paneInfo)
paneInfo = wx.aui.AuiPaneInfo()
paneInfo.name = APP_NAME
paneInfo.Resizable(True).FloatingSize(wx.Size(250, 280)).BestSize(wx.Size(200, 230))
paneInfo.Movable(True).DestroyOnClose(True)
paneInfo.Name(APP_NAME).Caption("+++ NEED A NAME FOR THIS +++")
savedinfo = CONFIG.get("Perspective", None)
if savedinfo is not None:
aui.LoadPaneInfo(savedinfo, paneInfo)
MainPanel = AppPanel(parent=root, name=APP_NAME)
aui.AddPane(MainPanel, paneInfo)
paneInfo.Show()
aui.Update()
OK. Works with different figures now.
Ran into one issue up to now: Had to change line 655: It made it to call SetLabel() and not SetValue()
My interest by the way is of pure self-interest. I need to update 'MDBridge for Poser' with something more robust. The thing was set up as a proof of concept. Do not look at the code. It does the trick though and responses are positive.
This Blender bridge is a kickstart of what I had in mind for the 'real' version. I spent ages trying to get around the bugs in the Poser importer-exporter and more in particular the morph loader. Had to make workarounds there.
I do hope it is allowed to copy your good practice.
This Blender bridge is a kickstart of what I had in mind for the 'real' version. I spent ages trying to get around the bugs in the Poser importer-exporter and more in particular the morph loader. Had to make workarounds there.
And when I look at the code, I myself am amazed at how simple it is.
Anyway: I'm always glad to support creation of freeware (the reason while I post here) :) Give credit in your docu/sourecode as usual and anybody will be happy.
To get around the problems with the RR editor, and because it's stupid to post such big sources all the time, I'm in the process of setting up a simple webserver on a Odroid-C2 lying around here and putting it on the net. Just have to secure it properly.
My next update will be downloadable from there (with your change - thanks for that!).
They are using a lightweight version called PythonQt. It does not support all of Qt though. There appears to be no list of classes not supported so it is a bit of guess and miss.
MD on the other hand does not support all of Python. import os for example will not work. File access must go via PythonQt functions. But I better not go off topic.
FVerbaas posted at 3:19PM Mon, 30 December 2019 - #4374962
They are using a lightweight version called PythonQt.
More import question is: Will they upgrade from QT4 to QT5?
QT5 supports allmost anything one needs for fast 3D display and computing.
Maybe a RPC-Interface QT5 <-> Poser could be something.
FVerbaas posted at 3:37PM Mon, 30 December 2019 - #4374922
OK. Works with different figures now.
Ran into one issue up to now: Had to change line 655: It made it to call SetLabel() and not SetValue()
SetLabel isn't right. But the wrong obj is changed. It has to be:
check.SetValue(morphname)
# **not** obj.SetValue
adp001 posted at 4:27PM Mon, 30 December 2019 - #4374975
More import question is: Will they upgrade from QT4 to QT5?
I do not think MD will be doing that. I have the impression the Python API is not high on their list and they rely on development by an external party. I wrote the MD side bridgehead the way I wrote it, linking in one layer below the level they suggest, The level they suggest to use is/was plain buggy. I heard of no-one who used it with success.
Note that the API came with MD7 in fall 2017, yet they use Python 2.7. I do not see them changing unless they are requested by an important large client.
QT5 supports allmost anything one needs for fast 3D display and computing.
Maybe a RPC-Interface QT5 <-> Poser could be something.
Complete new Poser interface based on QT5, right? You could propose this to Jenn. ;-)
FVerbaas posted at 6:07PM Mon, 30 December 2019 - #4374983
adp001 posted at 4:27PM Mon, 30 December 2019 - #4374975
More import question is: Will they upgrade from QT4 to QT5?
I do not think MD will be doing that. I have the impression the Python API is not high on their list and they rely on development by an external party. I wrote the MD side bridgehead the way I wrote it, linking in one layer below the level they suggest, The level they suggest to use is/was plain buggy. I heard of no-one who used it with success.
Note that the API came with MD7 in fall 2017, yet they use Python 2.7. I do not see them changing unless they are requested by an important large client.
Changing from Python 2.7 to 3.x needs a lot of manpower.
And including QT5 in commercial software costs money. Maybe this is a point too.
QT5 supports allmost anything one needs for fast 3D display and computing.
Maybe a RPC-Interface QT5 <-> Poser could be something.
Complete new Poser interface based on QT5, right? You could propose this to Jenn. ;-)
No. Just a bridge to bring things in and out. But with a more direct binding (changes made in Poser are reflected directly over the bridge and vice versa). I started something like this years ago for C4D (very basic and for a special need).
I don't think Bondware has an interest in QT, because, as said: it cost money to implement it.
FVerbaas posted at 9:12PM Mon, 30 December 2019 - #4374978
adp001 posted at 4:26PM Mon, 30 December 2019 - #4374976
SetLabel isn't right. But the wrong obj is changed. It has to be:
check.SetValue(morphname) # **not** obj.SetValue
OK. Will change
Wait a minute: _check _is a checkbox that can be checked, or not.
morphname is a string which was just assembled, with incrementing counter, which makes no sense as a value for a checkbox.
Does that string not need to go into the text box obj?
You are totally right. The whole thing got out of my control :)
Here is the correct part:
def onImportModel(self, ev):
try:
sc = wx.FindWindowByName("CTRL_Scale")
CONFIG["Scale"] = sc.GetValue()
idx = self.figurelist.GetCurrentSelection()
if idx < 0:
ErrDialog("No figure selected.")
return
figurename = self.figurelist.GetString(idx)
figure = poser.Scene().Figure(figurename.strip())
morph_ctrl = wx.FindWindowByName("CTRL_MorphName")
morphname = morph_ctrl.GetValue().strip() if morph_ctrl else "MORPH"
obj = ev.GetEventObject()
obj.SetBackgroundColour((0xff, 0, 0))
obj.Update()
do_import(figure, morphname)
obj.SetBackgroundColour(STD_COLOR)
check = wx.FindWindowByName("CTRL_RenumberMorph")
if check and check.GetValue():
morphname, _, nr = morphname.partition(" ")
morphname += " " + str(int(nr or 0) + 1)
morph_ctrl.SetValue(morphname)
except Exception:
raise
finally:
ev.Skip()
Hi there. I am interested in this project and am using a Mac. Are there special installation instructions for this osX? I installed the script to my Poser runtime and it calls up, but the GUI does not appear the same as the screenshot in the first post of this thread. I downloaded the script from the adp.spdns.org site link and would like to bridge it with blender 2.81
Please let me know if you have any suggestions or updates. Thanks!
emjay247 posted at 3:06AM Mon, 13 January 2020 - #4376389
Hi there. I am interested in this project and am using a Mac. Are there special installation instructions for this osX? I installed the script to my Poser runtime and it calls up, but the GUI does not appear the same as the screenshot in the first post of this thread. I downloaded the script from the adp.spdns.org site link and would like to bridge it with blender 2.81
Please let me know if you have any suggestions or updates. Thanks!
No, there is nothing special for osX.
Can you post a screenshot so I can see what you mean?
Good Morning,
I was able to get the script to work, though it doesn't look the same as yours (see screenshot). For some reason, when I set the file path, Poser reverts it back to it's own folder in applications. I was having trouble finding the exported obj because it was NOT sending it to the file path I assigned (shared folder). I tested it with two figures (LaFemme and Angela by Ali) and they both interchanged well with blender as long as you keep the same vertex order on import and export.
Thank you for sharing this script. This will help immensely with body morph creation!
emjay247 posted at 4:03PM Mon, 13 January 2020 - #4376406
I was able to get the script to work, though it doesn't look the same as yours (see screenshot).
Seems to me that wxPython does funny things with scaled widgets (Buttons etc). At least this version used by Poser (a real old version, by the way). Maybe it's better to not scale/manipulate the widgets on OSX. But I'm really not good with UIs (lack of interest ;) )
For some reason, when I set the file path, Poser reverts it back to it's own folder in applications. I was having trouble finding the exported obj because it was NOT sending it to the file path I assigned (shared folder).
Yes, this is an error in the script. I changed it already and I do the update within a couple of minutes.
I tested it with two figures (LaFemme and Angela by Ali) and they both interchanged well with blender as long as you keep the same vertex order on import and export.
Yes, same old rule for any morph procedure ;)
emjay247 posted at 4:39PM Mon, 13 January 2020 - #4376406
For some reason, when I set the file path, Poser reverts it back to it's own folder in applications. I was having trouble finding the exported obj because it was NOT sending it to the file path I assigned (shared folder).
In your screenshot "Use Fileselection" is not checked. Please try it. The script will give you ex- and import dialogs if checked.
emjay247 posted at 2:37AM Tue, 14 January 2020 - #4376460
Ok, so that's what that button does. Thanks for the explanation. Should it be selected by default for new users? I'll let you know if there is any other trouble understanding how this works. Thanks again, this script will be very useful.
Yes please. let me know.
And feel free to make sugestions. Even for correct lettering on buttons etc (I have serious problems with english).
I'm really open for improvement proposals.
Would be nice if someone could write some sort of a short manual.
Within the next days I will look into the Mac-OS problem wxPython seems to have.
I have to say I'm extremely excite by this script a way import and export with reverse deformation is sorely needed in poser.I down loaded your scripts and tried for over an hour to get them to work. I tried to exprt with groups, I tried to export with the default settings I tried several figures and modelling tools. I tried to export to a number of newly created folders, and old working folders. However every time I tried to import a morph I got an error saying (to paraphrase) that the exported vertices number didn't match the import number.
I'm open (very) to accept this as user error, but I can't think of anything else to try. I get the with both the Manipulate morph and the FullBody morph scripts.
Any suggestions would be welcome as I really want to see this work?
Mike
Letterworks posted at 10:41PM Tue, 14 January 2020 - #4376631
However every time I tried to import a morph I got an error saying (to paraphrase) that the exported vertices number didn't match the import number.
Sounds like your modeler "saved" some vertices. Most modelers (including Blender) have an option named "Keep vertex order" or something like that. You have to check this (import and export).
Problem is: While Poser creates the unimesh, several vertices become dispensable (those vertices where bodyparts are glued together). But they decided not to discard those vertices, but set them to all zero and put them in the file. Modeler don't like that and tend to ignore these redundant vertices. Except: If you check "Keep vertex order" :) Same game on export. Modelers don't output wastful vertices. Except you force them.
I would like to try your script. Should I use the text created on the first page? Is there an updated one?
____________________________________________________________________________________________________________________________
Asus N50-600 - Intel Core i5-8400 CPU @ 2.80GHz · Windows 10 Home/11 upgrade 64-bit · 16GB DDR4 RAM · 1TB SSD and 1TB HDD; Graphics: NVIDIA Geforce GTX 1060 - 6GB GDDR5 VRAM; Software: Poser Pro 11x
jartz posted at 7:13AM Thu, 16 January 2020 - #4376720
I would like to try your script. Should I use the text created on the first page? Is there an updated one?
Yes, you can find the last version on my website as a zip-file (see my footer). Posting here makes problems because the Rendo-Editor kills some parts from posted sourcecode.
Okay under the Pose Modeler bridge. Great, I'll check it out.
____________________________________________________________________________________________________________________________
Asus N50-600 - Intel Core i5-8400 CPU @ 2.80GHz · Windows 10 Home/11 upgrade 64-bit · 16GB DDR4 RAM · 1TB SSD and 1TB HDD; Graphics: NVIDIA Geforce GTX 1060 - 6GB GDDR5 VRAM; Software: Poser Pro 11x
Letterworks posted at 7:46PM Sat, 18 January 2020 - #4376631
I have to say I'm extremely excite by this script a way import and export with reverse deformation is sorely needed in poser.I down loaded your scripts and tried for over an hour to get them to work. I tried to exprt with groups, I tried to export with the default settings I tried several figures and modelling tools. I tried to export to a number of newly created folders, and old working folders. However every time I tried to import a morph I got an error saying (to paraphrase) that the exported vertices number didn't match the import number.
Problem solved. New version on my website: PoMo18012020.zip
adp001 posted at 2:05PM Tue, 21 January 2020 - #4377063
Letterworks posted at 7:46PM Sat, 18 January 2020 - #4376631
I have to say I'm extremely excite by this script a way import and export with reverse deformation is sorely needed in poser.I down loaded your scripts and tried for over an hour to get them to work. I tried to exprt with groups, I tried to export with the default settings I tried several figures and modelling tools. I tried to export to a number of newly created folders, and old working folders. However every time I tried to import a morph I got an error saying (to paraphrase) that the exported vertices number didn't match the import number.
Problem solved. New version on my website: PoMo18012020.zip
Quoting myself:
This version should only be used if your modeler has problems with the standard version.
I got this to work on my Mac but not without poking and prodding. I'm using the wx3 version of your script. The buttons act kinda funny. Sometimes I have to click the light gray area AROUND the button rather than the button itself. Also I have to click "use fileselection" when exporting but click it off when importing. When importing it will give me an error that includes "file not found" if I navigate to the folder and click the modified file itself. If I tick off the checkbox it will find the file to be imported but only if it's named exactly like the exported file with _mod at the end. Maybe the wx4 would work better. I couldn't get that one to work but I understand the quirks a bit better now.
I've used it with LaFemme, but also with an older, heavily morphed, Chip character. So it's actually quite amazing that this is possible at all. Thanks for working on this
________________________________
I know there is a problem with OSX. Apple does not use standards while handle UI-buttons. And the old wxPython version in Poser has no workaround. I can't work on a fix, because I use Linux and Win, not OSX. So I'm not able to do something.
But the script is more a prof of concept anyway. So someone with OSX may do an interface.
This site uses cookies to deliver the best experience. Our own cookies make user accounts and other features possible. Third-party cookies are used to display relevant ads and to analyze how Renderosity is used. By using our site, you acknowledge that you have read and understood our Terms of Service, including our Cookie Policy and our Privacy Policy.
Can export current state of a figure (posed, morphed) to wavefront OBJ file.
Exported file can be imported in any modeler (tested with C4D and Blender). Make sure your modeler does not touch vertex-order. For Blender import set scale to 1 or 10. For C4D something between 100 and 1000.
Use the same scale for export and import.
Change the figure as you like (morph), but don't move, rotate or scale the figure in your modeler. If you do, make sure you set anything back to zero before you make the export.
Export from your modeller to wavefront OBJ with another name (don't overwrite the file exported from Poser!).
Back in Poser press "Import Morph". You can change the name of the morph before importing.
Select the file you saved from your modeler. Any changes you made in your modeller are extracted and put into the right actor as morph.
Pressed buttons (Import/Export) will change color to red as long as export or import runs (just a second or two).
Don't touch the exported obj-file before you have imported your morph. The file is required for the import process, because the difference between the saved file from your modeller (the morph) and the previousely saved file from Poser is your actual morph (file B - file A == morph).
Script follows with the next post.