Scripts:
[blender.git] / release / scripts / truespace_export.py
1 #!BPY
2
3 """
4 Name: 'TrueSpace (.cob)...'
5 Blender: 232
6 Group: 'Export'
7 Tooltip: 'Export selected meshes to TrueSpace File Format (.cob)'
8 """
9
10 __author__ = "Anthony D'Agostino (Scorpius)"
11 __url__ = ("blender", "elysiun",
12 "Author's homepage, http://www.redrival.com/scorpius")
13 __version__ = "Part of IOSuite 0.5"
14
15 __bpydoc__ = """\
16 This script exports meshes to TrueSpace file format.
17
18 TrueSpace is a commercial modeling and rendering application. The .cob
19 file format is composed of 'chunks,' is well defined, and easy to read and
20 write. It's very similar to LightWave's lwo format.
21
22 Usage:<br>
23         Select meshes to be exported and run this script from "File->Export" menu.
24
25 Supported:<br>
26         Vertex colors will be exported, if they are present.
27
28 Known issues:<br>
29         Before exporting to .cob format, the mesh must have real-time UV
30 coordinates.  Press the FKEY to assign them.
31
32 Notes:<br>
33         There are a few differences between how Blender & TrueSpace represent
34 their objects' transformation matrices. Blender simply uses a 4x4 matrix,
35 and trueSpace splits it into the following two fields.
36
37         For the 'Local Axes' values: The x, y, and z-axis represent a simple
38 rotation matrix.  This is equivalent to Blender's object matrix before
39 it was combined with the object's scaling matrix. Dividing each value by
40 the appropriate scaling factor (and transposing at the same time)
41 produces the original rotation matrix.
42
43         For the 'Current Position' values:      This is equivalent to Blender's
44 object matrix except that the last row is omitted and the xyz location
45 is used in the last column. Binary format uses a 4x3 matrix, ascii
46 format uses a 4x4 matrix.
47
48 For Cameras: The matrix here gets a little confusing, and I'm not sure of 
49 how to handle it.
50 """
51
52
53 # $Id$
54 #
55 # +---------------------------------------------------------+
56 # | Copyright (c) 2001 Anthony D'Agostino                   |
57 # | http://www.redrival.com/scorpius                        |
58 # | scorpius@netzero.com                                    |
59 # | June 12, 2001                                           |
60 # | Released under the Blender Artistic Licence (BAL)       |
61 # | Import Export Suite v0.5                                |
62 # +---------------------------------------------------------+
63 # | Read and write Caligari trueSpace File Format (*.cob)   |
64 # +---------------------------------------------------------+
65
66 import Blender, mod_meshtools
67 import struct, os, cStringIO, time
68
69 # ==============================
70 # === Write trueSpace Format ===
71 # ==============================
72 def write(filename):
73         start = time.clock()
74         file = open(filename, "wb")
75         objects = Blender.Object.GetSelected()
76
77         write_header(file)
78
79         G,P,V,U,M = 1000,2000,3000,4000,5000
80         for object in objects:
81                 objname = object.name
82                 meshname = object.data.name
83                 mesh = Blender.NMesh.GetRaw(meshname)
84                 obj = Blender.Object.Get(objname)
85                 if not mesh: continue
86
87                 grou = generate_grou('Group ' + `objects.index(object)+1`)
88                 polh = generate_polh(objname, obj, mesh)
89                 if mod_meshtools.has_vertex_colors(mesh): vcol = generate_vcol(mesh)
90                 unit = generate_unit()
91                 mat1 = generate_mat1(mesh)
92
93                 if objects.index(object) == 0: X = 0
94
95                 write_chunk(file, "Grou", 0, 1, G, X, grou)
96                 write_chunk(file, "PolH", 0, 4, P, G, polh)
97                 if mod_meshtools.has_vertex_colors(mesh) and vcol:
98                         write_chunk(file, "VCol", 1, 0, V, P, vcol)
99                 write_chunk(file, "Unit", 0, 1, U, P, unit)
100                 write_chunk(file, "Mat1", 0, 5, M, P, mat1)
101
102                 X = G
103                 G,P,V,U,M = map(lambda x: x+1, [G,P,V,U,M])
104
105         write_chunk(file, "END ", 1, 0, 0, 0, '') # End Of File Chunk
106
107         Blender.Window.DrawProgressBar(1.0, '')  # clear progressbar
108         file.close()
109         end = time.clock()
110         seconds = " in %.2f %s" % (end-start, "seconds")
111         message = "Successfully exported " + os.path.basename(filename) + seconds
112         mod_meshtools.print_boxed(message)
113
114 # =============================
115 # === Write COB File Header ===
116 # =============================
117 def write_header(file):
118         file.write("Caligari V00.01BLH"+" "*13+"\n")
119
120 # ===================
121 # === Write Chunk ===
122 # ===================
123 def write_chunk(file, name, major, minor, chunk_id, parent_id, data):
124         file.write(name)
125         file.write(struct.pack("<2h", major, minor))
126         file.write(struct.pack("<2l", chunk_id, parent_id))
127         file.write(struct.pack("<1l", len(data)))
128         file.write(data)
129
130 # ============================================
131 # === Generate PolH (Polygonal Data) Chunk ===
132 # ============================================
133 def generate_polh(objname, obj, mesh):
134         data = cStringIO.StringIO()
135         write_ObjectName(data, objname)
136         write_LocalAxes(data, obj)
137         write_CurrentPosition(data, obj)
138         write_VertexList(data, mesh)
139         uvcoords = write_UVCoordsList(data, mesh)
140         write_FaceList(data, mesh, uvcoords)
141         return data.getvalue()
142
143 # === Write Object Name ===
144 def write_ObjectName(data, objname):
145         data.write(struct.pack("<h", 0))  # dupecount
146         data.write(struct.pack("<h", len(objname)))
147         data.write(objname)
148
149 # === Write Local Axes ===
150 def write_LocalAxes(data, obj):
151         data.write(struct.pack("<fff", obj.mat[3][0], obj.mat[3][1], obj.mat[3][2]))
152         data.write(struct.pack("<fff", obj.mat[0][0]/obj.SizeX, obj.mat[1][0]/obj.SizeX, obj.mat[2][0]/obj.SizeX))
153         data.write(struct.pack("<fff", obj.mat[0][1]/obj.SizeY, obj.mat[1][1]/obj.SizeY, obj.mat[2][1]/obj.SizeY))
154         data.write(struct.pack("<fff", obj.mat[0][2]/obj.SizeZ, obj.mat[1][2]/obj.SizeZ, obj.mat[2][2]/obj.SizeZ))
155
156 # === Write Current Position ===
157 def write_CurrentPosition(data, obj):
158         data.write(struct.pack("<ffff", obj.mat[0][0], obj.mat[0][1], obj.mat[0][2], obj.mat[3][0]))
159         data.write(struct.pack("<ffff", obj.mat[1][0], obj.mat[1][1], obj.mat[1][2], obj.mat[3][1]))
160         data.write(struct.pack("<ffff", obj.mat[2][0], obj.mat[2][1], obj.mat[2][2], obj.mat[3][2]))
161
162 # === Write Vertex List ===
163 def write_VertexList(data, mesh):
164         data.write(struct.pack("<l", len(mesh.verts)))
165         for i in range(len(mesh.verts)):
166                 if not i%100 and mod_meshtools.show_progress:
167                         Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Writing Verts")
168                 x, y, z = mesh.verts[i].co
169                 data.write(struct.pack("<fff", -y, x, z))
170
171 # === Write UV Vertex List ===
172 def write_UVCoordsList(data, mesh):
173         if not mesh.hasFaceUV():
174                 data.write(struct.pack("<l", 1))
175                 data.write(struct.pack("<2f", 0,0))
176                 return {(0,0): 0}
177                 # === Default UV Coords (one image per face) ===
178                 # data.write(struct.pack("<l", 4))
179                 # data.write(struct.pack("<8f", 0,0, 0,1, 1,1, 1,0))
180                 # return {(0,0): 0, (0,1): 1, (1,1): 2, (1,0): 3}
181                 # === Default UV Coords (one image per face) ===
182
183         # === collect, remove duplicates, add indices, and write the uv list ===
184         uvdata = cStringIO.StringIO()
185         uvcoords = {}
186         uvidx = 0
187         for i in range(len(mesh.faces)):
188                 if not i%100 and mod_meshtools.show_progress:
189                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing UV Coords")
190                 numfaceverts = len(mesh.faces[i].v)
191                 for j in range(numfaceverts-1, -1, -1):         # Reverse order
192                         u,v = mesh.faces[i].uv[j]
193                         if not uvcoords.has_key((u,v)):
194                                 uvcoords[(u,v)] = uvidx
195                                 uvidx += 1
196                                 uvdata.write(struct.pack("<ff", u,v))
197         uvdata = uvdata.getvalue()
198
199         numuvcoords = len(uvdata)/8
200         data.write(struct.pack("<l", numuvcoords))
201         data.write(uvdata)
202         #print "Number of uvcoords:", numuvcoords, '=', len(uvcoords)
203         return uvcoords
204
205 # === Write Face List ===
206 def write_FaceList(data, mesh, uvcoords):
207         data.write(struct.pack("<l", len(mesh.faces)))
208         for i in range(len(mesh.faces)):
209                 if not i%100 and mod_meshtools.show_progress:
210                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Faces")
211                 numfaceverts = len(mesh.faces[i].v)
212                 data.write(struct.pack("<B", 0x10))         # Cull Back Faces Flag
213                 data.write(struct.pack("<h", numfaceverts))
214                 data.write(struct.pack("<h", 0))            # Material Index
215                 for j in range(numfaceverts-1, -1, -1):         # Reverse order
216                         index = mesh.faces[i].v[j].index
217                         if mesh.hasFaceUV():
218                                 uv = mesh.faces[i].uv[j]
219                                 uvidx = uvcoords[uv]
220                         else:
221                                 uvidx = 0
222                         data.write(struct.pack("<ll", index, uvidx))
223
224 # ===========================================
225 # === Generate VCol (Vertex Colors) Chunk ===
226 # ===========================================
227 def generate_vcol(mesh):
228         data = cStringIO.StringIO()
229         data.write(struct.pack("<l", len(mesh.faces)))
230         uniquecolors = {}
231         unique_alpha = {}
232         for i in range(len(mesh.faces)):
233                 if not i%100 and mod_meshtools.show_progress:
234                         Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), "Writing Vertex Colors")
235                 numfaceverts = len(mesh.faces[i].v)
236                 data.write(struct.pack("<ll", i, numfaceverts))
237                 for j in range(numfaceverts-1, -1, -1):         # Reverse order
238                         r = mesh.faces[i].col[j].r
239                         g = mesh.faces[i].col[j].g
240                         b = mesh.faces[i].col[j].b
241                         a = 100  # 100 is opaque in ts
242                         uniquecolors[(r,g,b)] = None
243                         unique_alpha[mesh.faces[i].col[j].a] = None
244                         data.write(struct.pack("<BBBB", r,g,b,a))
245
246         #print "uniquecolors:", uniquecolors.keys()
247         #print "unique_alpha:", unique_alpha.keys()
248         if len(uniquecolors) == 1:
249                 return None
250         else:
251                 return data.getvalue()
252
253 # ==================================
254 # === Generate Unit (Size) Chunk ===
255 # ==================================
256 def generate_unit():
257         data = cStringIO.StringIO()
258         data.write(struct.pack("<h", 2))
259         return data.getvalue()
260
261 # ======================================
262 # === Generate Mat1 (Material) Chunk ===
263 # ======================================
264 def generate_mat1(mesh):
265         data = cStringIO.StringIO()
266         data.write(struct.pack("<h", 0))
267         data.write(struct.pack("<ccB", "p", "a", 0))
268         data.write(struct.pack("<fff", 1.0, 1.0, 1.0))  # rgb (0.0 - 1.0)
269         data.write(struct.pack("<fffff", 1, 1, 0, 0, 1))
270         if mesh.hasFaceUV():
271                 tex_mapname = r"c:\image\maps\one-dot.tga"
272                 data.write("t:")
273                 data.write(struct.pack("<B", 0x00))
274                 data.write(struct.pack("<h", len(tex_mapname)))
275                 data.write(tex_mapname)
276                 data.write(struct.pack("<4f", 0,0, 1,1))
277         return data.getvalue()
278
279 # ============================
280 # === Generate Group Chunk ===
281 # ============================
282 def generate_grou(name):
283         data = cStringIO.StringIO()
284         write_ObjectName(data, name)
285         data.write(struct.pack("<12f", 0,0,0, 1,0,0, 0,1,0, 0,0,1))
286         data.write(struct.pack("<12f", 1,0,0,0, 0,1,0,0, 0,0,1,0))
287         return data.getvalue()
288
289 def fs_callback(filename):
290         if filename.find('.cob', -4) <= 0: filename += '.cob'
291         write(filename)
292
293 Blender.Window.FileSelector(fs_callback, "Export COB")
294
295 # === Matrix Differences between Blender & trueSpace ===
296 #
297 # For the 'Local Axes' values:
298 # The x, y, and z-axis represent a simple rotation matrix.
299 # This is equivalent to Blender's object matrix before it was
300 # combined with the object's scaling matrix.  Dividing each value
301 # by the appropriate scaling factor (and transposing at the same
302 # time) produces the original rotation matrix.
303 #
304 # For the 'Current Position' values:
305 # This is equivalent to Blender's object matrix except that the
306 # last row is omitted and the xyz location is used in the last
307 # column.  Binary format uses a 4x3 matrix, ascii format uses a 4x4
308 # matrix.
309 #
310 # For Cameras: The matrix is a little confusing.