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