from __future__ import print_function, division
import os
import sys
import time
from shutil import copyfile
import numpy as np
import wx
if sys.version_info.major > 2:
basestring = str
map = lambda a, b: [a(_b) for _b in b]
try:
import poser
except ImportError:
import POSER_FAKE as poser
IMEX_CONFIG = globals().setdefault("IMEX_CONFIG", dict())
NP_PRECISION = np.float32
PRECISION = 6
vertex_frmt = " ".join(["{:.%df}" % PRECISION] * 3)
tex_vertex_frmt = " ".join(["{:.%df}" % PRECISION] * 2)
def set_precision(decimal_places):
global vertex_frmt, tex_vertex_frmt
vertex_frmt = " ".join(["{:.%df}" % decimal_places] * 3)
tex_vertex_frmt = " ".join(["{:.%df}" % decimal_places] * 2)
def ErrDialog(err, msg=None):
dlg = wx.MessageDialog(None, caption=err, message=msg, style=wx.ICON_ERROR)
dlg.ShowModal()
dlg.Close()
return False
def get_or_create_morphdial(actor, parametername):
assert isinstance(actor, poser.ActorType)
p = actor.Parameter(parametername)
if not p:
actor.SpawnTarget(parametername)
p = actor.Parameter(parametername)
return p
def collect_geometry(figure, use_world=True):
if figure is None:
return None
def np_vertex(v):
return np.array((v.X(), v.Y(), v.Z()), NP_PRECISION)
def used_list(_geom):
"""
Return a list with 1 at positions of used vertices
and 0 at unused vertices.
"""
sets = _geom.Sets()
ar = np.zeros(_geom.NumVertices(), np.int8)
for poly in _geom.Polygons():
start = poly.Start()
numv = poly.NumVertices()
for _idx in sets[start:start + numv]:
ar[_idx] = 1
return ar
def crosslist(_used):
length = len(_used)
cl = np.zeros(length, np.int32)
not_empty_idx = 0
for _idx in range(length):
if _used[_idx] != 0:
cl[_idx] = not_empty_idx
not_empty_idx += 1
return cl
geom, actorlist, actor_indices = figure.UnimeshInfo()
used = used_list(geom)
if use_world:
newverts = np.zeros((geom.NumVertices(), 3), NP_PRECISION)
for ac_idx, ac in enumerate(actorlist):
ac_worldverts = ac.Geometry().WorldVertices()
for i, vertex_idx in enumerate(actor_indices[ac_idx]):
newverts[vertex_idx] = np_vertex(ac_worldverts[i])
else:
newverts = np.array([[_v.X(), _v.Y(), _v.Z()]
for _v in geom.Vertices()], NP_PRECISION)
return dict(vertices=newverts,
geom=geom,
actorlist=actorlist,
actor_indices=actor_indices,
crosslist=crosslist(used),
used=used
)
def write_matfile(filename, materials):
"""
Write out a simple material-file.
"""
def move_image(map2move, imgpath="./"):
fname = os.path.join(imgpath, os.path.basename(map2move))
if os.path.exists(fname):
if os.path.getmtime(map2move) == os.path.getmtime(fname):
copyfile(map2move, fname)
else:
copyfile(map2move, fname)
return fname
try:
tmp = open(filename, "w")
tmp.close()
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:
move_tex = IMEX_CONFIG.get("CTRL_MoveTextures", False)
imgpath = os.path.dirname(filename)
for mat in materials: # type: poser.MaterialType
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.TransparencyMax():
print("D", mat.TransparencyMax(), file=mfh)
if mat.TransparencyMapFileName():
texmap = mat.TextureMapFileName()
if move_tex:
texmap = os.path.basename(move_image(texmap, imgpath))
print("map_D", texmap, file=mfh)
if mat.TextureMapFileName():
texmap = mat.TextureMapFileName()
if move_tex:
texmap = os.path.basename(move_image(texmap, imgpath))
print("map_Kd", texmap, file=mfh)
if mat.BumpMapFileName():
texmap = mat.BumpMapFileName()
if move_tex:
texmap = os.path.basename(move_image(texmap, imgpath))
print("map_Bump -bm %s" % (mat.BumpStrength() / 10.0), texmap, file=mfh)
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.",
"Vertices 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, filename=None, scale=1, use_groups=False, use_mat=True,
resetmorph_first=None):
assert isinstance(figure, poser.FigureType)
if filename is None:
filename = figure.Name() + ".obj"
if not os.path.exists(os.path.dirname(filename)):
return ErrDialog("Export Error", "Given path\n%s\ndoes not exist." % filename)
oldvalues = list()
if resetmorph_first:
# Use this if you want to save the model without
# the actual morph (morphname must be given in resetmorph_first).
for ac in figure.Actors():
for parm in ac.Parameters():
if parm.Name() == resetmorph_first:
oldvalues.append((ac.Name(), parm.Value()))
parm.SetValue(0)
unigeom = collect_geometry(figure=figure, use_world=True)
vertices = unigeom.get("vertices") * scale
geom = unigeom.get("geom") # type: poser.GeomType
vertex_crosslist = unigeom.get("crosslist")
used = unigeom.get("used")
with open(filename, "w") as fh:
print("### Date : %s" % time.asctime(), file=fh)
print("### Figure : %s" % figure.Name(), file=fh)
print("### Vertices: %s" % len(vertices), file=fh)
if use_mat and geom.Materials():
matfile = filename.rsplit(".", 1)[0] + ".mtl"
write_matfile(matfile, geom.Materials())
print("mtllib ./" + os.path.basename(matfile), file=fh)
print("o %s" % figure.Name(), file=fh)
for idx, vertex in enumerate(vertices):
if used[idx] != 0:
print("v", vertex_frmt.format(*vertex), file=fh)
if use_mat:
for idx, tvert in enumerate(geom.TexVertices()):
print("vt {} {}".format(tvert.U(), tvert.V()), file=fh)
current_groups = list()
current_mat = list()
s_done = False
polys = geom.Polygons()
tpolys = geom.TexPolygons()
sets = geom.Sets()
for idx in range(len(sets)):
sets[idx] = vertex_crosslist[sets[idx]]
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_mat:
if poly.MaterialName() != current_mat:
current_mat = poly.MaterialName()
print("usemtl", current_mat, file=fh)
if not s_done:
print("s 1", file=fh)
s_done = True
line = [str(sets[idx + poly.Start()] + 1) for idx in range(poly.NumVertices())]
if use_mat:
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)
if resetmorph_first:
for ac_name, value in oldvalues:
figure.Actor(ac_name).Parameter(resetmorph_first).SetValue(value)
return filename
def do_import(figure, new_filename, old_filename, morphname,
add_morphdata=False, force_center=False, scale=1, PRECISION=6):
assert isinstance(figure, poser.FigureType)
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, geom, actorlist, actor_indices, \
verts_crosslist, used = collect_geometry(figure, use_world=False).values()
# Overwrite vertices with the diff of loaded files.
# The original vertices are of no use here.
vertices = (verts_new - verts_old) / int(scale)
del verts_new
del verts_old
body = figure.RootActor()
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[verts_crosslist[v_idx]])
if x != 0 or y != 0 or z != 0:
morph.append((i, x, y, z))
if len(morph) == 0:
continue
morphparm = get_or_create_morphdial(actor, morphname) # type: poser.ParmType
zero_idx = set()
if force_center:
for idx, v in enumerate(actor.Geometry().Vertices()):
if v.X() == 0:
zero_idx.add(idx)
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)
if add_morphdata:
# Leave the old morph as it is and add the new data to it.
# Use this if the figure is again exported (and reloaded in
# the modeller) so that the diff between the old and new morph
# is just your last change made in the modeller; the morph
# in the figure is not just overwritten.
for i, x, y, z in morph:
xx, yy, zz = morphparm.MorphTargetDelta(i)
if force_center and i in zero_idx:
xx = 0
morphparm.SetMorphTargetDelta(i, x + xx, y + yy, z + zz)
else:
for i in range(actor.Geometry().NumVertices()):
morphparm.SetMorphTargetDelta(i, 0, 0, 0)
for i, x, y, z in morph:
if force_center and i in zero_idx:
x = 0
morphparm.SetMorphTargetDelta(i, x, y, z)
if force_center:
fix_morph_at_zero_x(actor, morphparm)
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(0)
masterdial.SetMaxValue(1)
masterdial.SetForceLimits(1)
masterdial.SetValue(1)
return morphname
def fix_morph_at_zero_x(poser_obj, morph2fix=None):
"""
Replace morph X movement where original vertices are at zero position.
"""
def fix_actor(ac, morphname=morph2fix):
def fix_morph(morph, indices):
for vert_idx in indices:
x, y, z = morph.MorphTargetDelta(vert_idx)
if x != 0:
morph.SetMorphTargetDelta(vert_idx, 0, y, z)
if ac.IsBodyPart() and hasattr(ac, "Geometry") and ac.Geometry() is not None:
vert_indices = []
for idx, v in enumerate(ac.Geometry().Vertices()):
if v.X() == 0:
vert_indices.append(idx)
if isinstance(morphname, basestring):
fix_morph(ac.Parameter(morphname), vert_indices)
elif isinstance(morphname, poser.ParmType):
fix_morph(morphname, vert_indices)
elif morphname is None:
for morph in (p for p in ac.Parameters() if p.IsMorphTarget()): # type: poser.ParmType
fix_morph(morph, vert_indices)
if isinstance(poser_obj, poser.FigureType):
for ac in poser_obj.Actors(): # type: poser.ActorType
fix_actor(ac)
elif isinstance(poser_obj, poser.ActorType):
fix_actor(poser_obj)