source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/split_obj.py@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 12.4 KB
Line 
1"""Split single OBJ model into mutliple OBJ files by materials
2
3-------------------------------------
4How to use
5-------------------------------------
6
7python split_obj.py -i infile.obj -o outfile
8
9Will generate:
10
11outfile_000.obj
12outfile_001.obj
13
14...
15
16outfile_XXX.obj
17
18-------------------------------------
19Parser based on format description
20-------------------------------------
21
22 http://en.wikipedia.org/wiki/Obj
23
24------
25Author
26------
27AlteredQualia http://alteredqualia.com
28
29"""
30
31import fileinput
32import operator
33import random
34import os.path
35import getopt
36import sys
37import struct
38import math
39import glob
40
41# #####################################################
42# Configuration
43# #####################################################
44TRUNCATE = False
45SCALE = 1.0
46
47
48# #####################################################
49# Templates
50# #####################################################
51TEMPLATE_OBJ = u"""\
52################################
53# OBJ generated by split_obj.py
54################################
55# Faces: %(nfaces)d
56# Vertices: %(nvertices)d
57# Normals: %(nnormals)d
58# UVs: %(nuvs)d
59################################
60
61# vertices
62
63%(vertices)s
64
65# normals
66
67%(normals)s
68
69# uvs
70
71%(uvs)s
72
73# faces
74
75%(faces)s
76"""
77
78TEMPLATE_VERTEX = "v %f %f %f"
79TEMPLATE_VERTEX_TRUNCATE = "v %d %d %d"
80
81TEMPLATE_NORMAL = "vn %.5g %.5g %.5g"
82TEMPLATE_UV = "vt %.5g %.5g"
83
84TEMPLATE_FACE3_V = "f %d %d %d"
85TEMPLATE_FACE4_V = "f %d %d %d %d"
86
87TEMPLATE_FACE3_VT = "f %d/%d %d/%d %d/%d"
88TEMPLATE_FACE4_VT = "f %d/%d %d/%d %d/%d %d/%d"
89
90TEMPLATE_FACE3_VN = "f %d//%d %d//%d %d//%d"
91TEMPLATE_FACE4_VN = "f %d//%d %d//%d %d//%d %d//%d"
92
93TEMPLATE_FACE3_VTN = "f %d/%d/%d %d/%d/%d %d/%d/%d"
94TEMPLATE_FACE4_VTN = "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d"
95
96
97# #####################################################
98# Utils
99# #####################################################
100def file_exists(filename):
101 """Return true if file exists and is accessible for reading.
102
103 Should be safer than just testing for existence due to links and
104 permissions magic on Unix filesystems.
105
106 @rtype: boolean
107 """
108
109 try:
110 f = open(filename, 'r')
111 f.close()
112 return True
113 except IOError:
114 return False
115
116# #####################################################
117# OBJ parser
118# #####################################################
119def parse_vertex(text):
120 """Parse text chunk specifying single vertex.
121
122 Possible formats:
123 vertex index
124 vertex index / texture index
125 vertex index / texture index / normal index
126 vertex index / / normal index
127 """
128
129 v = 0
130 t = 0
131 n = 0
132
133 chunks = text.split("/")
134
135 v = int(chunks[0])
136 if len(chunks) > 1:
137 if chunks[1]:
138 t = int(chunks[1])
139 if len(chunks) > 2:
140 if chunks[2]:
141 n = int(chunks[2])
142
143 return { 'v': v, 't': t, 'n': n }
144
145def parse_obj(fname):
146 """Parse OBJ file.
147 """
148
149 vertices = []
150 normals = []
151 uvs = []
152
153 faces = []
154
155 materials = {}
156 mcounter = 0
157 mcurrent = 0
158
159 mtllib = ""
160
161 # current face state
162 group = 0
163 object = 0
164 smooth = 0
165
166 for line in fileinput.input(fname):
167 chunks = line.split()
168 if len(chunks) > 0:
169
170 # Vertices as (x,y,z) coordinates
171 # v 0.123 0.234 0.345
172 if chunks[0] == "v" and len(chunks) == 4:
173 x = float(chunks[1])
174 y = float(chunks[2])
175 z = float(chunks[3])
176 vertices.append([x,y,z])
177
178 # Normals in (x,y,z) form; normals might not be unit
179 # vn 0.707 0.000 0.707
180 if chunks[0] == "vn" and len(chunks) == 4:
181 x = float(chunks[1])
182 y = float(chunks[2])
183 z = float(chunks[3])
184 normals.append([x,y,z])
185
186 # Texture coordinates in (u,v[,w]) coordinates, w is optional
187 # vt 0.500 -1.352 [0.234]
188 if chunks[0] == "vt" and len(chunks) >= 3:
189 u = float(chunks[1])
190 v = float(chunks[2])
191 w = 0
192 if len(chunks)>3:
193 w = float(chunks[3])
194 uvs.append([u,v,w])
195
196 # Face
197 if chunks[0] == "f" and len(chunks) >= 4:
198 vertex_index = []
199 uv_index = []
200 normal_index = []
201
202 for v in chunks[1:]:
203 vertex = parse_vertex(v)
204 if vertex['v']:
205 vertex_index.append(vertex['v'])
206 if vertex['t']:
207 uv_index.append(vertex['t'])
208 if vertex['n']:
209 normal_index.append(vertex['n'])
210
211 faces.append({
212 'vertex':vertex_index,
213 'uv':uv_index,
214 'normal':normal_index,
215
216 'material':mcurrent,
217 'group':group,
218 'object':object,
219 'smooth':smooth,
220 })
221
222 # Group
223 if chunks[0] == "g" and len(chunks) == 2:
224 group = chunks[1]
225
226 # Object
227 if chunks[0] == "o" and len(chunks) == 2:
228 object = chunks[1]
229
230 # Materials definition
231 if chunks[0] == "mtllib" and len(chunks) == 2:
232 mtllib = chunks[1]
233
234 # Material
235 if chunks[0] == "usemtl" and len(chunks) == 2:
236 material = chunks[1]
237 if not material in materials:
238 mcurrent = mcounter
239 materials[material] = mcounter
240 mcounter += 1
241 else:
242 mcurrent = materials[material]
243
244 # Smooth shading
245 if chunks[0] == "s" and len(chunks) == 2:
246 smooth = chunks[1]
247
248 return faces, vertices, uvs, normals, materials, mtllib
249
250# #############################################################################
251# API - Breaker
252# #############################################################################
253def break_obj(infile, outfile):
254 """Break infile.obj to outfile.obj
255 """
256
257 if not file_exists(infile):
258 print "Couldn't find [%s]" % infile
259 return
260
261 faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
262
263 # sort faces by materials
264
265 chunks = {}
266
267 for face in faces:
268 material = face["material"]
269 if not material in chunks:
270 chunks[material] = {"faces": [], "vertices": set(), "normals": set(), "uvs": set()}
271
272 chunks[material]["faces"].append(face)
273
274 # extract unique vertex / normal / uv indices used per chunk
275
276 for material in chunks:
277 chunk = chunks[material]
278 for face in chunk["faces"]:
279 for i in face["vertex"]:
280 chunk["vertices"].add(i)
281
282 for i in face["normal"]:
283 chunk["normals"].add(i)
284
285 for i in face["uv"]:
286 chunk["uvs"].add(i)
287
288 # generate new OBJs
289
290 for mi, material in enumerate(chunks):
291 chunk = chunks[material]
292
293 # generate separate vertex / normal / uv index lists for each chunk
294 # (including mapping from original to new indices)
295
296 # get well defined order
297
298 new_vertices = list(chunk["vertices"])
299 new_normals = list(chunk["normals"])
300 new_uvs = list(chunk["uvs"])
301
302 # map original => new indices
303
304 vmap = {}
305 for i, v in enumerate(new_vertices):
306 vmap[v] = i + 1
307
308 nmap = {}
309 for i, n in enumerate(new_normals):
310 nmap[n] = i + 1
311
312 tmap = {}
313 for i, t in enumerate(new_uvs):
314 tmap[t] = i + 1
315
316
317 # vertices
318
319 pieces = []
320 for i in new_vertices:
321 vertex = vertices[i-1]
322 txt = TEMPLATE_VERTEX % (vertex[0], vertex[1], vertex[2])
323 pieces.append(txt)
324
325 str_vertices = "\n".join(pieces)
326
327 # normals
328
329 pieces = []
330 for i in new_normals:
331 normal = normals[i-1]
332 txt = TEMPLATE_NORMAL % (normal[0], normal[1], normal[2])
333 pieces.append(txt)
334
335 str_normals = "\n".join(pieces)
336
337 # uvs
338
339 pieces = []
340 for i in new_uvs:
341 uv = uvs[i-1]
342 txt = TEMPLATE_UV % (uv[0], uv[1])
343 pieces.append(txt)
344
345 str_uvs = "\n".join(pieces)
346
347 # faces
348
349 pieces = []
350
351 for face in chunk["faces"]:
352
353 txt = ""
354
355 fv = face["vertex"]
356 fn = face["normal"]
357 ft = face["uv"]
358
359 if len(fv) == 3:
360
361 va = vmap[fv[0]]
362 vb = vmap[fv[1]]
363 vc = vmap[fv[2]]
364
365 if len(fn) == 3 and len(ft) == 3:
366 na = nmap[fn[0]]
367 nb = nmap[fn[1]]
368 nc = nmap[fn[2]]
369
370 ta = tmap[ft[0]]
371 tb = tmap[ft[1]]
372 tc = tmap[ft[2]]
373
374 txt = TEMPLATE_FACE3_VTN % (va, ta, na, vb, tb, nb, vc, tc, nc)
375
376 elif len(fn) == 3:
377 na = nmap[fn[0]]
378 nb = nmap[fn[1]]
379 nc = nmap[fn[2]]
380
381 txt = TEMPLATE_FACE3_VN % (va, na, vb, nb, vc, nc)
382
383 elif len(ft) == 3:
384 ta = tmap[ft[0]]
385 tb = tmap[ft[1]]
386 tc = tmap[ft[2]]
387
388 txt = TEMPLATE_FACE3_VT % (va, ta, vb, tb, vc, tc)
389
390 else:
391 txt = TEMPLATE_FACE3_V % (va, vb, vc)
392
393 elif len(fv) == 4:
394
395 va = vmap[fv[0]]
396 vb = vmap[fv[1]]
397 vc = vmap[fv[2]]
398 vd = vmap[fv[3]]
399
400 if len(fn) == 4 and len(ft) == 4:
401 na = nmap[fn[0]]
402 nb = nmap[fn[1]]
403 nc = nmap[fn[2]]
404 nd = nmap[fn[3]]
405
406 ta = tmap[ft[0]]
407 tb = tmap[ft[1]]
408 tc = tmap[ft[2]]
409 td = tmap[ft[3]]
410
411 txt = TEMPLATE_FACE4_VTN % (va, ta, na, vb, tb, nb, vc, tc, nc, vd, td, nd)
412
413 elif len(fn) == 4:
414 na = nmap[fn[0]]
415 nb = nmap[fn[1]]
416 nc = nmap[fn[2]]
417 nd = nmap[fn[3]]
418
419 txt = TEMPLATE_FACE4_VN % (va, na, vb, nb, vc, nc, vd, nd)
420
421 elif len(ft) == 4:
422 ta = tmap[ft[0]]
423 tb = tmap[ft[1]]
424 tc = tmap[ft[2]]
425 td = tmap[ft[3]]
426
427 txt = TEMPLATE_FACE4_VT % (va, ta, vb, tb, vc, tc, vd, td)
428
429 else:
430 txt = TEMPLATE_FACE4_V % (va, vb, vc, vd)
431
432 pieces.append(txt)
433
434
435 str_faces = "\n".join(pieces)
436
437 # generate OBJ string
438
439 content = TEMPLATE_OBJ % {
440 "nfaces" : len(chunk["faces"]),
441 "nvertices" : len(new_vertices),
442 "nnormals" : len(new_normals),
443 "nuvs" : len(new_uvs),
444
445 "vertices" : str_vertices,
446 "normals" : str_normals,
447 "uvs" : str_uvs,
448 "faces" : str_faces
449 }
450
451 # write OBJ file
452
453 outname = "%s_%03d.obj" % (outfile, mi)
454
455 f = open(outname, "w")
456 f.write(content)
457 f.close()
458
459
460# #############################################################################
461# Helpers
462# #############################################################################
463def usage():
464 print "Usage: %s -i filename.obj -o prefix" % os.path.basename(sys.argv[0])
465
466# #####################################################
467# Main
468# #####################################################
469if __name__ == "__main__":
470
471 # get parameters from the command line
472
473 try:
474 opts, args = getopt.getopt(sys.argv[1:], "hi:o:x:", ["help", "input=", "output=", "truncatescale="])
475
476 except getopt.GetoptError:
477 usage()
478 sys.exit(2)
479
480 infile = outfile = ""
481
482 for o, a in opts:
483 if o in ("-h", "--help"):
484 usage()
485 sys.exit()
486
487 elif o in ("-i", "--input"):
488 infile = a
489
490 elif o in ("-o", "--output"):
491 outfile = a
492
493 elif o in ("-x", "--truncatescale"):
494 TRUNCATE = True
495 SCALE = float(a)
496
497 if infile == "" or outfile == "":
498 usage()
499 sys.exit(2)
500
501 print "Splitting [%s] into [%s_XXX.obj] ..." % (infile, outfile)
502
503 break_obj(infile, outfile)
504
Note: See TracBrowser for help on using the repository browser.