Advertisement
iPLEOMAX

[Blender 2.69+ Addon] LWO Exporter | io_export_scene_lwo.py

May 11th, 2014
603
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.76 KB | None | 0 0
  1. # LightWave Object File exporter for Blender 2.69+
  2. # Copyright (C) 2014  Morshidul Chowdhury
  3.  
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8.  
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13.  
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16.  
  17. bl_info = {
  18.     "name": "Export LightWave Objects",
  19.     "author": "Morshidul Chowdhury (iPLEOMAX)",
  20.     "version": (1, 0),
  21.     "blender": (2, 6, 9),
  22.     "location": "File > Export > LightWave Object (.lwo)",
  23.     "description": "Exports selected objects to LightWave (.lwo) format",
  24.     "warning": "",
  25.     "category": "Import-Export"
  26. }
  27.  
  28. # http://pleo.code5gaming.com/
  29. # http://blenderartists.org/forum/member.php?220524-iPLEOMAX
  30.  
  31. # This is a complete remake of Anthony D'Agostino's LightWave plugin.
  32. #
  33. # Features in R1 (current):
  34. # - Creates an extra '.lwo' option in export menu.
  35. # - Links all image paths to the lightwave file.
  36. # - Supports multi-surfaces (although there are some incompatibility issues)
  37. # - Supports multi-meshes.
  38. # - Supports multi-uv layers.
  39. # - Supports multi-vertex colors.
  40.  
  41. import bpy
  42. import io
  43. import struct
  44. import time
  45. import operator
  46. from functools import reduce
  47.  
  48. LWO_VCOLOR_MATERIAL = "LWO.VColorMaterial"
  49. LWO_DEFAULT_MATERIAL = "LWO.DefaultMaterial"
  50. LWO_ACTIVE_UV = "ActiveUVMap"
  51. LWO_ACTIVE_VC = "ActiveVColMap"
  52.  
  53. # Function from Scorpius's Plugin (Terminates the string and keeps the length even)
  54. def lwopad(str):
  55.     if len(str) % 2: str += '\0' #odd
  56.     else: str += '\0\0' #even
  57.     return str
  58.  
  59. # Function from Scorpius's Plugin (Decides whether to occupy a 4byte or 2byte space in file)
  60. def generate_vx(index):
  61.     if index < 0xFF00:
  62.         value = struct.pack(">H", index)                 # 2-byte index
  63.     else:
  64.         value = struct.pack(">L", index | 0xFF000000)    # 4-byte index
  65.     return value
  66.    
  67. def get_materials(meshes):
  68.     print("\t- Collecting materials..")
  69.     materials = []
  70.     for mesh in meshes:
  71.         if mesh.materials:
  72.             for mat in mesh.materials:
  73.                 if mat:
  74.                     if not mat.name in materials:
  75.                         materials.append(mat.name)
  76.         elif mesh.vertex_colors:
  77.             if not LWO_VCOLOR_MATERIAL in materials:
  78.                 materials.append(LWO_VCOLOR_MATERIAL)
  79.         else:
  80.             if not LWO_DEFAULT_MATERIAL in materials:
  81.                 materials.append(LWO_DEFAULT_MATERIAL)
  82.     return materials
  83.    
  84. def get_tags(materials):
  85.     print("\t- Generating tags..")
  86.     data = io.BytesIO()
  87.     for mat in materials:
  88.         data.write(bytes(lwopad(mat), 'UTF-8'))
  89.     return data.getvalue()
  90.  
  91. def get_layr(mesh, index):
  92.     print("\t- Generating layer for mesh '" + mesh.name + "'")
  93.     data = io.BytesIO()
  94.     data.write(struct.pack(">h", index))
  95.     data.write(struct.pack(">h", 0))
  96.     data.write(struct.pack(">fff", 0, 0, 0))
  97.     data.write(bytes(lwopad(mesh.name), 'UTF-8'))
  98.     return data.getvalue()
  99.    
  100. def get_pnts(mesh):
  101.     print("\t- Generating vertices for mesh '" + mesh.name + "'")
  102.     data = io.BytesIO()
  103.     for v in mesh.vertices:
  104.         x, y, z = v.co
  105.         data.write(struct.pack(">fff", x, z, y))
  106.     return data.getvalue()
  107.    
  108. def get_bbox(mesh):
  109.     print("\t- Generating bounding box for mesh '" + mesh.name + "'")
  110.     data = io.BytesIO()
  111.     if mesh.vertices:
  112.         nv = [v.co for v in mesh.vertices]
  113.         xx = [ co[0] for co in nv ]
  114.         yy = [ co[1] for co in nv ]
  115.         zz = [ co[2] for co in nv ]
  116.     else:
  117.         xx = yy = zz = [0.0,]
  118.    
  119.     data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
  120.     return data.getvalue()
  121.    
  122. def get_vmap_uv(mesh, uvlayer, activelayername):
  123.     print("\t\tUV Layer: '" + uvlayer.name + "'")
  124.     data = io.BytesIO()
  125.     data.write(b"TXUV")
  126.     data.write(struct.pack(">H", 2))
  127.    
  128.     if uvlayer.name == activelayername:
  129.         uvname = lwopad(LWO_ACTIVE_UV)
  130.         data.write(bytes(uvname, 'UTF-8'))
  131.     else:
  132.         uvname = lwopad(uvlayer.name)
  133.         data.write(bytes(uvname, 'UTF-8'))
  134.        
  135.     for face in mesh.polygons:
  136.         for vert, loop in zip(face.vertices, face.loop_indices):
  137.             uv = uvlayer.data[loop].uv
  138.             data.write(struct.pack(">H", vert))
  139.             data.write(struct.pack(">H", face.index))
  140.             data.write(struct.pack(">ff", uv.x, uv.y))
  141.     return data.getvalue()
  142.  
  143. def get_vmap_vc(mesh, vclayer, activevcname):
  144.     print("\t\tVCOL Layer: '" + vclayer.name + "'")
  145.     data = io.BytesIO()
  146.     data.write(b"RGB ")
  147.     data.write(struct.pack(">H", 3))
  148.    
  149.     if vclayer.name == activevcname:
  150.         vcname = lwopad(LWO_ACTIVE_VC)
  151.         data.write(bytes(vcname, 'UTF-8'))
  152.     else:
  153.         vcname = lwopad(vclayer.name)
  154.         data.write(bytes(vcname, 'UTF-8'))
  155.        
  156.     for face in mesh.polygons:
  157.         for vert, loop in zip(face.vertices, face.loop_indices):
  158.             color =  vclayer.data[loop].color
  159.             data.write(struct.pack(">H", vert))
  160.             data.write(struct.pack(">H", face.index))
  161.             data.write(struct.pack(">fff", color.r, color.g, color.b))
  162.     return data.getvalue()
  163.    
  164. def get_pols(mesh):
  165.     print("\t- Generating polygons for mesh '" + mesh.name + "'")
  166.     data = io.BytesIO()
  167.     data.write(b"FACE")
  168.     for face in mesh.polygons:
  169.         data.write(struct.pack(">H", face.loop_total))
  170.         for v in range(face.loop_total - 1, -1, -1):
  171.             data.write(generate_vx(face.vertices[v]))
  172.     return data.getvalue()
  173.    
  174. def get_ptag(mesh, materials):
  175.     print("\t- Generating per poly materials for mesh '" + mesh.name + "'")
  176.     data = io.BytesIO()
  177.     data.write(b"SURF")
  178.     for poly in mesh.polygons:
  179.         if mesh.materials:
  180.             matindex = poly.material_index
  181.             matname = mesh.materials[matindex].name
  182.             surfindex = materials.index(matname)
  183.            
  184.             data.write(generate_vx(poly.index))
  185.             data.write(struct.pack(">H", surfindex))
  186.         else:
  187.             data.write(generate_vx(poly.index))
  188.             data.write(struct.pack(">H", 0))
  189.     return data.getvalue()
  190.    
  191. def get_clip(image, clipid):
  192.     print("\t\tImage: '" + image.name + "'")
  193.     data = io.BytesIO()
  194.     path = image.filepath
  195.     path = path[0:2] + path.replace("\\", "/")[3:]
  196.     image = lwopad(path)
  197.     data.write(struct.pack(">L", clipid))
  198.     data.write(b"STIL")
  199.     data.write(struct.pack(">H", len(image)))
  200.     data.write(bytes(image, 'UTF-8'))
  201.     return data.getvalue()
  202.  
  203. def get_imap():
  204.     data = io.BytesIO()
  205.     data.write(struct.pack(">H", 0))
  206.     data.write(b"CHAN")
  207.     data.write(struct.pack(">H", 4))
  208.     data.write(b"COLR")
  209.     data.write(b"OPAC")
  210.     data.write(struct.pack(">H", 8))
  211.     data.write(struct.pack(">H", 0))
  212.     data.write(struct.pack(">f", 1.0))
  213.     data.write(struct.pack(">H", 0))
  214.     data.write(b"ENAB")
  215.     data.write(struct.pack(">HH", 2, 1))
  216.     data.write(b"NEGA")
  217.     data.write(struct.pack(">HH", 2, 0))
  218.     data.write(b"AXIS")
  219.     data.write(struct.pack(">HH", 2, 1))
  220.     return data.getvalue()
  221.  
  222. def create_custom_surf(mat):
  223.     print("\t\tCustom Surface: '" + mat.name + "'")
  224.     data = io.BytesIO()
  225.     data.write(bytes(lwopad(mat.name), 'UTF-8'))
  226.    
  227.     R = 0.9
  228.     G = 0.9
  229.     B = 0.9
  230.    
  231.     data.write(b"COLR")
  232.     data.write(struct.pack(">H", 0))
  233.    
  234.     data.write(b"COLR")
  235.     data.write(struct.pack(">H", 14))
  236.     data.write(struct.pack(">fffH", R, G, B, 0))
  237.  
  238.     data.write(b"DIFF")
  239.     data.write(struct.pack(">H", 6))
  240.     data.write(struct.pack(">fH", 0.8, 0))
  241.  
  242.     data.write(b"LUMI")
  243.     data.write(struct.pack(">H", 6))
  244.     data.write(struct.pack(">fH", 0.0, 0))
  245.  
  246.     data.write(b"SPEC")
  247.     data.write(struct.pack(">H", 6))
  248.     data.write(struct.pack(">fH", 0.5, 0))
  249.  
  250.     data.write(b"GLOS")
  251.     data.write(struct.pack(">H", 6))
  252.     gloss = 50 / (255/2.0)
  253.     gloss = round(gloss, 1)
  254.     data.write(struct.pack(">fH", gloss, 0))
  255.    
  256.     if mat and mat.node_tree and mat.node_tree.nodes:
  257.         for node in mat.node_tree.nodes:
  258.             if node:
  259.                 if node.type == 'TEX_IMAGE':
  260.                     data.write(b"BLOK")
  261.                     data.write(struct.pack(">H", 104))
  262.                     imap = get_imap()
  263.                     data.write(b"IMAP")
  264.                     data.write(struct.pack(">H", len(imap)))
  265.                     data.write(imap)
  266.                    
  267.                     data.write(b"IMAG")
  268.                     data.write(struct.pack(">HH", 2, 1))
  269.                     data.write(b"PROJ")
  270.                     data.write(struct.pack(">HH", 2, 5))
  271.                     data.write(b"VMAP")
  272.                     uvname = lwopad(LWO_ACTIVE_UV)
  273.                     data.write(struct.pack(">H", len(uvname)))
  274.                     data.write(bytes(uvname, 'UTF-8'))
  275.                            
  276.     return data.getvalue()
  277.    
  278. def create_surf(matname):
  279.     print("\t\tDefault Surface: '" + mat.name + "'")
  280.     data = io.BytesIO()
  281.     data.write(bytes(lwopad(matname), 'UTF-8'))
  282.    
  283.     R = 0.9
  284.     G = 0.9
  285.     B = 0.9
  286.    
  287.     data.write(b"COLR")
  288.     data.write(struct.pack(">H", 0))
  289.    
  290.     data.write(b"COLR")
  291.     data.write(struct.pack(">H", 14))
  292.     data.write(struct.pack(">fffH", R, G, B, 0))
  293.  
  294.     data.write(b"DIFF")
  295.     data.write(struct.pack(">H", 6))
  296.     data.write(struct.pack(">fH", 0.8, 0))
  297.  
  298.     data.write(b"LUMI")
  299.     data.write(struct.pack(">H", 6))
  300.     data.write(struct.pack(">fH", 0, 0))
  301.  
  302.     data.write(b"SPEC")
  303.     data.write(struct.pack(">H", 6))
  304.     data.write(struct.pack(">fH", 0.5, 0))
  305.  
  306.     data.write(b"GLOS")
  307.     data.write(struct.pack(">H", 6))
  308.     gloss = 50 / (255/2.0)
  309.     gloss = round(gloss, 1)
  310.     data.write(struct.pack(">fH", gloss, 0))
  311.    
  312.     return data.getvalue()
  313.    
  314. def get_surf(matname):
  315.     matid = bpy.data.materials.find(matname)
  316.     if matid != -1:
  317.         return create_custom_surf(bpy.data.materials[matid])
  318.     return create_surf(matname)
  319.    
  320. def get_header(chunks):
  321.     print("\t\tGenerating headers..")
  322.     data = io.BytesIO()
  323.     chunk_sizes = map(len, chunks)
  324.     chunk_sizes = reduce(operator.add, chunk_sizes)
  325.     form_size = chunk_sizes + len(chunks)*8 + len("FORM")
  326.     data.write(b"FORM")
  327.     data.write(struct.pack(">L", form_size))
  328.     data.write(b"LWO2")
  329.     return data.getvalue()
  330.    
  331. def write_lwo(filepath):
  332.    
  333.     print("LWO exporter started..")
  334.     start = time.time()
  335.    
  336.     if not bpy.context.selected_objects:
  337.         print("Please select an object before exporting.")
  338.         return
  339.    
  340.     meshes = []
  341.    
  342.     print("\t- Collecting meshes..")
  343.     for obj in bpy.context.selected_objects:
  344.         if obj.type == 'MESH':
  345.             mesh = obj.to_mesh(bpy.context.scene, True, 'PREVIEW')
  346.             meshes.append(mesh)
  347.             print("\t\tMesh: " + mesh.name)
  348.     del obj
  349.  
  350.     if not meshes:
  351.         print("\tThere are no meshes in your selection.")
  352.         return
  353.    
  354.     materials = get_materials(meshes)
  355.     tags = get_tags(materials)
  356.    
  357.     chunks = [tags]
  358.     meshdata = io.BytesIO()
  359.     meshindex = 0
  360.    
  361.     for mesh in meshes:
  362.         layr = get_layr(mesh, meshindex)
  363.         meshindex += 1
  364.         meshdata.write(b"LAYR")
  365.         meshdata.write(struct.pack(">L", len(layr)))
  366.         meshdata.write(layr)
  367.         chunks.append(layr)
  368.        
  369.         pnts = get_pnts(mesh)
  370.         meshdata.write(b"PNTS")
  371.         meshdata.write(struct.pack(">L", len(pnts)))
  372.         meshdata.write(pnts)
  373.         chunks.append(pnts)
  374.        
  375.         bbox = get_bbox(mesh)
  376.         meshdata.write(b"BBOX")
  377.         meshdata.write(struct.pack(">L", len(bbox)))
  378.         meshdata.write(bbox)
  379.         chunks.append(bbox)
  380.        
  381.         pols = get_pols(mesh)
  382.         meshdata.write(b"POLS")
  383.         meshdata.write(struct.pack(">L", len(pols)))
  384.         meshdata.write(pols)
  385.         chunks.append(pols)
  386.        
  387.         ptag = get_ptag(mesh, materials)
  388.         meshdata.write(b"PTAG")
  389.         meshdata.write(struct.pack(">L", len(ptag)))
  390.         meshdata.write(ptag)
  391.         chunks.append(ptag)
  392.  
  393.         if mesh.uv_layers:
  394.             print("\t- Generating UV layers for mesh '" + mesh.name + "'")
  395.             for uvlayer in mesh.uv_layers:
  396.                 vmap = get_vmap_uv(mesh, uvlayer, mesh.uv_layers.active.name)
  397.                 meshdata.write(b"VMAD")
  398.                 meshdata.write(struct.pack(">L", len(vmap)))
  399.                 meshdata.write(vmap)
  400.                 chunks.append(vmap)
  401.        
  402.         clipid = 1
  403.        
  404.         print("\t- Generating Images for mesh '" + mesh.name + "'")
  405.         for mat in mesh.materials:
  406.             if mat and mat.node_tree and mat.node_tree.nodes:
  407.                 for node in mat.node_tree.nodes:
  408.                     if node:
  409.                         if node.type == 'TEX_IMAGE':
  410.                             clip = get_clip(node.image, clipid)
  411.                             meshdata.write(b"CLIP")
  412.                             meshdata.write(struct.pack(">L", len(clip)))
  413.                             meshdata.write(clip)
  414.                             chunks.append(clip)
  415.                             clipid += 1
  416.        
  417.         if mesh.vertex_colors:
  418.             print("\t- Generating Vertex colors for mesh '" + mesh.name + "'")
  419.             for vclayer in mesh.vertex_colors:
  420.                 vmap = get_vmap_vc(mesh, vclayer, mesh.vertex_colors.active.name)
  421.                 meshdata.write(b"VMAD")
  422.                 meshdata.write(struct.pack(">L", len(vmap)))
  423.                 meshdata.write(vmap)
  424.                 chunks.append(vmap)
  425.        
  426.         bpy.data.meshes.remove(mesh)
  427.    
  428.     print("\t- Generating Surfaces")
  429.     for mat in materials:
  430.         surf = get_surf(mat)
  431.         meshdata.write(b"SURF")
  432.         meshdata.write(struct.pack(">L", len(surf)))
  433.         meshdata.write(surf)
  434.         chunks.append(surf)
  435.    
  436.     header = get_header(chunks)
  437.    
  438.     if not filepath.lower().endswith('.lwo'): filepath += '.lwo'
  439.     file = open(filepath, 'wb')
  440.    
  441.     file.write(header)
  442.    
  443.     file.write(b"TAGS")
  444.     file.write(struct.pack(">L", len(tags)))
  445.     file.write(tags)
  446.    
  447.     file.write(meshdata.getvalue())
  448.     meshdata.close()
  449.    
  450.     file.close()
  451.    
  452.     print("LWO exporter ended in %.03f seconds!" % (time.time() - start))
  453.    
  454. from bpy_extras.io_utils import ExportHelper
  455. from bpy.props import StringProperty, BoolProperty, EnumProperty
  456.  
  457. class EXPORT_OT_lwo(bpy.types.Operator):
  458.  
  459.     bl_idname= "export_scene.lwo"
  460.     bl_label= "Export LightWave"
  461.     bl_description= "Export a LightWave Object file"
  462.     bl_options= {'REGISTER', 'UNDO'}
  463.  
  464.     filepath= StringProperty(name="File Path", description="File name for exporting the LWO file", maxlen=1024, default="")
  465.  
  466.     def execute(self, context):
  467.         write_lwo(self.filepath)
  468.         return {'FINISHED'}
  469.  
  470.     def invoke(self, context, event):
  471.         wm = context.window_manager
  472.         wm.fileselect_add(self)
  473.         return {'RUNNING_MODAL'}
  474.  
  475.  
  476. def menu_func(self, context):
  477.     self.layout.operator(EXPORT_OT_lwo.bl_idname, text="LightWave Object (.lwo)")
  478.  
  479.  
  480. def register():
  481.     bpy.utils.register_module(__name__)
  482.     bpy.types.INFO_MT_file_export.append(menu_func)
  483.  
  484.  
  485. def unregister():
  486.     bpy.utils.unregister_module(__name__)
  487.     bpy.types.INFO_MT_file_export.remove(menu_func)
  488.  
  489. if __name__ == "__main__":
  490.     register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement