fix [#36995] FBX Importer does not import fbx model
[blender-addons.git] / io_import_scene_lwo.py
blobe711718d7a77428f30a80cabc63238952e503ee3
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://projects.blender.org/tracker/index.php?"\
31 "func=detail&aid=23623",
32 "category": "Import-Export"}
34 # Copyright (c) Ken Nign 2010
35 # ken@virginpi.com
37 # Version 1.3 - Aug 11, 2011
39 # Loads a LightWave .lwo object file, including the vertex maps such as
40 # UV, Morph, Color and Weight maps.
42 # Will optionally create an Armature from an embedded Skelegon rig.
44 # Point orders are maintained so that .mdds can exchanged with other
45 # 3D programs.
48 # Notes:
49 # NGons, polygons with more than 4 points are supported, but are
50 # added (as triangles) after the vertex maps have been applied. Thus they
51 # won't contain all the vertex data that the original ngon had.
53 # Blender is limited to only 8 UV Texture and 8 Vertex Color maps,
54 # thus only the first 8 of each can be imported.
56 # History:
58 # 1.3 Fixed CC Edge Weight loading.
60 # 1.2 Added Absolute Morph and CC Edge Weight support.
61 # Made edge creation safer.
62 # 1.0 First Release
65 import os
66 import struct
67 import chunk
69 import bpy
70 import mathutils
71 from mathutils.geometry import tessellate_polygon
74 class _obj_layer(object):
75 __slots__ = (
76 "name",
77 "index",
78 "parent_index",
79 "pivot",
80 "pols",
81 "bones",
82 "bone_names",
83 "bone_rolls",
84 "pnts",
85 "wmaps",
86 "colmaps",
87 "uvmaps",
88 "morphs",
89 "edge_weights",
90 "surf_tags",
91 "has_subds",
93 def __init__(self):
94 self.name= ""
95 self.index= -1
96 self.parent_index= -1
97 self.pivot= [0, 0, 0]
98 self.pols= []
99 self.bones= []
100 self.bone_names= {}
101 self.bone_rolls= {}
102 self.pnts= []
103 self.wmaps= {}
104 self.colmaps= {}
105 self.uvmaps= {}
106 self.morphs= {}
107 self.edge_weights= {}
108 self.surf_tags= {}
109 self.has_subds= False
112 class _obj_surf(object):
113 __slots__ = (
114 "bl_mat",
115 "name",
116 "source_name",
117 "colr",
118 "diff",
119 "lumi",
120 "spec",
121 "refl",
122 "rblr",
123 "tran",
124 "rind",
125 "tblr",
126 "trnl",
127 "glos",
128 "shrp",
129 "smooth",
132 def __init__(self):
133 self.bl_mat= None
134 self.name= "Default"
135 self.source_name= ""
136 self.colr= [1.0, 1.0, 1.0]
137 self.diff= 1.0 # Diffuse
138 self.lumi= 0.0 # Luminosity
139 self.spec= 0.0 # Specular
140 self.refl= 0.0 # Reflectivity
141 self.rblr= 0.0 # Reflection Bluring
142 self.tran= 0.0 # Transparency (the opposite of Blender's Alpha value)
143 self.rind= 1.0 # RT Transparency IOR
144 self.tblr= 0.0 # Refraction Bluring
145 self.trnl= 0.0 # Translucency
146 self.glos= 0.4 # Glossiness
147 self.shrp= 0.0 # Diffuse Sharpness
148 self.smooth= False # Surface Smoothing
151 def load_lwo(filename,
152 context,
153 ADD_SUBD_MOD=True,
154 LOAD_HIDDEN=False,
155 SKEL_TO_ARM=True):
156 """Read the LWO file, hand off to version specific function."""
157 name, ext= os.path.splitext(os.path.basename(filename))
158 file= open(filename, 'rb')
160 try:
161 header, chunk_size, chunk_name = struct.unpack(">4s1L4s", file.read(12))
162 except:
163 print("Error parsing file header!")
164 file.close()
165 return
167 layers= []
168 surfs= {}
169 tags= []
170 # Gather the object data using the version specific handler.
171 if chunk_name == b'LWO2':
172 read_lwo2(file, filename, layers, surfs, tags, ADD_SUBD_MOD, LOAD_HIDDEN, SKEL_TO_ARM)
173 elif chunk_name == b'LWOB' or chunk_name == b'LWLO':
174 # LWOB and LWLO are the old format, LWLO is a layered object.
175 read_lwob(file, filename, layers, surfs, tags, ADD_SUBD_MOD)
176 else:
177 print("Not a supported file type!")
178 file.close()
179 return
181 file.close()
183 # With the data gathered, build the object(s).
184 build_objects(layers, surfs, tags, name, ADD_SUBD_MOD, SKEL_TO_ARM)
186 layers= None
187 surfs.clear()
188 tags= None
191 def read_lwo2(file, filename, layers, surfs, tags, add_subd_mod, load_hidden, skel_to_arm):
192 """Read version 2 file, LW 6+."""
193 handle_layer= True
194 last_pols_count= 0
195 just_read_bones= False
196 print("Importing LWO: " + filename + "\nLWO v2 Format")
198 while True:
199 try:
200 rootchunk = chunk.Chunk(file)
201 except EOFError:
202 break
204 if rootchunk.chunkname == b'TAGS':
205 read_tags(rootchunk.read(), tags)
206 elif rootchunk.chunkname == b'LAYR':
207 handle_layer= read_layr(rootchunk.read(), layers, load_hidden)
208 elif rootchunk.chunkname == b'PNTS' and handle_layer:
209 read_pnts(rootchunk.read(), layers)
210 elif rootchunk.chunkname == b'VMAP' and handle_layer:
211 vmap_type = rootchunk.read(4)
213 if vmap_type == b'WGHT':
214 read_weightmap(rootchunk.read(), layers)
215 elif vmap_type == b'MORF':
216 read_morph(rootchunk.read(), layers, False)
217 elif vmap_type == b'SPOT':
218 read_morph(rootchunk.read(), layers, True)
219 elif vmap_type == b'TXUV':
220 read_uvmap(rootchunk.read(), layers)
221 elif vmap_type == b'RGB ' or vmap_type == b'RGBA':
222 read_colmap(rootchunk.read(), layers)
223 else:
224 rootchunk.skip()
226 elif rootchunk.chunkname == b'VMAD' and handle_layer:
227 vmad_type= rootchunk.read(4)
229 if vmad_type == b'TXUV':
230 read_uv_vmad(rootchunk.read(), layers, last_pols_count)
231 elif vmad_type == b'RGB ' or vmad_type == b'RGBA':
232 read_color_vmad(rootchunk.read(), layers, last_pols_count)
233 elif vmad_type == b'WGHT':
234 # We only read the Edge Weight map if it's there.
235 read_weight_vmad(rootchunk.read(), layers)
236 else:
237 rootchunk.skip()
239 elif rootchunk.chunkname == b'POLS' and handle_layer:
240 face_type = rootchunk.read(4)
241 just_read_bones= False
242 # PTCH is LW's Subpatches, SUBD is CatmullClark.
243 if (face_type == b'FACE' or face_type == b'PTCH' or
244 face_type == b'SUBD') and handle_layer:
245 last_pols_count= read_pols(rootchunk.read(), layers)
246 if face_type != b'FACE':
247 layers[-1].has_subds= True
248 elif face_type == b'BONE' and handle_layer:
249 read_bones(rootchunk.read(), layers)
250 just_read_bones= True
251 else:
252 rootchunk.skip()
254 elif rootchunk.chunkname == b'PTAG' and handle_layer:
255 tag_type,= struct.unpack("4s", rootchunk.read(4))
256 if tag_type == b'SURF' and not just_read_bones:
257 # Ignore the surface data if we just read a bones chunk.
258 read_surf_tags(rootchunk.read(), layers, last_pols_count)
260 elif skel_to_arm:
261 if tag_type == b'BNUP':
262 read_bone_tags(rootchunk.read(), layers, tags, 'BNUP')
263 elif tag_type == b'BONE':
264 read_bone_tags(rootchunk.read(), layers, tags, 'BONE')
265 else:
266 rootchunk.skip()
267 else:
268 rootchunk.skip()
269 elif rootchunk.chunkname == b'SURF':
270 read_surf(rootchunk.read(), surfs)
271 else:
272 #if handle_layer:
273 #print("Skipping Chunk:", rootchunk.chunkname)
274 rootchunk.skip()
277 def read_lwob(file, filename, layers, surfs, tags, add_subd_mod):
278 """Read version 1 file, LW < 6."""
279 last_pols_count= 0
280 print("Importing LWO: " + filename + "\nLWO v1 Format")
282 while True:
283 try:
284 rootchunk = chunk.Chunk(file)
285 except EOFError:
286 break
288 if rootchunk.chunkname == b'SRFS':
289 read_tags(rootchunk.read(), tags)
290 elif rootchunk.chunkname == b'LAYR':
291 read_layr_5(rootchunk.read(), layers)
292 elif rootchunk.chunkname == b'PNTS':
293 if len(layers) == 0:
294 # LWOB files have no LAYR chunk to set this up.
295 nlayer= _obj_layer()
296 nlayer.name= "Layer 1"
297 layers.append(nlayer)
298 read_pnts(rootchunk.read(), layers)
299 elif rootchunk.chunkname == b'POLS':
300 last_pols_count= read_pols_5(rootchunk.read(), layers)
301 elif rootchunk.chunkname == b'PCHS':
302 last_pols_count= read_pols_5(rootchunk.read(), layers)
303 layers[-1].has_subds= True
304 elif rootchunk.chunkname == b'PTAG':
305 tag_type,= struct.unpack("4s", rootchunk.read(4))
306 if tag_type == b'SURF':
307 read_surf_tags_5(rootchunk.read(), layers, last_pols_count)
308 else:
309 rootchunk.skip()
310 elif rootchunk.chunkname == b'SURF':
311 read_surf_5(rootchunk.read(), surfs)
312 else:
313 # For Debugging \/.
314 #if handle_layer:
315 #print("Skipping Chunk: ", rootchunk.chunkname)
316 rootchunk.skip()
319 def read_lwostring(raw_name):
320 """Parse a zero-padded string."""
322 i = raw_name.find(b'\0')
323 name_len = i + 1
324 if name_len % 2 == 1: # Test for oddness.
325 name_len += 1
327 if i > 0:
328 # Some plugins put non-text strings in the tags chunk.
329 name = raw_name[0:i].decode("utf-8", "ignore")
330 else:
331 name = ""
333 return name, name_len
336 def read_vx(pointdata):
337 """Read a variable-length index."""
338 if pointdata[0] != 255:
339 index= pointdata[0]*256 + pointdata[1]
340 size= 2
341 else:
342 index= pointdata[1]*65536 + pointdata[2]*256 + pointdata[3]
343 size= 4
345 return index, size
348 def read_tags(tag_bytes, object_tags):
349 """Read the object's Tags chunk."""
350 offset= 0
351 chunk_len= len(tag_bytes)
353 while offset < chunk_len:
354 tag, tag_len= read_lwostring(tag_bytes[offset:])
355 offset+= tag_len
356 object_tags.append(tag)
359 def read_layr(layr_bytes, object_layers, load_hidden):
360 """Read the object's layer data."""
361 new_layr= _obj_layer()
362 new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
364 if flags > 0 and not load_hidden:
365 return False
367 print("Reading Object Layer")
368 offset= 4
369 pivot= struct.unpack(">fff", layr_bytes[offset:offset+12])
370 # Swap Y and Z to match Blender's pitch.
371 new_layr.pivot= [pivot[0], pivot[2], pivot[1]]
372 offset+= 12
373 layr_name, name_len = read_lwostring(layr_bytes[offset:])
374 offset+= name_len
376 if layr_name:
377 new_layr.name= layr_name
378 else:
379 new_layr.name= "Layer %d" % (new_layr.index + 1)
381 if len(layr_bytes) == offset+2:
382 new_layr.parent_index,= struct.unpack(">h", layr_bytes[offset:offset+2])
384 object_layers.append(new_layr)
385 return True
388 def read_layr_5(layr_bytes, object_layers):
389 """Read the object's layer data."""
390 # XXX: Need to check what these two exactly mean for a LWOB/LWLO file.
391 new_layr= _obj_layer()
392 new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
394 print("Reading Object Layer")
395 offset= 4
396 layr_name, name_len = read_lwostring(layr_bytes[offset:])
397 offset+= name_len
399 if name_len > 2 and layr_name != 'noname':
400 new_layr.name= layr_name
401 else:
402 new_layr.name= "Layer %d" % new_layr.index
404 object_layers.append(new_layr)
407 def read_pnts(pnt_bytes, object_layers):
408 """Read the layer's points."""
409 print("\tReading Layer ("+object_layers[-1].name+") Points")
410 offset= 0
411 chunk_len= len(pnt_bytes)
413 while offset < chunk_len:
414 pnts= struct.unpack(">fff", pnt_bytes[offset:offset+12])
415 offset+= 12
416 # Re-order the points so that the mesh has the right pitch,
417 # the pivot already has the correct order.
418 pnts= [pnts[0] - object_layers[-1].pivot[0],\
419 pnts[2] - object_layers[-1].pivot[1],\
420 pnts[1] - object_layers[-1].pivot[2]]
421 object_layers[-1].pnts.append(pnts)
424 def read_weightmap(weight_bytes, object_layers):
425 """Read a weight map's values."""
426 chunk_len= len(weight_bytes)
427 offset= 2
428 name, name_len= read_lwostring(weight_bytes[offset:])
429 offset+= name_len
430 weights= []
432 while offset < chunk_len:
433 pnt_id, pnt_id_len= read_vx(weight_bytes[offset:offset+4])
434 offset+= pnt_id_len
435 value,= struct.unpack(">f", weight_bytes[offset:offset+4])
436 offset+= 4
437 weights.append([pnt_id, value])
439 object_layers[-1].wmaps[name]= weights
442 def read_morph(morph_bytes, object_layers, is_abs):
443 """Read an endomorph's relative or absolute displacement values."""
444 chunk_len= len(morph_bytes)
445 offset= 2
446 name, name_len= read_lwostring(morph_bytes[offset:])
447 offset+= name_len
448 deltas= []
450 while offset < chunk_len:
451 pnt_id, pnt_id_len= read_vx(morph_bytes[offset:offset+4])
452 offset+= pnt_id_len
453 pos= struct.unpack(">fff", morph_bytes[offset:offset+12])
454 offset+= 12
455 pnt= object_layers[-1].pnts[pnt_id]
457 if is_abs:
458 deltas.append([pnt_id, pos[0], pos[2], pos[1]])
459 else:
460 # Swap the Y and Z to match Blender's pitch.
461 deltas.append([pnt_id, pnt[0]+pos[0], pnt[1]+pos[2], pnt[2]+pos[1]])
463 object_layers[-1].morphs[name]= deltas
466 def read_colmap(col_bytes, object_layers):
467 """Read the RGB or RGBA color map."""
468 chunk_len= len(col_bytes)
469 dia,= struct.unpack(">H", col_bytes[0:2])
470 offset= 2
471 name, name_len= read_lwostring(col_bytes[offset:])
472 offset+= name_len
473 colors= {}
475 if dia == 3:
476 while offset < chunk_len:
477 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
478 offset+= pnt_id_len
479 col= struct.unpack(">fff", col_bytes[offset:offset+12])
480 offset+= 12
481 colors[pnt_id]= (col[0], col[1], col[2])
482 elif dia == 4:
483 while offset < chunk_len:
484 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
485 offset+= pnt_id_len
486 col= struct.unpack(">ffff", col_bytes[offset:offset+16])
487 offset+= 16
488 colors[pnt_id]= (col[0], col[1], col[2])
490 if name in object_layers[-1].colmaps:
491 if "PointMap" in object_layers[-1].colmaps[name]:
492 object_layers[-1].colmaps[name]["PointMap"].update(colors)
493 else:
494 object_layers[-1].colmaps[name]["PointMap"]= colors
495 else:
496 object_layers[-1].colmaps[name]= dict(PointMap=colors)
499 def read_color_vmad(col_bytes, object_layers, last_pols_count):
500 """Read the Discontinous (per-polygon) RGB values."""
501 chunk_len= len(col_bytes)
502 dia,= struct.unpack(">H", col_bytes[0:2])
503 offset= 2
504 name, name_len= read_lwostring(col_bytes[offset:])
505 offset+= name_len
506 colors= {}
507 abs_pid= len(object_layers[-1].pols) - last_pols_count
509 if dia == 3:
510 while offset < chunk_len:
511 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
512 offset+= pnt_id_len
513 pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
514 offset+= pol_id_len
516 # The PolyID in a VMAD can be relative, this offsets it.
517 pol_id+= abs_pid
518 col= struct.unpack(">fff", col_bytes[offset:offset+12])
519 offset+= 12
520 if pol_id in colors:
521 colors[pol_id][pnt_id]= (col[0], col[1], col[2])
522 else:
523 colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
524 elif dia == 4:
525 while offset < chunk_len:
526 pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
527 offset+= pnt_id_len
528 pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
529 offset+= pol_id_len
531 pol_id+= abs_pid
532 col= struct.unpack(">ffff", col_bytes[offset:offset+16])
533 offset+= 16
534 if pol_id in colors:
535 colors[pol_id][pnt_id]= (col[0], col[1], col[2])
536 else:
537 colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
539 if name in object_layers[-1].colmaps:
540 if "FaceMap" in object_layers[-1].colmaps[name]:
541 object_layers[-1].colmaps[name]["FaceMap"].update(colors)
542 else:
543 object_layers[-1].colmaps[name]["FaceMap"]= colors
544 else:
545 object_layers[-1].colmaps[name]= dict(FaceMap=colors)
548 def read_uvmap(uv_bytes, object_layers):
549 """Read the simple UV coord values."""
550 chunk_len= len(uv_bytes)
551 offset= 2
552 name, name_len= read_lwostring(uv_bytes[offset:])
553 offset+= name_len
554 uv_coords= {}
556 while offset < chunk_len:
557 pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
558 offset+= pnt_id_len
559 pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
560 offset+= 8
561 uv_coords[pnt_id]= (pos[0], pos[1])
563 if name in object_layers[-1].uvmaps:
564 if "PointMap" in object_layers[-1].uvmaps[name]:
565 object_layers[-1].uvmaps[name]["PointMap"].update(uv_coords)
566 else:
567 object_layers[-1].uvmaps[name]["PointMap"]= uv_coords
568 else:
569 object_layers[-1].uvmaps[name]= dict(PointMap=uv_coords)
572 def read_uv_vmad(uv_bytes, object_layers, last_pols_count):
573 """Read the Discontinous (per-polygon) uv values."""
574 chunk_len= len(uv_bytes)
575 offset= 2
576 name, name_len= read_lwostring(uv_bytes[offset:])
577 offset+= name_len
578 uv_coords= {}
579 abs_pid= len(object_layers[-1].pols) - last_pols_count
581 while offset < chunk_len:
582 pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
583 offset+= pnt_id_len
584 pol_id, pol_id_len= read_vx(uv_bytes[offset:offset+4])
585 offset+= pol_id_len
587 pol_id+= abs_pid
588 pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
589 offset+= 8
590 if pol_id in uv_coords:
591 uv_coords[pol_id][pnt_id]= (pos[0], pos[1])
592 else:
593 uv_coords[pol_id]= dict({pnt_id: (pos[0], pos[1])})
595 if name in object_layers[-1].uvmaps:
596 if "FaceMap" in object_layers[-1].uvmaps[name]:
597 object_layers[-1].uvmaps[name]["FaceMap"].update(uv_coords)
598 else:
599 object_layers[-1].uvmaps[name]["FaceMap"]= uv_coords
600 else:
601 object_layers[-1].uvmaps[name]= dict(FaceMap=uv_coords)
604 def read_weight_vmad(ew_bytes, object_layers):
605 """Read the VMAD Weight values."""
606 chunk_len= len(ew_bytes)
607 offset= 2
608 name, name_len= read_lwostring(ew_bytes[offset:])
609 if name != "Edge Weight":
610 return # We just want the Catmull-Clark edge weights
612 offset+= name_len
613 # Some info: LW stores a face's points in a clock-wize order (with the
614 # normal pointing at you). This gives edges a 'direction' which is used
615 # when it comes to storing CC edge weight values. The weight is given
616 # to the point preceding the edge that the weight belongs to.
617 while offset < chunk_len:
618 pnt_id, pnt_id_len = read_vx(ew_bytes[offset:offset+4])
619 offset+= pnt_id_len
620 pol_id, pol_id_len= read_vx(ew_bytes[offset:offset+4])
621 offset+= pol_id_len
622 weight,= struct.unpack(">f", ew_bytes[offset:offset+4])
623 offset+= 4
625 face_pnts= object_layers[-1].pols[pol_id]
626 try:
627 # Find the point's location in the polygon's point list
628 first_idx= face_pnts.index(pnt_id)
629 except:
630 continue
632 # Then get the next point in the list, or wrap around to the first
633 if first_idx == len(face_pnts) - 1:
634 second_pnt= face_pnts[0]
635 else:
636 second_pnt= face_pnts[first_idx + 1]
638 object_layers[-1].edge_weights["{0} {1}".format(second_pnt, pnt_id)]= weight
641 def read_pols(pol_bytes, object_layers):
642 """Read the layer's polygons, each one is just a list of point indexes."""
643 print("\tReading Layer ("+object_layers[-1].name+") Polygons")
644 offset= 0
645 pols_count = len(pol_bytes)
646 old_pols_count= len(object_layers[-1].pols)
648 while offset < pols_count:
649 pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
650 offset+= 2
651 all_face_pnts= []
652 for j in range(pnts_count):
653 face_pnt, data_size= read_vx(pol_bytes[offset:offset+4])
654 offset+= data_size
655 all_face_pnts.append(face_pnt)
657 object_layers[-1].pols.append(all_face_pnts)
659 return len(object_layers[-1].pols) - old_pols_count
662 def read_pols_5(pol_bytes, object_layers):
664 Read the polygons, each one is just a list of point indexes.
665 But it also includes the surface index.
667 print("\tReading Layer ("+object_layers[-1].name+") Polygons")
668 offset= 0
669 chunk_len= len(pol_bytes)
670 old_pols_count= len(object_layers[-1].pols)
671 poly= 0
673 while offset < chunk_len:
674 pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
675 offset+= 2
676 all_face_pnts= []
677 for j in range(pnts_count):
678 face_pnt,= struct.unpack(">H", pol_bytes[offset:offset+2])
679 offset+= 2
680 all_face_pnts.append(face_pnt)
682 object_layers[-1].pols.append(all_face_pnts)
683 sid,= struct.unpack(">h", pol_bytes[offset:offset+2])
684 offset+= 2
685 sid= abs(sid) - 1
686 if sid not in object_layers[-1].surf_tags:
687 object_layers[-1].surf_tags[sid]= []
688 object_layers[-1].surf_tags[sid].append(poly)
689 poly+= 1
691 return len(object_layers[-1].pols) - old_pols_count
694 def read_bones(bone_bytes, object_layers):
695 """Read the layer's skelegons."""
696 print("\tReading Layer ("+object_layers[-1].name+") Bones")
697 offset= 0
698 bones_count = len(bone_bytes)
700 while offset < bones_count:
701 pnts_count,= struct.unpack(">H", bone_bytes[offset:offset+2])
702 offset+= 2
703 all_bone_pnts= []
704 for j in range(pnts_count):
705 bone_pnt, data_size= read_vx(bone_bytes[offset:offset+4])
706 offset+= data_size
707 all_bone_pnts.append(bone_pnt)
709 object_layers[-1].bones.append(all_bone_pnts)
712 def read_bone_tags(tag_bytes, object_layers, object_tags, type):
713 """Read the bone name or roll tags."""
714 offset= 0
715 chunk_len= len(tag_bytes)
717 if type == 'BONE':
718 bone_dict= object_layers[-1].bone_names
719 elif type == 'BNUP':
720 bone_dict= object_layers[-1].bone_rolls
721 else:
722 return
724 while offset < chunk_len:
725 pid, pid_len= read_vx(tag_bytes[offset:offset+4])
726 offset+= pid_len
727 tid,= struct.unpack(">H", tag_bytes[offset:offset+2])
728 offset+= 2
729 bone_dict[pid]= object_tags[tid]
732 def read_surf_tags(tag_bytes, object_layers, last_pols_count):
733 """Read the list of PolyIDs and tag indexes."""
734 print("\tReading Layer ("+object_layers[-1].name+") Surface Assignments")
735 offset= 0
736 chunk_len= len(tag_bytes)
738 # Read in the PolyID/Surface Index pairs.
739 abs_pid= len(object_layers[-1].pols) - last_pols_count
740 while offset < chunk_len:
741 pid, pid_len= read_vx(tag_bytes[offset:offset+4])
742 offset+= pid_len
743 sid,= struct.unpack(">H", tag_bytes[offset:offset+2])
744 offset+=2
745 if sid not in object_layers[-1].surf_tags:
746 object_layers[-1].surf_tags[sid]= []
747 object_layers[-1].surf_tags[sid].append(pid + abs_pid)
750 def read_surf(surf_bytes, object_surfs):
751 """Read the object's surface data."""
752 if len(object_surfs) == 0:
753 print("Reading Object Surfaces")
755 surf= _obj_surf()
756 name, name_len= read_lwostring(surf_bytes)
757 if len(name) != 0:
758 surf.name = name
760 # We have to read this, but we won't use it...yet.
761 s_name, s_name_len= read_lwostring(surf_bytes[name_len:])
762 offset= name_len+s_name_len
763 block_size= len(surf_bytes)
764 while offset < block_size:
765 subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
766 offset+= 4
767 subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
768 offset+= 2
770 # Now test which subchunk it is.
771 if subchunk_name == b'COLR':
772 surf.colr= struct.unpack(">fff", surf_bytes[offset:offset+12])
773 # Don't bother with any envelopes for now.
775 elif subchunk_name == b'DIFF':
776 surf.diff,= struct.unpack(">f", surf_bytes[offset:offset+4])
778 elif subchunk_name == b'LUMI':
779 surf.lumi,= struct.unpack(">f", surf_bytes[offset:offset+4])
781 elif subchunk_name == b'SPEC':
782 surf.spec,= struct.unpack(">f", surf_bytes[offset:offset+4])
784 elif subchunk_name == b'REFL':
785 surf.refl,= struct.unpack(">f", surf_bytes[offset:offset+4])
787 elif subchunk_name == b'RBLR':
788 surf.rblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
790 elif subchunk_name == b'TRAN':
791 surf.tran,= struct.unpack(">f", surf_bytes[offset:offset+4])
793 elif subchunk_name == b'RIND':
794 surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
796 elif subchunk_name == b'TBLR':
797 surf.tblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
799 elif subchunk_name == b'TRNL':
800 surf.trnl,= struct.unpack(">f", surf_bytes[offset:offset+4])
802 elif subchunk_name == b'GLOS':
803 surf.glos,= struct.unpack(">f", surf_bytes[offset:offset+4])
805 elif subchunk_name == b'SHRP':
806 surf.shrp,= struct.unpack(">f", surf_bytes[offset:offset+4])
808 elif subchunk_name == b'SMAN':
809 s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
810 if s_angle > 0.0:
811 surf.smooth = True
813 offset+= subchunk_len
815 object_surfs[surf.name]= surf
818 def read_surf_5(surf_bytes, object_surfs):
819 """Read the object's surface data."""
820 if len(object_surfs) == 0:
821 print("Reading Object Surfaces")
823 surf= _obj_surf()
824 name, name_len= read_lwostring(surf_bytes)
825 if len(name) != 0:
826 surf.name = name
828 offset= name_len
829 chunk_len= len(surf_bytes)
830 while offset < chunk_len:
831 subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
832 offset+= 4
833 subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
834 offset+= 2
836 # Now test which subchunk it is.
837 if subchunk_name == b'COLR':
838 color= struct.unpack(">BBBB", surf_bytes[offset:offset+4])
839 surf.colr= [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0]
841 elif subchunk_name == b'DIFF':
842 surf.diff,= struct.unpack(">h", surf_bytes[offset:offset+2])
843 surf.diff/= 256.0 # Yes, 256 not 255.
845 elif subchunk_name == b'LUMI':
846 surf.lumi,= struct.unpack(">h", surf_bytes[offset:offset+2])
847 surf.lumi/= 256.0
849 elif subchunk_name == b'SPEC':
850 surf.spec,= struct.unpack(">h", surf_bytes[offset:offset+2])
851 surf.spec/= 256.0
853 elif subchunk_name == b'REFL':
854 surf.refl,= struct.unpack(">h", surf_bytes[offset:offset+2])
855 surf.refl/= 256.0
857 elif subchunk_name == b'TRAN':
858 surf.tran,= struct.unpack(">h", surf_bytes[offset:offset+2])
859 surf.tran/= 256.0
861 elif subchunk_name == b'RIND':
862 surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
864 elif subchunk_name == b'GLOS':
865 surf.glos,= struct.unpack(">h", surf_bytes[offset:offset+2])
867 elif subchunk_name == b'SMAN':
868 s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
869 if s_angle > 0.0:
870 surf.smooth = True
872 offset+= subchunk_len
874 object_surfs[surf.name]= surf
877 def create_mappack(data, map_name, map_type):
878 """Match the map data to faces."""
879 pack= {}
881 def color_pointmap(map):
882 for fi in range(len(data.pols)):
883 if fi not in pack:
884 pack[fi]= []
885 for pnt in data.pols[fi]:
886 if pnt in map:
887 pack[fi].append(map[pnt])
888 else:
889 pack[fi].append((1.0, 1.0, 1.0))
891 def color_facemap(map):
892 for fi in range(len(data.pols)):
893 if fi not in pack:
894 pack[fi]= []
895 for p in data.pols[fi]:
896 pack[fi].append((1.0, 1.0, 1.0))
897 if fi in map:
898 for po in range(len(data.pols[fi])):
899 if data.pols[fi][po] in map[fi]:
900 pack[fi].insert(po, map[fi][data.pols[fi][po]])
901 del pack[fi][po+1]
903 def uv_pointmap(map):
904 for fi in range(len(data.pols)):
905 if fi not in pack:
906 pack[fi]= []
907 for p in data.pols[fi]:
908 pack[fi].append((-0.1,-0.1))
909 for po in range(len(data.pols[fi])):
910 pnt_id= data.pols[fi][po]
911 if pnt_id in map:
912 pack[fi].insert(po, map[pnt_id])
913 del pack[fi][po+1]
915 def uv_facemap(map):
916 for fi in range(len(data.pols)):
917 if fi not in pack:
918 pack[fi]= []
919 for p in data.pols[fi]:
920 pack[fi].append((-0.1,-0.1))
921 if fi in map:
922 for po in range(len(data.pols[fi])):
923 pnt_id= data.pols[fi][po]
924 if pnt_id in map[fi]:
925 pack[fi].insert(po, map[fi][pnt_id])
926 del pack[fi][po+1]
928 if map_type == "COLOR":
929 # Look at the first map, is it a point or face map
930 if "PointMap" in data.colmaps[map_name]:
931 color_pointmap(data.colmaps[map_name]["PointMap"])
933 if "FaceMap" in data.colmaps[map_name]:
934 color_facemap(data.colmaps[map_name]["FaceMap"])
935 elif map_type == "UV":
936 if "PointMap" in data.uvmaps[map_name]:
937 uv_pointmap(data.uvmaps[map_name]["PointMap"])
939 if "FaceMap" in data.uvmaps[map_name]:
940 uv_facemap(data.uvmaps[map_name]["FaceMap"])
942 return pack
945 def build_armature(layer_data, bones):
946 """Build an armature from the skelegon data in the mesh."""
947 print("Building Armature")
949 # New Armatures include a default bone, remove it.
950 bones.remove(bones[0])
952 # Now start adding the bones at the point locations.
953 prev_bone= None
954 for skb_idx in range(len(layer_data.bones)):
955 if skb_idx in layer_data.bone_names:
956 nb= bones.new(layer_data.bone_names[skb_idx])
957 else:
958 nb= bones.new("Bone")
960 nb.head= layer_data.pnts[layer_data.bones[skb_idx][0]]
961 nb.tail= layer_data.pnts[layer_data.bones[skb_idx][1]]
963 if skb_idx in layer_data.bone_rolls:
964 xyz= layer_data.bone_rolls[skb_idx].split(' ')
965 vec= mathutils.Vector((float(xyz[0]), float(xyz[1]), float(xyz[2])))
966 quat= vec.to_track_quat('Y', 'Z')
967 nb.roll= max(quat.to_euler('YZX'))
968 if nb.roll == 0.0:
969 nb.roll= min(quat.to_euler('YZX')) * -1
970 # YZX order seems to produce the correct roll value.
971 else:
972 nb.roll= 0.0
974 if prev_bone != None:
975 if nb.head == prev_bone.tail:
976 nb.parent= prev_bone
978 nb.use_connect= True
979 prev_bone= nb
982 def build_objects(object_layers, object_surfs, object_tags, object_name, add_subd_mod, skel_to_arm):
983 """Using the gathered data, create the objects."""
984 ob_dict= {} # Used for the parenting setup.
985 print("Adding %d Materials" % len(object_surfs))
987 for surf_key in object_surfs:
988 surf_data= object_surfs[surf_key]
989 surf_data.bl_mat= bpy.data.materials.new(surf_data.name)
990 surf_data.bl_mat.diffuse_color= (surf_data.colr[:])
991 surf_data.bl_mat.diffuse_intensity= surf_data.diff
992 surf_data.bl_mat.emit= surf_data.lumi
993 surf_data.bl_mat.specular_intensity= surf_data.spec
994 if surf_data.refl != 0.0:
995 surf_data.bl_mat.raytrace_mirror.use= True
996 surf_data.bl_mat.raytrace_mirror.reflect_factor= surf_data.refl
997 surf_data.bl_mat.raytrace_mirror.gloss_factor= 1.0-surf_data.rblr
998 if surf_data.tran != 0.0:
999 surf_data.bl_mat.use_transparency= True
1000 surf_data.bl_mat.transparency_method= 'RAYTRACE'
1001 surf_data.bl_mat.alpha= 1.0 - surf_data.tran
1002 surf_data.bl_mat.raytrace_transparency.ior= surf_data.rind
1003 surf_data.bl_mat.raytrace_transparency.gloss_factor= 1.0 - surf_data.tblr
1004 surf_data.bl_mat.translucency= surf_data.trnl
1005 surf_data.bl_mat.specular_hardness= int(4*((10*surf_data.glos)*(10*surf_data.glos)))+4
1006 # The Gloss is as close as possible given the differences.
1008 # Single layer objects use the object file's name instead.
1009 if len(object_layers) and object_layers[-1].name == 'Layer 1':
1010 object_layers[-1].name= object_name
1011 print("Building '%s' Object" % object_name)
1012 else:
1013 print("Building %d Objects" % len(object_layers))
1015 # Before adding any meshes or armatures go into Object mode.
1016 if bpy.ops.object.mode_set.poll():
1017 bpy.ops.object.mode_set(mode='OBJECT')
1019 for layer_data in object_layers:
1020 me= bpy.data.meshes.new(layer_data.name)
1021 me.vertices.add(len(layer_data.pnts))
1022 me.tessfaces.add(len(layer_data.pols))
1023 # for vi in range(len(layer_data.pnts)):
1024 # me.vertices[vi].co= layer_data.pnts[vi]
1026 # faster, would be faster again to use an array
1027 me.vertices.foreach_set("co", [axis for co in layer_data.pnts for axis in co])
1029 ngons= {} # To keep the FaceIdx consistent, handle NGons later.
1030 edges= [] # Holds the FaceIdx of the 2-point polys.
1031 for fi, fpol in enumerate(layer_data.pols):
1032 fpol.reverse() # Reversing gives correct normal directions
1033 # PointID 0 in the last element causes Blender to think it's un-used.
1034 if fpol[-1] == 0:
1035 fpol.insert(0, fpol[-1])
1036 del fpol[-1]
1038 vlen= len(fpol)
1039 if vlen == 3 or vlen == 4:
1040 for i in range(vlen):
1041 me.tessfaces[fi].vertices_raw[i]= fpol[i]
1042 elif vlen == 2:
1043 edges.append(fi)
1044 elif vlen != 1:
1045 ngons[fi]= fpol # Deal with them later
1047 ob= bpy.data.objects.new(layer_data.name, me)
1048 bpy.context.scene.objects.link(ob)
1049 ob_dict[layer_data.index]= [ob, layer_data.parent_index]
1051 # Move the object so the pivot is in the right place.
1052 ob.location= layer_data.pivot
1054 # Create the Material Slots and assign the MatIndex to the correct faces.
1055 mat_slot= 0
1056 for surf_key in layer_data.surf_tags:
1057 if object_tags[surf_key] in object_surfs:
1058 me.materials.append(object_surfs[object_tags[surf_key]].bl_mat)
1060 for fi in layer_data.surf_tags[surf_key]:
1061 me.tessfaces[fi].material_index= mat_slot
1062 me.tessfaces[fi].use_smooth= object_surfs[object_tags[surf_key]].smooth
1064 mat_slot+=1
1066 # Create the Vertex Groups (LW's Weight Maps).
1067 if len(layer_data.wmaps) > 0:
1068 print("Adding %d Vertex Groups" % len(layer_data.wmaps))
1069 for wmap_key in layer_data.wmaps:
1070 vgroup= ob.vertex_groups.new()
1071 vgroup.name= wmap_key
1072 wlist= layer_data.wmaps[wmap_key]
1073 for pvp in wlist:
1074 vgroup.add((pvp[0], ), pvp[1], 'REPLACE')
1076 # Create the Shape Keys (LW's Endomorphs).
1077 if len(layer_data.morphs) > 0:
1078 print("Adding %d Shapes Keys" % len(layer_data.morphs))
1079 ob.shape_key_add('Basis') # Got to have a Base Shape.
1080 for morph_key in layer_data.morphs:
1081 skey= ob.shape_key_add(morph_key)
1082 dlist= layer_data.morphs[morph_key]
1083 for pdp in dlist:
1084 me.shape_keys.key_blocks[skey.name].data[pdp[0]].co= [pdp[1], pdp[2], pdp[3]]
1086 # Create the Vertex Color maps.
1087 if len(layer_data.colmaps) > 0:
1088 print("Adding %d Vertex Color Maps" % len(layer_data.colmaps))
1089 for cmap_key in layer_data.colmaps:
1090 map_pack= create_mappack(layer_data, cmap_key, "COLOR")
1091 me.vertex_colors.new(cmap_key)
1092 vcol= me.tessface_vertex_colors[-1]
1093 if not vcol or not vcol.data:
1094 break
1095 for fi in map_pack:
1096 if fi > len(vcol.data):
1097 continue
1098 face= map_pack[fi]
1099 colf= vcol.data[fi]
1101 if len(face) > 2:
1102 colf.color1= face[0]
1103 colf.color2= face[1]
1104 colf.color3= face[2]
1105 if len(face) == 4:
1106 colf.color4= face[3]
1108 # Create the UV Maps.
1109 if len(layer_data.uvmaps) > 0:
1110 print("Adding %d UV Textures" % len(layer_data.uvmaps))
1111 for uvmap_key in layer_data.uvmaps:
1112 map_pack= create_mappack(layer_data, uvmap_key, "UV")
1113 me.uv_textures.new(name=uvmap_key)
1114 uvm= me.tessface_uv_textures[-1]
1115 if not uvm or not uvm.data:
1116 break
1117 for fi in map_pack:
1118 if fi > len(uvm.data):
1119 continue
1120 face= map_pack[fi]
1121 uvf= uvm.data[fi]
1123 if len(face) > 2:
1124 uvf.uv1= face[0]
1125 uvf.uv2= face[1]
1126 uvf.uv3= face[2]
1127 if len(face) == 4:
1128 uvf.uv4= face[3]
1130 # Now add the NGons.
1131 if len(ngons) > 0:
1132 for ng_key in ngons:
1133 face_offset= len(me.tessfaces)
1134 ng= ngons[ng_key]
1135 v_locs= []
1136 for vi in range(len(ng)):
1137 v_locs.append(mathutils.Vector(layer_data.pnts[ngons[ng_key][vi]]))
1138 tris= tessellate_polygon([v_locs])
1139 me.tessfaces.add(len(tris))
1140 for tri in tris:
1141 face= me.tessfaces[face_offset]
1142 face.vertices_raw[0]= ng[tri[0]]
1143 face.vertices_raw[1]= ng[tri[1]]
1144 face.vertices_raw[2]= ng[tri[2]]
1145 face.material_index= me.tessfaces[ng_key].material_index
1146 face.use_smooth= me.tessfaces[ng_key].use_smooth
1147 face_offset+= 1
1149 # FaceIDs are no longer a concern, so now update the mesh.
1150 has_edges= len(edges) > 0 or len(layer_data.edge_weights) > 0
1151 me.update(calc_edges=has_edges)
1153 # Add the edges.
1154 edge_offset= len(me.edges)
1155 me.edges.add(len(edges))
1156 for edge_fi in edges:
1157 me.edges[edge_offset].vertices[0]= layer_data.pols[edge_fi][0]
1158 me.edges[edge_offset].vertices[1]= layer_data.pols[edge_fi][1]
1159 edge_offset+= 1
1161 # Apply the Edge Weighting.
1162 if len(layer_data.edge_weights) > 0:
1163 for edge in me.edges:
1164 edge_sa= "{0} {1}".format(edge.vertices[0], edge.vertices[1])
1165 edge_sb= "{0} {1}".format(edge.vertices[1], edge.vertices[0])
1166 if edge_sa in layer_data.edge_weights:
1167 edge.crease= layer_data.edge_weights[edge_sa]
1168 elif edge_sb in layer_data.edge_weights:
1169 edge.crease= layer_data.edge_weights[edge_sb]
1171 # Unfortunately we can't exlude certain faces from the subdivision.
1172 if layer_data.has_subds and add_subd_mod:
1173 ob.modifiers.new(name="Subsurf", type='SUBSURF')
1175 # Should we build an armature from the embedded rig?
1176 if len(layer_data.bones) > 0 and skel_to_arm:
1177 bpy.ops.object.armature_add()
1178 arm_object= bpy.context.active_object
1179 arm_object.name= "ARM_" + layer_data.name
1180 arm_object.data.name= arm_object.name
1181 arm_object.location= layer_data.pivot
1182 bpy.ops.object.mode_set(mode='EDIT')
1183 build_armature(layer_data, arm_object.data.edit_bones)
1184 bpy.ops.object.mode_set(mode='OBJECT')
1186 # Clear out the dictionaries for this layer.
1187 layer_data.bone_names.clear()
1188 layer_data.bone_rolls.clear()
1189 layer_data.wmaps.clear()
1190 layer_data.colmaps.clear()
1191 layer_data.uvmaps.clear()
1192 layer_data.morphs.clear()
1193 layer_data.surf_tags.clear()
1195 # We may have some invalid mesh data, See: [#27916]
1196 # keep this last!
1197 print("validating mesh: %r..." % me.name)
1198 me.validate(verbose=1)
1199 print("done!")
1201 # With the objects made, setup the parents and re-adjust the locations.
1202 for ob_key in ob_dict:
1203 if ob_dict[ob_key][1] != -1 and ob_dict[ob_key][1] in ob_dict:
1204 parent_ob = ob_dict[ob_dict[ob_key][1]]
1205 ob_dict[ob_key][0].parent= parent_ob[0]
1206 ob_dict[ob_key][0].location-= parent_ob[0].location
1208 bpy.context.scene.update()
1210 print("Done Importing LWO File")
1213 from bpy.props import StringProperty, BoolProperty
1216 class IMPORT_OT_lwo(bpy.types.Operator):
1217 """Import LWO Operator"""
1218 bl_idname= "import_scene.lwo"
1219 bl_label= "Import LWO"
1220 bl_description= "Import a LightWave Object file"
1221 bl_options= {'REGISTER', 'UNDO'}
1223 filepath= StringProperty(name="File Path", description="Filepath used for importing the LWO file", maxlen=1024, default="")
1225 ADD_SUBD_MOD= BoolProperty(name="Apply SubD Modifier", description="Apply the Subdivision Surface modifier to layers with Subpatches", default=True)
1226 LOAD_HIDDEN= BoolProperty(name="Load Hidden Layers", description="Load object layers that have been marked as hidden", default=False)
1227 SKEL_TO_ARM= BoolProperty(name="Create Armature", description="Create an armature from an embedded Skelegon rig", default=True)
1229 def execute(self, context):
1230 load_lwo(self.filepath,
1231 context,
1232 self.ADD_SUBD_MOD,
1233 self.LOAD_HIDDEN,
1234 self.SKEL_TO_ARM)
1235 return {'FINISHED'}
1237 def invoke(self, context, event):
1238 wm= context.window_manager
1239 wm.fileselect_add(self)
1240 return {'RUNNING_MODAL'}
1243 def menu_func(self, context):
1244 self.layout.operator(IMPORT_OT_lwo.bl_idname, text="LightWave Object (.lwo)")
1247 def register():
1248 bpy.utils.register_module(__name__)
1250 bpy.types.INFO_MT_file_import.append(menu_func)
1253 def unregister():
1254 bpy.utils.unregister_module(__name__)
1256 bpy.types.INFO_MT_file_import.remove(menu_func)
1258 if __name__ == "__main__":
1259 register()