[#18388] PLY Import fails if line ending is not \n
[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                       'int': 'i',
153                       'int32': 'i',
154                       'uint': 'I',
155                       'uint32': 'I',
156                       'float': 'f',
157                       'float32': 'f',
158                       'float64': 'd',
159                       'string': 's'}
160         obj_spec = object_spec()
161
162         try:
163                 file = open(filename, 'rU') # Only for parsing the header, not binary data
164                 signature = file.readline()
165                 
166                 if not signature.startswith('ply'):
167                         print 'Signature line was invalid'
168                         return None
169                 
170                 while 1:
171                         tokens = re.split(r'[ \n]+', file.readline())
172                         
173                         if (len(tokens) == 0):
174                                 continue
175                         if (tokens[0] == 'end_header'):
176                                 break
177                         elif (tokens[0] == 'comment' or tokens[0] == 'obj_info'):
178                                 continue
179                         elif (tokens[0] == 'format'):
180                                 if (len(tokens) < 3):
181                                         print 'Invalid format line'
182                                         return None
183                                 if (tokens[1] not in format_specs): # .keys()): # keys is implicit
184                                         print 'Unknown format', tokens[1]
185                                         return None
186                                 if (tokens[2] != version):
187                                         print 'Unknown version', tokens[2]
188                                         return None
189                                 format = tokens[1]
190                         elif (tokens[0] == 'element'):
191                                 if (len(tokens) < 3):
192                                         print 'Invalid element line'
193                                         return None
194                                 obj_spec.specs.append(element_spec(tokens[1], int(tokens[2])))
195                         elif (tokens[0] == 'property'):
196                                 if (not len(obj_spec.specs)):
197                                         print 'Property without element'
198                                         return None
199                                 if (tokens[1] == 'list'):
200                                         obj_spec.specs[-1].properties.append(property_spec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]]))
201                                 else:
202                                         obj_spec.specs[-1].properties.append(property_spec(tokens[2], None, type_specs[tokens[1]]))
203                 
204                 if format != 'ascii':
205                         file.close() # was ascii, now binary
206                         file = open(filename, 'rb')
207                         
208                         # skip the header...
209                         while not file.readline().startswith('end_header'):
210                                 pass
211                 
212                 obj = obj_spec.load(format_specs[format], file)
213                 
214         except IOError, (errno, strerror):
215                 try:    file.close()
216                 except: pass
217                 
218                 return None
219         try:    file.close()
220         except: pass
221         
222         return (obj_spec, obj);
223
224 def load_ply(filename):
225         t = Blender.sys.time()
226         obj_spec, obj = read(filename)
227         if obj == None:
228                 print 'Invalid file'
229                 return
230         
231         uvindices = colindices = None
232         # noindices = None # Ignore normals
233         
234         for el in obj_spec.specs:
235                 if el.name == 'vertex':
236                         vindices = vindices_x, vindices_y, vindices_z = (el.index('x'), el.index('y'), el.index('z'))
237                         # noindices = (el.index('nx'), el.index('ny'), el.index('nz'))
238                         # if -1 in noindices: noindices = None
239                         uvindices = (el.index('s'), el.index('t'))
240                         if -1 in uvindices: uvindices = None
241                         colindices = (el.index('red'), el.index('green'), el.index('blue'))
242                         if -1 in colindices: colindices = None
243                 elif el.name == 'face':
244                         findex = el.index('vertex_indices')
245         
246         mesh_faces = []
247         mesh_uvs = []
248         mesh_colors = []
249         
250         def add_face(vertices, indices, uvindices, colindices):
251                 mesh_faces.append(indices)
252                 if uvindices:   mesh_uvs.append([ (vertices[index][uvindices[0]], 1.0 - vertices[index][uvindices[1]]) for index in indices])
253                 if colindices:  mesh_colors.append([ (vertices[index][colindices[0]], vertices[index][colindices[1]], vertices[index][colindices[2]]) for index in indices])
254         
255         
256         if uvindices or colindices:
257                 # If we have Cols or UVs then we need to check the face order.
258                 add_face_simple = add_face
259                 
260                 # EVIL EEKADOODLE - face order annoyance.
261                 def add_face(vertices, indices, uvindices, colindices):
262                         if len(indices)==4:
263                                 if indices[2]==0 or indices[3]==0:
264                                         indices= indices[2], indices[3], indices[0], indices[1]
265                         elif len(indices)==3:
266                                 if indices[2]==0:
267                                         indices= indices[1], indices[2], indices[0]
268                         
269                         add_face_simple(vertices, indices, uvindices, colindices)
270         
271         verts = obj['vertex']
272         
273         if 'face' in obj:
274                 for f in obj['face']:
275                         ind = f[findex]
276                         len_ind = len(ind)
277                         if len_ind <= 4:
278                                 add_face(verts, ind, uvindices, colindices)
279                         else:
280                                 # Fan fill the face
281                                 for j in xrange(len_ind - 2):
282                                         add_face(verts, (ind[0], ind[j + 1], ind[j + 2]), uvindices, colindices)
283         
284         mesh = Blender.Mesh.New()
285         
286         mesh.verts.extend([(v[vindices_x], v[vindices_y], v[vindices_z]) for v in obj['vertex']])
287         
288         if mesh_faces:
289                 mesh.faces.extend(mesh_faces, smooth=True, ignoreDups=True)
290                 
291                 if uvindices or colindices:
292                         if uvindices:   mesh.faceUV = True
293                         if colindices:  mesh.vertexColors = True
294                         
295                         for i, f in enumerate(mesh.faces):
296                                 if uvindices:
297                                         ply_uv = mesh_uvs[i]
298                                         for j, uv in enumerate(f.uv):
299                                                 uv[:] = ply_uv[j]
300                                 
301                                 if colindices:
302                                         ply_col = mesh_colors[i]
303                                         for j, col in enumerate(f.col):
304                                                 col.r, col.g, col.b = ply_col[j]
305         
306         mesh.calcNormals()
307         
308         
309         objname = Blender.sys.splitext(Blender.sys.basename(filename))[0]
310         scn= Blender.Scene.GetCurrent()
311         scn.objects.selected = []
312         
313         mesh.name= objname
314         scn.objects.active = scn.objects.new(mesh)
315         
316         Blender.Redraw()
317         Blender.Window.DrawProgressBar(1.0, '')
318         print '\nSuccessfully imported "%s" in %.3f sec' %  (filename, Blender.sys.time()-t)
319
320 def main():
321         if not struct:
322                 msg = 'This importer requires a full python install'
323                 if Blender.mode == 'background':        print msg
324                 else:   Blender.Draw.PupMenu(msg)
325                 return
326         
327         Blender.Window.FileSelector(load_ply, 'Import PLY', '*.ply')
328
329 if __name__=='__main__':
330         main()
331
332 '''
333 import bpy
334 import os
335 files = os.popen('find /fe/ply -iname "*.ply"').readlines()
336
337
338 files.sort()
339 tot = len(files)
340 for i, f in enumerate(files):
341         if i < 26 or i > 1000000:
342                 continue
343         #if i != 12686:
344         #       continue
345         
346         f = f.strip()
347         print f, i, tot
348         sce = bpy.data.scenes.new(f.split('/')[-1])
349         bpy.data.scenes.active = sce
350         # Window.
351         load_ply(f)
352 '''