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 #####
20 "name": "Import LightWave Objects",
21 "author": "Ken Nign (Ken9)",
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.",
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
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
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.
57 # 1.3 Fixed CC Edge Weight loading.
59 # 1.2 Added Absolute Morph and CC Edge Weight support.
60 # Made edge creation safer.
70 from mathutils
.geometry
import tessellate_polygon
73 class _obj_layer(object):
106 self
.edge_weights
= {}
108 self
.has_subds
= False
111 class _obj_surf(object):
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
,
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')
160 header
, chunk_size
, chunk_name
= struct
.unpack(">4s1L4s", file.read(12))
162 print("Error parsing file header!")
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
)
176 print("Not a supported file type!")
182 # With the data gathered, build the object(s).
183 build_objects(layers
, surfs
, tags
, name
, ADD_SUBD_MOD
, SKEL_TO_ARM
)
190 def read_lwo2(file, filename
, layers
, surfs
, tags
, add_subd_mod
, load_hidden
, skel_to_arm
):
191 """Read version 2 file, LW 6+."""
194 just_read_bones
= False
195 print("Importing LWO: " + filename
+ "\nLWO v2 Format")
199 rootchunk
= chunk
.Chunk(file)
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
)
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
)
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
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
)
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')
268 elif rootchunk
.chunkname
== b
'SURF':
269 read_surf(rootchunk
.read(), surfs
)
272 #print("Skipping Chunk:", rootchunk.chunkname)
276 def read_lwob(file, filename
, layers
, surfs
, tags
, add_subd_mod
):
277 """Read version 1 file, LW < 6."""
279 print("Importing LWO: " + filename
+ "\nLWO v1 Format")
283 rootchunk
= chunk
.Chunk(file)
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':
293 # LWOB files have no LAYR chunk to set this up.
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
)
309 elif rootchunk
.chunkname
== b
'SURF':
310 read_surf_5(rootchunk
.read(), surfs
)
314 #print("Skipping Chunk: ", rootchunk.chunkname)
318 def read_lwostring(raw_name
):
319 """Parse a zero-padded string."""
321 i
= raw_name
.find(b
'\0')
323 if name_len
% 2 == 1: # Test for oddness.
327 # Some plugins put non-text strings in the tags chunk.
328 name
= raw_name
[0:i
].decode("utf-8", "ignore")
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]
341 index
= pointdata
[1]*65536 + pointdata
[2]*256 + pointdata
[3]
347 def read_tags(tag_bytes
, object_tags
):
348 """Read the object's Tags chunk."""
350 chunk_len
= len(tag_bytes
)
352 while offset
< chunk_len
:
353 tag
, tag_len
= read_lwostring(tag_bytes
[offset
:])
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
:
366 print("Reading Object Layer")
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]]
372 layr_name
, name_len
= read_lwostring(layr_bytes
[offset
:])
376 new_layr
.name
= layr_name
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
)
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")
395 layr_name
, name_len
= read_lwostring(layr_bytes
[offset
:])
398 if name_len
> 2 and layr_name
!= 'noname':
399 new_layr
.name
= layr_name
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")
410 chunk_len
= len(pnt_bytes
)
412 while offset
< chunk_len
:
413 pnts
= struct
.unpack(">fff", pnt_bytes
[offset
: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
)
427 name
, name_len
= read_lwostring(weight_bytes
[offset
:])
431 while offset
< chunk_len
:
432 pnt_id
, pnt_id_len
= read_vx(weight_bytes
[offset
:offset
+4])
434 value
,= struct
.unpack(">f", weight_bytes
[offset
: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
)
445 name
, name_len
= read_lwostring(morph_bytes
[offset
:])
449 while offset
< chunk_len
:
450 pnt_id
, pnt_id_len
= read_vx(morph_bytes
[offset
:offset
+4])
452 pos
= struct
.unpack(">fff", morph_bytes
[offset
:offset
+12])
454 pnt
= object_layers
[-1].pnts
[pnt_id
]
457 deltas
.append([pnt_id
, pos
[0], pos
[2], pos
[1]])
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])
470 name
, name_len
= read_lwostring(col_bytes
[offset
:])
475 while offset
< chunk_len
:
476 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
478 col
= struct
.unpack(">fff", col_bytes
[offset
:offset
+12])
480 colors
[pnt_id
]= (col
[0], col
[1], col
[2])
482 while offset
< chunk_len
:
483 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
485 col
= struct
.unpack(">ffff", col_bytes
[offset
: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
)
493 object_layers
[-1].colmaps
[name
]["PointMap"]= colors
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])
503 name
, name_len
= read_lwostring(col_bytes
[offset
:])
506 abs_pid
= len(object_layers
[-1].pols
) - last_pols_count
509 while offset
< chunk_len
:
510 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
512 pol_id
, pol_id_len
= read_vx(col_bytes
[offset
:offset
+4])
515 # The PolyID in a VMAD can be relative, this offsets it.
517 col
= struct
.unpack(">fff", col_bytes
[offset
:offset
+12])
520 colors
[pol_id
][pnt_id
]= (col
[0], col
[1], col
[2])
522 colors
[pol_id
]= dict({pnt_id
: (col
[0], col
[1], col
[2])})
524 while offset
< chunk_len
:
525 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
527 pol_id
, pol_id_len
= read_vx(col_bytes
[offset
:offset
+4])
531 col
= struct
.unpack(">ffff", col_bytes
[offset
:offset
+16])
534 colors
[pol_id
][pnt_id
]= (col
[0], col
[1], col
[2])
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
)
542 object_layers
[-1].colmaps
[name
]["FaceMap"]= colors
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
)
551 name
, name_len
= read_lwostring(uv_bytes
[offset
:])
555 while offset
< chunk_len
:
556 pnt_id
, pnt_id_len
= read_vx(uv_bytes
[offset
:offset
+4])
558 pos
= struct
.unpack(">ff", uv_bytes
[offset
: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
)
566 object_layers
[-1].uvmaps
[name
]["PointMap"]= uv_coords
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
)
575 name
, name_len
= read_lwostring(uv_bytes
[offset
:])
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])
583 pol_id
, pol_id_len
= read_vx(uv_bytes
[offset
:offset
+4])
587 pos
= struct
.unpack(">ff", uv_bytes
[offset
:offset
+8])
589 if pol_id
in uv_coords
:
590 uv_coords
[pol_id
][pnt_id
]= (pos
[0], pos
[1])
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
)
598 object_layers
[-1].uvmaps
[name
]["FaceMap"]= uv_coords
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
)
607 name
, name_len
= read_lwostring(ew_bytes
[offset
:])
608 if name
!= "Edge Weight":
609 return # We just want the Catmull-Clark edge weights
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])
619 pol_id
, pol_id_len
= read_vx(ew_bytes
[offset
:offset
+4])
621 weight
,= struct
.unpack(">f", ew_bytes
[offset
:offset
+4])
624 face_pnts
= object_layers
[-1].pols
[pol_id
]
626 # Find the point's location in the polygon's point list
627 first_idx
= face_pnts
.index(pnt_id
)
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]
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")
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])
651 for j
in range(pnts_count
):
652 face_pnt
, data_size
= read_vx(pol_bytes
[offset
:offset
+4])
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")
668 chunk_len
= len(pol_bytes
)
669 old_pols_count
= len(object_layers
[-1].pols
)
672 while offset
< chunk_len
:
673 pnts_count
,= struct
.unpack(">H", pol_bytes
[offset
:offset
+2])
676 for j
in range(pnts_count
):
677 face_pnt
,= struct
.unpack(">H", pol_bytes
[offset
: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])
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
)
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")
697 bones_count
= len(bone_bytes
)
699 while offset
< bones_count
:
700 pnts_count
,= struct
.unpack(">H", bone_bytes
[offset
:offset
+2])
703 for j
in range(pnts_count
):
704 bone_pnt
, data_size
= read_vx(bone_bytes
[offset
:offset
+4])
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."""
714 chunk_len
= len(tag_bytes
)
717 bone_dict
= object_layers
[-1].bone_names
719 bone_dict
= object_layers
[-1].bone_rolls
723 while offset
< chunk_len
:
724 pid
, pid_len
= read_vx(tag_bytes
[offset
:offset
+4])
726 tid
,= struct
.unpack(">H", tag_bytes
[offset
: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")
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])
742 sid
,= struct
.unpack(">H", tag_bytes
[offset
: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")
755 name
, name_len
= read_lwostring(surf_bytes
)
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])
766 subchunk_len
,= struct
.unpack(">H", surf_bytes
[offset
: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])
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")
823 name
, name_len
= read_lwostring(surf_bytes
)
828 chunk_len
= len(surf_bytes
)
829 while offset
< chunk_len
:
830 subchunk_name
,= struct
.unpack("4s", surf_bytes
[offset
:offset
+4])
832 subchunk_len
,= struct
.unpack(">H", surf_bytes
[offset
: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])
848 elif subchunk_name
== b
'SPEC':
849 surf
.spec
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
852 elif subchunk_name
== b
'REFL':
853 surf
.refl
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
856 elif subchunk_name
== b
'TRAN':
857 surf
.tran
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
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])
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."""
880 def color_pointmap(map):
881 for fi
in range(len(data
.pols
)):
884 for pnt
in data
.pols
[fi
]:
886 pack
[fi
].append(map[pnt
])
888 pack
[fi
].append((1.0, 1.0, 1.0))
890 def color_facemap(map):
891 for fi
in range(len(data
.pols
)):
894 for p
in data
.pols
[fi
]:
895 pack
[fi
].append((1.0, 1.0, 1.0))
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
]])
902 def uv_pointmap(map):
903 for fi
in range(len(data
.pols
)):
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
]
911 pack
[fi
].insert(po
, map[pnt_id
])
915 for fi
in range(len(data
.pols
)):
918 for p
in data
.pols
[fi
]:
919 pack
[fi
].append((-0.1,-0.1))
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
])
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"])
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.
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
])
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'))
968 nb
.roll
= min(quat
.to_euler('YZX')) * -1
969 # YZX order seems to produce the correct roll value.
973 if prev_bone
!= None:
974 if nb
.head
== prev_bone
.tail
:
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
)
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.
1034 fpol
.insert(0, fpol
[-1])
1038 if vlen
== 3 or vlen
== 4:
1039 for i
in range(vlen
):
1040 me
.tessfaces
[fi
].vertices_raw
[i
]= fpol
[i
]
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.
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
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
]
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
]
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
:
1095 if fi
> len(vcol
.data
):
1101 colf
.color1
= face
[0]
1102 colf
.color2
= face
[1]
1103 colf
.color3
= face
[2]
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
:
1117 if fi
> len(uvm
.data
):
1129 # Now add the NGons.
1131 for ng_key
in ngons
:
1132 face_offset
= len(me
.tessfaces
)
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
))
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
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
)
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]
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]
1196 print("validating mesh: %r..." % me
.name
)
1197 me
.validate(verbose
=1)
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
,
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)")
1247 bpy
.utils
.register_module(__name__
)
1249 bpy
.types
.INFO_MT_file_import
.append(menu_func
)
1253 bpy
.utils
.unregister_module(__name__
)
1255 bpy
.types
.INFO_MT_file_import
.remove(menu_func
)
1257 if __name__
== "__main__":