im_export.py
1    from __future__ import print_function, division
2    
3    import os
4    import sys
5    import time
6    import wx
7    from shutil import copyfile
8    
9    import numpy as np
10   
11   if sys.version_info.major > 2:
12       basestring = str
13   
14   try:
15       import poser
16   except ImportError:
17       import POSER_FAKE as poser
18   
19   IMEX_CONFIG = globals().setdefault("IMEX_CONFIG", dict())
20   NP_PRECISION = np.float32
21   PRECISION = 6
22   vertex_frmt = " ".join(["{:.%df}" % PRECISION] * 3)
23   tex_vertex_frmt = " ".join(["{:.%df}" % PRECISION] * 2)
24   
25   
26   def set_precision(decimal_places):
27       global vertex_frmt, tex_vertex_frmt
28       vertex_frmt = " ".join(["{:.%df}" % decimal_places] * 3)
29       tex_vertex_frmt = " ".join(["{:.%df}" % decimal_places] * 2)
30   
31   
32 def ErrDialog(err, msg=None): 33 dlg = wx.MessageDialog(None, caption=err, message=msg, style=wx.ICON_ERROR) 34 dlg.ShowModal() 35 dlg.Close() 36 37
38 def get_or_create_morphdial(actor, parametername): 39 assert isinstance(actor, poser.ActorType) 40 p = actor.Parameter(parametername) 41 if not p: 42 actor.SpawnTarget(parametername) 43 p = actor.Parameter(parametername) 44 return p 45 46
47 def collect_geometry(figure, use_world=True): 48 if figure is None: 49 return None 50 51 def np_vertex(v): 52 return np.array((v.X(), v.Y(), v.Z()), NP_PRECISION) 53 54 def used_list(_geom): 55 """ 56 Return a list with 1 at positions of used vertices 57 and 0 at unused vertices. 58 """ 59 sets = _geom.Sets() 60 ar = np.zeros(_geom.NumVertices(), np.int8) 61 for poly in _geom.Polygons(): 62 start = poly.Start() 63 numv = poly.NumVertices() 64 for _idx in sets[start:start + numv]: 65 ar[_idx] = 1 66 return ar 67 68 def crosslist(_used): 69 length = len(_used) 70 cl = np.zeros(length, np.int32) 71 not_empty_idx = 0 72 for _idx in range(length): 73 if _used[_idx] != 0: 74 cl[_idx] = not_empty_idx 75 not_empty_idx += 1 76 return cl 77 78 geom, actorlist, actor_indices = figure.UnimeshInfo() 79 used = used_list(geom) 80 81 if use_world: 82 newverts = np.zeros((geom.NumVertices(), 3), NP_PRECISION) 83 for ac_idx, ac in enumerate(actorlist): 84 ac_worldverts = ac.Geometry().WorldVertices() 85 for i, vertex_idx in enumerate(actor_indices[ac_idx]): 86 newverts[vertex_idx] = np_vertex(ac_worldverts[i]) 87 else: 88 newverts = np.array([[_v.X(), _v.Y(), _v.Z()] 89 for _v in geom.Vertices()], NP_PRECISION) 90 91 return dict(vertices=newverts, 92 geom=geom, 93 actorlist=actorlist, 94 actor_indices=actor_indices, 95 crosslist=crosslist(used), 96 used=used 97 ) 98 99
100 def write_matfile(filename, materials): 101 """ 102 Write out a simple material-file. 103 """ 104 105 def move_image(map2move): 106 fname = os.path.join(IMEX_CONFIG.get("MatPath", ""), os.path.basename(map2move)) 107 if os.path.exists(fname): 108 if os.path.getmtime(map2move) == os.path.getmtime(fname): 109 copyfile(map2move, fname) 110 else: 111 copyfile(map2move, fname) 112 return fname 113 114 try: 115 tmp = open(filename, "w") 116 tmp.close() 117 except IOError: 118 return ErrDialog("File Error.", "Can't create or write to file '{}'.\n" 119 "Make sure directory '{}' exist and is writable.". 120 format(filename, os.path.dirname(filename))) 121 122 with open(filename, "w") as mfh: 123 for mat in materials: # type: poser.MaterialType 124 print("newmtl", mat.Name(), file=mfh) 125 print("Ns", mat.Ns(), file=mfh) 126 print("Ka", "0 0 0", file=mfh) 127 print("Kd", " ".join(map(str, mat.DiffuseColor())), file=mfh) 128 print("Ks", "0 0 0", file=mfh) 129 if mat.TextureMapFileName(): 130 texmap = mat.TextureMapFileName() 131 if IMEX_CONFIG.get("CTRL_MoveTextures", False): 132 texmap = move_image(texmap) 133 print("map_Kd", texmap, file=mfh) 134 if mat.BumpMapFileName(): 135 texmap = mat.BumpMapFileName() 136 if IMEX_CONFIG.get("CTRL_MoveTextures", False): 137 texmap = move_image(texmap) 138 print("map_Bump", texmap, file=mfh) 139 140
141 def read_vertices(filename): 142 """ 143 Read Wavefront obj-file saved to file. Typically a figure exported 144 from Poser and modified with an external modeller (Blender etc). 145 """ 146 147 vertices = list() 148 try: 149 with open(filename, "r") as fh: 150 for line in fh: 151 if not line: 152 break 153 c, _, v = line.strip().partition(" ") 154 if c == "v": 155 vertices.append(map(float, v.split())) 156 157 # Remove following line if vertices are not in one block, 158 # so the whole file is processed. 159 elif c in ("vt", "vn", "f"): 160 break 161 except IndexError: 162 return ErrDialog("Vertex Error.", 163 "Vertices in file '%filename' corrupted.") 164 except IOError: 165 return ErrDialog("File Error.", 166 "File '{}' does not exist or is not accessible.". 167 format(filename)) 168 169 return np.array(vertices, NP_PRECISION) 170 171
172 def do_export(figure, filename=None, scale=1, use_groups=False, use_mat=True, 173 resetmorph_first=None): 174 assert isinstance(figure, poser.FigureType) 175 if filename is None: 176 filename = figure.Name() + ".obj" 177 178 if not os.path.exists(os.path.dirname(filename)): 179 return ErrDialog("Export Error", "Given path\n%s\ndoes not exist." % filename) 180 181 oldvalues = list() 182 if resetmorph_first: 183 # Use this if you want to save the model without 184 # the actual morph (morphname must be given in resetmorph_first). 185 for ac in figure.Actors(): 186 for parm in ac.Parameters(): 187 if parm.Name() == resetmorph_first: 188 oldvalues.append((ac.Name(), parm.Value())) 189 parm.SetValue(0) 190 191 unigeom = collect_geometry(figure=figure, use_world=True) 192 vertices = unigeom.get("vertices") * scale 193 geom = unigeom.get("geom") # type: poser.GeomType 194 vertex_crosslist = unigeom.get("crosslist") 195 used = unigeom.get("used") 196 197 with open(filename, "w") as fh: 198 print("### Date : %s" % time.asctime(), file=fh) 199 print("### Figure : %s" % figure.Name(), file=fh) 200 print("### Vertices: %s" % len(vertices), file=fh) 201 202 if use_mat and geom.Materials(): 203 matfile = filename.rsplit(".", 1)[0] + ".mtl" 204 write_matfile(matfile, geom.Materials()) 205 print("mtllib ./" + os.path.basename(matfile), file=fh) 206 207 print("o %s" % figure.Name(), file=fh) 208 for idx, vertex in enumerate(vertices): 209 if used[idx] != 0: 210 print("v", vertex_frmt.format(*vertex), file=fh) 211 212 if use_mat: 213 for idx, tvert in enumerate(geom.TexVertices()): 214 print("vt {} {}".format(tvert.U(), tvert.V()), file=fh) 215 216 current_groups = list() 217 current_mat = list() 218 219 s_done = False 220 polys = geom.Polygons() 221 tpolys = geom.TexPolygons() 222 sets = geom.Sets() 223 for idx in range(len(sets)): 224 sets[idx] = vertex_crosslist[sets[idx]] 225 tsets = geom.TexSets() 226 227 for index, poly in enumerate(polys): 228 if use_groups: 229 if poly.Groups() != current_groups: 230 current_groups = poly.Groups() 231 print("g", ", ".join(current_groups), file=fh) 232 233 if use_mat: 234 if poly.MaterialName() != current_mat: 235 current_mat = poly.MaterialName() 236 print("usemtl", current_mat, file=fh) 237 238 if not s_done: 239 print("s 1", file=fh) 240 s_done = True 241 242 line = [str(sets[idx + poly.Start()] + 1) for idx in range(poly.NumVertices())] 243 if use_mat: 244 tpoly = tpolys[index] 245 for tidx, v in enumerate((tsets[idx + tpoly.Start()] + 1) for idx in range(tpoly.NumTexVertices())): 246 line[tidx] += "/%d" % v 247 248 print("f", " ".join(map(str, line)), file=fh) 249 250 if resetmorph_first: 251 for ac_name, value in oldvalues: 252 figure.Actor(ac_name).Parameter(resetmorph_first).SetValue(value) 253 254 return filename 255 256
257 def do_import(figure, new_filename, old_filename, morphname, 258 add_morphdata=False, scale=1, PRECISION=6): 259 assert isinstance(figure, poser.FigureType) 260 261 verts_new = read_vertices(new_filename) 262 verts_old = read_vertices(old_filename) 263 264 if len(verts_old) != len(verts_new): 265 ErrDialog("Vertices mismatch.", "Old number of vertices: {}." 266 "New number of vertices: {}.". 267 format(len(verts_old), len(verts_new))) 268 return 269 270 vertices, geom, actorlist, actor_indices, \ 271 verts_crosslist, used = collect_geometry(figure, use_world=False).values() 272 273 # Overwrite vertices with the diff of loaded files. 274 # The original vertices are of no use here. 275 vertices = (verts_new - verts_old) / int(scale) 276 del verts_new 277 del verts_old 278 279 body = figure.RootActor() 280 masterdial = body.Parameter(morphname) 281 if masterdial is None: 282 body.CreateValueParameter(morphname) 283 masterdial = body.Parameter(morphname) 284 if masterdial is None: 285 return ErrDialog("Morph Error.", "Can't find or create morph in body actor.") 286 287 for actor_idx, actor in enumerate(actorlist): 288 morph = list() 289 for i, v_idx in enumerate(actor_indices[actor_idx]): 290 x, y, z = map(lambda a: round(a, PRECISION), 291 vertices[verts_crosslist[v_idx]]) 292 if x != 0 or y != 0 or z != 0: 293 morph.append((i, x, y, z)) 294 295 if len(morph) == 0: 296 continue 297 298 morphparm = get_or_create_morphdial(actor, morphname) # type: poser.ParmType 299 if morphparm is None: 300 return ErrDialog("Morph Error", "Can't create Morphtarget.") 301 if not morphparm.IsMorphTarget(): 302 return ErrDialog("Morph error.", "Morph Parametername ('%s')\n" 303 "already exist but is not a morph." 304 % morphname) 305 306 if add_morphdata: 307 # Leave the old morph as it is and add the new data to it. 308 # Use this if the figure is again exported (and reloaded in 309 # the modeller) so that the diff between the old and new morph 310 # is just your last change made in the modeller; the morph 311 # in the figure is not just overwritten. 312 for i, x, y, z in morph: 313 xx, yy, zz = morphparm.MorphTargetDelta(i) 314 morphparm.SetMorphTargetDelta(i, x + xx, y + yy, z + zz) 315 else: 316 for i in range(actor.Geometry().NumVertices()): 317 morphparm.SetMorphTargetDelta(i, 0, 0, 0) 318 for i, x, y, z in morph: 319 morphparm.SetMorphTargetDelta(i, x, y, z) 320 321 while morphparm.NumValueOperations(): 322 morphparm.DeleteValueOperation(0) 323 324 morphparm.AddValueOperation(poser.kValueOpTypeCodeKEY, masterdial) 325 vop = morphparm.ValueOperations()[0] 326 vop.InsertKey(0, 0) 327 vop.InsertKey(1, 1) 328 329 masterdial.SetMinValue(0) 330 masterdial.SetMaxValue(1) 331 masterdial.SetForceLimits(1) 332 return morphname 333