Forum: Poser Python Scripting


Subject: Posed export/Import for full body morph

adp001 opened this issue on Dec 22, 2019 ยท 53 posts


adp001 posted Sun, 22 December 2019 at 2:34 PM

from __future__ import print_function

import numpy as NP
import os
import sys
import time

try:
    import poser
except ImportError:
    raise RuntimeError("Script must run in Poser.")

SCENE = poser.Scene()

SCALEFACTOR = 100  # Change this to the needs for your modeller.

USE_GROUPS = False  # export groupnames (False for full-body-morphs)
EXPORT_UV = True  # not needed for morphs, but nicer to model with textures ;)
USE_MATERIAL = True  # export materialnames (should be True if UV is exported)

# Directory to store OBJ files.
# Default: Directory where this script lives + "MORPHS".
_scriptdir = os.path.dirname(sys.argv[0])
_dirname = "MORPHS"
BASE_DIR = os.path.abspath(os.path.join(_scriptdir, _dirname))

if not os.path.isdir(BASE_DIR):
    try:
        os.makedirs(BASE_DIR)
    except IOError:
        raise RuntimeError("Cant't create directory '{}'.n"
                           "Please change variable BASE_DIR in line 25.".format(BASE_DIR))

# Figure to work with.
FIGURE = SCENE.CurrentFigure()

# Default filename is "Fullbody-.obj" (Poser output)
# and "Fullbody-_mod.obj" (Poser input).
#       //Fullbody-LaFemme_1R1.obj
#       //Fullbody-LaFemme_1R1_mod.obj
OUT_FILE = os.path.join(BASE_DIR, "Fullbody-{}.obj".format(FIGURE.Name()))
IN_FILE = os.path.join(BASE_DIR, "Fullbody-{}_mod.obj".format(FIGURE.Name()))

# Forced floatingpoint precision. Mainly to help avoiding floatingpoint
# errors while reading files from external modelers.
PRECISION = 8
NP_PRECISION = NP.float32
# ---------------- end of editable constants

PATH = os.path.dirname(__file__)  # default path for save (path from this scripts)

# helper to get one numpy vertex from Poser
np_vertex = lambda v: NP.array((v.X(), v.Y(), v.Z()), NP_PRECISION)
np_tvertex = lambda v: NP.array((v.U(), v.V()), NP_PRECISION)


def collect_geometry(figure=FIGURE, scalefactor=SCALEFACTOR):
    geom, actors, actor_indices = figure.UnimeshInfo()
    verts = NP.zeros((geom.NumVertices(), 3), NP_PRECISION)
    tverts = NP.array([np_tvertex(v) for v in geom.TexVertices()], NP_PRECISION)
    sets = NP.array(geom.Sets(), NP.int32)
    tsets = NP.array(geom.TexSets(), NP.int32)

    for actor_idx, actor in enumerate(actors):
        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 * scalefactor,
                sets=sets,
                polygons=geom.Polygons,
                tex_vertices=tverts,
                tsets=tsets,
                tex_polygons=geom.TexPolygons,
                actorlist=actors,
                actor_indices=actor_indices,
                materials=geom.Materials
                )


def read_verts_from_file(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()
    idx = 0
    with open(filename, "r") as fh:
        for line in fh:
            if not line:
                break
            c, _, v = line.strip().partition(" ")
            if c == "v":
                vert = map(float, v.split())
                if len(vert) == 3:
                    vertices.append(vert)
                else:
                    raise RuntimeError("Problem at vertex # {}: {}".
                                       format(idx, line))
                idx += 1
            # 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 write_matfile(filename, materials):
    """
    Write out a simple material-file.
    """
    try:
        open(filename, "w")
    except IOError:
        raise RuntimeError("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 export(filename,
           vertices=None, sets=None, polygons=None,
           tex_vertices=None, tsets=None, tex_polygons=None,
           materials=None, **kwargs):
    """
    Export Wavefront obj-file to file.
    """
    try:
        open(filename, "w")
    except IOError:
        raise RuntimeError("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 fh:
        print("### Date    : %s" % time.asctime(), file=fh)
        print("### Figure  : %s" % FIGURE.Name(), file=fh)
        print("### Vertices: %s" % len(vertices), file=fh)

        if USE_MATERIAL and materials:
            matfile = filename.rsplit(".", 1)[0] + ".mtl"
            write_matfile(matfile, materials())
            print("mtllib ./" + os.path.basename(matfile), file=fh)

        for vertex in vertices:
            print("v {} {} {}".format(*vertex), file=fh)

        if EXPORT_UV or USE_MATERIAL:
            for uv in tex_vertices:
                print("vt {} {}".format(*uv), file=fh)

        current_groups = list()
        current_mat = list()
        if not USE_GROUPS:
            print("g", FIGURE.Name(), file=fh)

        polys = polygons()
        tpolys = tex_polygons()

        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 EXPORT_UV:
                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)

    del polys
    del tpolys
    return True


def fullbodymorph_import(figure, morphname, old_filename, new_filename,
                         actorlist, actor_indices,
                         **kwargs):
    verts_new = read_verts_from_file(new_filename)
    verts_old = read_verts_from_file(old_filename)
    if len(verts_old) != len(verts_new):
        raise RuntimeError("!!!Failed!!!n"
                           "Old number of vertices: {}n"
                           "New number of vertices: {}".
                           format(len(verts_old), len(verts_new)))

    vertices = (verts_new - verts_old) / SCALEFACTOR
    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:
        raise RuntimeError("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)

        assert morphparm is not None, "Could not create Morphtarget"
        assert morphparm.IsMorphTarget(), "Parametername ('%s') " + 
                                          "already exist but is not a morph" 
                                          % morphname
        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)