use f.area where possible over python function and use len(mface) over len(mface.v)
[blender.git] / release / scripts / ply_import.py
1 #!BPY
2
3 """
4 Name: 'PLY...'
5 Blender: 237
6 Group: 'Import'
7 Tip: 'Import a Stanford PLY file'
8 """
9
10 __author__ = 'Bruce Merry'
11 __version__ = '0.92'
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 # Updated by Campbell Barton AKA Ideasman, 10% faster code.
39
40 # Portions of this code are taken from mod_meshtools.py in Blender
41 # 2.32.
42
43 import Blender, meshtools
44 import re, struct, StringIO
45
46 class element_spec:
47         name = ''
48         count = 0
49         def __init__(self, name, count):
50                 self.name = name
51                 self.count = count
52                 self.properties = []
53
54         def load(self, format, stream):
55                 if format == 'ascii':
56                         stream = re.split('\s+', stream.readline())
57                 return map(lambda x: x.load(format, stream), self.properties)
58
59         def index(self, name):
60                 for i, p in enumerate(self.properties):
61                         if p.name == name: return i
62                 return -1
63
64 class property_spec:
65         name = ''
66         list_type = ''
67         numeric_type = ''
68         def __init__(self, name, list_type, numeric_type):
69                 self.name = name
70                 self.list_type = list_type
71                 self.numeric_type = numeric_type
72
73         def read_format(self, format, count, num_type, stream):
74                 if format == 'ascii':
75                         if (num_type == 's'):
76                                 ans = []
77                                 for i in xrange(count):
78                                         s = stream[i]
79                                         if len(s) < 2 or s[0] != '"' or s[-1] != '"':
80                                                 print 'Invalid string', s
81                                                 print 'Note: ply_import.py does not handle whitespace in strings'
82                                                 return None
83                                         ans.append(s[1:-1])
84                                 stream[:count] = []
85                                 return ans
86                         if (num_type == 'f' or num_type == 'd'):
87                                 mapper = float
88                         else:
89                                 mapper = int
90                         ans = map(lambda x: mapper(x), stream[:count])
91                         stream[:count] = []
92                         return ans
93                 else:
94                         if (num_type == 's'):
95                                 ans = []
96                                 for i in xrange(count):
97                                         fmt = format + 'i'
98                                         data = stream.read(struct.calcsize(fmt))
99                                         length = struct.unpack(fmt, data)[0]
100                                         fmt = '%s%is' % (format, length)
101                                         data = stream.read(struct.calcsize(fmt))
102                                         s = struct.unpack(fmt, data)[0]
103                                         ans.append(s[:-1]) # strip the NULL
104                                 return ans
105                         else:
106                                 fmt = '%s%i%s' % (format, count, num_type)
107                                 data = stream.read(struct.calcsize(fmt));
108                                 return struct.unpack(fmt, data)
109
110         def load(self, format, stream):
111                 if (self.list_type != None):
112                         count = int(self.read_format(format, 1, self.list_type, stream)[0])
113                         return self.read_format(format, count, self.numeric_type, stream)
114                 else:
115                         return self.read_format(format, 1, self.numeric_type, stream)[0]
116
117 class object_spec:
118         'A list of element_specs'
119         specs = []
120
121         def load(self, format, stream):
122                 return dict([(i.name,[i.load(format, stream) for j in xrange(i.count) ]) for i in self.specs])
123                 
124                 '''
125                 answer = {}
126                 for i in self.specs:
127                         answer[i.name] = []
128                         for j in xrange(i.count):
129                                 if not j % 100 and meshtools.show_progress:
130                                         Blender.Window.DrawProgressBar(float(j) / i.count, 'Loading ' + i.name)
131                                 answer[i.name].append(i.load(format, stream))
132                 return answer
133                         '''
134                 
135
136 def read(filename):
137         format = ''
138         version = '1.0'
139         format_specs = {'binary_little_endian': '<',
140                         'binary_big_endian': '>',
141                         'ascii': 'ascii'}
142         type_specs = {'char': 'b',
143                       'uchar': 'B',
144                       'int8': 'b',
145                       'uint8': 'B',
146                       'int16': 'h',
147                       'uint16': 'H',
148                       'int': 'i',
149                       'int32': 'i',
150                       'uint': 'I',
151                       'uint32': 'I',
152                       'float': 'f',
153                       'float32': 'f',
154                       'float64': 'd',
155                       'string': 's'}
156         obj_spec = object_spec()
157
158         try:
159                 file = open(filename, 'rb')
160                 signature = file.readline()
161                 if (signature != 'ply\n'):
162                         print 'Signature line was invalid'
163                         return None
164                 while 1:
165                         tokens = re.split(r'[ \n]+', file.readline())
166                         if (len(tokens) == 0):
167                                 continue
168                         if (tokens[0] == 'end_header'):
169                                 break
170                         elif (tokens[0] == 'comment' or tokens[0] == 'obj_info'):
171                                 continue
172                         elif (tokens[0] == 'format'):
173                                 if (len(tokens) < 3):
174                                         print 'Invalid format line'
175                                         return None
176                                 if (tokens[1] not in format_specs.keys()):
177                                         print 'Unknown format', tokens[1]
178                                         return None
179                                 if (tokens[2] != version):
180                                         print 'Unknown version', tokens[2]
181                                         return None
182                                 format = tokens[1]
183                         elif (tokens[0] == 'element'):
184                                 if (len(tokens) < 3):
185                                         print 'Invalid element line'
186                                         return None
187                                 obj_spec.specs.append(element_spec(tokens[1], int(tokens[2])))
188                         elif (tokens[0] == 'property'):
189                                 if (not len(obj_spec.specs)):
190                                         print 'Property without element'
191                                         return None
192                                 if (tokens[1] == 'list'):
193                                         obj_spec.specs[-1].properties.append(property_spec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]]))
194                                 else:
195                                         obj_spec.specs[-1].properties.append(property_spec(tokens[2], None, type_specs[tokens[1]]))
196                 obj = obj_spec.load(format_specs[format], file)
197
198         except IOError, (errno, strerror):
199                 file.close()
200                 return None
201
202         file.close()
203         return (obj_spec, obj);
204
205 def add_face(vertices, varr, indices, uvindices, colindices):
206         face = Blender.NMesh.Face([varr[i] for i in indices])
207         for index in indices:
208                 vertex = vertices[index];
209                 
210                 if uvindices:
211                         face.uv.append((vertex[uvindices[0]], 1.0 - vertex[uvindices[1]]))
212                 if colindices:
213                         if not uvindices: face.uv.append((0, 0)) # Force faceUV
214                         face.col.append(Blender.NMesh.Col(vertex[colindices[0]], vertex[colindices[1]], vertex[colindices[2]], 255))
215         return face
216
217 def filesel_callback(filename):
218         t = Blender.sys.time()
219         (obj_spec, obj) = read(filename)
220         if obj == None:
221                 print 'Invalid file'
222                 return
223         vmap = {}
224         varr = []
225         uvindices = None
226         noindices = None
227         colindices = None
228         for el in obj_spec.specs:
229                 if el.name == 'vertex':
230                         vindices = vindices_x, vindices_y, vindices_z = (el.index('x'), el.index('y'), el.index('z'))
231                         if el.index('nx') >= 0 and el.index('ny') >= 0 and el.index('nz') >= 0:
232                                 noindices = (el.index('nx'), el.index('ny'), el.index('nz'))
233                         if el.index('s') >= 0 and el.index('t') >= 0:
234                                 uvindices = (el.index('s'), el.index('t'))
235                         if el.index('red') >= 0 and el.index('green') and el.index('blue') >= 0:
236                                 colindices = (el.index('red'), el.index('green'), el.index('blue'))
237                 elif el.name == 'face':
238                         findex = el.index('vertex_indices')
239                 
240
241         mesh = Blender.NMesh.GetRaw()
242         NMVert = Blender.NMesh.Vert
243         for v in obj['vertex']:
244                 
245                 if noindices > 0:
246                         x,y,z,nx,ny,nz = vkey =\
247                         (v[vindices_x], v[vindices_y], v[vindices_z],\
248                         v[noindices[0]], v[noindices[1]], v[noindices[2]])
249                 else:
250                         x,y,z = vkey = (v[vindices_x], v[vindices_y], v[vindices_z])
251                 #if not vmap.has_key(vkey):
252                 try: # try uses 1 less dict lookup
253                         varr.append(vmap[vkey])
254                 except:
255                         nmv = NMVert(vkey[0], vkey[1], vkey[2])
256                         mesh.verts.append(nmv)
257                         if noindices > 0:
258                                 nmv.no[0] = vkey[3]
259                                 nmv.no[1] = vkey[4]
260                                 nmv.no[2] = vkey[5]
261                         vmap[vkey] = nmv
262                         varr.append(vmap[vkey])
263         
264         verts = obj['vertex']
265         for f in obj['face']:
266                 ind = f[findex]
267                 nind = len(ind)
268                 if nind <= 4:
269                         mesh.faces.append(add_face(verts, varr, ind, uvindices, colindices))
270                 else:
271                         for j in xrange(nind - 2):
272                                 mesh.faces.append(add_face(verts, varr, (ind[0], ind[j + 1], ind[j + 2]), uvindices, colindices))
273
274         
275         del obj # Reclaim memory
276
277         if noindices:
278                 normals = 1
279         else:
280                 normals = 0
281         objname = Blender.sys.splitext(Blender.sys.basename(filename))[0]
282         if not meshtools.overwrite_mesh_name:
283                 objname = meshtools.versioned_name(objname)
284         Blender.NMesh.PutRaw(mesh, objname, not normals)
285         Blender.Object.GetSelected()[0].name = objname
286         Blender.Redraw()
287         Blender.Window.DrawProgressBar(1.0, '')
288         message = 'Successfully imported ' + Blender.sys.basename(filename) + ' ' + str(Blender.sys.time()-t)
289         meshtools.print_boxed(message)
290
291 Blender.Window.FileSelector(filesel_callback, 'Import PLY')
292