Welcome to mirror list, hosted at ThFree Co, Russian Federation.

export_map.py « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2262ae3b89b44fcfce206ec2be163857fe2d98f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#!BPY

"""
Name: 'Quake 3 (.map)'
Blender: 249
Group: 'Export'
Tooltip: 'Export to Quake map format'
"""

__author__ = 'Campbell Barton'
__version__ = '0.1a'
__email__ = "ideasman42@gmail.com"
__bpydoc__ = """\
This script Exports a Quake 3 map format.

 Supports meshes, lights and nurbs patch surfaces
"""

# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C): Campbell Barton
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------




from Blender import *
import BPyMesh

PREF_SCALE= Draw.Create(100)
PREF_FACE_THICK= Draw.Create(0.1)
PREF_GRID_SNAP= Draw.Create(0)
# Quake 1/2?
# PREF_DEF_TEX_OPTS= Draw.Create(' 0 0 0 1 1\n') # not user settable yet
# Quake 3+?
PREF_DEF_TEX_OPTS= Draw.Create(' 0 0 0 1 1 0 0 0\n') # not user settable yet

PREF_NULL_TEX= Draw.Create('NULL') # not user settable yet
PREF_INVIS_TEX= Draw.Create('common/caulk')

def write_cube2brush(file, faces):
	'''
	Takes 6 faces and writes a brush,
	these faces can be from 1 mesh, 1 cube within a mesh of larger cubes
	Faces could even come from different meshes or be contrived.
	'''
	# comment only
	# file.write('// brush "%s", "%s"\n' % (ob.name, ob.getData(name_only=1)))
	file.write('// brush from cube\n{\n')

	if PREF_GRID_SNAP.val:		format_vec= '( %d %d %d ) '
	else:						format_vec= '( %.8f %.8f %.8f ) '

	for f in faces:
		# from 4 verts this gets them in reversed order and only 3 of them
		# 0,1,2,3 -> 2,1,0
		for v in f.v[2::-1]:
			file.write(format_vec % tuple(v.co) )	
		
		try:	mode= f.mode
		except:	mode= 0
		
		if mode & Mesh.FaceModes.INVISIBLE:
			file.write(PREF_INVIS_TEX.val)
		else:
			try:	image= f.image
			except:	image= None
			
			if image:	file.write(sys.splitext(sys.basename(image.filename))[0])
			else:		file.write(PREF_NULL_TEX.val)
			
		# Texture stuff ignored for now
		file.write(PREF_DEF_TEX_OPTS.val)
	file.write('}\n')


def round_vec(v):
	if PREF_GRID_SNAP.val:
		return round(v.x), round(v.y), round(v.z)
	else:
		return tuple(v)

def write_face2brush(file, face):
	'''
	takes a face and writes it as a brush
	each face is a cube/brush
	'''
	
	if PREF_GRID_SNAP.val:		format_vec= '( %d %d %d ) '
	else:						format_vec= '( %.8f %.8f %.8f ) '
	
	
	image_text= PREF_NULL_TEX.val
	
	try:	mode= face.mode
	except:	mode= 0
	
	if mode & Mesh.FaceModes.INVISIBLE:
		image_text= PREF_INVIS_TEX.val
	else:
		try:	image= face.image
		except:	image= None
		if image:	image_text = sys.splitext(sys.basename(image.filename))[0]
	
	# original verts as tuples for writing
	orig_vco= [tuple(v.co) for v in face]
	
	# new verts that give the face a thickness
	dist= PREF_SCALE.val * PREF_FACE_THICK.val
	new_vco= [round_vec(v.co - (v.no * dist)) for v in face]
	#new_vco= [round_vec(v.co - (face.no * dist)) for v in face]
	
	file.write('// brush from face\n{\n')
	# front
	for co in orig_vco[2::-1]:
		file.write(format_vec % co )
	file.write(image_text)
	# Texture stuff ignored for now
	file.write(PREF_DEF_TEX_OPTS.val)
	
	
	for co in new_vco[:3]:
		file.write(format_vec % co )
	if mode & Mesh.FaceModes.TWOSIDE:
		file.write(image_text)
	else:
		file.write(PREF_INVIS_TEX.val)
	
	# Texture stuff ignored for now
	file.write(PREF_DEF_TEX_OPTS.val)
	
	# sides.
	if len(orig_vco)==3: # Tri, it seemms tri brushes are supported.
		index_pairs= ((0,1), (1,2), (2,0))
	else:
		index_pairs= ((0,1), (1,2), (2,3), (3,0))
	
	for i1, i2 in index_pairs:
		for co in orig_vco[i1], orig_vco[i2], new_vco[i2]:
			file.write( format_vec %  co )
		file.write(PREF_INVIS_TEX.val)
		file.write(PREF_DEF_TEX_OPTS.val)

	file.write('}\n')

def is_cube_facegroup(faces):
	'''
	Returens a bool, true if the faces make up a cube
	'''
	# cube must have 6 faces
	if len(faces) != 6:
		print '1'
		return False
	
	# Check for quads and that there are 6 unique verts
	verts= {}
	for f in faces:
		if len(f)!= 4:
			return False
		
		for v in f:
			verts[v.index]= 0
	
	if len(verts) != 8:
		return False
	
	# Now check that each vert has 3 face users
	for f in faces:
		for v in f:
			verts[v.index] += 1
	
	for v in verts.itervalues():
		if v != 3: # vert has 3 users?
			return False
	
	# Could we check for 12 unique edges??, probably not needed.
	return True
	
def is_tricyl_facegroup(faces):
	'''
	is the face group a tri cylinder
	Returens a bool, true if the faces make an extruded tri solid
	'''
	
	# cube must have 5 faces
	if len(faces) != 5:
		print '1'
		return False
	
	# Check for quads and that there are 6 unique verts
	verts= {}
	tottri= 0
	for f in faces:
		if len(f)== 3:
			tottri+=1
		
		for v in f:
			verts[v.index]= 0
	
	if len(verts) != 6 or tottri != 2:
		return False
	
	# Now check that each vert has 3 face users
	for f in faces:
		for v in f:
			verts[v.index] += 1
	
	for v in verts.itervalues():
		if v != 3: # vert has 3 users?
			return False
	
	# Could we check for 12 unique edges??, probably not needed.
	return True

def write_node_map(file, ob):
	'''
	Writes the properties of an object (empty in this case)
	as a MAP node as long as it has the property name - classname
	returns True/False based on weather a node was written
	'''
	props= [(p.name, p.data) for p in ob.game_properties]
	
	IS_MAP_NODE= False
	for name, value in props:
		if name=='classname':
			IS_MAP_NODE= True
			break
		
	if not IS_MAP_NODE:
		return False
	
	# Write a node
	file.write('{\n')
	for name_value in props:
		file.write('"%s" "%s"\n' % name_value)
	if PREF_GRID_SNAP.val:
		file.write('"origin" "%d %d %d"\n' % tuple([round(axis*PREF_SCALE.val) for axis in ob.getLocation('worldspace')]) )
	else:
		file.write('"origin" "%.6f %.6f %.6f"\n' % tuple([axis*PREF_SCALE.val for axis in ob.getLocation('worldspace')]) )
	file.write('}\n')
	return True


def export_map(filepath):
	
	pup_block = [\
	('Scale:', PREF_SCALE, 1, 1000, 'Scale the blender scene by this value.'),\
	('Face Width:', PREF_FACE_THICK, 0.01, 10, 'Thickness of faces exported as brushes.'),\
	('Grid Snap', PREF_GRID_SNAP, 'snaps floating point values to whole numbers.'),\
	'Null Texture',\
	('', PREF_NULL_TEX, 1, 128, 'Export textureless faces with this texture'),\
	'Unseen Texture',\
	('', PREF_INVIS_TEX, 1, 128, 'Export invisible faces with this texture'),\
	]
	
	if not Draw.PupBlock('map export', pup_block):
		return
	
	Window.WaitCursor(1)
	time= sys.time()
	print 'Map Exporter 0.0'
	file= open(filepath, 'w')
	
	
	obs_mesh= []
	obs_lamp= []
	obs_surf= []
	obs_empty= []
	
	SCALE_MAT= Mathutils.Matrix()
	SCALE_MAT[0][0]= SCALE_MAT[1][1]= SCALE_MAT[2][2]= PREF_SCALE.val
	
	dummy_mesh= Mesh.New()
	
	TOTBRUSH= TOTLAMP= TOTNODE= 0
	
	for ob in Object.GetSelected():
		type= ob.type
		if type == 'Mesh':		obs_mesh.append(ob)
		elif type == 'Surf':	obs_surf.append(ob)
		elif type == 'Lamp':	obs_lamp.append(ob)
		elif type == 'Empty':	obs_empty.append(ob)
	
	if obs_mesh or obs_surf:
		# brushes and surf's must be under worldspan
		file.write('\n// entity 0\n')
		file.write('{\n')
		file.write('"classname" "worldspawn"\n')
	
	
	print '\twriting cubes from meshes'
	for ob in obs_mesh:
		dummy_mesh.getFromObject(ob.name)
		
		#print len(mesh_split2connected(dummy_mesh))
		
		# Is the object 1 cube? - object-is-a-brush
		dummy_mesh.transform(ob.matrixWorld*SCALE_MAT) # 1 to tx the normals also
		
		if PREF_GRID_SNAP.val:
			for v in dummy_mesh.verts:
				co= v.co
				co.x= round(co.x)
				co.y= round(co.y)
				co.z= round(co.z)
		
		# High quality normals
		BPyMesh.meshCalcNormals(dummy_mesh)
		
		# Split mesh into connected regions
		for face_group in BPyMesh.mesh2linkedFaces(dummy_mesh):
			if is_cube_facegroup(face_group):
				write_cube2brush(file, face_group)
				TOTBRUSH+=1
			elif is_tricyl_facegroup(face_group):
				write_cube2brush(file, face_group)
				TOTBRUSH+=1
			else:
				for f in face_group:
					write_face2brush(file, f)
					TOTBRUSH+=1
			
			#print 'warning, not exporting "%s" it is not a cube' % ob.name
			
	
	dummy_mesh.verts= None
	

	valid_dims= 3,5,7,9,11,13,15
	for ob in obs_surf:
		'''
		Surf, patches
		'''
		surf_name= ob.getData(name_only=1)
		data= Curve.Get(surf_name)
		mat = ob.matrixWorld*SCALE_MAT
		
		# This is what a valid patch looks like
		
		"""
// brush 0
{
patchDef2
{
NULL
( 3 3 0 0 0 )
(
( ( -64 -64 0 0 0 ) ( -64 0 0 0 -2 ) ( -64 64 0 0 -4 ) )
( ( 0 -64 0 2 0 ) ( 0 0 0 2 -2 ) ( 0 64 0 2 -4 ) )
( ( 64 -64 0 4 0 ) ( 64 0 0 4 -2 ) ( 80 88 0 4 -4 ) )
)
}
}
		"""
		for i, nurb in enumerate(data):
			u= nurb.pointsU
			v= nurb.pointsV
			if u in valid_dims and v in valid_dims:
				
				file.write('// brush %d surf_name\n' % i)
				file.write('{\n')
				file.write('patchDef2\n')
				file.write('{\n')
				file.write('NULL\n')
				file.write('( %d %d 0 0 0 )\n' % (u, v) )
				file.write('(\n')
				
				u_iter = 0
				for p in nurb:
					
					if u_iter == 0:
						file.write('(')
					
					u_iter += 1
					
					# add nmapping 0 0 ?
					if PREF_GRID_SNAP.val:
						file.write(' ( %d %d %d 0 0 )' % round_vec(Mathutils.Vector(p[0:3]) * mat))
					else:
						file.write(' ( %.6f %.6f %.6f 0 0 )' % tuple(Mathutils.Vector(p[0:3]) * mat))
					
					# Move to next line
					if u_iter == u:
						file.write(' )\n')
						u_iter = 0
				
				file.write(')\n')
				file.write('}\n')
				file.write('}\n')
				
				
				# Debugging
				# for p in nurb: print 'patch', p
				
			else:
				print "NOT EXPORTING PATCH", surf_name, u,v, 'Unsupported'
	
	
	if obs_mesh or obs_surf:
		file.write('}\n') # end worldspan
	
	
	print '\twriting lamps'
	for ob in obs_lamp:
		print '\t\t%s' % ob.name
		lamp= ob.data
		file.write('{\n')
		file.write('"classname" "light"\n')
		file.write('"light" "%.6f"\n' % (lamp.dist* PREF_SCALE.val))
		if PREF_GRID_SNAP.val:
			file.write('"origin" "%d %d %d"\n' % tuple([round(axis*PREF_SCALE.val) for axis in ob.getLocation('worldspace')]) )
		else:
			file.write('"origin" "%.6f %.6f %.6f"\n' % tuple([axis*PREF_SCALE.val for axis in ob.getLocation('worldspace')]) )
		file.write('"_color" "%.6f %.6f %.6f"\n' % tuple(lamp.col))
		file.write('"style" "0"\n')
		file.write('}\n')
		TOTLAMP+=1
	
	
	print '\twriting empty objects as nodes'
	for ob in obs_empty:
		if write_node_map(file, ob):
			print '\t\t%s' % ob.name
			TOTNODE+=1
		else:
			print '\t\tignoring %s' % ob.name
	
	Window.WaitCursor(0)
	
	print 'Exported Map in %.4fsec' % (sys.time()-time)
	print 'Brushes: %d  Nodes: %d  Lamps %d\n' % (TOTBRUSH, TOTNODE, TOTLAMP)
	
	
def main():
	Window.FileSelector(export_map, 'EXPORT MAP', '*.map')

if __name__ == '__main__': main()
# export_map('/foo.map')