adp001 opened this issue on Dec 27, 2019 · 92 posts
adp001 posted Fri, 27 December 2019 at 3:10 PM
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.
adp001 posted Fri, 27 December 2019 at 3:11 PM
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()
adp001 posted Fri, 27 December 2019 at 3:14 PM
The script is still WIP. Please report errors or problems.
Feel free to make suggestions.
adp001 posted Fri, 27 December 2019 at 3:40 PM
This crappy editor here isn't able to handle sourcecode!!!
All Linefeed characters in my script where removed (written as backslash N – the backslash is cutted out).
So, If text appears strange, insert a backslash in front of this missplaced N.
structure posted Fri, 27 December 2019 at 3:44 PM Forum Coordinator
the backslash has always been a problem
"\"
I used a double backslash for this
Locked Out
adp001 posted Fri, 27 December 2019 at 3:57 PM
Oh, nice to know. Ok for one-liner. But with a whole script (732 line like above)?
There are plenty perfectly working javascript-editors out there. Can't believe we have to deal with something like this.
FVerbaas posted Sat, 28 December 2019 at 6:38 AM Forum Coordinator
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.
FVerbaas posted Sat, 28 December 2019 at 11:16 AM Forum Coordinator
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.
adp001 posted Sat, 28 December 2019 at 11:34 AM
Oh, my post was no offense.
MD does surly a good job. But isn't the cheapest piece of software ($50 monthly subscription, or $300 per year). A little much for someone who only makes a handful of clothes :)
FVerbaas posted Sat, 28 December 2019 at 11:59 AM Forum Coordinator
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
.
FVerbaas posted Sat, 28 December 2019 at 1:07 PM Forum Coordinator
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.
adp001 posted Sat, 28 December 2019 at 6:37 PM
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.
adp001 posted Sat, 28 December 2019 at 6:37 PM
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()
FVerbaas posted Sun, 29 December 2019 at 2:08 PM Forum Coordinator
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.
FVerbaas posted Sun, 29 December 2019 at 2:43 PM Forum Coordinator
With credits as due, of course.
adp001 posted Sun, 29 December 2019 at 2:45 PM
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.
adp001 posted Sun, 29 December 2019 at 2:53 PM
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!).
adp001 posted Sun, 29 December 2019 at 2:56 PM
By the way interesting that MD uses Qt. I wish we had that with Poser too (instead of the stupid wxWindow).
infinity10 posted Mon, 30 December 2019 at 4:32 AM
wow
Eternal Hobbyist
TaishoBee posted Mon, 30 December 2019 at 4:40 AM
exciting!
Poser Pro 11 (always updated to current version available) . Laptop Specs: Intel Core i7-7700 CPU @ 3.60GHz 3.60 GHz :: 16 GB Ram :: Windows 10 Pro
FVerbaas posted Mon, 30 December 2019 at 4:43 AM Forum Coordinator
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.
adp001 posted Mon, 30 December 2019 at 8:08 AM
I had a quick look into the C++ Code. Seems they support QtQml/QtQuick. Is Javascript allowed in QML-Widgets?
Talking about Python can never be off topic :)
adp001 posted Mon, 30 December 2019 at 8:09 AM
@TaishoBee and @infinity10:
Thanks for interest.
adp001 posted Mon, 30 December 2019 at 8:33 AM
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.
adp001 posted Mon, 30 December 2019 at 8:39 AM
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
FVerbaas posted Mon, 30 December 2019 at 9:27 AM Forum Coordinator
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
FVerbaas posted Mon, 30 December 2019 at 10:05 AM Forum Coordinator
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. ;-)
adp001 posted Mon, 30 December 2019 at 11:18 AM
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.
adp001 posted Mon, 30 December 2019 at 1:40 PM
Actual sourcecode can be downloaded from
adp.spdns.org
Above webpage is completly free of ads and trackers.
(sorry, no direct links allowed at RR)
FVerbaas posted Mon, 30 December 2019 at 2:12 PM Forum Coordinator
I assume MD will therefore be on Python 2.7 and QT4 for the foreseeable future.
FVerbaas posted Mon, 30 December 2019 at 2:23 PM Forum Coordinator
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?
adp001 posted Mon, 30 December 2019 at 5:16 PM
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()
adp001 posted Mon, 30 December 2019 at 5:17 PM
And now I'm going to sleep for several days...
emjay247 posted Sun, 12 January 2020 at 7:27 PM
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!
adp001 posted Sun, 12 January 2020 at 8:06 PM
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?
emjay247 posted Mon, 13 January 2020 at 7:59 AM
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!
adp001 posted Mon, 13 January 2020 at 9:25 AM
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 ;)
adp001 posted Mon, 13 January 2020 at 9:42 AM
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 Mon, 13 January 2020 at 1:54 PM
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.
adp001 posted Mon, 13 January 2020 at 7:48 PM
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.
Letterworks posted Tue, 14 January 2020 at 3:09 PM
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
adp001 posted Tue, 14 January 2020 at 3:52 PM
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.
Letterworks posted Tue, 14 January 2020 at 4:19 PM
adp001, I will look at that, mostly I use Silo, I also have Carrara 8 for some testing and have never noticed those commands but I will look, thanks for the quick reply
adp001 posted Tue, 14 January 2020 at 6:14 PM
I did a search with "silo vertex order". On a DAZ page a user has a similar problem with Genesis import/export. According to another user, Silo has that option to preserve vertex-order.
jartz posted Wed, 15 January 2020 at 1:39 PM
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
adp001 posted Thu, 16 January 2020 at 12:17 AM
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.
jartz posted Thu, 16 January 2020 at 1:47 AM
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
adp001 posted Sat, 18 January 2020 at 12:48 PM
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 Tue, 21 January 2020 at 7:06 AM
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.
false1 posted Tue, 04 February 2020 at 11:36 AM
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
________________________________
adp001 posted Tue, 04 February 2020 at 12:52 PM
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.
false1 posted Tue, 04 February 2020 at 1:49 PM
adp001 posted at 2:46PM Tue, 04 February 2020 - #4379012
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.
That's cool. It's functional and consistent from my limited tests. The inability to morph a modified character has been a huge hole in my workflow. I'll just have to copy this post so I can remember my steps. Thanks again.
________________________________
Zaycrow posted Mon, 10 February 2020 at 12:51 AM
I noticed the script export everything. Also the parts that are hidden in figures. Is there a way to export so the parts that are hidden doesn't get exported? Like the RhinoExport.py that Smith Micro use to have on their site.
adp001 posted Mon, 10 February 2020 at 7:03 PM
Zaycrow posted at 1:47AM Tue, 11 February 2020 - #4379717
I noticed the script export everything. Also the parts that are hidden in figures. Is there a way to export so the parts that are hidden doesn't get exported? Like the RhinoExport.py that Smith Micro use to have on their site.
RhinoExport does a simple object export, if I see it right.
If you want something not exported, delete it. As long as it is there, it influences vertex-numbering. What this script does depends hardly on vertex-orders. A simple thing like not exporting something means destroying anything. To avoid that, a complicated process is required what makes this simple script big, complicated and error-prone.
But maybe anyone comes out with an easy to implement idea. For me the script does what I need it to do and I'm busy with other things more important for me.
But if anyone feels called: Don't hold back.
FVerbaas posted Tue, 11 February 2020 at 9:38 AM Forum Coordinator
The method I use to filter out internals is to select by material. For avatars for garment development I just use the skin material for body, head, limbs and maybe lips. Alternative is to define a facet group. Then you can add or remove as you like.
RAMWorks posted Fri, 21 February 2020 at 9:20 AM
I'm interested in this script but looked on your downloads page and wasn't sure which it was if it's posted there. I'm not good at writing code so I'll wait to download any until your done with this one.
I'm from DAZ Studio but have been working a little bit with Poses's La'Femme and L'Homme and really like both of them but I'm more of an underdog sort of guy, I like supporting the male figures (or shapes, which ever! LOL).
I work primarily with ZBrush as my program of choice to create stuff in (clothing, props and morphs mostly) and while I know there is a built in ZBrush bridge in Poser now I've had some niggles with it. So I wanted a way to export a workable OBJ file of L'Homme but since he's just a morph of La'Femme there is no OBJ, just a PMD and I've found no way to make that happen.
There are issues when I export L'Homme from Poser and import him into ZBrush via their importer. I've created some morphs, saved that out as a new morph target and then in Poser, Poser wants to import it as a prop so I'm guessing the export from Poser does something to the mesh ... something I have no idea how to fix so it's basically useless.
I have a thread here at 'Rosity around L'Homme and they pointed me to your project here.
Hope it works out as I need this!
Thanks so much Richard
---Wolff On The Prowl---
adp001 posted Fri, 21 February 2020 at 11:55 AM
Last version is this:
http://adp.spdns.org/FullbodyMorphs_wx4.py.zip
If you are not on a Mac the script should work fine. With Macs there is something with the UI. Poser's wxPython implementation (a really old one) has problems with some UI elements (if used like in this script).
RAMWorks posted Fri, 21 February 2020 at 12:57 PM
Cool, I'll download that when I get home from work. Thanks so much!
---Wolff On The Prowl---
adp001 posted Fri, 21 February 2020 at 1:14 PM
RAMWolff posted at 8:12PM Fri, 21 February 2020 - #4381298
Cool, I'll download that when I get home from work. Thanks so much!
This script supports no Sub-D currently. Seems this is important for you.
RAMWorks posted Fri, 21 February 2020 at 4:07 PM
When dealing with Genesis 8 for DAZ I always make sure the figures Sub D is set to the lowest when making morphs so that's no worry for me.
---Wolff On The Prowl---
adp001 posted Fri, 21 February 2020 at 4:17 PM
RAMWolff posted at 11:14PM Fri, 21 February 2020 - #4381329
When dealing with Genesis 8 for DAZ I always make sure the figures Sub D is set to the lowest when making morphs so that's no worry for me.
Same here. I see no reason for hi-res morphs. Actually, a morph is nothing more than a displacement map on a hires mesh. So: subdivide and use a displacement-map if needed :)
RAMWorks posted Fri, 21 February 2020 at 9:36 PM
WOW, just lowered the Sub D to 0 and his hips popped out as in looking more feminized. Very strange. Don't get that with Genesis, I just get more blocky looking mesh when close up. The only area that's the give away is the ears, those get a bit blocky but I have to say I'm impressed in how well this figure is designed with the amount of attention that went into making sure it's smoothed nicely even with the zeroed out Sub D. I even zeroed out the Sub D for Render
---Wolff On The Prowl---
RAMWorks posted Fri, 21 February 2020 at 9:38 PM
RAMWorks posted Sat, 22 February 2020 at 9:58 AM
OK, not sure where to put this script to make it show up in Poser. I have it currently here:
C:Program FilesPoser SoftwarePoser 11RuntimePythonposerScripts
Didn't show up!
Help?
---Wolff On The Prowl---
RAMWorks posted Sun, 23 February 2020 at 12:18 PM
OK, using the script via import the script via the File> Run Python Script. So that's nice. Figured out something but still would love to see it in the Scripts drop down menu!
So I'm having a little issue with this. Exported L'Homme via your script, opened him up in ZBrush and did a test morph, saved it out as an OBJ and used the import button to bring the morph in. What it seems to have done is unhooked all the other morphs I've managed to make like my "Masculine Hip Position" morph. I made a morph called "Chest Larger". Interestingly enough the one morph that seems operational is the face morph I made, so that seems odd to me.
So then I had two morphs operational, the face morph and the Chest Larger morph. So then I went back and undid the steps in ZBrush, back to the original version your py script exported for me. I then recreated the Masculine Hip Position morph, exported that, named it and used that button to bring it in. It seemed to overwrite the Chest Larger morph but strangely enough the Masculine Hip Position morph was loaded into the Chest Larger morph channel. Very strange to me.
Can you set up the script to create a new channel for each morph imported? I don't know any other way around this! YIKES!
---Wolff On The Prowl---
FVerbaas posted Sun, 23 February 2020 at 1:44 PM Forum Coordinator
Put it in (a subfolder of) this location:
C:Program FilesPoser SoftwarePoser 11RuntimePythonposerScriptsScriptsMenu
adp001 posted Sun, 23 February 2020 at 2:55 PM
The script gives you the difference of what you exported from Poser (including poses, dialed in morphs, weightmaps in action, etc) and the work you did actually in you modeller.
If you output a figure with a morph dialed in, this morph is visible in Z-Brush, but will not be part of the new imported morph. The reason for that is being able to make morphs based on another already existing morph/deformations.
Starting from a base figure: Do morph one. Name it "One". Don't export again from Poser, don't change "morphname" until you are satisfied with the imported morph. For the next step name your morph "Two" (change morphname). Export the figure from Poser with morph One dialed in. Make your modifications in ZBrush as in part one. You will see the effect from morph One in ZBrush, but it will not be part of the new exported morph. The next export-result imported into Poser is only what you modified in the second step.
This process does not take care of deformations cased on poses (PCM, weightmap) or dialed in other morphs. Your new morph is exactly the result from the exported figure (Poser's export) minus your export from the modeller.
This way you are able to create a set of morphs in an external modeller and each morph plays nicely with the other morphs.
If you plan to make a complete "full-bodymorph" (that is one with one single name) part by part, use the same Poser-exported mesh on each session.
Name of the new import channel is always what you typed in into "morphname".
On import into Poser each vertex is checked if it differs from the exported mesh this new import is based on. If yes (even minimal), a morphchannel is created inside the actor this vertex belongs to. So, if you find a morphchannel in an actor unexpectedly, check your modifications made in your modeller.
I have no plans to work further on this script as long as it does what I need it to do. It's kind of a proof-of-concept for me. It's free code. Anyone can take it and make it work for non-programmers or extend/mofiy it for special cases.
For the "real thing" we need a module inside the modeller. The Poser side has too mutch restrictions. This is the case why my next step is developing a modul for Blender. A direct-connection based on RPC, reflecting anything important from Poser in Blender – nearly in realtime. And it will work fine with Poser2014 and lower :)
emjay247 posted Sun, 23 February 2020 at 4:31 PM
"This is the case why my next step is developing a modul for Blender. A direct-connection based on RPC, reflecting anything important from Poser in Blender – nearly in realtime. And it will work fine with Poser2014 and lower :)"
That would be amazing!
bantha posted Thu, 27 February 2020 at 8:18 AM
Just a thought, when thinking about vertex order - would it possible to reconstruct the vertex order using the uv-coordinated of the model, as an option? Usually, you don't change the uv-coordinates when morphing a figure. That way you could even use parts of the mesh for morphing and still restore the vertex order.
Is this idea usable in any way?
Great work, by the way. Very impressive.
A ship in port is safe;
but that is not what ships are built for.
Sail out to sea and do new things.
-"Amazing
Grace" Hopper
Avatar image of me done by Chidori.
adp001 posted Thu, 27 February 2020 at 2:48 PM
bantha posted at 9:35PM Thu, 27 February 2020 - #4381962
Just a thought, when thinking about vertex order - would it possible to reconstruct the vertex order using the uv-coordinated of the model, as an option? Usually, you don't change the uv-coordinates when morphing a figure. That way you could even use parts of the mesh for morphing and still restore the vertex order.
Is this idea usable in any way?
Great work, by the way. Very impressive.
I'm thinking about a welding-function in Python. Maybe based on a KdTree. I'm testing it at the moment. Because I'm not so familar with KdTree's I have to experiment a bit. This planed function will create a database at the end. So it must only run once per figure (as long as the database is reachable). Having that makes a lot more things possible. Especially support for cloth-creation.
unrealblue posted Mon, 01 March 2021 at 9:34 PM
No output. No errors, no output.
Nuts. I was messing around with poser morphs in blender and came upon this script. Well, 3 scripts:
Poser 12 on a Mac (Mojave). I tried them all. The GUI looks the same in each (more or less). My desktop (~/Desktop) is the path (import and export).
Figure is selected. Click export, nothing happens. No output file. No error. I haven't had time to go trace through the code. I thought maybe there's a "no duh" something I'm missing.
Cheers
adp001 posted Tue, 02 March 2021 at 11:48 AM
Sorry, I'm late.
Did an update to OBJ_ImExporter today. With a little help file.
Untested in P12: OBJ_ImExport.zip
adp001 posted Tue, 02 March 2021 at 11:51 AM
OBJ_ImExport is based on Posers Python Script Menu. I think this is better than WX Python for several reasons.
Best use: Unzip all contained files to a seperate directory (Poser can reach and work with), then start "Main.py" from Poser.
unrealblue posted Tue, 02 March 2021 at 9:46 PM
I was wondering...
I'm doing some scripting in support of morphs. I've found, "mesh shaping" is easier (for the greater part) in Blender. I want to go back and forth between them.
There is the export posed+morphed obj from Poser. Fine. Over in Blender, I import it, keeping the vert order the same. Again, fine. But, for multiple-actor meshes, of course this doesn't produce a mesh I can conveniently sculpt: breaks at actor boundaries. What I need is a form of "glue" in Blender. I thought of a way to accomplish this:
Effectively, I need a duplicate mesh that has been run through "merge" with a low enough threshold. In un-scaled form, this is about 0.000001 or so. In any case, to "connect" those two meshes, I need isupplemental data. I need a map of new vert to old vert. Each new vert would have one (or more) id's of old verts. After morphing, you would "apply to old mesh" which would simply take the position of each new vert, and push that to the indexed verts. Viola. Then export that original mesh, with new positions that come from sculpting a blender "uni-mesh". Then, in Poser you could simply "load full body morph". It really only cares about vert index and new position.
Right?
To make the map, it could (inefficiently/naively) be done by iterating through each new mesh vert, and find each old vert position that is the same (within the tolerance of the merge). A list (new vert id's) of lists (old vert id's). No holes. Each new vert would be one or more old vert.
Wouldn't have to care anything about details of the Poser mesh. All we're doing is translating that into a Blender unimesh, map new verts to old verts, moving the new verts around, then taking those positions reverse translated back to the Poser mesh, which is exported then imported by poser.
An effective "weld" by using the weld feature in Blender, with additional data of "for each point, which old point(s) are represented by this point"
Or am I being too naive, here?
unrealblue posted Tue, 02 March 2021 at 9:59 PM
I realize the effect could be called a "soft merge" or "reversible merge", and might be useful for other things. You could use a similar effect for sculpting multiple objects as a combine mesh, complete with everything you can do in sculpting (except re-topo, of course) and take the sculpt result back out to the original mesh(es). In this case, the lookup data needs to also include the mesh id. Each "unimesh" vert would have a list of original mesh_id.vert_id.
I must be up the wrong tree. This seems like something obvious enough someone would make it :o
adp001 posted Wed, 03 March 2021 at 5:55 AM
If I understand your text correctly (English is not my native language) then you describe pretty much what the script "ImExport.py" does.
The current Poser figure, which consists of individual "Actors", is combined into a Unimesh and exported (morphed and posed) as an OBJ file.
This Unimesh can be edited in Blender with all the tools available there. Afterwards, from Blender, it is saved again as an OBJ file (necessarily under a different name).
The script in Poser takes the two saved meshes (OBJ files saved from Poser and Blender) and creates a difference from them. The result, the difference, is the "Unimesh Morph". The Unimesh Morph is then decomposed and saved into the affected Actors as a Morph.
What did I get wrong?
adp001 posted Wed, 03 March 2021 at 6:05 AM
Maybe it will help me understand if you write down a real example. Something you would like to achieve. Is it about making clothes?
unrealblue posted Wed, 03 March 2021 at 4:45 PM
Ah, then it exists! What you describe is, effectively, what I mean.
It sounds like the script you mention does more or less the same thing I want, all in the Poser side. Assuming it works on a mac, I could use this immediately.
(long description follows)
For me, clothes. But I imagine that it would be anywhere you wanted to create morphs in Blender that cross actor boundaries. You have to have a "uni-mesh" for sculpt in Blender to work (you don't need it for vert editing, so long as you're careful in the selection of verts.)
Example: A simple leotard for La Femme
The mesh is symmetric, which makes it easier since I wrote a script that mirrors all the JCMs on one side to the other
For this one, a lot of weight map work had to be done at the legs as the movement of close fitting cloth mesh is very different than the HR cloth donor mesh when the openings are right at a joint like the thigh/hip)
A manual looked something like this:
In Poser:
In Blender:
In Poser
Obviously scripting on the Poser side makes this easier, even with "uni-mesh-ification" being in Blender.
First, the Poser script can export the entire series of poses necessary for all the JCMs, as a stack of OBJs.
Second, It can rewrite the all the JCMs on import of a stack of OBJs. Either creating new or editing existing. Combined with the mirror JCMs script, it could create the other side's JCMs.
Part of the reason I thought "Blender script" for "uni-mesh-ification" is that this has utility outside of Poser. There are times where I have multiple meshes/objects and I want to sculpt them together, while keeping them as different meshes. I want common vertices "glued" together, for sculpting.
Sorry for the length of that :)
adp001 posted Wed, 03 March 2021 at 6:41 PM
thoennes posted at 6:39PM Wed, 03 March 2021 - #4414206
It sounds like the script you mention does more or less the same thing I want, all in the Poser side. Assuming it works on a mac, I could use this immediately.
Then just try it. I don't have a Mac and I don't use Poser 12. So it up to you to test it :)
adp001 posted Wed, 03 March 2021 at 6:52 PM
thoennes posted at 6:51PM Wed, 03 March 2021 - #4414206
It sounds like the script you mention does more or less the same thing I want, all in the Poser side.
Doing it in Poser has the advantage that it can be used with most modellers. Not only with Blender.
unrealblue posted Sat, 06 March 2021 at 3:14 AM
Tried the script. The Poser menu stuff doesn't work. Commented them out and just called the export and import on their own. They work. The wx dialogs work. I had to change a single thing (python3 map returns map object; wrapped in in a list) Exported fine (Poser12 on MacOS Mojave), imported in Blender (2.91.2). Sculpted. Exported that out, imported, named the morph and it worked fine.
Sweet thanks. I'll sort the Poser menu.
adp001 posted Sat, 06 March 2021 at 8:20 AM
Thanks for testing.
The mapping error can be easily fixed.
if sys.version_info.major > 2:
basestring = str
map = lambda a, b: [a(_b) for _b in b]
I'll update the script accordingly.
For the Script-Button: Don't know how to start searching why it will not work.
I think a simple wxPython Dialog is the best solution for the moment. I'll make one today.
unrealblue posted Sat, 06 March 2021 at 3:28 PM
adp001 posted at 3:11PM Sat, 06 March 2021 - #4414391
Thanks for testing.
The mapping error can be easily fixed.
if sys.version_info.major > 2: basestring = str map = lambda a, b: [a(_b) for _b in b]
I'll update the script accordingly.
For the Script-Button: Don't know how to start searching why it will not work.
I think a simple wxPython Dialog is the best solution for the moment. I'll make one today.
No worries!
I was lazy and just used list() right in the code rather than a lambda function dependent on the version. You're much more meticulous than me :D
unrealblue posted Sat, 06 March 2021 at 7:57 PM
OK, I just modified (slightly) the FullbodyMorphs_wx4.py script.
Again, Poser 12 on MacOS Mojave.
Changes I had to make:
I did not look into wx (yet) to see if maybe the API had changed such that it was splitting those two things up, in previous versions or other platforms.
Those minor changes being made, the script ran without error. So far I've only tested it for simple case: Clothing conformed to figure. Figure posed. Exported. Posed clothing sculpted in Blender 2.91.2. Exported back out. Scales at 1. vertex order maintained. No re-topo happening in sculpt. Imported as a new morph. The FBM works exactly as expected. It correctly implemented the morph, subtracting the "effective morph" the pose causes. This should be super useful for constructing JCMs.
awesome!
This script is going to be an important part of clothing creation workflow using Blender :)
Next, I'm going to try a complete (single) JCM creation workflow using this script.
unrealblue posted Sat, 06 March 2021 at 8:14 PM
weird, it won't let me edit all the mistakes in the last post.
adp001 posted Sun, 07 March 2021 at 11:58 AM
Ok, I made an update with a WX Dialog. This one should work with Mac and with Python 3 (P12 untested, because I don't have it).
Download: Click here
adp001 posted Sun, 07 March 2021 at 12:06 PM
"Scale": Used for im- and export. find out what your modeller needs (blender works fine with 10).
"Silent IM-/Export": Don't ask for filenames. Uses the same file name as last time (if a filename is known already).
"Create Masterbutton" does nothing at the moment.
"Export Groups": Will create groupnames (actor names) in the OBJ file. Blender can use this to create vertexgroups.
"Export UV info": Saves Material und UV-Textures if checked.
"Copy Imagefiles": Creates copies of the texturefiles in the path the exported object resides (so your modeller can find it).
adp001 posted Sun, 07 March 2021 at 12:10 PM
Unzip into a seperate Directory and start WxMain.py
unrealblue posted Sun, 07 March 2021 at 3:05 PM
It works!
Thank you.
I don't have the modules on my PATH or installed anywhere Poser can find them and it took me a second to realize that this needed the other module :)
This is Poser 12 on MacOS Mojave.
Super useful script. Now, if I can just nail down a good JCM workflow :D
adp001 posted Sun, 07 March 2021 at 3:24 PM
adp001 posted Sun, 12 September 2021 at 9:18 AM
Newest version is now here:
https://www.renderosity.com/rr/mod/forumpro/?thread_id=2955142
RAMWorks posted Tue, 28 December 2021 at 1:41 PM
This looks amazing. I really could use something like this now that I'm adopting Blender and work from Poser. I'm on Windows 11 and using Blender 3.X
---Wolff On The Prowl---