remove '.' from descriptions
[blender-addons.git] / io_import_scene_lwo.py
blobb01763f328c3b629abc7591b2cfb9613a0c91f45
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 #####
19 bl_info= {
20 "name": "Import LightWave Objects",
21 "author": "Ken Nign (Ken9)",
22 "version": (1, 2),
23 "blender": (2, 57, 0),
24 "location": "File > Import > LightWave Object (.lwo)",
25 "description": "Imports a LWO file including any UV, Morph and Color maps. "
26 "Can convert Skelegons to an Armature.",
27 "warning": "",
28 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
29 "Scripts/Import-Export/LightWave_Object",
30 "tracker_url": "https://developer.blender.org/T23623",
31 "category": "Import-Export"}
33 # Copyright (c) Ken Nign 2010
34 # ken@virginpi.com
36 # Version 1.3 - Aug 11, 2011
38 # Loads a LightWave .lwo object file, including the vertex maps such as
39 # UV, Morph, Color and Weight maps.
41 # Will optionally create an Armature from an embedded Skelegon rig.
43 # Point orders are maintained so that .mdds can exchanged with other
44 # 3D programs.
47 # Notes:
48 # NGons, polygons with more than 4 points are supported, but are
49 # added (as triangles) after the vertex maps have been applied. Thus they
50 # won't contain all the vertex data that the original ngon had.
52 # Blender is limited to only 8 UV Texture and 8 Vertex Color maps,
53 # thus only the first 8 of each can be imported.
55 # History:
57 # 1.3 Fixed CC Edge Weight loading.
59 # 1.2 Added Absolute Morph and CC Edge Weight support.
60 # Made edge creation safer.
61 # 1.0 First Release
64 import os
65 import struct
66 import chunk
68 import bpy
69 import mathutils
70 from mathutils.geometry import tessellate_polygon
73 class _obj_layer(object):
74 __slots__ = (
75 "name",
76 "index",
77 "parent_index",
78 "pivot",
79 "pols",
80 "bones",
81 "bone_names",
82 "bone_rolls",
83 "pnts",
84 "wmaps",
85 "colmaps",
86 "uvmaps",
87 "morphs",
88 "edge_weights",
89 "surf_tags",
90 "has_subds",
92 def __init__(self):
93 self.name= ""
94 self.index= -1
95 self.parent_index= -1
96 self.pivot= [0, 0, 0]
97 self.pols= []
98 self.bones= []
99 self.bone_names= {}
100 self.bone_rolls= {}
101 self.pnts= []
102 self.wmaps= {}
103 self.colmaps= {}
104 self.uvmaps= {}
105 self.morphs= {}
106 self.edge_weights= {}
107 self.surf_tags= {}
108 self.has_subds= False
111 class _obj_surf(object):
112 __slots__ = (
113 "bl_mat",
114 "name",
115 "source_name",
116 "colr",
117 "diff",
118 "lumi",
119 "spec",
120 "refl",
121 "rblr",
122 "tran",
123 "rind",
124 "tblr",
125 "trnl",
126 "glos",
127 "shrp",
128 "smooth",
131 def __init__(self):
132 self.bl_mat= None
133 self.name= "Default"
134 self.source_name= ""
135 self.colr= [1.0, 1.0, 1.0]
136 self.diff= 1.0 # Diffuse
137 self.lumi= 0.0 # Luminosity
138 self.spec= 0.0 # Specular
139 self.refl= 0.0 # Reflectivity
140 self.rblr= 0.0 # Reflection Bluring
141 self.tran= 0.0 # Transparency (the opposite of Blender's Alpha value)
142 self.rind= 1.0 # RT Transparency IOR
143 self.tblr= 0.0 # Refraction Bluring
144 self.trnl= 0.0 # Translucency
145 self.glos= 0.4 # Glossiness
146 self.shrp= 0.0 # Diffuse Sharpness
147 self.smooth= False # Surface Smoothing
150 def load_lwo(filename,
151 context,
152 ADD_SUBD_MOD=True,
153 LOAD_HIDDEN=False,
154 SKEL_TO_ARM=True):
155 """Read the LWO file, hand off to version specific function."""
156 name, ext= os.path.splitext(os.path.basename(filename))
157 file= open(filename, 'rb')
159 try:
160 header, chunk_size, chunk_name = struct.unpack(">4s1L4s", file.read(12))
161 except:
162 print("Error parsing file header!")
163 file.close()
164 return
166 layers= []
167 surfs= {}
168 tags= []
169 # Gather the object data using the version specific handler.
170 if chunk_name == b'LWO2':
171 read_lwo2(file, filename, layers, surfs, tags, ADD_SUBD_MOD, LOAD_HIDDEN, SKEL_TO_ARM)
172 elif chunk_name == b'LWOB' or chunk_name == b'LWLO':
173 # LWOB and LWLO are the old format, LWLO is a layered object.
174 read_lwob(file, filename, layers, surfs, tags, ADD_SUBD_MOD)
175 else:
176 print("Not a supported file type!")
177 file.close()
178 return
180 file.close()
182 # With the data gathered, build the object(s).
183 build_objects(layers, surfs, tags, name, ADD_SUBD_MOD, SKEL_TO_ARM)
185 layers= None
186 surfs.clear()
187 tags= None
190 def read_lwo2(file, filename, layers, surfs, tags, add_subd_mod, load_hidden, skel_to_arm):
191 """Read version 2 file, LW 6+."""
192 handle_layer= True
193 last_pols_count= 0
194 just_read_bones= False
195 print("Importing LWO: " + filename + "\nLWO v2 Format")
197 while True:
198 try:
199 rootchunk = chunk.Chunk(file)
200 except EOFError:
201 break
203 if rootchunk.chunkname == b'TAGS':
204 read_tags(rootchunk.read(), tags)
205 elif rootchunk.chunkname == b'LAYR':
206 handle_layer= read_layr(rootchunk.read(), layers, load_hidden)
207 elif rootchunk.chunkname == b'PNTS' and handle_layer:
208 read_pnts(rootchunk.read(), layers)
209 elif rootchunk.chunkname == b'VMAP' and handle_layer:
210 vmap_type = rootchunk.read(4)
212 if vmap_type == b'WGHT':
213 read_weightmap(rootchunk.read(), layers)
214 elif vmap_type == b'MORF':
215 read_morph(rootchunk.read(), layers, False)
216 elif vmap_type == b'SPOT':
217 read_morph(rootchunk.read(), layers, True)
218 elif vmap_type == b'TXUV':
219 read_uvmap(rootchunk.read(), layers)
220 elif vmap_type == b'RGB ' or vmap_type == b'RGBA':
221 read_colmap(rootchunk.read(), layers)
222 else:
223 rootchunk.skip()
225 elif rootchunk.chunkname == b'VMAD' and handle_layer:
226 vmad_type= rootchunk.read(4)
228 if vmad_type == b'TXUV':
229 read_uv_vmad(rootchunk.read(), layers, last_pols_count)
230 elif vmad_type == b'RGB ' or vmad_type == b'RGBA':
231 read_color_vmad(rootchunk.read(), layers, last_pols_count)
232 elif vmad_type == b'WGHT':
233 # We only read the Edge Weight map if it's there.
234 read_weight_vmad(rootchunk.read(), layers)
235 else:
236 rootchunk.skip()
238 elif rootchunk.chunkname == b'POLS' and handle_layer:
239 face_type = rootchunk.read(4)
240 just_read_bones= False
241 # PTCH is LW's Subpatches, SUBD is CatmullClark.
242 if (face_type == b'FACE' or face_type == b'PTCH' or
243 face_type == b'SUBD') and handle_layer:
244 last_pols_count= read_pols(rootchunk.read(), layers)
245 if face_type != b'FACE':
246 layers[-1].has_subds= True
247 elif face_type == b'BONE' and handle_layer:
248 read_bones(rootchunk.read(), layers)
249 just_read_bones= True
250 else:
251 rootchunk.skip()
253 elif rootchunk.chunkname == b'PTAG' and handle_layer:
254 tag_type,= struct.unpack("4s", rootchunk.read(4))
255 if tag_type == b'SURF' and not just_read_bones:
256 # Ignore the surface data if we just read a bones chunk.
257 read_surf_tags(rootchunk.read(), layers, last_pols_count)
259 elif skel_to_arm:
260 if tag_type == b'BNUP':
261 read_bone_tags(rootchunk.read(), layers, tags, 'BNUP')
262 elif tag_type == b'BONE':
263 read_bone_tags(rootchunk.read(), layers, tags, 'BONE')
264 else:
265 rootchunk.skip()
266 else:
267 rootchunk.skip()
268 elif rootchunk.chunkname == b'SURF':
269 read_surf(rootchunk.read(), surfs)
270 else:
271 #if handle_layer:
272 #print("Skipping Chunk:", rootchunk.chunkname)
273 rootchunk.skip()
276 def read_lwob(file, filename, layers, surfs, tags, add_subd_mod):
277 """Read version 1 file, LW < 6."""
278 last_pols_count= 0
279 print("Importing LWO: " + filename + "\nLWO v1 Format")
281 while True:
282 try:
283 rootchunk = chunk.Chunk(file)
284 except EOFError:
285 break
287 if rootchunk.chunkname == b'SRFS':
288 read_tags(rootchunk.read(), tags)
289 elif rootchunk.chunkname == b'LAYR':
290 read_layr_5(rootchunk.read(), layers)
291 elif rootchunk.chunkname == b'PNTS':
292 if len(layers) == 0:
293 # LWOB files have no LAYR chunk to set this up.
294 nlayer= _obj_layer()
295 nlayer.name= "Layer 1"
296 layers.append(nlayer)
297 read_pnts(rootchunk.read(), layers)
298 elif rootchunk.chunkname == b'POLS':
299 last_pols_count= read_pols_5(rootchunk.read(), layers)
300 elif rootchunk.chunkname == b'PCHS':
301 last_pols_count= read_pols_5(rootchunk.read(), layers)
302 layers[-1].has_subds= True
303 elif rootchunk.chunkname == b'PTAG':
304 tag_type,= struct.unpack("4s", rootchunk.read(4))
305 if tag_type == b'SURF':
306 read_surf_tags_5(rootchunk.read(), layers, last_pols_count)
307 else:
308 rootchunk.skip()
309 elif rootchunk.chunkname == b'SURF':
310 read_surf_5(rootchunk.read(), surfs)
311 else:
312 # For Debugging \/.
313 #if handle_layer:
314 #print("Skipping Chunk: ", rootchunk.chunkname)
315 rootchunk.skip()
318 def read_lwostring(raw_name):
319 """Parse a zero-padded string."""
321 i = raw_name.find(b'\0')
322 name_len = i + 1
323 if name_len % 2 == 1: # Test for oddness.
324 name_len += 1
326 if i > 0:
327 # Some plugins put non-text strings in the tags chunk.
328 name = raw_name[0:i].decode("utf-8", "ignore")
329 else:
330 name = ""
332 return name, name_len
335 def read_vx(pointdata):
336 """Read a variable-length index."""
337 if pointdata[0] != 255:
338 index= pointdata[0]*256 + pointdata[1]
339 size= 2
340 else:
341 index= pointdata[1]*65536 + pointdata[2]*256 + pointdata[3]
342 size= 4
344 return index, size
347 def read_tags(tag_bytes, object_tags):
348 """Read the object's Tags chunk."""
349 offset= 0
350 chunk_len= len(tag_bytes)
352 while offset < chunk_len:
353 tag, tag_len= read_lwostring(tag_bytes[offset:])
354 offset+= tag_len
355 object_tags.append(tag)
358 def read_layr(layr_bytes, object_layers, load_hidden):
359 """Read the object's layer data."""
360 new_layr= _obj_layer()
361 new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
363 if flags > 0 and not load_hidden:
364 return False
366 print("Reading Object Layer")
367 offset= 4
368 pivot= struct.unpack(">fff", layr_bytes[offset:offset+12])
369 # Swap Y and Z to match Blender's pitch.
370 new_layr.pivot= [pivot[0], pivot[2], pivot[1]]
371 offset+= 12
372 layr_name, name_len = read_lwostring(layr_bytes[offset:])
373 offset+= name_len
375 if layr_name:
376 new_layr.name= layr_name
377 else:
378 new_layr.name= "Layer %d" % (new_layr.index + 1)
380 if len(layr_bytes) == offset+2:
381 new_layr.parent_index,= struct.unpack(">h", layr_bytes[offset:offset+2])
383 object_layers.append(new_layr)
384 return True
387 def read_layr_5(layr_bytes, object_layers):
388 """Read the object's layer data."""
389 # XXX: Need to check what these two exactly mean for a LWOB/LWLO file.
390 new_layr= _obj_layer()
391 new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
393 print("Reading Object Layer")
394 offset= 4
395 layr_name, name_len = read_lwostring(layr_bytes[offset:])
396 offset+= name_len
398 if name_len > 2 and layr_name != 'noname':
399 new_layr.name= layr_name
400 else:
401 new_layr.name= "Layer %d" % new_layr.index
403 object_layers.append(new_layr)
406 def read_pnts(pnt_bytes, object_layers):
407 """Read the layer's points."""
408 print("\tReading Layer ("+object_layers[-1].name+") Points")
409 offset= 0
410 chunk_len= len(pnt_bytes)
412 while offset < chunk_len:
413 pnts= struct.unpack(">fff", pnt_bytes[offset:offset+12])
414 offset+= 12
415 # Re-order the points so that the mesh has the right pitch,
416 # the pivot already has the correct order.
417 pnts= [pnts[0] - object_layers[-1].pivot[0],\
418 pnts[2] - object_layers[-1].pivot[1],\
419 pnts[1] - object_layers[-1].pivot[2]]
420 object_layers[-1].pnts.append(pnts)
423 def read_weightmap(weight_bytes, object_layers):
424 """Read a weight map's values."""
425 chunk_len= len(weight_bytes)
426 offset= 2
427 name, name_len= read_lwostring(weight_bytes[offset:])
428 offset+= name_len
429 weights= []
431 while offset < chunk_len:
432 pnt_id, pnt_id_len= read_vx(weight_bytes[offset:offset+4])
433 offset+= pnt_id_len
434 value,= struct.unpack(">f", weight_bytes[offset:offset+4])
435 offset+= 4
436 weights.append([pnt_id, value])
438 object_layers[-1].wmaps[name]= weights
441 def read_morph(morph_bytes, object_layers, is_abs):
442 """Read an endomorph's relative or absolute displacement values."""
443 chunk_len= len(morph_bytes)
444 offset= 2
445 name, name_len= read_lwostring(morph_bytes[offset:])
446 offset+= name_len
447 deltas= []
449 while offset < chunk_len:
450 pnt_id, pnt_id_len= read_vx(morph_bytes[offset:offset+4])
451 offset+= pnt_id_len
452 pos= struct.unpack(">fff", morph_bytes[offset:offset+12])
453 offset+= 12
454 pnt= object_layers[-1].pnts[pnt_id]
456 if is_abs:
457 deltas.append([pnt_id, pos[0], pos[2], pos[1]])
458 else:
459 # Swap the Y and Z to match Blender's pitch.
460 deltas.append([pnt_id, pnt[0]+pos[0], pnt[1]+pos[2], pnt[2]+pos[1]])
462 object_layers[-1].morphs[name]= deltas
465 def read_colmap(col_bytes, object_layers):
466 """Read the RGB or RGBA color map."""
467 chunk_len= len(col_bytes)
468 dia,= struct.unpack(">H", col_bytes[0:2])
469 offset= 2
470 name, name_len= read_lwostring(col_bytes[offset:])
471 offset+= name_len
472 colors= {}
474 if dia == 3:
475 while offset < chunk_len:
476 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
477 offset+= pnt_id_len
478 col= struct.unpack(">fff", col_bytes[offset:offset+12])
479 offset+= 12
480 colors[pnt_id]= (col[0], col[1], col[2])
481 elif dia == 4:
482 while offset < chunk_len:
483 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
484 offset+= pnt_id_len
485 col= struct.unpack(">ffff", col_bytes[offset:offset+16])
486 offset+= 16
487 colors[pnt_id]= (col[0], col[1], col[2])
489 if name in object_layers[-1].colmaps:
490 if "PointMap" in object_layers[-1].colmaps[name]:
491 object_layers[-1].colmaps[name]["PointMap"].update(colors)
492 else:
493 object_layers[-1].colmaps[name]["PointMap"]= colors
494 else:
495 object_layers[-1].colmaps[name]= dict(PointMap=colors)
498 def read_color_vmad(col_bytes, object_layers, last_pols_count):
499 """Read the Discontinous (per-polygon) RGB values."""
500 chunk_len= len(col_bytes)
501 dia,= struct.unpack(">H", col_bytes[0:2])
502 offset= 2
503 name, name_len= read_lwostring(col_bytes[offset:])
504 offset+= name_len
505 colors= {}
506 abs_pid= len(object_layers[-1].pols) - last_pols_count
508 if dia == 3:
509 while offset < chunk_len:
510 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
511 offset+= pnt_id_len
512 pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
513 offset+= pol_id_len
515 # The PolyID in a VMAD can be relative, this offsets it.
516 pol_id+= abs_pid
517 col= struct.unpack(">fff", col_bytes[offset:offset+12])
518 offset+= 12
519 if pol_id in colors:
520 colors[pol_id][pnt_id]= (col[0], col[1], col[2])
521 else:
522 colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
523 elif dia == 4:
524 while offset < chunk_len:
525 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
526 offset+= pnt_id_len
527 pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
528 offset+= pol_id_len
530 pol_id+= abs_pid
531 col= struct.unpack(">ffff", col_bytes[offset:offset+16])
532 offset+= 16
533 if pol_id in colors:
534 colors[pol_id][pnt_id]= (col[0], col[1], col[2])
535 else:
536 colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
538 if name in object_layers[-1].colmaps:
539 if "FaceMap" in object_layers[-1].colmaps[name]:
540 object_layers[-1].colmaps[name]["FaceMap"].update(colors)
541 else:
542 object_layers[-1].colmaps[name]["FaceMap"]= colors
543 else:
544 object_layers[-1].colmaps[name]= dict(FaceMap=colors)
547 def read_uvmap(uv_bytes, object_layers):
548 """Read the simple UV coord values."""
549 chunk_len= len(uv_bytes)
550 offset= 2
551 name, name_len= read_lwostring(uv_bytes[offset:])
552 offset+= name_len
553 uv_coords= {}
555 while offset < chunk_len:
556 pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
557 offset+= pnt_id_len
558 pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
559 offset+= 8
560 uv_coords[pnt_id]= (pos[0], pos[1])
562 if name in object_layers[-1].uvmaps:
563 if "PointMap" in object_layers[-1].uvmaps[name]:
564 object_layers[-1].uvmaps[name]["PointMap"].update(uv_coords)
565 else:
566 object_layers[-1].uvmaps[name]["PointMap"]= uv_coords
567 else:
568 object_layers[-1].uvmaps[name]= dict(PointMap=uv_coords)
571 def read_uv_vmad(uv_bytes, object_layers, last_pols_count):
572 """Read the Discontinous (per-polygon) uv values."""
573 chunk_len= len(uv_bytes)
574 offset= 2
575 name, name_len= read_lwostring(uv_bytes[offset:])
576 offset+= name_len
577 uv_coords= {}
578 abs_pid= len(object_layers[-1].pols) - last_pols_count
580 while offset < chunk_len:
581 pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
582 offset+= pnt_id_len
583 pol_id, pol_id_len= read_vx(uv_bytes[offset:offset+4])
584 offset+= pol_id_len
586 pol_id+= abs_pid
587 pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
588 offset+= 8
589 if pol_id in uv_coords:
590 uv_coords[pol_id][pnt_id]= (pos[0], pos[1])
591 else:
592 uv_coords[pol_id]= dict({pnt_id: (pos[0], pos[1])})
594 if name in object_layers[-1].uvmaps:
595 if "FaceMap" in object_layers[-1].uvmaps[name]:
596 object_layers[-1].uvmaps[name]["FaceMap"].update(uv_coords)
597 else:
598 object_layers[-1].uvmaps[name]["FaceMap"]= uv_coords
599 else:
600 object_layers[-1].uvmaps[name]= dict(FaceMap=uv_coords)
603 def read_weight_vmad(ew_bytes, object_layers):
604 """Read the VMAD Weight values."""
605 chunk_len= len(ew_bytes)
606 offset= 2
607 name, name_len= read_lwostring(ew_bytes[offset:])
608 if name != "Edge Weight":
609 return # We just want the Catmull-Clark edge weights
611 offset+= name_len
612 # Some info: LW stores a face's points in a clock-wize order (with the
613 # normal pointing at you). This gives edges a 'direction' which is used
614 # when it comes to storing CC edge weight values. The weight is given
615 # to the point preceding the edge that the weight belongs to.
616 while offset < chunk_len:
617 pnt_id, pnt_id_len = read_vx(ew_bytes[offset:offset+4])
618 offset+= pnt_id_len
619 pol_id, pol_id_len= read_vx(ew_bytes[offset:offset+4])
620 offset+= pol_id_len
621 weight,= struct.unpack(">f", ew_bytes[offset:offset+4])
622 offset+= 4
624 face_pnts= object_layers[-1].pols[pol_id]
625 try:
626 # Find the point's location in the polygon's point list
627 first_idx= face_pnts.index(pnt_id)
628 except:
629 continue
631 # Then get the next point in the list, or wrap around to the first
632 if first_idx == len(face_pnts) - 1:
633 second_pnt= face_pnts[0]
634 else:
635 second_pnt= face_pnts[first_idx + 1]
637 object_layers[-1].edge_weights["{0} {1}".format(second_pnt, pnt_id)]= weight
640 def read_pols(pol_bytes, object_layers):
641 """Read the layer's polygons, each one is just a list of point indexes."""
642 print("\tReading Layer ("+object_layers[-1].name+") Polygons")
643 offset= 0
644 pols_count = len(pol_bytes)
645 old_pols_count= len(object_layers[-1].pols)
647 while offset < pols_count:
648 pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
649 offset+= 2
650 all_face_pnts= []
651 for j in range(pnts_count):
652 face_pnt, data_size= read_vx(pol_bytes[offset:offset+4])
653 offset+= data_size
654 all_face_pnts.append(face_pnt)
656 object_layers[-1].pols.append(all_face_pnts)
658 return len(object_layers[-1].pols) - old_pols_count
661 def read_pols_5(pol_bytes, object_layers):
663 Read the polygons, each one is just a list of point indexes.
664 But it also includes the surface index.
666 print("\tReading Layer ("+object_layers[-1].name+") Polygons")
667 offset= 0
668 chunk_len= len(pol_bytes)
669 old_pols_count= len(object_layers[-1].pols)
670 poly= 0
672 while offset < chunk_len:
673 pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
674 offset+= 2
675 all_face_pnts= []
676 for j in range(pnts_count):
677 face_pnt,= struct.unpack(">H", pol_bytes[offset:offset+2])
678 offset+= 2
679 all_face_pnts.append(face_pnt)
681 object_layers[-1].pols.append(all_face_pnts)
682 sid,= struct.unpack(">h", pol_bytes[offset:offset+2])
683 offset+= 2
684 sid= abs(sid) - 1
685 if sid not in object_layers[-1].surf_tags:
686 object_layers[-1].surf_tags[sid]= []
687 object_layers[-1].surf_tags[sid].append(poly)
688 poly+= 1
690 return len(object_layers[-1].pols) - old_pols_count
693 def read_bones(bone_bytes, object_layers):
694 """Read the layer's skelegons."""
695 print("\tReading Layer ("+object_layers[-1].name+") Bones")
696 offset= 0
697 bones_count = len(bone_bytes)
699 while offset < bones_count:
700 pnts_count,= struct.unpack(">H", bone_bytes[offset:offset+2])
701 offset+= 2
702 all_bone_pnts= []
703 for j in range(pnts_count):
704 bone_pnt, data_size= read_vx(bone_bytes[offset:offset+4])
705 offset+= data_size
706 all_bone_pnts.append(bone_pnt)
708 object_layers[-1].bones.append(all_bone_pnts)
711 def read_bone_tags(tag_bytes, object_layers, object_tags, type):
712 """Read the bone name or roll tags."""
713 offset= 0
714 chunk_len= len(tag_bytes)
716 if type == 'BONE':
717 bone_dict= object_layers[-1].bone_names
718 elif type == 'BNUP':
719 bone_dict= object_layers[-1].bone_rolls
720 else:
721 return
723 while offset < chunk_len:
724 pid, pid_len= read_vx(tag_bytes[offset:offset+4])
725 offset+= pid_len
726 tid,= struct.unpack(">H", tag_bytes[offset:offset+2])
727 offset+= 2
728 bone_dict[pid]= object_tags[tid]
731 def read_surf_tags(tag_bytes, object_layers, last_pols_count):
732 """Read the list of PolyIDs and tag indexes."""
733 print("\tReading Layer ("+object_layers[-1].name+") Surface Assignments")
734 offset= 0
735 chunk_len= len(tag_bytes)
737 # Read in the PolyID/Surface Index pairs.
738 abs_pid= len(object_layers[-1].pols) - last_pols_count
739 while offset < chunk_len:
740 pid, pid_len= read_vx(tag_bytes[offset:offset+4])
741 offset+= pid_len
742 sid,= struct.unpack(">H", tag_bytes[offset:offset+2])
743 offset+=2
744 if sid not in object_layers[-1].surf_tags:
745 object_layers[-1].surf_tags[sid]= []
746 object_layers[-1].surf_tags[sid].append(pid + abs_pid)
749 def read_surf(surf_bytes, object_surfs):
750 """Read the object's surface data."""
751 if len(object_surfs) == 0:
752 print("Reading Object Surfaces")
754 surf= _obj_surf()
755 name, name_len= read_lwostring(surf_bytes)
756 if len(name) != 0:
757 surf.name = name
759 # We have to read this, but we won't use it...yet.
760 s_name, s_name_len= read_lwostring(surf_bytes[name_len:])
761 offset= name_len+s_name_len
762 block_size= len(surf_bytes)
763 while offset < block_size:
764 subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
765 offset+= 4
766 subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
767 offset+= 2
769 # Now test which subchunk it is.
770 if subchunk_name == b'COLR':
771 surf.colr= struct.unpack(">fff", surf_bytes[offset:offset+12])
772 # Don't bother with any envelopes for now.
774 elif subchunk_name == b'DIFF':
775 surf.diff,= struct.unpack(">f", surf_bytes[offset:offset+4])
777 elif subchunk_name == b'LUMI':
778 surf.lumi,= struct.unpack(">f", surf_bytes[offset:offset+4])
780 elif subchunk_name == b'SPEC':
781 surf.spec,= struct.unpack(">f", surf_bytes[offset:offset+4])
783 elif subchunk_name == b'REFL':
784 surf.refl,= struct.unpack(">f", surf_bytes[offset:offset+4])
786 elif subchunk_name == b'RBLR':
787 surf.rblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
789 elif subchunk_name == b'TRAN':
790 surf.tran,= struct.unpack(">f", surf_bytes[offset:offset+4])
792 elif subchunk_name == b'RIND':
793 surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
795 elif subchunk_name == b'TBLR':
796 surf.tblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
798 elif subchunk_name == b'TRNL':
799 surf.trnl,= struct.unpack(">f", surf_bytes[offset:offset+4])
801 elif subchunk_name == b'GLOS':
802 surf.glos,= struct.unpack(">f", surf_bytes[offset:offset+4])
804 elif subchunk_name == b'SHRP':
805 surf.shrp,= struct.unpack(">f", surf_bytes[offset:offset+4])
807 elif subchunk_name == b'SMAN':
808 s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
809 if s_angle > 0.0:
810 surf.smooth = True
812 offset+= subchunk_len
814 object_surfs[surf.name]= surf
817 def read_surf_5(surf_bytes, object_surfs):
818 """Read the object's surface data."""
819 if len(object_surfs) == 0:
820 print("Reading Object Surfaces")
822 surf= _obj_surf()
823 name, name_len= read_lwostring(surf_bytes)
824 if len(name) != 0:
825 surf.name = name
827 offset= name_len
828 chunk_len= len(surf_bytes)
829 while offset < chunk_len:
830 subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
831 offset+= 4
832 subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
833 offset+= 2
835 # Now test which subchunk it is.
836 if subchunk_name == b'COLR':
837 color= struct.unpack(">BBBB", surf_bytes[offset:offset+4])
838 surf.colr= [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0]
840 elif subchunk_name == b'DIFF':
841 surf.diff,= struct.unpack(">h", surf_bytes[offset:offset+2])
842 surf.diff/= 256.0 # Yes, 256 not 255.
844 elif subchunk_name == b'LUMI':
845 surf.lumi,= struct.unpack(">h", surf_bytes[offset:offset+2])
846 surf.lumi/= 256.0
848 elif subchunk_name == b'SPEC':
849 surf.spec,= struct.unpack(">h", surf_bytes[offset:offset+2])
850 surf.spec/= 256.0
852 elif subchunk_name == b'REFL':
853 surf.refl,= struct.unpack(">h", surf_bytes[offset:offset+2])
854 surf.refl/= 256.0
856 elif subchunk_name == b'TRAN':
857 surf.tran,= struct.unpack(">h", surf_bytes[offset:offset+2])
858 surf.tran/= 256.0
860 elif subchunk_name == b'RIND':
861 surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
863 elif subchunk_name == b'GLOS':
864 surf.glos,= struct.unpack(">h", surf_bytes[offset:offset+2])
866 elif subchunk_name == b'SMAN':
867 s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
868 if s_angle > 0.0:
869 surf.smooth = True
871 offset+= subchunk_len
873 object_surfs[surf.name]= surf
876 def create_mappack(data, map_name, map_type):
877 """Match the map data to faces."""
878 pack= {}
880 def color_pointmap(map):
881 for fi in range(len(data.pols)):
882 if fi not in pack:
883 pack[fi]= []
884 for pnt in data.pols[fi]:
885 if pnt in map:
886 pack[fi].append(map[pnt])
887 else:
888 pack[fi].append((1.0, 1.0, 1.0))
890 def color_facemap(map):
891 for fi in range(len(data.pols)):
892 if fi not in pack:
893 pack[fi]= []
894 for p in data.pols[fi]:
895 pack[fi].append((1.0, 1.0, 1.0))
896 if fi in map:
897 for po in range(len(data.pols[fi])):
898 if data.pols[fi][po] in map[fi]:
899 pack[fi].insert(po, map[fi][data.pols[fi][po]])
900 del pack[fi][po+1]
902 def uv_pointmap(map):
903 for fi in range(len(data.pols)):
904 if fi not in pack:
905 pack[fi]= []
906 for p in data.pols[fi]:
907 pack[fi].append((-0.1,-0.1))
908 for po in range(len(data.pols[fi])):
909 pnt_id= data.pols[fi][po]
910 if pnt_id in map:
911 pack[fi].insert(po, map[pnt_id])
912 del pack[fi][po+1]
914 def uv_facemap(map):
915 for fi in range(len(data.pols)):
916 if fi not in pack:
917 pack[fi]= []
918 for p in data.pols[fi]:
919 pack[fi].append((-0.1,-0.1))
920 if fi in map:
921 for po in range(len(data.pols[fi])):
922 pnt_id= data.pols[fi][po]
923 if pnt_id in map[fi]:
924 pack[fi].insert(po, map[fi][pnt_id])
925 del pack[fi][po+1]
927 if map_type == "COLOR":
928 # Look at the first map, is it a point or face map
929 if "PointMap" in data.colmaps[map_name]:
930 color_pointmap(data.colmaps[map_name]["PointMap"])
932 if "FaceMap" in data.colmaps[map_name]:
933 color_facemap(data.colmaps[map_name]["FaceMap"])
934 elif map_type == "UV":
935 if "PointMap" in data.uvmaps[map_name]:
936 uv_pointmap(data.uvmaps[map_name]["PointMap"])
938 if "FaceMap" in data.uvmaps[map_name]:
939 uv_facemap(data.uvmaps[map_name]["FaceMap"])
941 return pack
944 def build_armature(layer_data, bones):
945 """Build an armature from the skelegon data in the mesh."""
946 print("Building Armature")
948 # New Armatures include a default bone, remove it.
949 bones.remove(bones[0])
951 # Now start adding the bones at the point locations.
952 prev_bone= None
953 for skb_idx in range(len(layer_data.bones)):
954 if skb_idx in layer_data.bone_names:
955 nb= bones.new(layer_data.bone_names[skb_idx])
956 else:
957 nb= bones.new("Bone")
959 nb.head= layer_data.pnts[layer_data.bones[skb_idx][0]]
960 nb.tail= layer_data.pnts[layer_data.bones[skb_idx][1]]
962 if skb_idx in layer_data.bone_rolls:
963 xyz= layer_data.bone_rolls[skb_idx].split(' ')
964 vec= mathutils.Vector((float(xyz[0]), float(xyz[1]), float(xyz[2])))
965 quat= vec.to_track_quat('Y', 'Z')
966 nb.roll= max(quat.to_euler('YZX'))
967 if nb.roll == 0.0:
968 nb.roll= min(quat.to_euler('YZX')) * -1
969 # YZX order seems to produce the correct roll value.
970 else:
971 nb.roll= 0.0
973 if prev_bone != None:
974 if nb.head == prev_bone.tail:
975 nb.parent= prev_bone
977 nb.use_connect= True
978 prev_bone= nb
981 def build_objects(object_layers, object_surfs, object_tags, object_name, add_subd_mod, skel_to_arm):
982 """Using the gathered data, create the objects."""
983 ob_dict= {} # Used for the parenting setup.
984 print("Adding %d Materials" % len(object_surfs))
986 for surf_key in object_surfs:
987 surf_data= object_surfs[surf_key]
988 surf_data.bl_mat= bpy.data.materials.new(surf_data.name)
989 surf_data.bl_mat.diffuse_color= (surf_data.colr[:])
990 surf_data.bl_mat.diffuse_intensity= surf_data.diff
991 surf_data.bl_mat.emit= surf_data.lumi
992 surf_data.bl_mat.specular_intensity= surf_data.spec
993 if surf_data.refl != 0.0:
994 surf_data.bl_mat.raytrace_mirror.use= True
995 surf_data.bl_mat.raytrace_mirror.reflect_factor= surf_data.refl
996 surf_data.bl_mat.raytrace_mirror.gloss_factor= 1.0-surf_data.rblr
997 if surf_data.tran != 0.0:
998 surf_data.bl_mat.use_transparency= True
999 surf_data.bl_mat.transparency_method= 'RAYTRACE'
1000 surf_data.bl_mat.alpha= 1.0 - surf_data.tran
1001 surf_data.bl_mat.raytrace_transparency.ior= surf_data.rind
1002 surf_data.bl_mat.raytrace_transparency.gloss_factor= 1.0 - surf_data.tblr
1003 surf_data.bl_mat.translucency= surf_data.trnl
1004 surf_data.bl_mat.specular_hardness= int(4*((10*surf_data.glos)*(10*surf_data.glos)))+4
1005 # The Gloss is as close as possible given the differences.
1007 # Single layer objects use the object file's name instead.
1008 if len(object_layers) and object_layers[-1].name == 'Layer 1':
1009 object_layers[-1].name= object_name
1010 print("Building '%s' Object" % object_name)
1011 else:
1012 print("Building %d Objects" % len(object_layers))
1014 # Before adding any meshes or armatures go into Object mode.
1015 if bpy.ops.object.mode_set.poll():
1016 bpy.ops.object.mode_set(mode='OBJECT')
1018 for layer_data in object_layers:
1019 me= bpy.data.meshes.new(layer_data.name)
1020 me.vertices.add(len(layer_data.pnts))
1021 me.tessfaces.add(len(layer_data.pols))
1022 # for vi in range(len(layer_data.pnts)):
1023 # me.vertices[vi].co= layer_data.pnts[vi]
1025 # faster, would be faster again to use an array
1026 me.vertices.foreach_set("co", [axis for co in layer_data.pnts for axis in co])
1028 ngons= {} # To keep the FaceIdx consistent, handle NGons later.
1029 edges= [] # Holds the FaceIdx of the 2-point polys.
1030 for fi, fpol in enumerate(layer_data.pols):
1031 fpol.reverse() # Reversing gives correct normal directions
1032 # PointID 0 in the last element causes Blender to think it's un-used.
1033 if fpol[-1] == 0:
1034 fpol.insert(0, fpol[-1])
1035 del fpol[-1]
1037 vlen= len(fpol)
1038 if vlen == 3 or vlen == 4:
1039 for i in range(vlen):
1040 me.tessfaces[fi].vertices_raw[i]= fpol[i]
1041 elif vlen == 2:
1042 edges.append(fi)
1043 elif vlen != 1:
1044 ngons[fi]= fpol # Deal with them later
1046 ob= bpy.data.objects.new(layer_data.name, me)
1047 bpy.context.scene.objects.link(ob)
1048 ob_dict[layer_data.index]= [ob, layer_data.parent_index]
1050 # Move the object so the pivot is in the right place.
1051 ob.location= layer_data.pivot
1053 # Create the Material Slots and assign the MatIndex to the correct faces.
1054 mat_slot= 0
1055 for surf_key in layer_data.surf_tags:
1056 if object_tags[surf_key] in object_surfs:
1057 me.materials.append(object_surfs[object_tags[surf_key]].bl_mat)
1059 for fi in layer_data.surf_tags[surf_key]:
1060 me.tessfaces[fi].material_index= mat_slot
1061 me.tessfaces[fi].use_smooth= object_surfs[object_tags[surf_key]].smooth
1063 mat_slot+=1
1065 # Create the Vertex Groups (LW's Weight Maps).
1066 if len(layer_data.wmaps) > 0:
1067 print("Adding %d Vertex Groups" % len(layer_data.wmaps))
1068 for wmap_key in layer_data.wmaps:
1069 vgroup= ob.vertex_groups.new()
1070 vgroup.name= wmap_key
1071 wlist= layer_data.wmaps[wmap_key]
1072 for pvp in wlist:
1073 vgroup.add((pvp[0], ), pvp[1], 'REPLACE')
1075 # Create the Shape Keys (LW's Endomorphs).
1076 if len(layer_data.morphs) > 0:
1077 print("Adding %d Shapes Keys" % len(layer_data.morphs))
1078 ob.shape_key_add('Basis') # Got to have a Base Shape.
1079 for morph_key in layer_data.morphs:
1080 skey= ob.shape_key_add(morph_key)
1081 dlist= layer_data.morphs[morph_key]
1082 for pdp in dlist:
1083 me.shape_keys.key_blocks[skey.name].data[pdp[0]].co= [pdp[1], pdp[2], pdp[3]]
1085 # Create the Vertex Color maps.
1086 if len(layer_data.colmaps) > 0:
1087 print("Adding %d Vertex Color Maps" % len(layer_data.colmaps))
1088 for cmap_key in layer_data.colmaps:
1089 map_pack= create_mappack(layer_data, cmap_key, "COLOR")
1090 me.vertex_colors.new(cmap_key)
1091 vcol= me.tessface_vertex_colors[-1]
1092 if not vcol or not vcol.data:
1093 break
1094 for fi in map_pack:
1095 if fi > len(vcol.data):
1096 continue
1097 face= map_pack[fi]
1098 colf= vcol.data[fi]
1100 if len(face) > 2:
1101 colf.color1= face[0]
1102 colf.color2= face[1]
1103 colf.color3= face[2]
1104 if len(face) == 4:
1105 colf.color4= face[3]
1107 # Create the UV Maps.
1108 if len(layer_data.uvmaps) > 0:
1109 print("Adding %d UV Textures" % len(layer_data.uvmaps))
1110 for uvmap_key in layer_data.uvmaps:
1111 map_pack= create_mappack(layer_data, uvmap_key, "UV")
1112 me.uv_textures.new(name=uvmap_key)
1113 uvm= me.tessface_uv_textures[-1]
1114 if not uvm or not uvm.data:
1115 break
1116 for fi in map_pack:
1117 if fi > len(uvm.data):
1118 continue
1119 face= map_pack[fi]
1120 uvf= uvm.data[fi]
1122 if len(face) > 2:
1123 uvf.uv1= face[0]
1124 uvf.uv2= face[1]
1125 uvf.uv3= face[2]
1126 if len(face) == 4:
1127 uvf.uv4= face[3]
1129 # Now add the NGons.
1130 if len(ngons) > 0:
1131 for ng_key in ngons:
1132 face_offset= len(me.tessfaces)
1133 ng= ngons[ng_key]
1134 v_locs= []
1135 for vi in range(len(ng)):
1136 v_locs.append(mathutils.Vector(layer_data.pnts[ngons[ng_key][vi]]))
1137 tris= tessellate_polygon([v_locs])
1138 me.tessfaces.add(len(tris))
1139 for tri in tris:
1140 face= me.tessfaces[face_offset]
1141 face.vertices_raw[0]= ng[tri[0]]
1142 face.vertices_raw[1]= ng[tri[1]]
1143 face.vertices_raw[2]= ng[tri[2]]
1144 face.material_index= me.tessfaces[ng_key].material_index
1145 face.use_smooth= me.tessfaces[ng_key].use_smooth
1146 face_offset+= 1
1148 # FaceIDs are no longer a concern, so now update the mesh.
1149 has_edges= len(edges) > 0 or len(layer_data.edge_weights) > 0
1150 me.update(calc_edges=has_edges)
1152 # Add the edges.
1153 edge_offset= len(me.edges)
1154 me.edges.add(len(edges))
1155 for edge_fi in edges:
1156 me.edges[edge_offset].vertices[0]= layer_data.pols[edge_fi][0]
1157 me.edges[edge_offset].vertices[1]= layer_data.pols[edge_fi][1]
1158 edge_offset+= 1
1160 # Apply the Edge Weighting.
1161 if len(layer_data.edge_weights) > 0:
1162 for edge in me.edges:
1163 edge_sa= "{0} {1}".format(edge.vertices[0], edge.vertices[1])
1164 edge_sb= "{0} {1}".format(edge.vertices[1], edge.vertices[0])
1165 if edge_sa in layer_data.edge_weights:
1166 edge.crease= layer_data.edge_weights[edge_sa]
1167 elif edge_sb in layer_data.edge_weights:
1168 edge.crease= layer_data.edge_weights[edge_sb]
1170 # Unfortunately we can't exlude certain faces from the subdivision.
1171 if layer_data.has_subds and add_subd_mod:
1172 ob.modifiers.new(name="Subsurf", type='SUBSURF')
1174 # Should we build an armature from the embedded rig?
1175 if len(layer_data.bones) > 0 and skel_to_arm:
1176 bpy.ops.object.armature_add()
1177 arm_object= bpy.context.active_object
1178 arm_object.name= "ARM_" + layer_data.name
1179 arm_object.data.name= arm_object.name
1180 arm_object.location= layer_data.pivot
1181 bpy.ops.object.mode_set(mode='EDIT')
1182 build_armature(layer_data, arm_object.data.edit_bones)
1183 bpy.ops.object.mode_set(mode='OBJECT')
1185 # Clear out the dictionaries for this layer.
1186 layer_data.bone_names.clear()
1187 layer_data.bone_rolls.clear()
1188 layer_data.wmaps.clear()
1189 layer_data.colmaps.clear()
1190 layer_data.uvmaps.clear()
1191 layer_data.morphs.clear()
1192 layer_data.surf_tags.clear()
1194 # We may have some invalid mesh data, See: [#27916]
1195 # keep this last!
1196 print("validating mesh: %r..." % me.name)
1197 me.validate(verbose=1)
1198 print("done!")
1200 # With the objects made, setup the parents and re-adjust the locations.
1201 for ob_key in ob_dict:
1202 if ob_dict[ob_key][1] != -1 and ob_dict[ob_key][1] in ob_dict:
1203 parent_ob = ob_dict[ob_dict[ob_key][1]]
1204 ob_dict[ob_key][0].parent= parent_ob[0]
1205 ob_dict[ob_key][0].location-= parent_ob[0].location
1207 bpy.context.scene.update()
1209 print("Done Importing LWO File")
1212 from bpy.props import StringProperty, BoolProperty
1215 class IMPORT_OT_lwo(bpy.types.Operator):
1216 """Import LWO Operator"""
1217 bl_idname= "import_scene.lwo"
1218 bl_label= "Import LWO"
1219 bl_description= "Import a LightWave Object file"
1220 bl_options= {'REGISTER', 'UNDO'}
1222 filepath= StringProperty(name="File Path", description="Filepath used for importing the LWO file", maxlen=1024, default="")
1224 ADD_SUBD_MOD= BoolProperty(name="Apply SubD Modifier", description="Apply the Subdivision Surface modifier to layers with Subpatches", default=True)
1225 LOAD_HIDDEN= BoolProperty(name="Load Hidden Layers", description="Load object layers that have been marked as hidden", default=False)
1226 SKEL_TO_ARM= BoolProperty(name="Create Armature", description="Create an armature from an embedded Skelegon rig", default=True)
1228 def execute(self, context):
1229 load_lwo(self.filepath,
1230 context,
1231 self.ADD_SUBD_MOD,
1232 self.LOAD_HIDDEN,
1233 self.SKEL_TO_ARM)
1234 return {'FINISHED'}
1236 def invoke(self, context, event):
1237 wm= context.window_manager
1238 wm.fileselect_add(self)
1239 return {'RUNNING_MODAL'}
1242 def menu_func(self, context):
1243 self.layout.operator(IMPORT_OT_lwo.bl_idname, text="LightWave Object (.lwo)")
1246 def register():
1247 bpy.utils.register_module(__name__)
1249 bpy.types.INFO_MT_file_import.append(menu_func)
1252 def unregister():
1253 bpy.utils.unregister_module(__name__)
1255 bpy.types.INFO_MT_file_import.remove(menu_func)
1257 if __name__ == "__main__":
1258 register()