1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
25 class element_spec(object):
31 def __init__(self
, name
, count
):
36 def load(self
, format
, stream
):
37 if format
== b
'ascii':
38 stream
= stream
.readline().split()
39 return [x
.load(format
, stream
) for x
in self
.properties
]
41 def index(self
, name
):
42 for i
, p
in enumerate(self
.properties
):
48 class property_spec(object):
54 def __init__(self
, name
, list_type
, numeric_type
):
56 self
.list_type
= list_type
57 self
.numeric_type
= numeric_type
59 def read_format(self
, format
, count
, num_type
, stream
):
60 if format
== b
'ascii':
63 for i
in range(count
):
65 if len(s
) < 2 or s
[0] != '"' or s
[-1] != '"':
66 print('Invalid string', s
)
67 print('Note: ply_import.py does not handle whitespace in strings')
72 if num_type
== 'f' or num_type
== 'd':
76 ans
= [mapper(x
) for x
in stream
[:count
]]
82 for i
in range(count
):
84 data
= stream
.read(struct
.calcsize(fmt
))
85 length
= struct
.unpack(fmt
, data
)[0]
86 fmt
= '%s%is' % (format
, length
)
87 data
= stream
.read(struct
.calcsize(fmt
))
88 s
= struct
.unpack(fmt
, data
)[0]
89 ans
.append(s
[:-1]) # strip the NULL
92 fmt
= '%s%i%s' % (format
, count
, num_type
)
93 data
= stream
.read(struct
.calcsize(fmt
))
94 return struct
.unpack(fmt
, data
)
96 def load(self
, format
, stream
):
97 if self
.list_type
is not None:
98 count
= int(self
.read_format(format
, 1, self
.list_type
, stream
)[0])
99 return self
.read_format(format
, count
, self
.numeric_type
, stream
)
101 return self
.read_format(format
, 1, self
.numeric_type
, stream
)[0]
104 class object_spec(object):
105 __slots__
= ("specs",
107 'A list of element_specs'
111 def load(self
, format
, stream
):
112 return dict([(i
.name
, [i
.load(format
, stream
) for j
in range(i
.count
)]) for i
in self
.specs
])
115 # Longhand for above LC
119 for j in range(i.count):
120 if not j % 100 and meshtools.show_progress:
121 Blender.Window.DrawProgressBar(float(j) / i.count, 'Loading ' + i.name)
122 answer[i.name].append(i.load(format, stream))
131 format_specs
= {b
'binary_little_endian': '<',
132 b
'binary_big_endian': '>',
134 type_specs
= {b
'char': 'b',
151 obj_spec
= object_spec()
152 invalid_ply
= (None, None, None)
154 with
open(filepath
, 'rb') as plyf
:
155 signature
= plyf
.readline()
157 if not signature
.startswith(b
'ply'):
158 print('Signature line was invalid')
163 tokens
= re
.split(br
'[ \r\n]+', line
)
167 if tokens
[0] == b
'end_header':
170 elif tokens
[0] == b
'comment':
173 elif tokens
[1] == b
'TextureFile':
175 print('Invalid texture line')
179 elif tokens
[0] == b
'obj_info':
181 elif tokens
[0] == b
'format':
183 print('Invalid format line')
185 if tokens
[1] not in format_specs
:
186 print('Unknown format', tokens
[1])
189 version_test
= float(tokens
[2])
190 except Exception as ex
:
191 print('Unknown version', ex
)
193 if version_test
!= float(version
):
194 print('Unknown version', tokens
[2])
198 elif tokens
[0] == b
'element':
200 print(b
'Invalid element line')
202 obj_spec
.specs
.append(element_spec(tokens
[1], int(tokens
[2])))
203 elif tokens
[0] == b
'property':
204 if not len(obj_spec
.specs
):
205 print('Property without element')
207 if tokens
[1] == b
'list':
208 obj_spec
.specs
[-1].properties
.append(property_spec(tokens
[4], type_specs
[tokens
[2]], type_specs
[tokens
[3]]))
210 obj_spec
.specs
[-1].properties
.append(property_spec(tokens
[2], None, type_specs
[tokens
[1]]))
212 print("Invalid header ('end_header' line not found!)")
215 obj
= obj_spec
.load(format_specs
[format
], plyf
)
217 return obj_spec
, obj
, texture
223 def load_ply_mesh(filepath
, ply_name
):
224 from bpy_extras
.io_utils
import unpack_face_list
225 # from bpy_extras.image_utils import load_image # UNUSED
227 obj_spec
, obj
, texture
= read(filepath
)
229 print('Invalid file')
232 uvindices
= colindices
= None
235 # noindices = None # Ignore normals
237 for el
in obj_spec
.specs
:
238 if el
.name
== b
'vertex':
239 vindices_x
, vindices_y
, vindices_z
= el
.index(b
'x'), el
.index(b
'y'), el
.index(b
'z')
240 # noindices = (el.index('nx'), el.index('ny'), el.index('nz'))
241 # if -1 in noindices: noindices = None
242 uvindices
= (el
.index(b
's'), el
.index(b
't'))
245 colindices
= el
.index(b
'red'), el
.index(b
'green'), el
.index(b
'blue'), el
.index(b
'alpha')
248 else: # if not a float assume uchar
249 colmultiply
= [1.0 if el
.properties
[i
].numeric_type
in {'f', 'd'} else (1.0 / 255.0) for i
in colindices
]
251 elif el
.name
== b
'face':
252 findex
= el
.index(b
'vertex_indices')
253 elif el
.name
== b
'tristrips':
254 trindex
= el
.index(b
'vertex_indices')
255 elif el
.name
== b
'edge':
256 eindex1
, eindex2
= el
.index(b
'vertex1'), el
.index(b
'vertex2')
262 def add_face(vertices
, indices
, uvindices
, colindices
):
263 mesh_faces
.append(indices
)
265 mesh_uvs
.append([(vertices
[index
][uvindices
[0]], vertices
[index
][uvindices
[1]]) for index
in indices
])
267 mesh_colors
.append([(vertices
[index
][colindices
[0]] * colmultiply
[0],
268 vertices
[index
][colindices
[1]] * colmultiply
[1],
269 vertices
[index
][colindices
[2]] * colmultiply
[2],
270 vertices
[index
][colindices
[3]] * colmultiply
[3],
271 ) for index
in indices
])
273 if uvindices
or colindices
:
274 # If we have Cols or UVs then we need to check the face order.
275 add_face_simple
= add_face
277 # EVIL EEKADOODLE - face order annoyance.
278 def add_face(vertices
, indices
, uvindices
, colindices
):
279 if len(indices
) == 4:
280 if indices
[2] == 0 or indices
[3] == 0:
281 indices
= indices
[2], indices
[3], indices
[0], indices
[1]
282 elif len(indices
) == 3:
284 indices
= indices
[1], indices
[2], indices
[0]
286 add_face_simple(vertices
, indices
, uvindices
, colindices
)
288 verts
= obj
[b
'vertex']
291 for f
in obj
[b
'face']:
295 add_face(verts
, ind
, uvindices
, colindices
)
298 for j
in range(len_ind
- 2):
299 add_face(verts
, (ind
[0], ind
[j
+ 1], ind
[j
+ 2]), uvindices
, colindices
)
301 if b
'tristrips' in obj
:
302 for t
in obj
[b
'tristrips']:
305 for j
in range(len_ind
- 2):
306 add_face(verts
, (ind
[j
], ind
[j
+ 1], ind
[j
+ 2]), uvindices
, colindices
)
308 mesh
= bpy
.data
.meshes
.new(name
=ply_name
)
310 mesh
.vertices
.add(len(obj
[b
'vertex']))
312 mesh
.vertices
.foreach_set("co", [a
for v
in obj
[b
'vertex'] for a
in (v
[vindices_x
], v
[vindices_y
], v
[vindices_z
])])
315 mesh
.edges
.add(len(obj
[b
'edge']))
316 mesh
.edges
.foreach_set("vertices", [a
for e
in obj
[b
'edge'] for a
in (e
[eindex1
], e
[eindex2
])])
319 mesh
.tessfaces
.add(len(mesh_faces
))
320 mesh
.tessfaces
.foreach_set("vertices_raw", unpack_face_list(mesh_faces
))
322 if uvindices
or colindices
:
324 uvlay
= mesh
.tessface_uv_textures
.new()
326 vcol_lay
= mesh
.tessface_vertex_colors
.new()
329 for i
, f
in enumerate(uvlay
.data
):
331 for j
, uv
in enumerate(f
.uv
):
332 uv
[0], uv
[1] = ply_uv
[j
]
335 for i
, f
in enumerate(vcol_lay
.data
):
336 # XXX, colors dont come in right, needs further investigation.
337 ply_col
= mesh_colors
[i
]
338 if len(ply_col
) == 4:
339 f_col
= f
.color1
, f
.color2
, f
.color3
, f
.color4
341 f_col
= f
.color1
, f
.color2
, f
.color3
343 for j
, col
in enumerate(f_col
):
344 col
[0] = ply_col
[j
][0]
345 col
[1] = ply_col
[j
][1]
346 col
[2] = ply_col
[j
][2]
347 col
[3] = ply_col
[j
][3]
352 if texture
and uvindices
:
356 from bpy_extras
.image_utils
import load_image
358 encoding
= sys
.getfilesystemencoding()
359 encoded_texture
= texture
.decode(encoding
=encoding
)
360 name
= bpy
.path
.display_name_from_filepath(texture
)
361 image
= load_image(encoded_texture
, os
.path
.dirname(filepath
), recursive
=True, place_holder
=True)
364 texture
= bpy
.data
.textures
.new(name
=name
, type='IMAGE')
365 texture
.image
= image
367 material
= bpy
.data
.materials
.new(name
=name
)
368 material
.use_shadeless
= True
370 mtex
= material
.texture_slots
.add()
371 mtex
.texture
= texture
372 mtex
.texture_coords
= 'UV'
373 mtex
.use_map_color_diffuse
= True
375 mesh
.materials
.append(material
)
376 for face
in mesh
.uv_textures
[0].data
:
382 def load_ply(filepath
):
386 ply_name
= bpy
.path
.display_name_from_filepath(filepath
)
388 mesh
= load_ply_mesh(filepath
, ply_name
)
392 scn
= bpy
.context
.scene
394 obj
= bpy
.data
.objects
.new(ply_name
, mesh
)
395 scn
.objects
.link(obj
)
396 scn
.objects
.active
= obj
399 print('\nSuccessfully imported %r in %.3f sec' % (filepath
, time
.time() - t
))
403 def load(operator
, context
, filepath
=""):
404 return load_ply(filepath
)