Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / ply_import.py
1 #!BPY
2
3 """
4 Name: 'Stanford PLY (*.ply)...'
5 Blender: 248
6 Group: 'Import'
7 Tip: 'Import a Stanford PLY file'
8 """
9
10 __author__ = 'Bruce Merry'
11 __version__ = '0.93'
12 __bpydoc__ = """\
13 This script imports Stanford PLY files into Blender. It supports per-vertex
14 normals, and per-face colours and texture coordinates.
15
16 Usage:
17
18 Run this script from "File->Import" and select the desired PLY file.
19 """
20
21 # Copyright (C) 2004, 2005: Bruce Merry, bmerry@cs.uct.ac.za
22 #
23 # This program is free software; you can redistribute it and/or
24 # modify it under the terms of the GNU General Public License
25 # as published by the Free Software Foundation; either version 2
26 # of the License, or (at your option) any later version.
27 #
28 # This program is distributed in the hope that it will be useful,
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31 # GNU General Public License for more details.
32 #
33 # You should have received a copy of the GNU General Public License
34 # along with this program; if not, write to the Free Software Foundation,
35 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
36
37
38 # 20th Oct 2008, 0.93 - Updated by Campbell Barton AKA ideasman42, use Mesh rather then NMesh, dont import normals, vcolors work again.
39 # Updated by Campbell Barton AKA Ideasman42, 10% faster code.
40
41 # Portions of this code are taken from mod_meshtools.py in Blender
42 # 2.32.
43
44 import Blender
45 try:
46         import re, struct
47 except:
48         struct= None
49
50 class element_spec(object):
51         __slots__ = 'name', 'count', 'properties'
52         def __init__(self, name, count):
53                 self.name = name
54                 self.count = count
55                 self.properties = []
56
57         def load(self, format, stream):
58                 if format == 'ascii':
59                         stream = re.split('\s+', stream.readline())
60                 return map(lambda x: x.load(format, stream), self.properties)
61
62         def index(self, name):
63                 for i, p in enumerate(self.properties):
64                         if p.name == name: return i
65                 return -1
66
67 class property_spec(object):
68         __slots__ = 'name', 'list_type', 'numeric_type'
69         def __init__(self, name, list_type, numeric_type):
70                 self.name = name
71                 self.list_type = list_type
72                 self.numeric_type = numeric_type
73
74         def read_format(self, format, count, num_type, stream):
75                 if format == 'ascii':
76                         if (num_type == 's'):
77                                 ans = []
78                                 for i in xrange(count):
79                                         s = stream[i]
80                                         if len(s) < 2 or s[0] != '"' or s[-1] != '"':
81                                                 print 'Invalid string', s
82                                                 print 'Note: ply_import.py does not handle whitespace in strings'
83                                                 return None
84                                         ans.append(s[1:-1])
85                                 stream[:count] = []
86                                 return ans
87                         if (num_type == 'f' or num_type == 'd'):
88                                 mapper = float
89                         else:
90                                 mapper = int
91                         ans = map(lambda x: mapper(x), stream[:count])
92                         stream[:count] = []
93                         return ans
94                 else:
95                         if (num_type == 's'):
96                                 ans = []
97                                 for i in xrange(count):
98                                         fmt = format + 'i'
99                                         data = stream.read(struct.calcsize(fmt))
100                                         length = struct.unpack(fmt, data)[0]
101                                         fmt = '%s%is' % (format, length)
102                                         data = stream.read(struct.calcsize(fmt))
103                                         s = struct.unpack(fmt, data)[0]
104                                         ans.append(s[:-1]) # strip the NULL
105                                 return ans
106                         else:
107                                 fmt = '%s%i%s' % (format, count, num_type)
108                                 data = stream.read(struct.calcsize(fmt));
109                                 return struct.unpack(fmt, data)
110
111         def load(self, format, stream):
112                 if (self.list_type != None):
113                         count = int(self.read_format(format, 1, self.list_type, stream)[0])
114                         return self.read_format(format, count, self.numeric_type, stream)
115                 else:
116                         return self.read_format(format, 1, self.numeric_type, stream)[0]
117
118 class object_spec(object):
119         __slots__ = 'specs'
120         'A list of element_specs'
121         def __init__(self):
122                 self.specs = []
123         
124         def load(self, format, stream):
125                 return dict([(i.name,[i.load(format, stream) for j in xrange(i.count) ]) for i in self.specs])
126                 
127                 '''
128                 # Longhand for above LC
129                 answer = {}
130                 for i in self.specs:
131                         answer[i.name] = []
132                         for j in xrange(i.count):
133                                 if not j % 100 and meshtools.show_progress:
134                                         Blender.Window.DrawProgressBar(float(j) / i.count, 'Loading ' + i.name)
135                                 answer[i.name].append(i.load(format, stream))
136                 return answer
137                         '''
138                 
139
140 def read(filename):
141         format = ''
142         version = '1.0'
143         format_specs = {'binary_little_endian': '<',
144                         'binary_big_endian': '>',
145                         'ascii': 'ascii'}
146         type_specs = {'char': 'b',
147                       'uchar': 'B',
148                       'int8': 'b',
149                       'uint8': 'B',
150                       'int16': 'h',
151                       'uint16': 'H',
152                       'ushort': 'H',
153                       'int': 'i',
154                       'int32': 'i',
155                       'uint': 'I',
156                       'uint32': 'I',
157                       'float': 'f',
158                       'float32': 'f',
159                       'float64': 'd',
160                       'double': 'd',
161                       'string': 's'}
162         obj_spec = object_spec()
163
164         try:
165                 file = open(filename, 'rU') # Only for parsing the header, not binary data
166                 signature = file.readline()
167                 
168                 if not signature.startswith('ply'):
169                         print 'Signature line was invalid'
170                         return None
171                 
172                 while 1:
173                         tokens = re.split(r'[ \n]+', file.readline())
174                         
175                         if (len(tokens) == 0):
176                                 continue
177                         if (tokens[0] == 'end_header'):
178                                 break
179                         elif (tokens[0] == 'comment' or tokens[0] == 'obj_info'):
180                                 continue
181                         elif (tokens[0] == 'format'):
182                                 if (len(tokens) < 3):
183                                         print 'Invalid format line'
184                                         return None
185                                 if (tokens[1] not in format_specs): # .keys()): # keys is implicit
186                                         print 'Unknown format', tokens[1]
187                                         return None
188                                 if (tokens[2] != version):
189                                         print 'Unknown version', tokens[2]
190                                         return None
191                                 format = tokens[1]
192                         elif (tokens[0] == 'element'):
193                                 if (len(tokens) < 3):
194                                         print 'Invalid element line'
195                                         return None
196                                 obj_spec.specs.append(element_spec(tokens[1], int(tokens[2])))
197                         elif (tokens[0] == 'property'):
198                                 if (not len(obj_spec.specs)):
199                                         print 'Property without element'
200                                         return None
201                                 if (tokens[1] == 'list'):
202                                         obj_spec.specs[-1].properties.append(property_spec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]]))
203                                 else:
204                                         obj_spec.specs[-1].properties.append(property_spec(tokens[2], None, type_specs[tokens[1]]))
205                 
206                 if format != 'ascii':
207                         file.close() # was ascii, now binary
208                         file = open(filename, 'rb')
209                         
210                         # skip the header...
211                         while not file.readline().startswith('end_header'):
212                                 pass
213                 
214                 obj = obj_spec.load(format_specs[format], file)
215                 
216         except IOError, (errno, strerror):
217                 try:    file.close()
218                 except: pass
219                 
220                 return None
221         try:    file.close()
222         except: pass
223         
224         return (obj_spec, obj);
225
226 def load_ply(filename):
227         t = Blender.sys.time()
228         obj_spec, obj = read(filename)
229         if obj == None:
230                 print 'Invalid file'
231                 return
232         
233         uvindices = colindices = None
234         # noindices = None # Ignore normals
235         
236         for el in obj_spec.specs:
237                 if el.name == 'vertex':
238                         vindices = vindices_x, vindices_y, vindices_z = (el.index('x'), el.index('y'), el.index('z'))
239                         # noindices = (el.index('nx'), el.index('ny'), el.index('nz'))
240                         # if -1 in noindices: noindices = None
241                         uvindices = (el.index('s'), el.index('t'))
242                         if -1 in uvindices: uvindices = None
243                         colindices = (el.index('red'), el.index('green'), el.index('blue'))
244                         if -1 in colindices: colindices = None
245                 elif el.name == 'face':
246                         findex = el.index('vertex_indices')
247         
248         mesh_faces = []
249         mesh_uvs = []
250         mesh_colors = []
251         
252         def add_face(vertices, indices, uvindices, colindices):
253                 mesh_faces.append(indices)
254                 if uvindices:   mesh_uvs.append([ (vertices[index][uvindices[0]], 1.0 - vertices[index][uvindices[1]]) for index in indices])
255                 if colindices:  mesh_colors.append([ (vertices[index][colindices[0]], vertices[index][colindices[1]], vertices[index][colindices[2]]) for index in indices])
256         
257         
258         if uvindices or colindices:
259                 # If we have Cols or UVs then we need to check the face order.
260                 add_face_simple = add_face
261                 
262                 # EVIL EEKADOODLE - face order annoyance.
263                 def add_face(vertices, indices, uvindices, colindices):
264                         if len(indices)==4:
265                                 if indices[2]==0 or indices[3]==0:
266                                         indices= indices[2], indices[3], indices[0], indices[1]
267                         elif len(indices)==3:
268                                 if indices[2]==0:
269                                         indices= indices[1], indices[2], indices[0]
270                         
271                         add_face_simple(vertices, indices, uvindices, colindices)
272         
273         verts = obj['vertex']
274         
275         if 'face' in obj:
276                 for f in obj['face']:
277                         ind = f[findex]
278                         len_ind = len(ind)
279                         if len_ind <= 4:
280                                 add_face(verts, ind, uvindices, colindices)
281                         else:
282                                 # Fan fill the face
283                                 for j in xrange(len_ind - 2):
284                                         add_face(verts, (ind[0], ind[j + 1], ind[j + 2]), uvindices, colindices)
285         
286         mesh = Blender.Mesh.New()
287         
288         mesh.verts.extend([(v[vindices_x], v[vindices_y], v[vindices_z]) for v in obj['vertex']])
289         
290         if mesh_faces:
291                 mesh.faces.extend(mesh_faces, smooth=True, ignoreDups=True)
292                 
293                 if uvindices or colindices:
294                         if uvindices:   mesh.faceUV = True
295                         if colindices:  mesh.vertexColors = True
296                         
297                         for i, f in enumerate(mesh.faces):
298                                 if uvindices:
299                                         ply_uv = mesh_uvs[i]
300                                         for j, uv in enumerate(f.uv):
301                                                 uv[:] = ply_uv[j]
302                                 
303                                 if colindices:
304                                         ply_col = mesh_colors[i]
305                                         for j, col in enumerate(f.col):
306                                                 col.r, col.g, col.b = ply_col[j]
307         
308         mesh.calcNormals()
309         
310         
311         objname = Blender.sys.splitext(Blender.sys.basename(filename))[0]
312         scn= Blender.Scene.GetCurrent()
313         scn.objects.selected = []
314         
315         mesh.name= objname
316         scn.objects.active = scn.objects.new(mesh)
317         
318         Blender.Redraw()
319         Blender.Window.DrawProgressBar(1.0, '')
320         print '\nSuccessfully imported "%s" in %.3f sec' %  (filename, Blender.sys.time()-t)
321
322 def main():
323         if not struct:
324                 msg = 'This importer requires a full python install'
325                 if Blender.mode == 'background':        print msg
326                 else:   Blender.Draw.PupMenu(msg)
327                 return
328         
329         Blender.Window.FileSelector(load_ply, 'Import PLY', '*.ply')
330
331 if __name__=='__main__':
332         main()
333
334 '''
335 import bpy
336 import os
337 files = os.popen('find /fe/ply -iname "*.ply"').readlines()
338
339
340 files.sort()
341 tot = len(files)
342 for i, f in enumerate(files):
343         if i < 26 or i > 1000000:
344                 continue
345         #if i != 12686:
346         #       continue
347         
348         f = f.strip()
349         print f, i, tot
350         sce = bpy.data.scenes.new(f.split('/')[-1])
351         bpy.data.scenes.active = sce
352         # Window.
353         load_ply(f)
354 '''