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 "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 USE_EXISTING_MATERIALS
=False):
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')
161 header
, chunk_size
, chunk_name
= struct
.unpack(">4s1L4s", file.read(12))
163 print("Error parsing file header!")
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
)
177 print("Not a supported file type!")
183 # With the data gathered, build the object(s).
184 build_objects(layers
, surfs
, tags
, name
, ADD_SUBD_MOD
, SKEL_TO_ARM
, USE_EXISTING_MATERIALS
)
191 def read_lwo2(file, filename
, layers
, surfs
, tags
, add_subd_mod
, load_hidden
, skel_to_arm
):
192 """Read version 2 file, LW 6+."""
195 just_read_bones
= False
196 print("Importing LWO: " + filename
+ "\nLWO v2 Format")
200 rootchunk
= chunk
.Chunk(file)
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
)
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
)
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
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
)
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')
269 elif rootchunk
.chunkname
== b
'SURF':
270 read_surf(rootchunk
.read(), surfs
)
273 #print("Skipping Chunk:", rootchunk.chunkname)
277 def read_lwob(file, filename
, layers
, surfs
, tags
, add_subd_mod
):
278 """Read version 1 file, LW < 6."""
280 print("Importing LWO: " + filename
+ "\nLWO v1 Format")
284 rootchunk
= chunk
.Chunk(file)
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':
294 # LWOB files have no LAYR chunk to set this up.
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
)
310 elif rootchunk
.chunkname
== b
'SURF':
311 read_surf_5(rootchunk
.read(), surfs
)
315 #print("Skipping Chunk: ", rootchunk.chunkname)
319 def read_lwostring(raw_name
):
320 """Parse a zero-padded string."""
322 i
= raw_name
.find(b
'\0')
324 if name_len
% 2 == 1: # Test for oddness.
328 # Some plugins put non-text strings in the tags chunk.
329 name
= raw_name
[0:i
].decode("utf-8", "ignore")
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]
342 index
= pointdata
[1]*65536 + pointdata
[2]*256 + pointdata
[3]
348 def read_tags(tag_bytes
, object_tags
):
349 """Read the object's Tags chunk."""
351 chunk_len
= len(tag_bytes
)
353 while offset
< chunk_len
:
354 tag
, tag_len
= read_lwostring(tag_bytes
[offset
:])
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
:
367 print("Reading Object Layer")
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]]
373 layr_name
, name_len
= read_lwostring(layr_bytes
[offset
:])
377 new_layr
.name
= layr_name
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
)
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")
396 layr_name
, name_len
= read_lwostring(layr_bytes
[offset
:])
399 if name_len
> 2 and layr_name
!= 'noname':
400 new_layr
.name
= layr_name
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")
411 chunk_len
= len(pnt_bytes
)
413 while offset
< chunk_len
:
414 pnts
= struct
.unpack(">fff", pnt_bytes
[offset
: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
)
428 name
, name_len
= read_lwostring(weight_bytes
[offset
:])
432 while offset
< chunk_len
:
433 pnt_id
, pnt_id_len
= read_vx(weight_bytes
[offset
:offset
+4])
435 value
,= struct
.unpack(">f", weight_bytes
[offset
: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
)
446 name
, name_len
= read_lwostring(morph_bytes
[offset
:])
450 while offset
< chunk_len
:
451 pnt_id
, pnt_id_len
= read_vx(morph_bytes
[offset
:offset
+4])
453 pos
= struct
.unpack(">fff", morph_bytes
[offset
:offset
+12])
455 pnt
= object_layers
[-1].pnts
[pnt_id
]
458 deltas
.append([pnt_id
, pos
[0], pos
[2], pos
[1]])
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])
471 name
, name_len
= read_lwostring(col_bytes
[offset
:])
476 while offset
< chunk_len
:
477 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
479 col
= struct
.unpack(">fff", col_bytes
[offset
:offset
+12])
481 colors
[pnt_id
]= (col
[0], col
[1], col
[2])
483 while offset
< chunk_len
:
484 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
486 col
= struct
.unpack(">ffff", col_bytes
[offset
: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
)
494 object_layers
[-1].colmaps
[name
]["PointMap"]= colors
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])
504 name
, name_len
= read_lwostring(col_bytes
[offset
:])
507 abs_pid
= len(object_layers
[-1].pols
) - last_pols_count
510 while offset
< chunk_len
:
511 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
513 pol_id
, pol_id_len
= read_vx(col_bytes
[offset
:offset
+4])
516 # The PolyID in a VMAD can be relative, this offsets it.
518 col
= struct
.unpack(">fff", col_bytes
[offset
:offset
+12])
521 colors
[pol_id
][pnt_id
]= (col
[0], col
[1], col
[2])
523 colors
[pol_id
]= dict({pnt_id
: (col
[0], col
[1], col
[2])})
525 while offset
< chunk_len
:
526 pnt_id
, pnt_id_len
= read_vx(col_bytes
[offset
:offset
+4])
528 pol_id
, pol_id_len
= read_vx(col_bytes
[offset
:offset
+4])
532 col
= struct
.unpack(">ffff", col_bytes
[offset
:offset
+16])
535 colors
[pol_id
][pnt_id
]= (col
[0], col
[1], col
[2])
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
)
543 object_layers
[-1].colmaps
[name
]["FaceMap"]= colors
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
)
552 name
, name_len
= read_lwostring(uv_bytes
[offset
:])
556 while offset
< chunk_len
:
557 pnt_id
, pnt_id_len
= read_vx(uv_bytes
[offset
:offset
+4])
559 pos
= struct
.unpack(">ff", uv_bytes
[offset
: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
)
567 object_layers
[-1].uvmaps
[name
]["PointMap"]= uv_coords
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
)
576 name
, name_len
= read_lwostring(uv_bytes
[offset
:])
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])
584 pol_id
, pol_id_len
= read_vx(uv_bytes
[offset
:offset
+4])
588 pos
= struct
.unpack(">ff", uv_bytes
[offset
:offset
+8])
590 if pol_id
in uv_coords
:
591 uv_coords
[pol_id
][pnt_id
]= (pos
[0], pos
[1])
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
)
599 object_layers
[-1].uvmaps
[name
]["FaceMap"]= uv_coords
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
)
608 name
, name_len
= read_lwostring(ew_bytes
[offset
:])
609 if name
!= "Edge Weight":
610 return # We just want the Catmull-Clark edge weights
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])
620 pol_id
, pol_id_len
= read_vx(ew_bytes
[offset
:offset
+4])
622 weight
,= struct
.unpack(">f", ew_bytes
[offset
:offset
+4])
625 face_pnts
= object_layers
[-1].pols
[pol_id
]
627 # Find the point's location in the polygon's point list
628 first_idx
= face_pnts
.index(pnt_id
)
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]
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")
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])
652 for j
in range(pnts_count
):
653 face_pnt
, data_size
= read_vx(pol_bytes
[offset
:offset
+4])
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")
669 chunk_len
= len(pol_bytes
)
670 old_pols_count
= len(object_layers
[-1].pols
)
673 while offset
< chunk_len
:
674 pnts_count
,= struct
.unpack(">H", pol_bytes
[offset
:offset
+2])
677 for j
in range(pnts_count
):
678 face_pnt
,= struct
.unpack(">H", pol_bytes
[offset
: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])
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
)
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")
698 bones_count
= len(bone_bytes
)
700 while offset
< bones_count
:
701 pnts_count
,= struct
.unpack(">H", bone_bytes
[offset
:offset
+2])
704 for j
in range(pnts_count
):
705 bone_pnt
, data_size
= read_vx(bone_bytes
[offset
:offset
+4])
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."""
715 chunk_len
= len(tag_bytes
)
718 bone_dict
= object_layers
[-1].bone_names
720 bone_dict
= object_layers
[-1].bone_rolls
724 while offset
< chunk_len
:
725 pid
, pid_len
= read_vx(tag_bytes
[offset
:offset
+4])
727 tid
,= struct
.unpack(">H", tag_bytes
[offset
: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")
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])
743 sid
,= struct
.unpack(">H", tag_bytes
[offset
: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")
756 name
, name_len
= read_lwostring(surf_bytes
)
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])
767 subchunk_len
,= struct
.unpack(">H", surf_bytes
[offset
: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])
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")
824 name
, name_len
= read_lwostring(surf_bytes
)
829 chunk_len
= len(surf_bytes
)
830 while offset
< chunk_len
:
831 subchunk_name
,= struct
.unpack("4s", surf_bytes
[offset
:offset
+4])
833 subchunk_len
,= struct
.unpack(">H", surf_bytes
[offset
: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])
849 elif subchunk_name
== b
'SPEC':
850 surf
.spec
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
853 elif subchunk_name
== b
'REFL':
854 surf
.refl
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
857 elif subchunk_name
== b
'TRAN':
858 surf
.tran
,= struct
.unpack(">h", surf_bytes
[offset
:offset
+2])
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])
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."""
881 def color_pointmap(map):
882 for fi
in range(len(data
.pols
)):
885 for pnt
in data
.pols
[fi
]:
887 pack
[fi
].append(map[pnt
])
889 pack
[fi
].append((1.0, 1.0, 1.0))
891 def color_facemap(map):
892 for fi
in range(len(data
.pols
)):
895 for p
in data
.pols
[fi
]:
896 pack
[fi
].append((1.0, 1.0, 1.0))
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
]])
903 def uv_pointmap(map):
904 for fi
in range(len(data
.pols
)):
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
]
912 pack
[fi
].insert(po
, map[pnt_id
])
916 for fi
in range(len(data
.pols
)):
919 for p
in data
.pols
[fi
]:
920 pack
[fi
].append((-0.1,-0.1))
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
])
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"])
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.
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
])
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'))
969 nb
.roll
= min(quat
.to_euler('YZX')) * -1
970 # YZX order seems to produce the correct roll value.
974 if prev_bone
is not None:
975 if nb
.head
== prev_bone
.tail
:
982 def build_objects(object_layers
, object_surfs
, object_tags
, object_name
, add_subd_mod
, skel_to_arm
, use_existing_materials
):
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
.get(surf_data
.name
) if use_existing_materials
else None
990 if surf_data
.bl_mat
is None:
991 surf_data
.bl_mat
= bpy
.data
.materials
.new(surf_data
.name
)
992 surf_data
.bl_mat
.diffuse_color
= (surf_data
.colr
[:])
993 surf_data
.bl_mat
.diffuse_intensity
= surf_data
.diff
994 surf_data
.bl_mat
.emit
= surf_data
.lumi
995 surf_data
.bl_mat
.specular_intensity
= surf_data
.spec
996 if surf_data
.refl
!= 0.0:
997 surf_data
.bl_mat
.raytrace_mirror
.use
= True
998 surf_data
.bl_mat
.raytrace_mirror
.reflect_factor
= surf_data
.refl
999 surf_data
.bl_mat
.raytrace_mirror
.gloss_factor
= 1.0-surf_data
.rblr
1000 if surf_data
.tran
!= 0.0:
1001 surf_data
.bl_mat
.use_transparency
= True
1002 surf_data
.bl_mat
.transparency_method
= 'RAYTRACE'
1003 surf_data
.bl_mat
.alpha
= 1.0 - surf_data
.tran
1004 surf_data
.bl_mat
.raytrace_transparency
.ior
= surf_data
.rind
1005 surf_data
.bl_mat
.raytrace_transparency
.gloss_factor
= 1.0 - surf_data
.tblr
1006 surf_data
.bl_mat
.translucency
= surf_data
.trnl
1007 surf_data
.bl_mat
.specular_hardness
= int(4*((10*surf_data
.glos
)*(10*surf_data
.glos
)))+4
1008 # The Gloss is as close as possible given the differences.
1010 # Single layer objects use the object file's name instead.
1011 if len(object_layers
) and object_layers
[-1].name
== 'Layer 1':
1012 object_layers
[-1].name
= object_name
1013 print("Building '%s' Object" % object_name
)
1015 print("Building %d Objects" % len(object_layers
))
1017 # Before adding any meshes or armatures go into Object mode.
1018 if bpy
.ops
.object.mode_set
.poll():
1019 bpy
.ops
.object.mode_set(mode
='OBJECT')
1021 for layer_data
in object_layers
:
1022 me
= bpy
.data
.meshes
.new(layer_data
.name
)
1023 me
.vertices
.add(len(layer_data
.pnts
))
1024 me
.tessfaces
.add(len(layer_data
.pols
))
1025 # for vi in range(len(layer_data.pnts)):
1026 # me.vertices[vi].co= layer_data.pnts[vi]
1028 # faster, would be faster again to use an array
1029 me
.vertices
.foreach_set("co", [axis
for co
in layer_data
.pnts
for axis
in co
])
1031 ngons
= {} # To keep the FaceIdx consistent, handle NGons later.
1032 edges
= [] # Holds the FaceIdx of the 2-point polys.
1033 for fi
, fpol
in enumerate(layer_data
.pols
):
1034 fpol
.reverse() # Reversing gives correct normal directions
1035 # PointID 0 in the last element causes Blender to think it's un-used.
1037 fpol
.insert(0, fpol
[-1])
1041 if vlen
== 3 or vlen
== 4:
1042 for i
in range(vlen
):
1043 me
.tessfaces
[fi
].vertices_raw
[i
]= fpol
[i
]
1047 ngons
[fi
]= fpol
# Deal with them later
1049 ob
= bpy
.data
.objects
.new(layer_data
.name
, me
)
1050 bpy
.context
.scene
.objects
.link(ob
)
1051 ob_dict
[layer_data
.index
]= [ob
, layer_data
.parent_index
]
1053 # Move the object so the pivot is in the right place.
1054 ob
.location
= layer_data
.pivot
1056 # Create the Material Slots and assign the MatIndex to the correct faces.
1058 for surf_key
in layer_data
.surf_tags
:
1059 if object_tags
[surf_key
] in object_surfs
:
1060 me
.materials
.append(object_surfs
[object_tags
[surf_key
]].bl_mat
)
1062 for fi
in layer_data
.surf_tags
[surf_key
]:
1063 me
.tessfaces
[fi
].material_index
= mat_slot
1064 me
.tessfaces
[fi
].use_smooth
= object_surfs
[object_tags
[surf_key
]].smooth
1068 # Create the Vertex Groups (LW's Weight Maps).
1069 if len(layer_data
.wmaps
) > 0:
1070 print("Adding %d Vertex Groups" % len(layer_data
.wmaps
))
1071 for wmap_key
in layer_data
.wmaps
:
1072 vgroup
= ob
.vertex_groups
.new()
1073 vgroup
.name
= wmap_key
1074 wlist
= layer_data
.wmaps
[wmap_key
]
1076 vgroup
.add((pvp
[0], ), pvp
[1], 'REPLACE')
1078 # Create the Shape Keys (LW's Endomorphs).
1079 if len(layer_data
.morphs
) > 0:
1080 print("Adding %d Shapes Keys" % len(layer_data
.morphs
))
1081 ob
.shape_key_add('Basis') # Got to have a Base Shape.
1082 for morph_key
in layer_data
.morphs
:
1083 skey
= ob
.shape_key_add(morph_key
)
1084 dlist
= layer_data
.morphs
[morph_key
]
1086 me
.shape_keys
.key_blocks
[skey
.name
].data
[pdp
[0]].co
= [pdp
[1], pdp
[2], pdp
[3]]
1088 # Create the Vertex Color maps.
1089 if len(layer_data
.colmaps
) > 0:
1090 print("Adding %d Vertex Color Maps" % len(layer_data
.colmaps
))
1091 for cmap_key
in layer_data
.colmaps
:
1092 map_pack
= create_mappack(layer_data
, cmap_key
, "COLOR")
1093 me
.vertex_colors
.new(cmap_key
)
1094 vcol
= me
.tessface_vertex_colors
[-1]
1095 if not vcol
or not vcol
.data
:
1098 if fi
> len(vcol
.data
):
1104 colf
.color1
= face
[0]
1105 colf
.color2
= face
[1]
1106 colf
.color3
= face
[2]
1108 colf
.color4
= face
[3]
1110 # Create the UV Maps.
1111 if len(layer_data
.uvmaps
) > 0:
1112 print("Adding %d UV Textures" % len(layer_data
.uvmaps
))
1113 for uvmap_key
in layer_data
.uvmaps
:
1114 map_pack
= create_mappack(layer_data
, uvmap_key
, "UV")
1115 me
.uv_textures
.new(name
=uvmap_key
)
1116 uvm
= me
.tessface_uv_textures
[-1]
1117 if not uvm
or not uvm
.data
:
1120 if fi
> len(uvm
.data
):
1132 # Now add the NGons.
1134 for ng_key
in ngons
:
1135 face_offset
= len(me
.tessfaces
)
1138 for vi
in range(len(ng
)):
1139 v_locs
.append(mathutils
.Vector(layer_data
.pnts
[ngons
[ng_key
][vi
]]))
1140 tris
= tessellate_polygon([v_locs
])
1141 me
.tessfaces
.add(len(tris
))
1143 face
= me
.tessfaces
[face_offset
]
1144 face
.vertices_raw
[0]= ng
[tri
[0]]
1145 face
.vertices_raw
[1]= ng
[tri
[1]]
1146 face
.vertices_raw
[2]= ng
[tri
[2]]
1147 face
.material_index
= me
.tessfaces
[ng_key
].material_index
1148 face
.use_smooth
= me
.tessfaces
[ng_key
].use_smooth
1151 # FaceIDs are no longer a concern, so now update the mesh.
1152 has_edges
= len(edges
) > 0 or len(layer_data
.edge_weights
) > 0
1153 me
.update(calc_edges
=has_edges
)
1156 edge_offset
= len(me
.edges
)
1157 me
.edges
.add(len(edges
))
1158 for edge_fi
in edges
:
1159 me
.edges
[edge_offset
].vertices
[0]= layer_data
.pols
[edge_fi
][0]
1160 me
.edges
[edge_offset
].vertices
[1]= layer_data
.pols
[edge_fi
][1]
1163 # Apply the Edge Weighting.
1164 if len(layer_data
.edge_weights
) > 0:
1165 for edge
in me
.edges
:
1166 edge_sa
= "{0} {1}".format(edge
.vertices
[0], edge
.vertices
[1])
1167 edge_sb
= "{0} {1}".format(edge
.vertices
[1], edge
.vertices
[0])
1168 if edge_sa
in layer_data
.edge_weights
:
1169 edge
.crease
= layer_data
.edge_weights
[edge_sa
]
1170 elif edge_sb
in layer_data
.edge_weights
:
1171 edge
.crease
= layer_data
.edge_weights
[edge_sb
]
1173 # Unfortunately we can't exlude certain faces from the subdivision.
1174 if layer_data
.has_subds
and add_subd_mod
:
1175 ob
.modifiers
.new(name
="Subsurf", type='SUBSURF')
1177 # Should we build an armature from the embedded rig?
1178 if len(layer_data
.bones
) > 0 and skel_to_arm
:
1179 bpy
.ops
.object.armature_add()
1180 arm_object
= bpy
.context
.active_object
1181 arm_object
.name
= "ARM_" + layer_data
.name
1182 arm_object
.data
.name
= arm_object
.name
1183 arm_object
.location
= layer_data
.pivot
1184 bpy
.ops
.object.mode_set(mode
='EDIT')
1185 build_armature(layer_data
, arm_object
.data
.edit_bones
)
1186 bpy
.ops
.object.mode_set(mode
='OBJECT')
1188 # Clear out the dictionaries for this layer.
1189 layer_data
.bone_names
.clear()
1190 layer_data
.bone_rolls
.clear()
1191 layer_data
.wmaps
.clear()
1192 layer_data
.colmaps
.clear()
1193 layer_data
.uvmaps
.clear()
1194 layer_data
.morphs
.clear()
1195 layer_data
.surf_tags
.clear()
1197 # We may have some invalid mesh data, See: [#27916]
1199 print("validating mesh: %r..." % me
.name
)
1200 me
.validate(verbose
=1)
1203 # With the objects made, setup the parents and re-adjust the locations.
1204 for ob_key
in ob_dict
:
1205 if ob_dict
[ob_key
][1] != -1 and ob_dict
[ob_key
][1] in ob_dict
:
1206 parent_ob
= ob_dict
[ob_dict
[ob_key
][1]]
1207 ob_dict
[ob_key
][0].parent
= parent_ob
[0]
1208 ob_dict
[ob_key
][0].location
-= parent_ob
[0].location
1210 bpy
.context
.scene
.update()
1212 print("Done Importing LWO File")
1215 from bpy
.props
import StringProperty
, BoolProperty
1218 class IMPORT_OT_lwo(bpy
.types
.Operator
):
1219 """Import LWO Operator"""
1220 bl_idname
= "import_scene.lwo"
1221 bl_label
= "Import LWO"
1222 bl_description
= "Import a LightWave Object file"
1223 bl_options
= {'REGISTER', 'UNDO'}
1225 filepath
= StringProperty(name
="File Path", description
="Filepath used for importing the LWO file", maxlen
=1024, default
="")
1227 ADD_SUBD_MOD
= BoolProperty(name
="Apply SubD Modifier", description
="Apply the Subdivision Surface modifier to layers with Subpatches", default
=True)
1228 LOAD_HIDDEN
= BoolProperty(name
="Load Hidden Layers", description
="Load object layers that have been marked as hidden", default
=False)
1229 SKEL_TO_ARM
= BoolProperty(name
="Create Armature", description
="Create an armature from an embedded Skelegon rig", default
=True)
1230 USE_EXISTING_MATERIALS
= BoolProperty(name
="Use Existing Materials", description
="Use existing materials if a material by that name already exists", default
=False)
1232 def execute(self
, context
):
1233 load_lwo(self
.filepath
,
1238 self
.USE_EXISTING_MATERIALS
)
1241 def invoke(self
, context
, event
):
1242 wm
= context
.window_manager
1243 wm
.fileselect_add(self
)
1244 return {'RUNNING_MODAL'}
1247 def menu_func(self
, context
):
1248 self
.layout
.operator(IMPORT_OT_lwo
.bl_idname
, text
="LightWave Object (.lwo)")
1252 bpy
.utils
.register_module(__name__
)
1254 bpy
.types
.INFO_MT_file_import
.append(menu_func
)
1258 bpy
.utils
.unregister_module(__name__
)
1260 bpy
.types
.INFO_MT_file_import
.remove(menu_func
)
1262 if __name__
== "__main__":