00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 VERSION = '0.6.0'
00018
00019 '''
00020 CHANGELOG
00021 0.6.0
00022 * patched to work with 2.66.
00023 0.5.9
00024 * apply patch from Thomas for Blender 2.6x support
00025 0.5.8
00026 * Clean all names that will be used as filenames on disk. Adjust all places
00027 that use these names for refs instead of ob.name/ob.data.name. Replaced chars
00028 are \, /, :, *, ?, ", <, >, | and spaces. Tested on work with ogre
00029 material, mesh and skeleton writing/refs inside the files and txml refs.
00030 Shows warning at final report if we had to resort to the renaming so user
00031 can possibly rename the object.
00032 * Added silent auto update checks if blender2ogre was installed using
00033 the .exe installer. This will keep people up to date when new versions are out.
00034 * Fix tracker issue 48: Needs to check if outputting to /tmp or
00035 ~/.wine/drive_c/tmp on Linux. Thanks to vax456 for providing the patch,
00036 added him to contributors. Preview mesh's are now placed under /tmp
00037 on Linux systems if the OgreMeshy executable ends with .exe
00038 * Fix tracker issue 46: add operationtype to <submesh>
00039 * Implement a modal dialog that reports if material names have invalid
00040 characters and cant be saved on disk. This small popup will show until
00041 user presses left or right mouse (anywhere).
00042 * Fix tracker issue 44: XML Attributes not properly escaped in .scene file
00043 * Implemented reading OgreXmlConverter path from windows registry.
00044 The .exe installer will ship with certain tools so we can stop guessing
00045 and making the user install tools separately and setting up paths.
00046 * Fix bug that .mesh files were not generated while doing a .txml export.
00047 This was result of the late 2.63 mods that forgot to update object
00048 facecount before determining if mesh should be exported.
00049 * Fix bug that changed settings in the export dialog were forgotten when you
00050 re-exported without closing blender. Now settings should persist always
00051 from the last export. They are also stored to disk so the same settings
00052 are in use when if you restart Blender.
00053 * Fix bug that once you did a export, the next time the export location was
00054 forgotten. Now on sequential exports, the last export path is remembered in
00055 the export dialog.
00056 * Remove all local:// from asset refs and make them relative in .txml export.
00057 Having relative refs is the best for local preview and importing the txml
00058 to existing scenes.
00059 * Make .material generate what version of this plugins was used to generate
00060 the material file. Will be helpful in production to catch things.
00061 Added pretty printing line endings so the raw .material data is easier to read.
00062 * Improve console logging for the export stages. Run Blender from
00063 cmd prompt to see this information.
00064 * Clean/fix documentation in code for future development
00065 * Add todo to code for future development
00066 * Restructure/move code for easier readability
00067 * Remove extra white spaces and convert tabs to space
00068 0.5.7
00069 * Update to Blender 2.6.3.
00070 * Fixed xz-y Skeleton rotation (again)
00071 * Added additional Keyframe at the end of each animation to prevent
00072 ogre from interpolating back to the start
00073 * Added option to ignore non-deformable bones
00074 * Added option to export nla-strips independently from each other
00075
00076 TODO
00077 * Remove this section and integrate below with code :)
00078 * Fix terrain collision offset bug
00079 * Add realtime transform (rotation is missing)
00080 * Fix camera rotated -90 ogre-dot-scene
00081 * Set description field for all pyRNA
00082 '''
00083
00084 bl_info = {
00085 "name": "OGRE Exporter (.scene, .mesh, .skeleton) and RealXtend (.txml)",
00086 "author": "Brett, S.Rombauts, F00bar, Waruck, Mind Calamity, Mr.Magne, Jonne Nauha, vax456",
00087 "version": (0, 6, 0),
00088 "blender": (2, 6, 6),
00089 "location": "File > Export...",
00090 "description": "Export to Ogre xml and binary formats",
00091 "wiki_url": "http://code.google.com/p/blender2ogre/w/list",
00092 "tracker_url": "http://code.google.com/p/blender2ogre/issues/list",
00093 "category": "Import-Export"
00094 }
00095
00096
00097
00098
00099 UI_CLASSES = []
00100
00101 def UI(cls):
00102 ''' Toggles the Ogre interface panels '''
00103 if cls not in UI_CLASSES:
00104 UI_CLASSES.append(cls)
00105 return cls
00106
00107 def hide_user_interface():
00108 for cls in UI_CLASSES:
00109 bpy.utils.unregister_class( cls )
00110
00111 def uid(ob):
00112 if ob.uid == 0:
00113 high = 0
00114 multires = 0
00115 for o in bpy.data.objects:
00116 if o.uid > high: high = o.uid
00117 if o.use_multires_lod: multires += 1
00118 high += 1 + (multires*10)
00119 if high < 100: high = 100
00120 ob.uid = high
00121 return ob.uid
00122
00123
00124
00125 import os, sys, time, array, ctypes, math
00126
00127 try:
00128
00129 import bpy, mathutils
00130 from bpy.props import *
00131 except ImportError:
00132
00133 assert __name__ == '__main__'
00134 print('Trying to compile Rpython C-library')
00135 assert sys.version_info.major == 2
00136 print('...searching for rpythonic...')
00137 sys.path.append('../rpythonic')
00138 import rpythonic
00139 rpythonic.set_pypy_root( '../pypy' )
00140 import pypy.rpython.lltypesystem.rffi as rffi
00141 from pypy.rlib import streamio
00142 rpy = rpythonic.RPython( 'blender2ogre' )
00143
00144 @rpy.bind(
00145 path=str,
00146 facesAddr=int,
00147 facesSmoothAddr=int,
00148 facesMatAddr=int,
00149 vertsPosAddr=int,
00150 vertsNorAddr=int,
00151 numFaces=int,
00152 numVerts=int,
00153 materialNames=str,
00154 )
00155
00156 def dotmesh( path, facesAddr, facesSmoothAddr, facesMatAddr, vertsPosAddr, vertsNorAddr, numFaces, numVerts, materialNames ):
00157 print('PATH----------------', path)
00158 materials = []
00159 for matname in materialNames.split(';'):
00160 print( 'Material Name: %s' %matname )
00161 materials.append( matname )
00162
00163 file = streamio.open_file_as_stream( path, 'w')
00164
00165 faces = rffi.cast( rffi.UINTP, facesAddr )
00166 facesSmooth = rffi.cast( rffi.CCHARP, facesSmoothAddr )
00167 facesMat = rffi.cast( rffi.USHORTP, facesMatAddr )
00168
00169 vertsPos = rffi.cast( rffi.FLOATP, vertsPosAddr )
00170 vertsNor = rffi.cast( rffi.FLOATP, vertsNorAddr )
00171
00172 VB = [
00173 '<sharedgeometry>',
00174 '<vertexbuffer positions="true" normals="true">'
00175 ]
00176 fastlookup = {}
00177 ogre_vert_index = 0
00178 triangles = []
00179 for fidx in range( numFaces ):
00180 smooth = ord( facesSmooth[ fidx ] )
00181
00182 matidx = facesMat[ fidx ]
00183 i = fidx*4
00184 ai = faces[ i ]; bi = faces[ i+1 ]
00185 ci = faces[ i+2 ]; di = faces[ i+3 ]
00186
00187 triangle = []
00188 for J in [ai, bi, ci]:
00189 i = J*3
00190 x = rffi.cast( rffi.DOUBLE, vertsPos[ i ] )
00191 y = rffi.cast( rffi.DOUBLE, vertsPos[ i+1 ] )
00192 z = rffi.cast( rffi.DOUBLE, vertsPos[ i+2 ] )
00193 pos = (x,y,z)
00194
00195 x = rffi.cast( rffi.DOUBLE, vertsNor[ i ] )
00196 y = rffi.cast( rffi.DOUBLE, vertsNor[ i+1 ] )
00197 z = rffi.cast( rffi.DOUBLE, vertsNor[ i+2 ] )
00198 nor = (x,y,z)
00199
00200 SIG = (pos,nor)
00201 skip = False
00202 if J in fastlookup:
00203 for otherSIG in fastlookup[ J ]:
00204 if SIG == otherSIG:
00205 triangle.append( fastlookup[J][otherSIG] )
00206 skip = True
00207 break
00208
00209 if not skip:
00210 triangle.append( ogre_vert_index )
00211 fastlookup[ J ][ SIG ] = ogre_vert_index
00212
00213 else:
00214 triangle.append( ogre_vert_index )
00215 fastlookup[ J ] = { SIG : ogre_vert_index }
00216
00217 if skip: continue
00218
00219 xml = [
00220 '<vertex>',
00221 '<position x="%s" y="%s" z="%s" />' %pos,
00222 '<normal x="%s" y="%s" z="%s" />' %nor,
00223 '</vertex>'
00224 ]
00225 VB.append( '\n'.join(xml) )
00226
00227 ogre_vert_index += 1
00228
00229 triangles.append( triangle )
00230 VB.append( '</vertexbuffer>' )
00231 VB.append( '</sharedgeometry>' )
00232
00233 file.write( '\n'.join(VB) )
00234 del VB
00235
00236 SMS = ['<submeshes>']
00237
00238 SM = [
00239 '<submesh usesharedvertices="true" use32bitindexes="true" material="%s" operationtype="triangle_list">' % 'somemat',
00240 '<faces count="%s">' %'100',
00241 ]
00242 for tri in triangles:
00243
00244
00245 assert isinstance(tri,tuple)
00246 s = '<face v1="%s" v2="%s" v3="%s" />' %tri
00247 SM.append( s )
00248 SM.append( '</faces>' )
00249 SM.append( '</submesh>' )
00250
00251 file.write( '\n'.join(SM) )
00252 file.close()
00253
00254 rpy.cache(refresh=1)
00255 sys.exit('OK: module compiled and cached')
00256
00257
00258
00259 import hashlib, getpass, tempfile, configparser, subprocess, pickle
00260 from xml.sax.saxutils import XMLGenerator, quoteattr
00261
00262 class CMesh(object):
00263
00264 def __init__(self, data):
00265 self.numVerts = N = len( data.vertices )
00266 self.numFaces = Nfaces = len(data.tessfaces)
00267
00268 self.vertex_positions = (ctypes.c_float * (N * 3))()
00269 data.vertices.foreach_get( 'co', self.vertex_positions )
00270 v = self.vertex_positions
00271
00272 self.vertex_normals = (ctypes.c_float * (N * 3))()
00273 data.vertices.foreach_get( 'normal', self.vertex_normals )
00274
00275 self.faces = (ctypes.c_uint * (Nfaces * 4))()
00276 data.tessfaces.foreach_get( 'vertices_raw', self.faces )
00277
00278 self.faces_normals = (ctypes.c_float * (Nfaces * 3))()
00279 data.tessfaces.foreach_get( 'normal', self.faces_normals )
00280
00281 self.faces_smooth = (ctypes.c_bool * Nfaces)()
00282 data.tessfaces.foreach_get( 'use_smooth', self.faces_smooth )
00283
00284 self.faces_material_index = (ctypes.c_ushort * Nfaces)()
00285 data.tessfaces.foreach_get( 'material_index', self.faces_material_index )
00286
00287 self.vertex_colors = []
00288 if len( data.vertex_colors ):
00289 vc = data.vertex_colors[0]
00290 n = len(vc.data)
00291
00292 self.vcolors1 = (ctypes.c_float * (n * 3))()
00293 vc.data.foreach_get( 'color1', self.vcolors1 )
00294 self.vertex_colors.append( self.vcolors1 )
00295
00296 self.vcolors2 = (ctypes.c_float * (n * 3))()
00297 vc.data.foreach_get( 'color2', self.vcolors2 )
00298 self.vertex_colors.append( self.vcolors2 )
00299
00300 self.vcolors3 = (ctypes.c_float * (n * 3))()
00301 vc.data.foreach_get( 'color3', self.vcolors3 )
00302 self.vertex_colors.append( self.vcolors3 )
00303
00304 self.vcolors4 = (ctypes.c_float * (n * 3))()
00305 vc.data.foreach_get( 'color4', self.vcolors4 )
00306 self.vertex_colors.append( self.vcolors4 )
00307
00308 self.uv_textures = []
00309 if data.uv_textures.active:
00310 for layer in data.uv_textures:
00311 n = len(layer.data) * 8
00312 a = (ctypes.c_float * n)()
00313 layer.data.foreach_get( 'uv_raw', a )
00314 self.uv_textures.append( a )
00315
00316 def save( blenderobject, path ):
00317 cmesh = Mesh( blenderobject.data )
00318 start = time.time()
00319 dotmesh(
00320 path,
00321 ctypes.addressof( cmesh.faces ),
00322 ctypes.addressof( cmesh.faces_smooth ),
00323 ctypes.addressof( cmesh.faces_material_index ),
00324 ctypes.addressof( cmesh.vertex_positions ),
00325 ctypes.addressof( cmesh.vertex_normals ),
00326 cmesh.numFaces,
00327 cmesh.numVerts,
00328 )
00329 print('Mesh dumped in %s seconds' % (time.time()-start))
00330
00331
00332 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
00333 if SCRIPT_DIR not in sys.path:
00334 sys.path.append( SCRIPT_DIR )
00335
00336
00337
00338 bpy.types.Object.use_avatar = BoolProperty(
00339 name='enable avatar',
00340 description='enables EC_Avatar',
00341 default=False)
00342 bpy.types.Object.avatar_reference = StringProperty(
00343 name='avatar reference',
00344 description='sets avatar reference URL',
00345 maxlen=128,
00346 default='')
00347 BoolProperty( name='enable avatar', description='enables EC_Avatar', default=False)
00348
00349
00350
00351 bpy.types.Object.uid = IntProperty(
00352 name="unique ID",
00353 description="unique ID for Tundra",
00354 default=0, min=0, max=2**14)
00355
00356
00357
00358 bpy.types.Object.use_draw_distance = BoolProperty(
00359 name='enable draw distance',
00360 description='use LOD draw distance',
00361 default=False)
00362 bpy.types.Object.draw_distance = FloatProperty(
00363 name='draw distance',
00364 description='distance at which to begin drawing object',
00365 default=0.0, min=0.0, max=10000.0)
00366 bpy.types.Object.cast_shadows = BoolProperty(
00367 name='cast shadows',
00368 description='cast shadows',
00369 default=False)
00370 bpy.types.Object.use_multires_lod = BoolProperty(
00371 name='Enable Multires LOD',
00372 description='enables multires LOD',
00373 default=False)
00374 bpy.types.Object.multires_lod_range = FloatProperty(
00375 name='multires LOD range',
00376 description='far distance at which multires is set to base level',
00377 default=30.0, min=0.0, max=10000.0)
00378
00379
00380
00381 _physics_modes = [
00382 ('NONE', 'NONE', 'no physics'),
00383 ('RIGID_BODY', 'RIGID_BODY', 'rigid body'),
00384 ('SOFT_BODY', 'SOFT_BODY', 'soft body'),
00385 ]
00386 _collision_modes = [
00387 ('NONE', 'NONE', 'no collision'),
00388 ('PRIMITIVE', 'PRIMITIVE', 'primitive collision type'),
00389 ('MESH', 'MESH', 'triangle-mesh or convex-hull collision type'),
00390 ('DECIMATED', 'DECIMATED', 'auto-decimated collision type'),
00391 ('COMPOUND', 'COMPOUND', 'children primitive compound collision type'),
00392 ('TERRAIN', 'TERRAIN', 'terrain (height map) collision type'),
00393 ]
00394
00395 bpy.types.Object.physics_mode = EnumProperty(
00396 items = _physics_modes,
00397 name = 'physics mode',
00398 description='physics mode',
00399 default='NONE')
00400 bpy.types.Object.physics_friction = FloatProperty(
00401 name='Simple Friction',
00402 description='physics friction',
00403 default=0.1, min=0.0, max=1.0)
00404 bpy.types.Object.physics_bounce = FloatProperty(
00405 name='Simple Bounce',
00406 description='physics bounce',
00407 default=0.01, min=0.0, max=1.0)
00408 bpy.types.Object.collision_terrain_x_steps = IntProperty(
00409 name="Ogre Terrain: x samples",
00410 description="resolution in X of height map",
00411 default=64, min=4, max=8192)
00412 bpy.types.Object.collision_terrain_y_steps = IntProperty(
00413 name="Ogre Terrain: y samples",
00414 description="resolution in Y of height map",
00415 default=64, min=4, max=8192)
00416 bpy.types.Object.collision_mode = EnumProperty(
00417 items = _collision_modes,
00418 name = 'primary collision mode',
00419 description='collision mode',
00420 default='NONE')
00421 bpy.types.Object.subcollision = BoolProperty(
00422 name="collision compound",
00423 description="member of a collision compound",
00424 default=False)
00425
00426
00427
00428 bpy.types.Speaker.play_on_load = BoolProperty(
00429 name='play on load',
00430 default=False)
00431 bpy.types.Speaker.loop = BoolProperty(
00432 name='loop sound',
00433 default=False)
00434 bpy.types.Speaker.use_spatial = BoolProperty(
00435 name='3D spatial sound',
00436 default=True)
00437
00438
00439
00440 _IMAGE_FORMATS = [
00441 ('NONE','NONE', 'do not convert image'),
00442 ('bmp', 'bmp', 'bitmap format'),
00443 ('jpg', 'jpg', 'jpeg format'),
00444 ('gif', 'gif', 'gif format'),
00445 ('png', 'png', 'png format'),
00446 ('tga', 'tga', 'targa format'),
00447 ('dds', 'dds', 'nvidia dds format'),
00448 ]
00449
00450 bpy.types.Image.use_convert_format = BoolProperty(
00451 name='use convert format',
00452 default=False
00453 )
00454 bpy.types.Image.convert_format = EnumProperty(
00455 name='convert to format',
00456 description='converts to image format using imagemagick',
00457 items=_IMAGE_FORMATS,
00458 default='NONE')
00459 bpy.types.Image.jpeg_quality = IntProperty(
00460 name="jpeg quality",
00461 description="quality of jpeg",
00462 default=80, min=0, max=100)
00463 bpy.types.Image.use_color_quantize = BoolProperty(
00464 name='use color quantize',
00465 default=False)
00466 bpy.types.Image.use_color_quantize_dither = BoolProperty(
00467 name='use color quantize dither',
00468 default=True)
00469 bpy.types.Image.color_quantize = IntProperty(
00470 name="color quantize",
00471 description="reduce to N colors (requires ImageMagick)",
00472 default=32, min=2, max=256)
00473 bpy.types.Image.use_resize_half = BoolProperty(
00474 name='resize by 1/2',
00475 default=False)
00476 bpy.types.Image.use_resize_absolute = BoolProperty(
00477 name='force image resize',
00478 default=False)
00479 bpy.types.Image.resize_x = IntProperty(
00480 name='resize X',
00481 description='only if image is larger than defined, use ImageMagick to resize it down',
00482 default=256, min=2, max=4096)
00483 bpy.types.Image.resize_y = IntProperty(
00484 name='resize Y',
00485 description='only if image is larger than defined, use ImageMagick to resize it down',
00486 default=256, min=2, max=4096)
00487
00488
00489
00490 bpy.types.Material.ogre_depth_write = BoolProperty(
00491
00492 name='depth write',
00493 default=True)
00494 bpy.types.Material.ogre_depth_check = BoolProperty(
00495
00496
00497
00498
00499 name='depth check',
00500 default=True)
00501 bpy.types.Material.ogre_alpha_to_coverage = BoolProperty(
00502
00503
00504
00505
00506
00507 name='multisample alpha edges',
00508 default=False)
00509 bpy.types.Material.ogre_light_scissor = BoolProperty(
00510
00511
00512
00513
00514
00515
00516
00517
00518
00519 name='light scissor',
00520 default=False)
00521 bpy.types.Material.ogre_light_clip_planes = BoolProperty(
00522 name='light clip planes',
00523 default=False)
00524 bpy.types.Material.ogre_normalise_normals = BoolProperty(
00525 name='normalise normals',
00526 default=False,
00527 description="Scaling objects causes normals to also change magnitude, which can throw off your lighting calculations. By default, the SceneManager detects this and will automatically re-normalise normals for any scaled object, but this has a cost. If you'd prefer to control this manually, call SceneManager::setNormaliseNormalsOnScale(false) and then use this option on materials which are sensitive to normals being resized.")
00528 bpy.types.Material.ogre_lighting = BoolProperty(
00529
00530
00531 name='dynamic lighting',
00532 default=True)
00533 bpy.types.Material.ogre_colour_write = BoolProperty(
00534
00535
00536
00537
00538
00539 name='color-write',
00540 default=True)
00541 bpy.types.Material.use_fixed_pipeline = BoolProperty(
00542
00543
00544 name='fixed pipeline',
00545 default=True)
00546 bpy.types.Material.use_material_passes = BoolProperty(
00547
00548
00549 name='use ogre extra material passes (layers)',
00550 default=False)
00551 bpy.types.Material.use_in_ogre_material_pass = BoolProperty(
00552 name='Layer Toggle',
00553 default=True)
00554 bpy.types.Material.use_ogre_advanced_options = BoolProperty(
00555 name='Show Advanced Options',
00556 default=False)
00557 bpy.types.Material.use_ogre_parent_material = BoolProperty(
00558 name='Use Script Inheritance',
00559 default=False)
00560 bpy.types.Material.ogre_parent_material = EnumProperty(
00561 name="Script Inheritence",
00562 description='ogre parent material class',
00563 items=[])
00564 bpy.types.Material.ogre_polygon_mode = EnumProperty(
00565 name='faces draw type',
00566 description="ogre face draw mode",
00567 items=[ ('solid', 'solid', 'SOLID'),
00568 ('wireframe', 'wireframe', 'WIREFRAME'),
00569 ('points', 'points', 'POINTS') ],
00570 default='solid')
00571 bpy.types.Material.ogre_shading = EnumProperty(
00572 name='hardware shading',
00573 description="Sets the kind of shading which should be used for representing dynamic lighting for this pass.",
00574 items=[ ('flat', 'flat', 'FLAT'),
00575 ('gouraud', 'gouraud', 'GOURAUD'),
00576 ('phong', 'phong', 'PHONG') ],
00577 default='gouraud')
00578 bpy.types.Material.ogre_cull_hardware = EnumProperty(
00579 name='hardware culling',
00580 description="If the option 'cull_hardware clockwise' is set, all triangles whose vertices are viewed in clockwise order from the camera will be culled by the hardware.",
00581 items=[ ('clockwise', 'clockwise', 'CLOCKWISE'),
00582 ('anticlockwise', 'anticlockwise', 'COUNTER CLOCKWISE'),
00583 ('none', 'none', 'NONE') ],
00584 default='clockwise')
00585 bpy.types.Material.ogre_transparent_sorting = EnumProperty(
00586 name='transparent sorting',
00587 description="By default all transparent materials are sorted such that renderables furthest away from the camera are rendered first. This is usually the desired behaviour but in certain cases this depth sorting may be unnecessary and undesirable. If for example it is necessary to ensure the rendering order does not change from one frame to the next. In this case you could set the value to 'off' to prevent sorting.",
00588 items=[ ('on', 'on', 'ON'),
00589 ('off', 'off', 'OFF'),
00590 ('force', 'force', 'FORCE ON') ],
00591 default='on')
00592 bpy.types.Material.ogre_illumination_stage = EnumProperty(
00593 name='illumination stage',
00594 description='When using an additive lighting mode (SHADOWTYPE_STENCIL_ADDITIVE or SHADOWTYPE_TEXTURE_ADDITIVE), the scene is rendered in 3 discrete stages, ambient (or pre-lighting), per-light (once per light, with shadowing) and decal (or post-lighting). Usually OGRE figures out how to categorise your passes automatically, but there are some effects you cannot achieve without manually controlling the illumination.',
00595 items=[ ('', '', 'autodetect'),
00596 ('ambient', 'ambient', 'ambient'),
00597 ('per_light', 'per_light', 'lights'),
00598 ('decal', 'decal', 'decal') ],
00599 default=''
00600 )
00601
00602 _ogre_depth_func = [
00603 ('less_equal', 'less_equal', '<='),
00604 ('less', 'less', '<'),
00605 ('equal', 'equal', '=='),
00606 ('not_equal', 'not_equal', '!='),
00607 ('greater_equal', 'greater_equal', '>='),
00608 ('greater', 'greater', '>'),
00609 ('always_fail', 'always_fail', 'false'),
00610 ('always_pass', 'always_pass', 'true'),
00611 ]
00612
00613 bpy.types.Material.ogre_depth_func = EnumProperty(
00614 items=_ogre_depth_func,
00615 name='depth buffer function',
00616 description='If depth checking is enabled (see depth_check) a comparison occurs between the depth value of the pixel to be written and the current contents of the buffer. This comparison is normally less_equal, i.e. the pixel is written if it is closer (or at the same distance) than the current contents',
00617 default='less_equal')
00618
00619 _ogre_scene_blend_ops = [
00620 ('add', 'add', 'DEFAULT'),
00621 ('subtract', 'subtract', 'SUBTRACT'),
00622 ('reverse_subtract', 'reverse_subtract', 'REVERSE SUBTRACT'),
00623 ('min', 'min', 'MIN'),
00624 ('max', 'max', 'MAX'),
00625 ]
00626
00627 bpy.types.Material.ogre_scene_blend_op = EnumProperty(
00628 items=_ogre_scene_blend_ops,
00629 name='scene blending operation',
00630 description='This directive changes the operation which is applied between the two components of the scene blending equation',
00631 default='add')
00632
00633 _ogre_scene_blend_types = [
00634 ('one zero', 'one zero', 'DEFAULT'),
00635 ('alpha_blend', 'alpha_blend', "The alpha value of the rendering output is used as a mask. Equivalent to 'scene_blend src_alpha one_minus_src_alpha'"),
00636 ('add', 'add', "The colour of the rendering output is added to the scene. Good for explosions, flares, lights, ghosts etc. Equivalent to 'scene_blend one one'."),
00637 ('modulate', 'modulate', "The colour of the rendering output is multiplied with the scene contents. Generally colours and darkens the scene, good for smoked glass, semi-transparent objects etc. Equivalent to 'scene_blend dest_colour zero'"),
00638 ('colour_blend', 'colour_blend', 'Colour the scene based on the brightness of the input colours, but dont darken. Equivalent to "scene_blend src_colour one_minus_src_colour"'),
00639 ]
00640 for mode in 'dest_colour src_colour one_minus_dest_colour dest_alpha src_alpha one_minus_dest_alpha one_minus_src_alpha'.split():
00641 _ogre_scene_blend_types.append( ('one %s'%mode, 'one %s'%mode, '') )
00642 del mode
00643
00644 bpy.types.Material.ogre_scene_blend = EnumProperty(
00645 items=_ogre_scene_blend_types,
00646 name='scene blend',
00647 description='blending operation of material to scene',
00648 default='one zero')
00649
00650
00651
00652 _faq_ = '''
00653
00654 Q: I have hundres of objects, is there a way i can merge them on export only?
00655 A: Yes, just add them to a group named starting with "merge", or link the group.
00656
00657 Q: Can i use subsurf or multi-res on a mesh with an armature?
00658 A: Yes.
00659
00660 Q: Can i use subsurf or multi-res on a mesh with shape animation?
00661 A: No.
00662
00663 Q: I don't see any objects when i export?
00664 A: You must select the objects you wish to export.
00665
00666 Q: I don't see my animations when exported?
00667 A: Make sure you created an NLA strip on the armature.
00668
00669 Q: Do i need to bake my IK and other constraints into FK on my armature before export?
00670 A: No.
00671
00672 '''
00673
00674
00675 ''' todo: Update the nonsense C:\Tundra2 paths from defaul config and fix this doc.
00676 Additionally point to some doc how to build opengl only version on windows if that really is needed and
00677 remove the old Tundra 7z link. '''
00678
00679 _doc_installing_ = '''
00680 Installing:
00681 Installing the Addon:
00682 You can simply copy io_export_ogreDotScene.py to your blender installation under blender/2.6x/scripts/addons/
00683 and enable it in the user-prefs interface (CTRL+ALT+U)
00684 Or you can use blenders interface, under user-prefs, click addons, and click 'install-addon'
00685 (its a good idea to delete the old version first)
00686
00687 Required:
00688 1. Blender 2.63
00689
00690 2. Install Ogre Command Line tools to the default path: C:\\OgreCommandLineTools from http://www.ogre3d.org/download/tools
00691 * These tools are used to create the binary Mesh from the .xml mesh generated by this plugin.
00692 * Linux users may use above and Wine, or install from source, or install via apt-get install ogre-tools.
00693
00694 Optional:
00695 3. Install NVIDIA DDS Legacy Utilities - Install them to default path.
00696 * http://developer.nvidia.com/object/dds_utilities_legacy.html
00697 * Linux users will need to use Wine.
00698
00699 4. Install Image Magick
00700 * http://www.imagemagick.org
00701
00702 5. Copy OgreMeshy to C:\\OgreMeshy
00703 * If your using 64bit Windows, you may need to download a 64bit OgreMeshy
00704 * Linux copy to your home folder.
00705
00706 6. realXtend Tundra
00707 * For latest Tundra releases see http://code.google.com/p/realxtend-naali/downloads/list
00708 - You may need to tweak the config to tell your Tundra path or install to C:\Tundra2
00709 * Old OpenGL only build can be found from http://blender2ogre.googlecode.com/files/realxtend-Tundra-2.1.2-OpenGL.7z
00710 - Windows: extract to C:\Tundra2
00711 - Linux: extract to ~/Tundra2
00712 '''
00713
00714
00715
00716 AXIS_MODES = [
00717 ('xyz', 'xyz', 'no swapping'),
00718 ('xz-y', 'xz-y', 'ogre standard'),
00719 ('-xzy', '-xzy', 'non standard'),
00720 ('aldeb', 'aldeb', 'invert x and z axis'),
00721 ]
00722
00723 def swap(vec):
00724 if CONFIG['SWAP_AXIS'] == 'xyz': return vec
00725 elif CONFIG['SWAP_AXIS'] == 'xzy':
00726 if len(vec) == 3: return mathutils.Vector( [vec.x, vec.z, vec.y] )
00727 elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, vec.x, vec.z, vec.y] )
00728 elif CONFIG['SWAP_AXIS'] == '-xzy':
00729 if len(vec) == 3: return mathutils.Vector( [-vec.x, vec.z, vec.y] )
00730 elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, -vec.x, vec.z, vec.y] )
00731 elif CONFIG['SWAP_AXIS'] == 'xz-y':
00732 if len(vec) == 3: return mathutils.Vector( [vec.x, vec.z, -vec.y] )
00733 elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, vec.x, vec.z, -vec.y] )
00734 elif CONFIG['SWAP_AXIS'] == 'aldeb':
00735 if len(vec) == 3: return mathutils.Vector( [vec.x, -vec.z, vec.y] )
00736 elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, vec.x, -vec.z, vec.y] )
00737 else:
00738 print( 'unknown swap axis mode', CONFIG['SWAP_AXIS'] )
00739 assert 0
00740
00741
00742
00743 CONFIG_PATH = bpy.utils.user_resource('CONFIG', path='scripts', create=True)
00744 CONFIG_FILENAME = 'blender2ogre.pickle'
00745 CONFIG_FILEPATH = os.path.join(CONFIG_PATH, CONFIG_FILENAME)
00746
00747 _CONFIG_DEFAULTS_ALL = {
00748 'TUNDRA_STREAMING' : True,
00749 'COPY_SHADER_PROGRAMS' : True,
00750 'MAX_TEXTURE_SIZE' : 4096,
00751 'SWAP_AXIS' : 'xz-y',
00752 'ONLY_DEFORMABLE_BONES' : False,
00753 'ONLY_KEYFRAMED_BONES' : False,
00754 'OGRE_INHERIT_SCALE' : False,
00755 'FORCE_IMAGE_FORMAT' : 'NONE',
00756 'TOUCH_TEXTURES' : True,
00757 'SEP_MATS' : True,
00758 'SCENE' : True,
00759 'SELONLY' : True,
00760 'EXPORT_HIDDEN' : True,
00761 'FORCE_CAMERA' : True,
00762 'FORCE_LAMPS' : True,
00763 'MESH' : True,
00764 'MESH_OVERWRITE' : True,
00765 'ARM_ANIM' : True,
00766 'SHAPE_ANIM' : True,
00767 'ARRAY' : True,
00768 'MATERIALS' : True,
00769 'DDS_MIPS' : True,
00770 'TRIM_BONE_WEIGHTS' : 0.01,
00771 'lodLevels' : 0,
00772 'lodDistance' : 300,
00773 'lodPercent' : 40,
00774 'nuextremityPoints' : 0,
00775 'generateEdgeLists' : False,
00776 'generateTangents' : True,
00777 'tangentSemantic' : 'tangent',
00778 'tangentUseParity' : 4,
00779 'tangentSplitMirrored' : False,
00780 'tangentSplitRotated' : False,
00781 'reorganiseBuffers' : True,
00782 'optimiseAnimations' : True,
00783 }
00784
00785 _CONFIG_TAGS_ = 'OGRETOOLS_XML_CONVERTER OGRETOOLS_MESH_MAGICK TUNDRA_ROOT OGRE_MESHY IMAGE_MAGICK_CONVERT NVCOMPRESS NVIDIATOOLS_EXE USER_MATERIALS SHADER_PROGRAMS TUNDRA_STREAMING'.split()
00786
00787 ''' todo: Change pretty much all of these windows ones. Make a smarter way of detecting
00788 Ogre tools and Tundra from various default folders. Also consider making a installer that
00789 ships Ogre cmd line tools to ease the setup steps for end users. '''
00790
00791 _CONFIG_DEFAULTS_WINDOWS = {
00792 'OGRETOOLS_XML_CONVERTER' : 'C:\\OgreCommandLineTools\\OgreXmlConverter.exe',
00793 'OGRETOOLS_MESH_MAGICK' : 'C:\\OgreCommandLineTools\\MeshMagick.exe',
00794 'TUNDRA_ROOT' : 'C:\\Tundra2',
00795 'OGRE_MESHY' : 'C:\\OgreMeshy\\Ogre Meshy.exe',
00796 'IMAGE_MAGICK_CONVERT' : 'C:\\Program Files\\ImageMagick\\convert.exe',
00797 'NVIDIATOOLS_EXE' : 'C:\\Program Files\\NVIDIA Corporation\\DDS Utilities\\nvdxt.exe',
00798 'USER_MATERIALS' : 'C:\\Tundra2\\media\\materials',
00799 'SHADER_PROGRAMS' : 'C:\\Tundra2\\media\\materials\\programs',
00800 'NVCOMPRESS' : 'C:\\nvcompress.exe'
00801 }
00802
00803 _CONFIG_DEFAULTS_UNIX = {
00804 'OGRETOOLS_XML_CONVERTER' : '/usr/local/bin/OgreXMLConverter',
00805 'OGRETOOLS_MESH_MAGICK' : '/usr/local/bin/MeshMagick',
00806 'TUNDRA_ROOT' : '~/Tundra2',
00807 'OGRE_MESHY' : '~/OgreMeshy/Ogre Meshy.exe',
00808 'IMAGE_MAGICK_CONVERT' : '/usr/bin/convert',
00809 'NVIDIATOOLS_EXE' : '~/.wine/drive_c/Program Files/NVIDIA Corporation/DDS Utilities',
00810 'USER_MATERIALS' : '~/Tundra2/media/materials',
00811 'SHADER_PROGRAMS' : '~/Tundra2/media/materials/programs',
00812
00813
00814 'NVCOMPRESS' : '/usr/local/bin/nvcompress'
00815 }
00816
00817
00818 if sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
00819 for tag in _CONFIG_DEFAULTS_UNIX:
00820 path = _CONFIG_DEFAULTS_UNIX[ tag ]
00821 if path.startswith('~'):
00822 _CONFIG_DEFAULTS_UNIX[ tag ] = os.path.expanduser( path )
00823 elif tag.startswith('OGRETOOLS') and not os.path.isfile( path ):
00824 _CONFIG_DEFAULTS_UNIX[ tag ] = os.path.join( '/usr/bin', os.path.split( path )[-1] )
00825 del tag
00826 del path
00827
00828
00829
00830
00831 CONFIG = {}
00832
00833 def load_config():
00834 global CONFIG
00835
00836 if os.path.isfile( CONFIG_FILEPATH ):
00837 try:
00838 with open( CONFIG_FILEPATH, 'rb' ) as f:
00839 CONFIG = pickle.load( f )
00840 except:
00841 print('[ERROR]: Can not read config from %s' %CONFIG_FILEPATH)
00842
00843 for tag in _CONFIG_DEFAULTS_ALL:
00844 if tag not in CONFIG:
00845 CONFIG[ tag ] = _CONFIG_DEFAULTS_ALL[ tag ]
00846
00847 for tag in _CONFIG_TAGS_:
00848 if tag not in CONFIG:
00849 if sys.platform.startswith('win'):
00850 CONFIG[ tag ] = _CONFIG_DEFAULTS_WINDOWS[ tag ]
00851 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
00852 CONFIG[ tag ] = _CONFIG_DEFAULTS_UNIX[ tag ]
00853 else:
00854 print( 'ERROR: unknown platform' )
00855 assert 0
00856
00857 try:
00858 if sys.platform.startswith('win'):
00859 import winreg
00860
00861 registry_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r'Software\blender2ogre', 0, winreg.KEY_READ)
00862 exe_install_dir = winreg.QueryValueEx(registry_key, "Path")[0]
00863 if exe_install_dir != "":
00864
00865 if os.path.isfile(exe_install_dir + "OgreXmlConverter.exe"):
00866 print ("Using OgreXmlConverter from install path:", exe_install_dir + "OgreXmlConverter.exe")
00867 CONFIG['OGRETOOLS_XML_CONVERTER'] = exe_install_dir + "OgreXmlConverter.exe"
00868
00869
00870
00871
00872 if os.path.isfile(exe_install_dir + "check-for-updates.exe"):
00873 subprocess.Popen([exe_install_dir + "check-for-updates.exe", "/silent"])
00874 except Exception as e:
00875 print("Exception while reading windows registry:", e)
00876
00877
00878 for tag in _CONFIG_TAGS_:
00879 default = CONFIG[ tag ]
00880 func = eval( 'lambda self,con: CONFIG.update( {"%s" : self.%s} )' %(tag,tag) )
00881 if type(default) is bool:
00882 prop = BoolProperty(
00883 name=tag, description='updates bool setting', default=default,
00884 options={'SKIP_SAVE'}, update=func
00885 )
00886 else:
00887 prop = StringProperty(
00888 name=tag, description='updates path setting', maxlen=128, default=default,
00889 options={'SKIP_SAVE'}, update=func
00890 )
00891 setattr( bpy.types.WindowManager, tag, prop )
00892
00893 return CONFIG
00894
00895 CONFIG = load_config()
00896
00897 def save_config():
00898
00899 if os.path.isdir( CONFIG_PATH ):
00900 try:
00901 with open( CONFIG_FILEPATH, 'wb' ) as f:
00902 pickle.dump( CONFIG, f, -1 )
00903 except:
00904 print('[ERROR]: Can not write to %s' %CONFIG_FILEPATH)
00905 else:
00906 print('[ERROR:] Config directory does not exist %s' %CONFIG_PATH)
00907
00908 class Blender2Ogre_ConfigOp(bpy.types.Operator):
00909 '''operator: saves current b2ogre configuration'''
00910 bl_idname = "ogre.save_config"
00911 bl_label = "save config file"
00912 bl_options = {'REGISTER'}
00913
00914 @classmethod
00915 def poll(cls, context):
00916 return True
00917 def invoke(self, context, event):
00918 save_config()
00919 Report.reset()
00920 Report.messages.append('SAVED %s' %CONFIG_FILEPATH)
00921 Report.show()
00922 return {'FINISHED'}
00923
00924
00925
00926
00927
00928
00929 MISSING_MATERIAL = '''
00930 material _missing_material_
00931 {
00932 receive_shadows off
00933 technique
00934 {
00935 pass
00936 {
00937 ambient 0.1 0.1 0.1 1.0
00938 diffuse 0.8 0.0 0.0 1.0
00939 specular 0.5 0.5 0.5 1.0 12.5
00940 emissive 0.3 0.3 0.3 1.0
00941 }
00942 }
00943 }
00944 '''
00945
00946
00947
00948 def timer_diff_str(start):
00949 return "%0.2f" % (time.time()-start)
00950
00951 def find_bone_index( ob, arm, groupidx):
00952 if groupidx < len(ob.vertex_groups):
00953 vg = ob.vertex_groups[ groupidx ]
00954 j = 0
00955 for i,bone in enumerate(arm.pose.bones):
00956 if not bone.bone.use_deform and CONFIG['ONLY_DEFORMABLE_BONES']:
00957 j+=1
00958 if bone.name == vg.name:
00959 return i-j
00960 else:
00961 print('WARNING: object vertex groups not in sync with armature', ob, arm, groupidx)
00962
00963 def mesh_is_smooth( mesh ):
00964 for face in mesh.tessfaces:
00965 if face.use_smooth: return True
00966
00967 def find_uv_layer_index( uvname, material=None ):
00968
00969 idx = 0
00970 for mesh in bpy.data.meshes:
00971 if material is None or material.name in mesh.materials:
00972 if mesh.uv_textures:
00973 names = [ uv.name for uv in mesh.uv_textures ]
00974 if uvname in names:
00975 idx = names.index( uvname )
00976 break
00977 return idx
00978
00979 def has_custom_property( a, name ):
00980 for prop in a.items():
00981 n,val = prop
00982 if n == name:
00983 return True
00984
00985 def is_strictly_simple_terrain( ob ):
00986
00987 if len(ob.data.vertices) != 4 and len(ob.data.tessfaces) != 1:
00988 return False
00989 elif len(ob.modifiers) < 2:
00990 return False
00991 elif ob.modifiers[0].type != 'SUBSURF' or ob.modifiers[1].type != 'DISPLACE':
00992 return False
00993 elif ob.modifiers[0].subdivision_type != 'SIMPLE':
00994 return False
00995 elif ob.modifiers[1].direction != 'Z':
00996 return False
00997 else:
00998 return True
00999
01000 def get_image_textures( mat ):
01001 r = []
01002 for s in mat.texture_slots:
01003 if s and s.texture.type == 'IMAGE':
01004 r.append( s )
01005 return r
01006
01007 def indent( level, *args ):
01008 if not args:
01009 return ' ' * level
01010 else:
01011 a = ''
01012 for line in args:
01013 a += ' ' * level
01014 a += line
01015 a += '\n'
01016 return a
01017
01018 def gather_instances():
01019 instances = {}
01020 for ob in bpy.context.scene.objects:
01021 if ob.data and ob.data.users > 1:
01022 if ob.data not in instances:
01023 instances[ ob.data ] = []
01024 instances[ ob.data ].append( ob )
01025 return instances
01026
01027 def select_instances( context, name ):
01028 for ob in bpy.context.scene.objects:
01029 ob.select = False
01030 ob = bpy.context.scene.objects[ name ]
01031 if ob.data:
01032 inst = gather_instances()
01033 for ob in inst[ ob.data ]: ob.select = True
01034 bpy.context.scene.objects.active = ob
01035
01036 def select_group( context, name, options={} ):
01037 for ob in bpy.context.scene.objects:
01038 ob.select = False
01039 for grp in bpy.data.groups:
01040 if grp.name == name:
01041
01042
01043
01044
01045
01046 bpy.context.scene.objects.active = grp.objects[0]
01047 for ob in grp.objects:
01048 ob.select = True
01049 else:
01050 pass
01051
01052 def get_objects_using_materials( mats ):
01053 obs = []
01054 for ob in bpy.data.objects:
01055 if ob.type == 'MESH':
01056 for mat in ob.data.materials:
01057 if mat in mats:
01058 if ob not in obs:
01059 obs.append( ob )
01060 break
01061 return obs
01062
01063 def get_materials_using_image( img ):
01064 mats = []
01065 for mat in bpy.data.materials:
01066 for slot in get_image_textures( mat ):
01067 if slot.texture.image == img:
01068 if mat not in mats:
01069 mats.append( mat )
01070 return mats
01071
01072 def get_parent_matrix( ob, objects ):
01073 if not ob.parent:
01074 return mathutils.Matrix(((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)))
01075 else:
01076 if ob.parent in objects:
01077 return ob.parent.matrix_world.copy()
01078 else:
01079 return get_parent_matrix(ob.parent, objects)
01080
01081 def merge_group( group ):
01082 print('--------------- merge group ->', group )
01083 copies = []
01084 for ob in group.objects:
01085 if ob.type == 'MESH':
01086 print( '\t group member', ob.name )
01087 o2 = ob.copy(); copies.append( o2 )
01088 o2.data = o2.to_mesh(bpy.context.scene, True, "PREVIEW")
01089 while o2.modifiers:
01090 o2.modifiers.remove( o2.modifiers[0] )
01091 bpy.context.scene.objects.link( o2 )
01092 merged = merge( copies )
01093 merged.name = group.name
01094 merged.data.name = group.name
01095 return merged
01096
01097 def merge_objects( objects, name='_temp_', transform=None ):
01098 assert objects
01099 copies = []
01100 for ob in objects:
01101 ob.select = False
01102 if ob.type == 'MESH':
01103 o2 = ob.copy(); copies.append( o2 )
01104 o2.data = o2.to_mesh(bpy.context.scene, True, "PREVIEW")
01105 while o2.modifiers:
01106 o2.modifiers.remove( o2.modifiers[0] )
01107 if transform:
01108 o2.matrix_world = transform * o2.matrix_local
01109 bpy.context.scene.objects.link( o2 )
01110 merged = merge( copies )
01111 merged.name = name
01112 merged.data.name = name
01113 return merged
01114
01115 def merge( objects ):
01116 print('MERGE', objects)
01117 for ob in bpy.context.selected_objects:
01118 ob.select = False
01119 for ob in objects:
01120 print('\t'+ob.name)
01121 ob.select = True
01122 assert not ob.library
01123 bpy.context.scene.objects.active = ob
01124 bpy.ops.object.join()
01125 return bpy.context.active_object
01126
01127 def get_merge_group( ob, prefix='merge' ):
01128 m = []
01129 for grp in ob.users_group:
01130 if grp.name.lower().startswith(prefix): m.append( grp )
01131 if len(m)==1:
01132
01133
01134
01135 return m[0]
01136 elif m:
01137 print('WARNING: an object can not be in two merge groups at the same time', ob)
01138 return
01139
01140 def wordwrap( txt ):
01141 r = ['']
01142 for word in txt.split(' '):
01143 word = word.replace('\t', ' '*3)
01144 r[-1] += word + ' '
01145 if len(r[-1]) > 90:
01146 r.append( '' )
01147 return r
01148
01149
01150
01151 class RElement(object):
01152 def appendChild( self, child ):
01153 self.childNodes.append( child )
01154
01155 def setAttribute( self, name, value ):
01156 self.attributes[name]=value
01157
01158 def __init__(self, tag):
01159 self.tagName = tag
01160 self.childNodes = []
01161 self.attributes = {}
01162
01163 def toprettyxml(self, lines, indent ):
01164 s = '<%s ' % self.tagName
01165 sortedNames = sorted( self.attributes.keys() )
01166 for name in sortedNames:
01167 value = self.attributes[name]
01168 if not isinstance(value, str):
01169 value = str(value)
01170 s += '%s=%s ' % (name, quoteattr(value))
01171 if not self.childNodes:
01172 s += '/>'; lines.append( (' '*indent)+s )
01173 else:
01174 s += '>'; lines.append( (' '*indent)+s )
01175 indent += 1
01176 for child in self.childNodes:
01177 child.toprettyxml( lines, indent )
01178 indent -= 1
01179 lines.append((' '*indent) + '</%s>' % self.tagName )
01180
01181 class RDocument(object):
01182 def __init__(self):
01183 self.documentElement = None
01184
01185 def appendChild(self, root):
01186 self.documentElement = root
01187
01188 def createElement(self, tag):
01189 e = RElement(tag)
01190 e.document = self
01191 return e
01192
01193 def toprettyxml(self):
01194 indent = 0
01195 lines = []
01196 self.documentElement.toprettyxml(lines, indent)
01197 return '\n'.join(lines)
01198
01199 class SimpleSaxWriter():
01200 def __init__(self, output, root_tag, root_attrs):
01201 self.output = output
01202 self.root_tag = root_tag
01203 self.indent=0
01204 output.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
01205 self.start_tag(root_tag, root_attrs)
01206
01207 def _out_tag(self, name, attrs, isLeaf):
01208
01209 self.output.write(" " * self.indent)
01210 self.output.write("<%s" % name)
01211 sortedNames = sorted( attrs.keys() )
01212 for name in sortedNames:
01213 value = attrs[ name ]
01214
01215 if not isinstance(value, str):
01216
01217 value = str(value)
01218 self.output.write(" %s=%s" % (name, quoteattr(value)))
01219 if isLeaf:
01220 self.output.write("/")
01221 else:
01222 self.indent += 4
01223 self.output.write(">\n")
01224
01225 def start_tag(self, name, attrs):
01226 self._out_tag(name, attrs, False)
01227
01228 def end_tag(self, name):
01229 self.indent -= 4
01230 self.output.write(" " * self.indent)
01231 self.output.write("</%s>\n" % name)
01232
01233 def leaf_tag(self, name, attrs):
01234 self._out_tag(name, attrs, True)
01235
01236 def close(self):
01237 self.end_tag( self.root_tag )
01238
01239
01240
01241 class ReportSingleton(object):
01242 def __init__(self):
01243 self.reset()
01244
01245 def show(self):
01246 bpy.ops.wm.call_menu( name='MiniReport' )
01247
01248 def reset(self):
01249 self.materials = []
01250 self.meshes = []
01251 self.lights = []
01252 self.cameras = []
01253 self.armatures = []
01254 self.armature_animations = []
01255 self.shape_animations = []
01256 self.textures = []
01257 self.vertices = 0
01258 self.orig_vertices = 0
01259 self.faces = 0
01260 self.triangles = 0
01261 self.warnings = []
01262 self.errors = []
01263 self.messages = []
01264 self.paths = []
01265
01266 def report(self):
01267 r = ['Report:']
01268 ex = ['Extended Report:']
01269 if self.errors:
01270 r.append( ' ERRORS:' )
01271 for a in self.errors: r.append( ' - %s' %a )
01272
01273
01274
01275 if self.warnings:
01276 r.append( ' WARNINGS:' )
01277 for a in self.warnings: r.append( ' - %s' %a )
01278
01279 if self.messages:
01280 r.append( ' MESSAGES:' )
01281 for a in self.messages: r.append( ' - %s' %a )
01282 if self.paths:
01283 r.append( ' PATHS:' )
01284 for a in self.paths: r.append( ' - %s' %a )
01285
01286 if self.vertices:
01287 r.append( ' Original Vertices: %s' %self.orig_vertices)
01288 r.append( ' Exported Vertices: %s' %self.vertices )
01289 r.append( ' Original Faces: %s' %self.faces )
01290 r.append( ' Exported Triangles: %s' %self.triangles )
01291
01292
01293 for tag in 'meshes lights cameras armatures armature_animations shape_animations materials textures'.split():
01294 attr = getattr(self, tag)
01295 if attr:
01296 name = tag.replace('_',' ').upper()
01297 r.append( ' %s: %s' %(name, len(attr)) )
01298 if attr:
01299 ex.append( ' %s:' %name )
01300 for a in attr: ex.append( ' . %s' %a )
01301
01302 txt = '\n'.join( r )
01303 ex = '\n'.join( ex )
01304 print('_'*80)
01305 print(txt)
01306 print(ex)
01307 print('_'*80)
01308 return txt
01309
01310 Report = ReportSingleton()
01311
01312 class MiniReport(bpy.types.Menu):
01313 bl_label = "Mini-Report | (see console for full report)"
01314 def draw(self, context):
01315 layout = self.layout
01316 txt = Report.report()
01317 for line in txt.splitlines():
01318 layout.label(text=line)
01319
01320
01321
01322 def _get_proxy_decimate_mod( ob ):
01323 proxy = None
01324 for child in ob.children:
01325 if child.subcollision and child.name.startswith('DECIMATED'):
01326 for mod in child.modifiers:
01327 if mod.type == 'DECIMATE':
01328 return mod
01329
01330 def bake_terrain( ob, normalize=True ):
01331 assert ob.collision_mode == 'TERRAIN'
01332 terrain = None
01333 for child in ob.children:
01334 if child.subcollision and child.name.startswith('TERRAIN'):
01335 terrain = child
01336 break
01337 assert terrain
01338 data = terrain.to_mesh(bpy.context.scene, True, "PREVIEW")
01339 raw = [ v.co.z for v in data.vertices ]
01340 Zmin = min( raw )
01341 Zmax = max( raw )
01342 depth = Zmax-Zmin
01343 m = 1.0 / depth
01344
01345 rows = []
01346 i = 0
01347 for x in range( ob.collision_terrain_x_steps ):
01348 row = []
01349 for y in range( ob.collision_terrain_y_steps ):
01350 v = data.vertices[ i ]
01351 if normalize:
01352 z = (v.co.z - Zmin) * m
01353 else:
01354 z = v.co.z
01355 row.append( z )
01356 i += 1
01357 if x%2:
01358 row.reverse()
01359 rows.append( row )
01360 return {'data':rows, 'min':Zmin, 'max':Zmax, 'depth':depth}
01361
01362 def save_terrain_as_NTF( path, ob ):
01363 info = bake_terrain( ob )
01364 url = os.path.join( path, '%s.ntf' % clean_object_name(ob.data.name) )
01365 f = open(url, "wb")
01366
01367 buf = array.array("I")
01368 xs = ob.collision_terrain_x_steps
01369 ys = ob.collision_terrain_y_steps
01370 xpatches = int(xs/16)
01371 ypatches = int(ys/16)
01372 header = [ xpatches, ypatches ]
01373 buf.fromlist( header )
01374 buf.tofile(f)
01375
01376 rows = info['data']
01377 for x in range( xpatches ):
01378 for y in range( ypatches ):
01379 patch = []
01380 for i in range(16):
01381 for j in range(16):
01382 v = rows[ (x*16)+i ][ (y*16)+j ]
01383 patch.append( v )
01384 buf = array.array("f")
01385 buf.fromlist( patch )
01386 buf.tofile(f)
01387 f.close()
01388 path,name = os.path.split(url)
01389 R = {
01390 'url':url, 'min':info['min'], 'max':info['max'], 'path':path, 'name':name,
01391 'xpatches': xpatches, 'ypatches': ypatches,
01392 'depth':info['depth'],
01393 }
01394 return R
01395
01396 class OgreCollisionOp(bpy.types.Operator):
01397 '''Ogre Collision'''
01398 bl_idname = "ogre.set_collision"
01399 bl_label = "modify collision"
01400 bl_options = {'REGISTER'}
01401 MODE = StringProperty(name="toggle mode", maxlen=32, default="disable")
01402
01403 @classmethod
01404 def poll(cls, context):
01405 if context.active_object and context.active_object.type == 'MESH':
01406 return True
01407
01408 def get_subcollisions( self, ob, create=True ):
01409 r = get_subcollisions( ob )
01410 if not r and create:
01411 method = getattr(self, 'create_%s'%ob.collision_mode)
01412 p = method(ob)
01413 p.name = '%s.%s' %(ob.collision_mode, ob.name)
01414 p.subcollision = True
01415 r.append( p )
01416 return r
01417
01418 def create_DECIMATED(self, ob):
01419 child = ob.copy()
01420 bpy.context.scene.objects.link( child )
01421 child.matrix_local = mathutils.Matrix()
01422 child.parent = ob
01423 child.hide_select = True
01424 child.draw_type = 'WIRE'
01425
01426 child.lock_location = [True]*3
01427 child.lock_rotation = [True]*3
01428 child.lock_scale = [True]*3
01429 decmod = child.modifiers.new('proxy', type='DECIMATE')
01430 decmod.ratio = 0.5
01431 return child
01432
01433 def create_TERRAIN(self, ob):
01434 x = ob.collision_terrain_x_steps
01435 y = ob.collision_terrain_y_steps
01436
01437
01438 bpy.ops.mesh.primitive_grid_add(
01439 x_subdivisions=x,
01440 y_subdivisions=y,
01441 size=1.0 )
01442 grid = bpy.context.active_object
01443 assert grid.name.startswith('Grid')
01444 grid.collision_terrain_x_steps = x
01445 grid.collision_terrain_y_steps = y
01446
01447 x,y,z = ob.dimensions
01448 sx,sy,sz = ob.scale
01449 x *= 1.0/sx
01450 y *= 1.0/sy
01451 z *= 1.0/sz
01452 grid.scale.x = x/2
01453 grid.scale.y = y/2
01454 grid.location.z -= z/2
01455 grid.data.show_all_edges = True
01456 grid.draw_type = 'WIRE'
01457 grid.hide_select = True
01458
01459 grid.lock_location = [True]*3
01460 grid.lock_rotation = [True]*3
01461 grid.lock_scale = [True]*3
01462 grid.parent = ob
01463 bpy.context.scene.objects.active = ob
01464 mod = grid.modifiers.new(name='temp', type='SHRINKWRAP')
01465 mod.wrap_method = 'PROJECT'
01466 mod.use_project_z = True
01467 mod.target = ob
01468 mod.cull_face = 'FRONT'
01469 return grid
01470
01471 def invoke(self, context, event):
01472 ob = context.active_object
01473 game = ob.game
01474 subtype = None
01475
01476 if ':' in self.MODE:
01477 mode, subtype = self.MODE.split(':')
01478
01479 if subtype in 'BOX SPHERE CYLINDER CONE CAPSULE'.split():
01480 ob.draw_bounds_type = subtype
01481 else:
01482 ob.draw_bounds_type = 'POLYHEDRON'
01483 ob.game.collision_bounds_type = subtype
01484 else:
01485 mode = self.MODE
01486 ob.collision_mode = mode
01487
01488 if ob.data.show_all_edges:
01489 ob.data.show_all_edges = False
01490 if ob.show_texture_space:
01491 ob.show_texture_space = False
01492 if ob.show_bounds:
01493 ob.show_bounds = False
01494 if ob.show_wire:
01495 ob.show_wire = False
01496 for child in ob.children:
01497 if child.subcollision and not child.hide:
01498 child.hide = True
01499
01500 if mode == 'NONE':
01501 game.use_ghost = True
01502 game.use_collision_bounds = False
01503 elif mode == 'PRIMITIVE':
01504 game.use_ghost = False
01505 game.use_collision_bounds = True
01506 ob.show_bounds = True
01507 elif mode == 'MESH':
01508 game.use_ghost = False
01509 game.use_collision_bounds = True
01510 ob.show_wire = True
01511 if game.collision_bounds_type == 'CONVEX_HULL':
01512 ob.show_texture_space = True
01513 else:
01514 ob.data.show_all_edges = True
01515 elif mode == 'DECIMATED':
01516 game.use_ghost = True
01517 game.use_collision_bounds = False
01518 game.use_collision_compound = True
01519 proxy = self.get_subcollisions(ob)[0]
01520 if proxy.hide: proxy.hide = False
01521 ob.game.use_collision_compound = True
01522 mod = _get_proxy_decimate_mod( ob )
01523 mod.show_viewport = True
01524 if not proxy.select:
01525 proxy.hide_select = False
01526 proxy.select = True
01527 proxy.hide_select = True
01528 if game.collision_bounds_type == 'CONVEX_HULL':
01529 ob.show_texture_space = True
01530 elif mode == 'TERRAIN':
01531 game.use_ghost = True
01532 game.use_collision_bounds = False
01533 game.use_collision_compound = True
01534 proxy = self.get_subcollisions(ob)[0]
01535 if proxy.hide:
01536 proxy.hide = False
01537 elif mode == 'COMPOUND':
01538 game.use_ghost = True
01539 game.use_collision_bounds = False
01540 game.use_collision_compound = True
01541 else:
01542 assert 0
01543
01544 return {'FINISHED'}
01545
01546
01547
01548 @UI
01549 class PANEL_Physics(bpy.types.Panel):
01550 bl_space_type = 'VIEW_3D'
01551 bl_region_type = 'UI'
01552 bl_label = "Physics"
01553
01554 @classmethod
01555 def poll(cls, context):
01556 if context.active_object:
01557 return True
01558 else:
01559 return False
01560
01561 def draw(self, context):
01562 layout = self.layout
01563 ob = context.active_object
01564 game = ob.game
01565
01566 if ob.type != 'MESH':
01567 return
01568 elif ob.subcollision == True:
01569 box = layout.box()
01570 if ob.parent:
01571 box.label(text='object is a collision proxy for: %s' %ob.parent.name)
01572 else:
01573 box.label(text='WARNING: collision proxy missing parent')
01574 return
01575
01576 box = layout.box()
01577 box.prop(ob, 'physics_mode')
01578 if ob.physics_mode != 'NONE':
01579 box.prop(game, 'mass', text='Mass')
01580 box.prop(ob, 'physics_friction', text='Friction', slider=True)
01581 box.prop(ob, 'physics_bounce', text='Bounce', slider=True)
01582
01583 box.label(text="Damping:")
01584 box.prop(game, 'damping', text='Translation', slider=True)
01585 box.prop(game, 'rotation_damping', text='Rotation', slider=True)
01586
01587 box.label(text="Velocity:")
01588 box.prop(game, "velocity_min", text="Minimum")
01589 box.prop(game, "velocity_max", text="Maximum")
01590
01591 @UI
01592 class PANEL_Collision(bpy.types.Panel):
01593 bl_space_type = 'VIEW_3D'
01594 bl_region_type = 'UI'
01595 bl_label = "Collision"
01596
01597 @classmethod
01598 def poll(cls, context):
01599 if context.active_object:
01600 return True
01601 else:
01602 return False
01603
01604 def draw(self, context):
01605 layout = self.layout
01606 ob = context.active_object
01607 game = ob.game
01608
01609 if ob.type != 'MESH':
01610 return
01611 elif ob.subcollision == True:
01612 box = layout.box()
01613 if ob.parent:
01614 box.label(text='object is a collision proxy for: %s' %ob.parent.name)
01615 else:
01616 box.label(text='WARNING: collision proxy missing parent')
01617 return
01618
01619 mode = ob.collision_mode
01620 if mode == 'NONE':
01621 box = layout.box()
01622 op = box.operator( 'ogre.set_collision', text='Enable Collision', icon='PHYSICS' )
01623 op.MODE = 'PRIMITIVE:%s' %game.collision_bounds_type
01624 else:
01625 prim = game.collision_bounds_type
01626
01627 box = layout.box()
01628 op = box.operator( 'ogre.set_collision', text='Disable Collision', icon='X' )
01629 op.MODE = 'NONE'
01630 box.prop(game, "collision_margin", text="Collision Margin", slider=True)
01631
01632 box = layout.box()
01633 if mode == 'PRIMITIVE':
01634 box.label(text='Primitive: %s' %prim)
01635 else:
01636 box.label(text='Primitive')
01637
01638 row = box.row()
01639 _icons = {
01640 'BOX':'MESH_CUBE', 'SPHERE':'MESH_UVSPHERE', 'CYLINDER':'MESH_CYLINDER',
01641 'CONE':'MESH_CONE', 'CAPSULE':'META_CAPSULE'}
01642 for a in 'BOX SPHERE CYLINDER CONE CAPSULE'.split():
01643 if prim == a and mode == 'PRIMITIVE':
01644 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
01645 op.MODE = 'PRIMITIVE:%s' %a
01646 else:
01647 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
01648 op.MODE = 'PRIMITIVE:%s' %a
01649
01650 box = layout.box()
01651 if mode == 'MESH': box.label(text='Mesh: %s' %prim.split('_')[0] )
01652 else: box.label(text='Mesh')
01653 row = box.row()
01654 row.label(text='- - - - - - - - - - - - - -')
01655 _icons = {'TRIANGLE_MESH':'MESH_ICOSPHERE', 'CONVEX_HULL':'SURFACE_NCURVE'}
01656 for a in 'TRIANGLE_MESH CONVEX_HULL'.split():
01657 if prim == a and mode == 'MESH':
01658 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
01659 op.MODE = 'MESH:%s' %a
01660 else:
01661 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
01662 op.MODE = 'MESH:%s' %a
01663
01664 box = layout.box()
01665 if mode == 'DECIMATED':
01666 box.label(text='Decimate: %s' %prim.split('_')[0] )
01667 row = box.row()
01668 mod = _get_proxy_decimate_mod( ob )
01669 assert mod
01670 row.label(text='Faces: %s' %mod.face_count )
01671 box.prop( mod, 'ratio', text='' )
01672 else:
01673 box.label(text='Decimate')
01674 row = box.row()
01675 row.label(text='- - - - - - - - - - - - - -')
01676
01677 _icons = {'TRIANGLE_MESH':'MESH_ICOSPHERE', 'CONVEX_HULL':'SURFACE_NCURVE'}
01678 for a in 'TRIANGLE_MESH CONVEX_HULL'.split():
01679 if prim == a and mode == 'DECIMATED':
01680 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
01681 op.MODE = 'DECIMATED:%s' %a
01682 else:
01683 op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
01684 op.MODE = 'DECIMATED:%s' %a
01685
01686 box = layout.box()
01687 if mode == 'TERRAIN':
01688 terrain = get_subcollisions( ob )[0]
01689 if ob.collision_terrain_x_steps != terrain.collision_terrain_x_steps or ob.collision_terrain_y_steps != terrain.collision_terrain_y_steps:
01690 op = box.operator( 'ogre.set_collision', text='Rebuild Terrain', icon='MESH_GRID' )
01691 op.MODE = 'TERRAIN'
01692 else:
01693 box.label(text='Terrain:')
01694 row = box.row()
01695 row.prop( ob, 'collision_terrain_x_steps', 'X' )
01696 row.prop( ob, 'collision_terrain_y_steps', 'Y' )
01697
01698 box.prop( terrain.modifiers[0], 'cull_face', text='Cull' )
01699 box.prop( terrain, 'location' )
01700 else:
01701 op = box.operator( 'ogre.set_collision', text='Terrain Collision', icon='MESH_GRID' )
01702 op.MODE = 'TERRAIN'
01703
01704 box = layout.box()
01705 if mode == 'COMPOUND':
01706 op = box.operator( 'ogre.set_collision', text='Compound Collision', icon='ROTATECOLLECTION' )
01707 else:
01708 op = box.operator( 'ogre.set_collision', text='Compound Collision', icon='ROTATECOLLECTION' )
01709 op.MODE = 'COMPOUND'
01710
01711 @UI
01712 class PANEL_Configure(bpy.types.Panel):
01713 bl_space_type = 'PROPERTIES'
01714 bl_region_type = 'WINDOW'
01715 bl_context = "scene"
01716 bl_label = "Ogre Configuration File"
01717
01718 def draw(self, context):
01719 layout = self.layout
01720 op = layout.operator( 'ogre.save_config', text='update config file', icon='FILE' )
01721 for tag in _CONFIG_TAGS_:
01722 layout.prop( context.window_manager, tag )
01723
01724
01725
01726
01727 popup_message = ""
01728
01729 class PopUpDialogOperator(bpy.types.Operator):
01730 bl_idname = "object.popup_dialog_operator"
01731 bl_label = "blender2ogre"
01732
01733 def __init__(self):
01734 print("dialog Start")
01735
01736 def __del__(self):
01737 print("dialog End")
01738
01739 def execute(self, context):
01740 print ("execute")
01741 return {'RUNNING_MODAL'}
01742
01743 def draw(self, context):
01744
01745
01746
01747
01748 global popup_message
01749 layout = self.layout
01750 col = layout.column()
01751 col.label(popup_message, 'ERROR')
01752
01753 def invoke(self, context, event):
01754 wm = context.window_manager
01755 wm.invoke_popup(self)
01756 wm.modal_handler_add(self)
01757 return {'RUNNING_MODAL'}
01758
01759 def modal(self, context, event):
01760
01761 if event.type == 'LEFTMOUSE':
01762 print ("Left mouse")
01763 return {'FINISHED'}
01764
01765 elif event.type in ('RIGHTMOUSE', 'ESC'):
01766 print ("right mouse")
01767 return {'FINISHED'}
01768
01769 print("running modal")
01770 return {'RUNNING_MODAL'}
01771
01772 def show_dialog(message):
01773 global popup_message
01774 popup_message = message
01775 bpy.ops.object.popup_dialog_operator('INVOKE_DEFAULT')
01776
01777
01778
01779 _game_logic_intro_doc_ = '''
01780 Hijacking the BGE
01781
01782 Blender contains a fully functional game engine (BGE) that is highly useful for learning the concepts of game programming by breaking it down into three simple parts: Sensor, Controller, and Actuator. An Ogre based game engine will likely have similar concepts in its internal API and game logic scripting. Without a custom interface to define game logic, very often game designers may have to resort to having programmers implement their ideas in purely handwritten script. This is prone to breakage because object names then end up being hard-coded. Not only does this lead to non-reusable code, its also a slow process. Why should we have to resort to this when Blender already contains a very rich interface for game logic? By hijacking a subset of the BGE interface we can make this workflow between game designer and game programmer much better.
01783
01784 The OgreDocScene format can easily be extened to include extra game logic data. While the BGE contains some features that can not be easily mapped to other game engines, there are many are highly useful generic features we can exploit, including many of the Sensors and Actuators. Blender uses the paradigm of: 1. Sensor -> 2. Controller -> 3. Actuator. In pseudo-code, this can be thought of as: 1. on-event -> 2. conditional logic -> 3. do-action. The designer is most often concerned with the on-events (the Sensors), and the do-actions (the Actuators); and the BGE interface provides a clear way for defining and editing those. Its a harder task to provide a good interface for the conditional logic (Controller), that is flexible enough to fit everyones different Ogre engine and requirements, so that is outside the scope of this exporter at this time. A programmer will still be required to fill the gap between Sensor and Actuator, but hopefully his work is greatly reduced and can write more generic/reuseable code.
01785
01786 The rules for which Sensors trigger which Actuators is left undefined, as explained above we are hijacking the BGE interface not trying to export and reimplement everything. BGE Controllers and all links are ignored by the exporter, so whats the best way to define Sensor/Actuator relationships? One convention that seems logical is to group Sensors and Actuators by name. More complex syntax could be used in Sensor/Actuators names, or they could be completely ignored and instead all the mapping is done by the game programmer using other rules. This issue is not easily solved so designers and the engine programmers will have to decide upon their own conventions, there is no one size fits all solution.
01787 '''
01788
01789 _ogre_logic_types_doc_ = '''
01790 Supported Sensors:
01791 . Collision
01792 . Near
01793 . Radar
01794 . Touching
01795 . Raycast
01796 . Message
01797
01798 Supported Actuators:
01799 . Shape Action*
01800 . Edit Object
01801 . Camera
01802 . Constraint
01803 . Message
01804 . Motion
01805 . Sound
01806 . Visibility
01807
01808 *note: Shape Action
01809 The most common thing a designer will want to do is have an event trigger an animation. The BGE contains an Actuator called "Shape Action", with useful properties like: start/end frame, and blending. It also contains a property called "Action" but this is hidden because the exporter ignores action names and instead uses the names of NLA strips when exporting Ogre animation tracks. The current workaround is to hijack the "Frame Property" attribute and change its name to "animation". The designer can then simply type the name of the animation track (NLA strip). Any custom syntax could actually be implemented here for calling animations, its up to the engine programmer to define how this field will be used. For example: "*.explode" could be implemented to mean "on all objects" play the "explode" animation.
01810 '''
01811
01812 class _WrapLogic(object):
01813 SwapName = { 'frame_property' : 'animation' }
01814
01815 def __init__(self, node):
01816 self.node = node
01817 self.name = node.name
01818 self.type = node.type
01819
01820 def widget(self, layout):
01821 box = layout.box()
01822 row = box.row()
01823 row.label( text=self.type )
01824 row.separator()
01825 row.prop( self.node, 'name', text='' )
01826 if self.type in self.TYPES:
01827 for name in self.TYPES[ self.type ]:
01828 if name in self.SwapName:
01829 box.prop( self.node, name, text=self.SwapName[name] )
01830 else:
01831 box.prop( self.node, name )
01832
01833 def xml( self, doc ):
01834 g = doc.createElement( self.LogicType )
01835 g.setAttribute('name', self.name)
01836 g.setAttribute('type', self.type)
01837 if self.type in self.TYPES:
01838 for name in self.TYPES[ self.type ]:
01839 attr = getattr( self.node, name )
01840 if name in self.SwapName: name = self.SwapName[name]
01841 a = doc.createElement( 'component' )
01842 g.appendChild(a)
01843 a.setAttribute('name', name)
01844 if attr is None: a.setAttribute('type', 'POINTER' )
01845 else: a.setAttribute('type', type(attr).__name__)
01846
01847 if type(attr) in (float, int, str, bool): a.setAttribute('value', str(attr))
01848 elif not attr: a.setAttribute('value', '')
01849 elif hasattr(attr,'filepath'): a.setAttribute('value', attr.filepath)
01850 elif hasattr(attr,'name'): a.setAttribute('value', attr.name)
01851 elif hasattr(attr,'x') and hasattr(attr,'y') and hasattr(attr,'z'):
01852 a.setAttribute('value', '%s %s %s' %(attr.x, attr.y, attr.z))
01853 else:
01854 print('ERROR: unknown type', attr)
01855 return g
01856
01857 class WrapSensor( _WrapLogic ):
01858 LogicType = 'sensor'
01859 TYPES = {
01860 'COLLISION': ['property'],
01861 'MESSAGE' : ['subject'],
01862 'NEAR' : ['property', 'distance', 'reset_distance'],
01863 'RADAR' : ['property', 'axis', 'angle', 'distance' ],
01864 'RAY' : ['ray_type', 'property', 'material', 'axis', 'range', 'use_x_ray'],
01865 'TOUCH' : ['material'],
01866 }
01867
01868 class WrapActuator( _WrapLogic ):
01869 LogicType = 'actuator'
01870 TYPES = {
01871 'CAMERA' : ['object', 'height', 'min', 'max', 'axis'],
01872 'CONSTRAINT' : ['mode', 'limit', 'limit_min', 'limit_max', 'damping'],
01873 'MESSAGE' : ['to_property', 'subject', 'body_message'],
01874 'OBJECT' : 'damping derivate_coefficient force force_max_x force_max_y force_max_z force_min_x force_min_y force_min_z integral_coefficient linear_velocity mode offset_location offset_rotation proportional_coefficient reference_object torque use_local_location use_local_rotation use_local_torque use_servo_limit_x use_servo_limit_y use_servo_limit_z'.split(),
01875 'SOUND' : 'cone_inner_angle_3d cone_outer_angle_3d cone_outer_gain_3d distance_3d_max distance_3d_reference gain_3d_max gain_3d_min mode pitch rolloff_factor_3d sound use_sound_3d volume'.split(),
01876 'VISIBILITY' : 'apply_to_children use_occlusion use_visible'.split(),
01877 'SHAPE_ACTION' : 'frame_blend_in frame_end frame_property frame_start mode property use_continue_last_frame'.split(),
01878 'EDIT_OBJECT' : 'dynamic_operation linear_velocity mass mesh mode object time track_object use_3d_tracking use_local_angular_velocity use_local_linear_velocity use_replace_display_mesh use_replace_physics_mesh'.split(),
01879 }
01880
01881 class _OgreMatPass( object ):
01882 bl_space_type = 'PROPERTIES'
01883 bl_region_type = 'WINDOW'
01884 bl_context = "material"
01885
01886 @classmethod
01887 def poll(cls, context):
01888 if context.active_object and context.active_object.active_material and context.active_object.active_material.use_material_passes:
01889 return True
01890
01891 def draw(self, context):
01892 if not hasattr(context, "material"):
01893 return
01894 if not context.active_object:
01895 return
01896 if not context.active_object.active_material:
01897 return
01898
01899 mat = context.material
01900 ob = context.object
01901 slot = context.material_slot
01902 layout = self.layout
01903
01904 if mat.use_material_passes:
01905 db = layout.box()
01906 nodes = bpyShaders.get_or_create_material_passes( mat )
01907 node = nodes[ self.INDEX ]
01908 split = db.row()
01909 if node.material: split.prop( node.material, 'use_in_ogre_material_pass', text='' )
01910 split.prop( node, 'material' )
01911 if not node.material:
01912 op = split.operator( 'ogre.helper_create_attach_material_layer', icon="PLUS", text='' )
01913 op.INDEX = self.INDEX
01914 if node.material and node.material.use_in_ogre_material_pass:
01915 dbb = db.box()
01916 ogre_material_panel( dbb, node.material, parent=mat )
01917 ogre_material_panel_extra( dbb, node.material )
01918
01919 class _create_new_material_layer_helper(bpy.types.Operator):
01920 '''helper to create new material layer'''
01921 bl_idname = "ogre.helper_create_attach_material_layer"
01922 bl_label = "creates and assigns new material to layer"
01923 bl_options = {'REGISTER'}
01924 INDEX = IntProperty(name="material layer index", description="index", default=0, min=0, max=8)
01925
01926 @classmethod
01927 def poll(cls, context):
01928 if context.active_object and context.active_object.active_material and context.active_object.active_material.use_material_passes:
01929 return True
01930
01931 def execute(self, context):
01932 mat = context.active_object.active_material
01933 nodes = bpyShaders.get_or_create_material_passes( mat )
01934 node = nodes[ self.INDEX ]
01935 node.material = bpy.data.materials.new( name='%s.LAYER%s'%(mat.name,self.INDEX) )
01936 node.material.use_fixed_pipeline = False
01937 node.material.offset_z = (self.INDEX*2) + 2
01938 return {'FINISHED'}
01939
01940
01941
01942 @UI
01943 class PANEL_properties_window_ogre_material( bpy.types.Panel ):
01944 bl_space_type = 'PROPERTIES'
01945 bl_region_type = 'WINDOW'
01946 bl_context = "material"
01947 bl_label = "Ogre Material (base pass)"
01948
01949 @classmethod
01950 def poll( self, context ):
01951 if not hasattr(context, "material"): return False
01952 if not context.active_object: return False
01953 if not context.active_object.active_material: return False
01954 return True
01955
01956 def draw(self, context):
01957 mat = context.material
01958 ob = context.object
01959 slot = context.material_slot
01960 layout = self.layout
01961 if not mat.use_material_passes:
01962 box = layout.box()
01963 box.operator( 'ogre.force_setup_material_passes', text="Ogre Material Layers", icon='SCENE_DATA' )
01964
01965 ogre_material_panel( layout, mat )
01966 ogre_material_panel_extra( layout, mat )
01967
01968 @UI
01969 class MatPass1( _OgreMatPass, bpy.types.Panel ): INDEX = 0; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01970 @UI
01971 class MatPass2( _OgreMatPass, bpy.types.Panel ): INDEX = 1; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01972 @UI
01973 class MatPass3( _OgreMatPass, bpy.types.Panel ): INDEX = 2; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01974 @UI
01975 class MatPass4( _OgreMatPass, bpy.types.Panel ): INDEX = 3; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01976 @UI
01977 class MatPass5( _OgreMatPass, bpy.types.Panel ): INDEX = 4; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01978 @UI
01979 class MatPass6( _OgreMatPass, bpy.types.Panel ): INDEX = 5; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01980 @UI
01981 class MatPass7( _OgreMatPass, bpy.types.Panel ): INDEX = 6; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01982 @UI
01983 class MatPass8( _OgreMatPass, bpy.types.Panel ): INDEX = 7; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
01984
01985 @UI
01986 class PANEL_Textures(bpy.types.Panel):
01987 bl_space_type = 'PROPERTIES'
01988 bl_region_type = 'WINDOW'
01989 bl_context = "texture"
01990 bl_label = "Ogre Texture"
01991
01992 @classmethod
01993 def poll(cls, context):
01994 if not hasattr(context, "texture_slot"):
01995 return False
01996 else: return True
01997
01998 def draw(self, context):
01999
02000
02001 layout = self.layout
02002
02003 slot = context.texture_slot
02004 if not slot or not slot.texture:
02005 return
02006
02007 btype = slot.blend_type
02008 ex = False; texop = None
02009 if btype in TextureUnit.colour_op:
02010 if btype=='MIX' and slot.use_map_alpha and not slot.use_stencil:
02011 if slot.diffuse_color_factor >= 1.0:
02012 texop = 'alpha_blend'
02013 else:
02014 texop = TextureUnit.colour_op_ex[ btype ]
02015 ex = True
02016 elif btype=='MIX' and slot.use_map_alpha and slot.use_stencil:
02017 texop = 'blend_current_alpha'; ex=True
02018 elif btype=='MIX' and not slot.use_map_alpha and slot.use_stencil:
02019 texop = 'blend_texture_alpha'; ex=True
02020 else:
02021 texop = TextureUnit.colour_op[ btype ]
02022 elif btype in TextureUnit.colour_op_ex:
02023 texop = TextureUnit.colour_op_ex[ btype ]
02024 ex = True
02025
02026 box = layout.box()
02027 row = box.row()
02028 if texop:
02029 if ex:
02030 row.prop(slot, "blend_type", text=texop, icon='NEW')
02031 else:
02032 row.prop(slot, "blend_type", text=texop)
02033 else:
02034 row.prop(slot, "blend_type", text='(invalid option)')
02035
02036 if btype == 'MIX':
02037 row.prop(slot, "use_stencil", text="")
02038 row.prop(slot, "use_map_alpha", text="")
02039 if texop == 'blend_manual':
02040 row = box.row()
02041 row.label(text="Alpha:")
02042 row.prop(slot, "diffuse_color_factor", text="")
02043
02044 if hasattr(slot.texture, 'image') and slot.texture.image:
02045 row = box.row()
02046 n = '(invalid option)'
02047 if slot.texture.extension in TextureUnit.tex_address_mode:
02048 n = TextureUnit.tex_address_mode[ slot.texture.extension ]
02049 row.prop(slot.texture, "extension", text=n)
02050 if slot.texture.extension == 'CLIP':
02051 row.prop(slot, "color", text="Border Color")
02052
02053 row = box.row()
02054 if slot.texture_coords == 'UV':
02055 row.prop(slot, "texture_coords", text="", icon='GROUP_UVS')
02056 row.prop(slot, "uv_layer", text='Layer')
02057 elif slot.texture_coords == 'REFLECTION':
02058 row.prop(slot, "texture_coords", text="", icon='MOD_UVPROJECT')
02059 n = '(invalid option)'
02060 if slot.mapping in 'FLAT SPHERE'.split(): n = ''
02061 row.prop(slot, "mapping", text=n)
02062 else:
02063 row.prop(slot, "texture_coords", text="(invalid mapping option)")
02064
02065
02066 split = layout.row()
02067 box = split.box()
02068 box.prop(slot, "offset", text="XY=offset, Z=rotation")
02069 box = split.box()
02070 box.prop(slot, "scale", text="XY=scale (Z ignored)")
02071
02072 box = layout.box()
02073 row = box.row()
02074 row.label(text='scrolling animation')
02075
02076
02077 row.prop(slot, "use_map_scatter", text="")
02078 row = box.row()
02079 row.prop(slot, "density_factor", text="X")
02080 row.prop(slot, "emission_factor", text="Y")
02081
02082 box = layout.box()
02083 row = box.row()
02084 row.label(text='rotation animation')
02085 row.prop(slot, "emission_color_factor", text="")
02086 row.prop(slot, "use_from_dupli", text="")
02087
02088
02089 if hasattr(slot.texture, 'image') and slot.texture.image:
02090 img = slot.texture.image
02091 box = layout.box()
02092 row = box.row()
02093 row.prop( img, 'use_convert_format' )
02094 if img.use_convert_format:
02095 row.prop( img, 'convert_format' )
02096 if img.convert_format == 'jpg':
02097 box.prop( img, 'jpeg_quality' )
02098
02099 row = box.row()
02100 row.prop( img, 'use_color_quantize', text='Reduce Colors' )
02101 if img.use_color_quantize:
02102 row.prop( img, 'use_color_quantize_dither', text='dither' )
02103 row.prop( img, 'color_quantize', text='colors' )
02104
02105 row = box.row()
02106 row.prop( img, 'use_resize_half' )
02107 if not img.use_resize_half:
02108 row.prop( img, 'use_resize_absolute' )
02109 if img.use_resize_absolute:
02110 row = box.row()
02111 row.prop( img, 'resize_x' )
02112 row.prop( img, 'resize_y' )
02113
02114
02115
02116 class OgreMeshyPreviewOp(bpy.types.Operator):
02117 '''helper to open ogremeshy'''
02118 bl_idname = 'ogremeshy.preview'
02119 bl_label = "opens ogremeshy in a subprocess"
02120 bl_options = {'REGISTER'}
02121 preview = BoolProperty(name="preview", description="fast preview", default=True)
02122 groups = BoolProperty(name="preview merge groups", description="use merge groups", default=False)
02123 mesh = BoolProperty(name="update mesh", description="update mesh (disable for fast material preview", default=True)
02124
02125 @classmethod
02126 def poll(cls, context):
02127 if context.active_object and context.active_object.type in ('MESH','EMPTY') and context.mode != 'EDIT_MESH':
02128 if context.active_object.type == 'EMPTY' and context.active_object.dupli_type != 'GROUP':
02129 return False
02130 else:
02131 return True
02132
02133 def execute(self, context):
02134 Report.reset()
02135 Report.messages.append('running %s' %CONFIG['OGRE_MESHY'])
02136
02137 if sys.platform.startswith('linux'):
02138
02139
02140 if CONFIG['OGRE_MESHY'].endswith('.exe'):
02141 path = '%s/.wine/drive_c/tmp' % os.environ['HOME']
02142 else:
02143 path = '/tmp'
02144 elif sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
02145 path = '/tmp'
02146 else:
02147 path = 'C:\\tmp'
02148
02149 mat = None
02150 mgroup = merged = None
02151 umaterials = []
02152
02153 if context.active_object.type == 'MESH':
02154 mat = context.active_object.active_material
02155 elif context.active_object.type == 'EMPTY':
02156 obs = []
02157 for e in context.selected_objects:
02158 if e.type != 'EMPTY' and e.dupli_group: continue
02159 grp = e.dupli_group
02160 subs = []
02161 for o in grp.objects:
02162 if o.type=='MESH': subs.append( o )
02163 if subs:
02164 m = merge_objects( subs, transform=e.matrix_world )
02165 obs.append( m )
02166 if obs:
02167 merged = merge_objects( obs )
02168 umaterials = dot_mesh( merged, path=path, force_name='preview' )
02169 for o in obs: context.scene.objects.unlink(o)
02170
02171 if not self.mesh:
02172 for ob in context.selected_objects:
02173 if ob.type == 'MESH':
02174 for mat in ob.data.materials:
02175 if mat and mat not in umaterials: umaterials.append( mat )
02176
02177 if not merged:
02178 mgroup = MeshMagick.get_merge_group( context.active_object )
02179 if not mgroup and self.groups:
02180 group = get_merge_group( context.active_object )
02181 if group:
02182 print('--------------- has merge group ---------------' )
02183 merged = merge_group( group )
02184 else:
02185 print('--------------- NO merge group ---------------' )
02186 elif len(context.selected_objects)>1 and context.selected_objects:
02187 merged = merge_objects( context.selected_objects )
02188
02189 if mgroup:
02190 for ob in mgroup.objects:
02191 nmats = dot_mesh( ob, path=path )
02192 for m in nmats:
02193 if m not in umaterials: umaterials.append( m )
02194 MeshMagick.merge( mgroup, path=path, force_name='preview' )
02195 elif merged:
02196 umaterials = dot_mesh( merged, path=path, force_name='preview' )
02197 else:
02198 umaterials = dot_mesh( context.active_object, path=path, force_name='preview' )
02199
02200 if mat or umaterials:
02201
02202
02203 data = ''
02204 for umat in umaterials:
02205 data += generate_material( umat, path=path, copy_programs=True, touch_textures=True )
02206 f=open( os.path.join( path, 'preview.material' ), 'wb' )
02207 f.write( bytes(data,'utf-8') ); f.close()
02208
02209 if merged: context.scene.objects.unlink( merged )
02210
02211 if sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
02212 if CONFIG['OGRE_MESHY'].endswith('.exe'):
02213 cmd = ['wine', CONFIG['OGRE_MESHY'], 'c:\\tmp\\preview.mesh' ]
02214 else:
02215 cmd = [CONFIG['OGRE_MESHY'], '/tmp/preview.mesh']
02216 print( cmd )
02217
02218 subprocess.Popen(cmd)
02219 else:
02220
02221 subprocess.Popen( [CONFIG['OGRE_MESHY'], 'C:\\tmp\\preview.mesh'] )
02222
02223 Report.show()
02224 return {'FINISHED'}
02225
02226
02227
02228 _OGRE_DOCS_ = []
02229 def ogredoc( cls ):
02230 tag = cls.__name__.split('_ogredoc_')[-1]
02231 cls.bl_label = tag.replace('_', ' ')
02232 _OGRE_DOCS_.append( cls )
02233 return cls
02234
02235 class INFO_MT_ogre_helper(bpy.types.Menu):
02236 bl_label = '_overloaded_'
02237
02238 def draw(self, context):
02239 layout = self.layout
02240
02241
02242
02243
02244
02245
02246 for line in self.mydoc.splitlines():
02247 if line.strip():
02248 for ww in wordwrap( line ): layout.label(text=ww)
02249 layout.separator()
02250
02251 class INFO_MT_ogre_docs(bpy.types.Menu):
02252 bl_label = "Ogre Help"
02253
02254 def draw(self, context):
02255 layout = self.layout
02256 for cls in _OGRE_DOCS_:
02257 layout.menu( cls.__name__ )
02258 layout.separator()
02259 layout.separator()
02260 layout.label(text='bug reports to: bhartsho@yahoo.com')
02261
02262 class INFO_MT_ogre_shader_pass_attributes(bpy.types.Menu):
02263 bl_label = "Shader-Pass"
02264
02265 def draw(self, context):
02266 layout = self.layout
02267 for cls in _OGRE_SHADER_REF_:
02268 layout.menu( cls.__name__ )
02269
02270 class INFO_MT_ogre_shader_texture_attributes(bpy.types.Menu):
02271 bl_label = "Shader-Texture"
02272
02273 def draw(self, context):
02274 layout = self.layout
02275 for cls in _OGRE_SHADER_REF_TEX_:
02276 layout.menu( cls.__name__ )
02277
02278 @ogredoc
02279 class _ogredoc_Installing( INFO_MT_ogre_helper ):
02280 mydoc = _doc_installing_
02281
02282 @ogredoc
02283 class _ogredoc_FAQ( INFO_MT_ogre_helper ):
02284 mydoc = _faq_
02285
02286 @ogredoc
02287 class _ogredoc_Animation_System( INFO_MT_ogre_helper ):
02288 mydoc = '''
02289 Armature Animation System | OgreDotSkeleton
02290 Quick Start:
02291 1. select your armature and set a single keyframe on the object (loc,rot, or scl)
02292 . note, this step is just a hack for creating an action so you can then create an NLA track.
02293 . do not key in pose mode, unless you want to only export animation on the keyed bones.
02294 2. open the NLA, and convert the action into an NLA strip
02295 3. name the NLA strip(s)
02296 4. set the in and out frames for each strip ( the strip name becomes the Ogre track name )
02297
02298 How it Works:
02299 The NLA strips can be blank, they are only used to define Ogre track names, and in and out frame ranges. You are free to animate the armature with constraints (no baking required), or you can used baked animation and motion capture. Blending that is driven by the NLA is also supported, if you don't want blending, put space between each strip.
02300
02301 The OgreDotSkeleton (.skeleton) format supports multiple named tracks that can contain some or all of the bones of an armature. This feature can be exploited by a game engine for segmenting and animation blending. For example: lets say we want to animate the upper torso independently of the lower body while still using a single armature. This can be done by hijacking the NLA of the armature.
02302
02303 Advanced NLA Hijacking (selected-bones-animation):
02304 . define an action and keyframe only the bones you want to 'group', ie. key all the upper torso bones
02305 . import the action into the NLA
02306 . name the strip (this becomes the track name in Ogre)
02307 . adjust the start and end frames of each strip
02308 ( you may use multiple NLA tracks, multiple strips per-track is ok, and strips may overlap in time )
02309
02310 '''
02311
02312 @ogredoc
02313 class _ogredoc_Physics( INFO_MT_ogre_helper ):
02314 mydoc = '''
02315 Ogre Dot Scene + BGE Physics
02316 extended format including external collision mesh, and BGE physics settings
02317 <node name="...">
02318 <entity name="..." meshFile="..." collisionFile="..." collisionPrim="..." [and all BGE physics attributes] />
02319 </node>
02320
02321 collisionFile : sets path to .mesh that is used for collision (ignored if collisionPrim is set)
02322 collisionPrim : sets optimal collision type [ cube, sphere, capsule, cylinder ]
02323 *these collisions are static meshes, animated deforming meshes should give the user a warning that they have chosen a static mesh collision type with an object that has an armature
02324
02325 Blender Collision Setup:
02326 1. If a mesh object has a child mesh with a name starting with 'collision', then the child becomes the collision mesh for the parent mesh.
02327
02328 2. If 'Collision Bounds' game option is checked, the bounds type [box, sphere, etc] is used. This will override above rule.
02329
02330 3. Instances (and instances generated by optimal array modifier) will share the same collision type of the first instance, you DO NOT need to set the collision type for each instance.
02331
02332 '''
02333
02334 @ogredoc
02335 class _ogredoc_Bugs( INFO_MT_ogre_helper ):
02336 mydoc = '''
02337 Known Issues:
02338 . shape animation breaks when using modifiers that change the vertex count
02339 (Any modifier that changes the vertex count is bad with shape anim or armature anim)
02340 . never rename the nodes created by enabling Ogre-Material-Layers
02341 . never rename collision proxy meshes created by the Collision Panel
02342 . lighting in Tundra is not excatly the same as in Blender
02343 Tundra Streaming:
02344 . only supports streaming transform of up to 10 objects selected objects
02345 . the 3D view must be shown at the time you open Tundra
02346 . the same 3D view must be visible to stream data to Tundra
02347 . only position and scale are updated, a bug on the Tundra side prevents rotation update
02348 . animation playback is broken if you rename your NLA strips after opening Tundra
02349 '''
02350
02351
02352
02353 def _mesh_entity_helper( doc, ob, o ):
02354
02355 o.setAttribute('mass', str(ob.game.mass))
02356 o.setAttribute('mass_radius', str(ob.game.radius))
02357 o.setAttribute('physics_type', ob.game.physics_type)
02358 o.setAttribute('actor', str(ob.game.use_actor))
02359 o.setAttribute('ghost', str(ob.game.use_ghost))
02360 o.setAttribute('velocity_min', str(ob.game.velocity_min))
02361 o.setAttribute('velocity_max', str(ob.game.velocity_max))
02362 o.setAttribute('lock_trans_x', str(ob.game.lock_location_x))
02363 o.setAttribute('lock_trans_y', str(ob.game.lock_location_y))
02364 o.setAttribute('lock_trans_z', str(ob.game.lock_location_z))
02365 o.setAttribute('lock_rot_x', str(ob.game.lock_rotation_x))
02366 o.setAttribute('lock_rot_y', str(ob.game.lock_rotation_y))
02367 o.setAttribute('lock_rot_z', str(ob.game.lock_rotation_z))
02368 o.setAttribute('anisotropic_friction', str(ob.game.use_anisotropic_friction))
02369 x,y,z = ob.game.friction_coefficients
02370 o.setAttribute('friction_x', str(x))
02371 o.setAttribute('friction_y', str(y))
02372 o.setAttribute('friction_z', str(z))
02373 o.setAttribute('damping_trans', str(ob.game.damping))
02374 o.setAttribute('damping_rot', str(ob.game.rotation_damping))
02375 o.setAttribute('inertia_tensor', str(ob.game.form_factor))
02376
02377 mesh = ob.data
02378
02379 for prop in mesh.items():
02380 propname, propvalue = prop
02381 if not propname.startswith('_'):
02382 user = doc.createElement('user_data')
02383 o.appendChild( user )
02384 user.setAttribute( 'name', propname )
02385 user.setAttribute( 'value', str(propvalue) )
02386 user.setAttribute( 'type', type(propvalue).__name__ )
02387
02388
02389
02390
02391 def get_lights_by_type( T ):
02392 r = []
02393 for ob in bpy.context.scene.objects:
02394 if ob.type=='LAMP':
02395 if ob.data.type==T: r.append( ob )
02396 return r
02397
02398 class _TXML_(object):
02399 '''
02400 <component type="EC_Script" sync="1" name="myscript">
02401 <attribute value="" name="Script ref"/>
02402 <attribute value="false" name="Run on load"/>
02403 <attribute value="0" name="Run mode"/>
02404 <attribute value="" name="Script application name"/>
02405 <attribute value="" name="Script class name"/>
02406 </component>
02407 '''
02408
02409 def create_tundra_document( self, context ):
02410
02411
02412
02413
02414
02415 proto = ''
02416
02417 doc = RDocument()
02418 scn = doc.createElement('scene')
02419 doc.appendChild( scn )
02420
02421
02422 if 0:
02423 e = doc.createElement( 'entity' )
02424 doc.documentElement.appendChild( e )
02425 e.setAttribute('id', len(doc.documentElement.childNodes)+1 )
02426
02427 c = doc.createElement( 'component' ); e.appendChild( c )
02428 c.setAttribute( 'type', 'EC_Script' )
02429 c.setAttribute( 'sync', '1' )
02430 c.setAttribute( 'name', 'myscript' )
02431
02432 a = doc.createElement('attribute'); c.appendChild( a )
02433 a.setAttribute('name', 'Script ref')
02434
02435
02436 a = doc.createElement('attribute'); c.appendChild( a )
02437 a.setAttribute('name', 'Run on load')
02438 a.setAttribute('value', 'true' )
02439
02440 a = doc.createElement('attribute'); c.appendChild( a )
02441 a.setAttribute('name', 'Run mode')
02442 a.setAttribute('value', '0' )
02443
02444 a = doc.createElement('attribute'); c.appendChild( a )
02445 a.setAttribute('name', 'Script application name')
02446 a.setAttribute('value', 'blender2ogre' )
02447
02448
02449 sun = hemi = None
02450 if get_lights_by_type('SUN'):
02451 sun = get_lights_by_type('SUN')[0]
02452 if get_lights_by_type('HEMI'):
02453 hemi = get_lights_by_type('HEMI')[0]
02454
02455
02456 if bpy.context.scene.world.mist_settings.use_mist or sun or hemi:
02457
02458 e = doc.createElement( 'entity' )
02459 doc.documentElement.appendChild( e )
02460 e.setAttribute('id', len(doc.documentElement.childNodes)+1 )
02461
02462
02463 c = doc.createElement( 'component' ); e.appendChild( c )
02464 c.setAttribute( 'type', 'EC_Fog' )
02465 c.setAttribute( 'sync', '1' )
02466 c.setAttribute( 'name', 'Fog' )
02467
02468 a = doc.createElement('attribute'); c.appendChild( a )
02469 a.setAttribute('name', 'Color')
02470 if bpy.context.scene.world.mist_settings.use_mist:
02471 A = bpy.context.scene.world.mist_settings.intensity
02472 R,G,B = bpy.context.scene.world.horizon_color
02473 a.setAttribute('value', '%s %s %s %s'%(R,G,B,A))
02474 else:
02475 a.setAttribute('value', '0.4 0.4 0.4 1.0')
02476
02477 if bpy.context.scene.world.mist_settings.use_mist:
02478 mist = bpy.context.scene.world.mist_settings
02479
02480 a = doc.createElement('attribute'); c.appendChild( a )
02481 a.setAttribute('name', 'Start distance')
02482 a.setAttribute('value', mist.start)
02483
02484 a = doc.createElement('attribute'); c.appendChild( a )
02485 a.setAttribute('name', 'End distance')
02486 a.setAttribute('value', mist.start+mist.depth)
02487
02488 a = doc.createElement('attribute'); c.appendChild( a )
02489 a.setAttribute('name', 'Exponential density')
02490 a.setAttribute('value', 0.001)
02491
02492
02493 c = doc.createElement( 'component' ); e.appendChild( c )
02494 c.setAttribute( 'type', 'EC_EnvironmentLight' )
02495 c.setAttribute( 'sync', '1' )
02496 c.setAttribute( 'name', 'Environment Light' )
02497
02498 a = doc.createElement('attribute'); c.appendChild( a )
02499 a.setAttribute('name', 'Sunlight color')
02500 if sun:
02501 R,G,B = sun.data.color
02502 a.setAttribute('value', '%s %s %s 1' %(R,G,B))
02503 else:
02504 a.setAttribute('value', '0 0 0 1')
02505
02506 a = doc.createElement('attribute'); c.appendChild( a )
02507 a.setAttribute('name', 'Brightness')
02508 if sun:
02509 a.setAttribute('value', sun.data.energy*10)
02510 else:
02511 a.setAttribute('value', '0')
02512
02513 a = doc.createElement('attribute'); c.appendChild( a )
02514 a.setAttribute('name', 'Ambient light color')
02515 if hemi:
02516 R,G,B = hemi.data.color * hemi.data.energy * 3.0
02517 if R>1.0: R=1.0
02518 if G>1.0: G=1.0
02519 if B>1.0: B=1.0
02520 a.setAttribute('value', '%s %s %s 1' %(R,G,B))
02521 else:
02522 a.setAttribute('value', '0 0 0 1')
02523
02524 a = doc.createElement('attribute'); c.appendChild( a )
02525 a.setAttribute('name', 'Sunlight direction vector')
02526 a.setAttribute('value', '-0.25 -1.0 -0.25')
02527
02528 a = doc.createElement('attribute'); c.appendChild( a )
02529 a.setAttribute('name', 'Sunlight cast shadows')
02530 a.setAttribute('value', 'true')
02531
02532
02533 if context.scene.world.ogre_skyX:
02534 c = doc.createElement( 'component' ); e.appendChild( c )
02535 c.setAttribute( 'type', 'EC_SkyX' )
02536 c.setAttribute( 'sync', '1' )
02537 c.setAttribute( 'name', 'SkyX' )
02538
02539 a = doc.createElement('attribute'); a.setAttribute('name', 'Weather (volumetric clouds only)')
02540 den = (
02541 context.scene.world.ogre_skyX_cloud_density_x,
02542 context.scene.world.ogre_skyX_cloud_density_y
02543 )
02544 a.setAttribute('value', '%s %s' %den)
02545 c.appendChild( a )
02546
02547 config = (
02548 ('time', 'Time multiplier'),
02549 ('volumetric_clouds','Volumetric clouds'),
02550 ('wind','Wind direction'),
02551 )
02552 for bname, aname in config:
02553 a = doc.createElement('attribute')
02554 a.setAttribute('name', aname)
02555 s = str( getattr(context.scene.world, 'ogre_skyX_'+bname) )
02556 a.setAttribute('value', s.lower())
02557 c.appendChild( a )
02558
02559 return doc
02560
02561
02562 def tundra_entity( self, doc, ob, path='/tmp', collision_proxies=[], parent=None, matrix=None,visible=True ):
02563 assert not ob.subcollision
02564
02565
02566 if not matrix:
02567 matrix = ob.matrix_world.copy()
02568
02569
02570
02571
02572
02573
02574 proto = ''
02575
02576
02577 entityid = uid(ob)
02578 objectname = clean_object_name(ob.name)
02579 print(" Creating Tundra Enitity with ID", entityid)
02580
02581 e = doc.createElement( 'entity' )
02582 doc.documentElement.appendChild( e )
02583 e.setAttribute('id', entityid)
02584
02585
02586 print (" - EC_Name with", objectname)
02587
02588 c = doc.createElement('component'); e.appendChild( c )
02589 c.setAttribute('type', "EC_Name")
02590 c.setAttribute('sync', '1')
02591 a = doc.createElement('attribute'); c.appendChild(a)
02592 a.setAttribute('name', "name" )
02593 a.setAttribute('value', objectname )
02594 a = doc.createElement('attribute'); c.appendChild(a)
02595 a.setAttribute('name', "description" )
02596 a.setAttribute('value', "" )
02597
02598
02599 print (" - EC_Placeable ")
02600
02601 c = doc.createElement('component'); e.appendChild( c )
02602 c.setAttribute('type', "EC_Placeable")
02603 c.setAttribute('sync', '1')
02604 a = doc.createElement('attribute'); c.appendChild(a)
02605 a.setAttribute('name', "Transform" )
02606 x,y,z = swap(matrix.to_translation())
02607 loc = '%6f,%6f,%6f' %(x,y,z)
02608 x,y,z = swap(matrix.to_euler())
02609 x = math.degrees( x ); y = math.degrees( y ); z = math.degrees( z )
02610 if ob.type == 'CAMERA':
02611 x -= 90
02612 elif ob.type == 'LAMP':
02613 x += 90
02614 rot = '%6f,%6f,%6f' %(x,y,z)
02615 x,y,z = swap(matrix.to_scale())
02616 scl = '%6f,%6f,%6f' %(abs(x),abs(y),abs(z))
02617 a.setAttribute('value', "%s,%s,%s" %(loc,rot,scl) )
02618
02619 a = doc.createElement('attribute'); c.appendChild(a)
02620 a.setAttribute('name', "Show bounding box" )
02621 a.setAttribute('value', "false" )
02622
02623
02624
02625
02626
02627
02628 a = doc.createElement('attribute'); c.appendChild(a)
02629 a.setAttribute('name', "Visible" )
02630 if visible:
02631 a.setAttribute('value', 'true')
02632 else:
02633 a.setAttribute('value', 'false')
02634
02635 a = doc.createElement('attribute'); c.appendChild(a)
02636 a.setAttribute('name', "Selection layer" )
02637 a.setAttribute('value', 1)
02638
02639
02640
02641
02642
02643 if parent:
02644 a = doc.createElement('attribute'); c.appendChild(a)
02645 a.setAttribute('name', "Parent entity ref" )
02646 a.setAttribute('value', parent)
02647
02648 if ob.type != 'MESH':
02649 c = doc.createElement('component'); e.appendChild( c )
02650 c.setAttribute('type', 'EC_Name')
02651 c.setAttribute('sync', '1')
02652 a = doc.createElement('attribute'); c.appendChild(a)
02653 a.setAttribute('name', "name" )
02654 a.setAttribute('value', objectname)
02655
02656
02657 if ob.type == 'SPEAKER':
02658 print (" - EC_Sound")
02659 c = doc.createElement('component'); e.appendChild( c )
02660 c.setAttribute('type', 'EC_Sound')
02661 c.setAttribute('sync', '1')
02662
02663 if ob.data.sound:
02664 abspath = bpy.path.abspath( ob.data.sound.filepath )
02665 soundpath, soundfile = os.path.split( abspath )
02666 soundref = "%s%s" % (proto, soundfile)
02667 print (" Sounds ref:", soundref)
02668 a = doc.createElement('attribute'); c.appendChild(a)
02669 a.setAttribute('name', 'Sound ref' )
02670 a.setAttribute('value', soundref)
02671 if not os.path.isfile( os.path.join(path,soundfile) ):
02672 open( os.path.join(path,soundfile), 'wb' ).write( open(abspath,'rb').read() )
02673
02674 a = doc.createElement('attribute'); c.appendChild(a)
02675 a.setAttribute('name', 'Sound radius inner' )
02676 a.setAttribute('value', ob.data.cone_angle_inner)
02677
02678 a = doc.createElement('attribute'); c.appendChild(a)
02679 a.setAttribute('name', 'Sound radius outer' )
02680 a.setAttribute('value', ob.data.cone_angle_outer)
02681
02682 a = doc.createElement('attribute'); c.appendChild(a)
02683 a.setAttribute('name', 'Sound gain' )
02684 a.setAttribute('value', ob.data.volume)
02685
02686 a = doc.createElement('attribute'); c.appendChild(a)
02687 a.setAttribute('name', 'Play on load' )
02688 if ob.data.play_on_load:
02689 a.setAttribute('value', 'true')
02690 else:
02691 a.setAttribute('value', 'false')
02692
02693 a = doc.createElement('attribute'); c.appendChild(a)
02694 a.setAttribute('name', 'Loop sound' )
02695 if ob.data.loop:
02696 a.setAttribute('value', 'true')
02697 else:
02698 a.setAttribute('value', 'false')
02699
02700 a = doc.createElement('attribute'); c.appendChild(a)
02701 a.setAttribute('name', 'Spatial' )
02702 if ob.data.use_spatial:
02703 a.setAttribute('value', 'true')
02704 else:
02705 a.setAttribute('value', 'false')
02706
02707
02708 ''' todo: This is really not very helpful. Apps define
02709 camera logic in Tundra. By default you will have
02710 a freecamera to move around the scene etc. This created
02711 camera wont be activated except if a script does so.
02712 Best leave camera (creation) logic for the inworld apps.
02713 At least remove the default "export cameras" for txml. '''
02714 if ob.type == 'CAMERA':
02715 print (" - EC_Camera")
02716 c = doc.createElement('component'); e.appendChild( c )
02717 c.setAttribute('type', 'EC_Camera')
02718 c.setAttribute('sync', '1')
02719 a = doc.createElement('attribute'); c.appendChild(a)
02720 a.setAttribute('name', "Up vector" )
02721 a.setAttribute('value', '0.0 1.0 0.0')
02722 a = doc.createElement('attribute'); c.appendChild(a)
02723 a.setAttribute('name', "Near plane" )
02724 a.setAttribute('value', '0.01')
02725 a = doc.createElement('attribute'); c.appendChild(a)
02726 a.setAttribute('name', "Far plane" )
02727 a.setAttribute('value', '2000')
02728 a = doc.createElement('attribute'); c.appendChild(a)
02729 a.setAttribute('name', "Vertical FOV" )
02730 a.setAttribute('value', '45')
02731 a = doc.createElement('attribute'); c.appendChild(a)
02732 a.setAttribute('name', "Aspect ratio" )
02733 a.setAttribute('value', '')
02734
02735 NTF = None
02736
02737
02738
02739
02740 if ob.physics_mode != 'NONE' or ob.collision_mode != 'NONE':
02741 TundraTypes = {
02742 'BOX' : 0,
02743 'SPHERE' : 1,
02744 'CYLINDER' : 2,
02745 'CONE' : 0,
02746 'CAPSULE' : 3,
02747 'TRIANGLE_MESH' : 4,
02748
02749 'CONVEX_HULL' : 6
02750 }
02751
02752 com = doc.createElement('component'); e.appendChild( com )
02753 com.setAttribute('type', 'EC_RigidBody')
02754 com.setAttribute('sync', '1')
02755
02756
02757
02758
02759 a = doc.createElement('attribute'); com.appendChild( a )
02760 a.setAttribute('name', 'Mass')
02761 if ob.physics_mode == 'RIGID_BODY':
02762 a.setAttribute('value', ob.game.mass)
02763 else:
02764 a.setAttribute('value', '0.0')
02765
02766 SHAPE = a = doc.createElement('attribute'); com.appendChild( a )
02767 a.setAttribute('name', 'Shape type')
02768 a.setAttribute('value', TundraTypes[ ob.game.collision_bounds_type ] )
02769
02770 print (" - EC_RigidBody with shape type", TundraTypes[ob.game.collision_bounds_type])
02771
02772 M = ob.game.collision_margin
02773 a = doc.createElement('attribute'); com.appendChild( a )
02774 a.setAttribute('name', 'Size')
02775 if ob.game.collision_bounds_type in 'TRIANGLE_MESH CONVEX_HULL'.split():
02776 a.setAttribute('value', '%s %s %s' %(1.0+M, 1.0+M, 1.0+M) )
02777 else:
02778
02779 x,y,z = swap(ob.dimensions)
02780 a.setAttribute('value', '%s %s %s' %(abs(x)+M,abs(y)+M,abs(z)+M) )
02781
02782 a = doc.createElement('attribute'); com.appendChild( a )
02783 a.setAttribute('name', 'Collision mesh ref')
02784
02785 if ob.collision_mode == 'DECIMATED':
02786 proxy = None
02787 for child in ob.children:
02788 if child.subcollision and child.name.startswith('DECIMATED'):
02789 proxy = child; break
02790 if proxy:
02791 collisionref = "%s_collision_%s.mesh" % (proto, proxy.data.name)
02792 a.setAttribute('value', collisionref)
02793 if proxy not in collision_proxies:
02794 collision_proxies.append( proxy )
02795 else:
02796 print('[WARNINIG]: Collision proxy mesh not found' )
02797 assert 0
02798 elif ob.collision_mode == 'TERRAIN':
02799 NTF = save_terrain_as_NTF( path, ob )
02800 SHAPE.setAttribute( 'value', '5' )
02801 elif ob.type == 'MESH':
02802
02803
02804
02805 collisionref = "%s%s.mesh" % (proto, clean_object_name(ob.data.name))
02806 a.setAttribute('value', collisionref)
02807
02808 a = doc.createElement('attribute'); com.appendChild( a )
02809 a.setAttribute('name', 'Friction')
02810
02811 a.setAttribute('value', ob.physics_friction)
02812
02813 a = doc.createElement('attribute'); com.appendChild( a )
02814 a.setAttribute('name', 'Restitution')
02815 a.setAttribute('value', ob.physics_bounce)
02816
02817 a = doc.createElement('attribute'); com.appendChild( a )
02818 a.setAttribute('name', 'Linear damping')
02819 a.setAttribute('value', ob.game.damping)
02820
02821 a = doc.createElement('attribute'); com.appendChild( a )
02822 a.setAttribute('name', 'Angular damping')
02823 a.setAttribute('value', ob.game.rotation_damping)
02824
02825 a = doc.createElement('attribute'); com.appendChild( a )
02826 a.setAttribute('name', 'Linear factor')
02827 a.setAttribute('value', '1.0 1.0 1.0')
02828
02829 a = doc.createElement('attribute'); com.appendChild( a )
02830 a.setAttribute('name', 'Angular factor')
02831 a.setAttribute('value', '1.0 1.0 1.0')
02832
02833 a = doc.createElement('attribute'); com.appendChild( a )
02834 a.setAttribute('name', 'Kinematic')
02835 a.setAttribute('value', 'false' )
02836
02837
02838
02839
02840 a = doc.createElement('attribute'); com.appendChild( a )
02841 a.setAttribute('name', 'Phantom')
02842 if ob.collision_mode == 'NONE':
02843 a.setAttribute('value', 'true' )
02844 else:
02845 a.setAttribute('value', 'false' )
02846
02847 a = doc.createElement('attribute'); com.appendChild( a )
02848 a.setAttribute('name', 'Draw Debug')
02849 a.setAttribute('value', 'false' )
02850
02851
02852
02853
02854
02855
02856
02857
02858 a = doc.createElement('attribute'); com.appendChild( a )
02859 a.setAttribute('name', 'Linear velocity')
02860 a.setAttribute('value', '0.0 0.0 0.0')
02861
02862 a = doc.createElement('attribute'); com.appendChild( a )
02863 a.setAttribute('name', 'Angular velocity')
02864 a.setAttribute('value', '0.0 0.0 0.0')
02865
02866 a = doc.createElement('attribute'); com.appendChild( a )
02867 a.setAttribute('name', 'Collision Layer')
02868 a.setAttribute('value', -1)
02869
02870 a = doc.createElement('attribute'); com.appendChild( a )
02871 a.setAttribute('name', 'Collision Mask')
02872 a.setAttribute('value', -1)
02873
02874
02875 if NTF:
02876 xp = NTF['xpatches']
02877 yp = NTF['ypatches']
02878 depth = NTF['depth']
02879
02880 print (" - EC_Terrain")
02881 com = doc.createElement('component'); e.appendChild( com )
02882 com.setAttribute('type', 'EC_Terrain')
02883 com.setAttribute('sync', '1')
02884
02885 a = doc.createElement('attribute'); com.appendChild( a )
02886 a.setAttribute('name', 'Transform')
02887 x,y,z = ob.dimensions
02888 sx,sy,sz = ob.scale
02889 x *= 1.0/sx
02890 y *= 1.0/sy
02891 z *= 1.0/sz
02892
02893 trans = '%s,%s,%s,' %(-xp/4, -depth, -yp/4)
02894
02895 nx = x/(xp*16)
02896 ny = y/(yp*16)
02897 trans += '0,0,0,%s,%s,%s' %(nx,depth, ny)
02898 a.setAttribute('value', trans )
02899
02900 a = doc.createElement('attribute'); com.appendChild( a )
02901 a.setAttribute('name', 'Grid Width')
02902 a.setAttribute('value', xp)
02903
02904 a = doc.createElement('attribute'); com.appendChild( a )
02905 a.setAttribute('name', 'Grid Height')
02906 a.setAttribute('value', yp)
02907
02908 a = doc.createElement('attribute'); com.appendChild( a )
02909 a.setAttribute('name', 'Tex. U scale')
02910 a.setAttribute('value', 1.0)
02911
02912 a = doc.createElement('attribute'); com.appendChild( a )
02913 a.setAttribute('name', 'Tex. V scale')
02914 a.setAttribute('value', 1.0)
02915
02916 a = doc.createElement('attribute'); com.appendChild( a )
02917 a.setAttribute('name', 'Material')
02918 a.setAttribute('value', '')
02919
02920 for i in range(4):
02921 a = doc.createElement('attribute'); com.appendChild( a )
02922 a.setAttribute('name', 'Texture %s' %i)
02923 a.setAttribute('value', '')
02924
02925
02926
02927 heightmapref = "%s%s" % (proto, NTF['name'])
02928 print (" Heightmap ref:", heightmapref)
02929 a = doc.createElement('attribute'); com.appendChild( a )
02930 a.setAttribute('name', 'Heightmap')
02931 a.setAttribute('value', heightmapref )
02932
02933
02934 return e
02935
02936
02937 def tundra_mesh( self, e, ob, url, exported_meshes ):
02938
02939
02940
02941
02942
02943 proto = ''
02944
02945 objectname = clean_object_name(ob.data.name)
02946 meshname = "%s.mesh" % objectname
02947 meshref = "%s%s.mesh" % (proto, objectname)
02948
02949 print (" - EC_Mesh")
02950 print (" - Mesh ref:", meshref)
02951
02952 if self.EX_MESH:
02953 murl = os.path.join( os.path.split(url)[0], meshname )
02954 exists = os.path.isfile( murl )
02955 if not exists or (exists and self.EX_MESH_OVERWRITE):
02956 if meshname not in exported_meshes:
02957 exported_meshes.append( meshname )
02958 self.dot_mesh( ob, os.path.split(url)[0] )
02959
02960 doc = e.document
02961
02962 if ob.find_armature():
02963 print (" - EC_AnimationController")
02964 c = doc.createElement('component'); e.appendChild( c )
02965 c.setAttribute('type', "EC_AnimationController")
02966 c.setAttribute('sync', '1')
02967
02968 c = doc.createElement('component'); e.appendChild( c )
02969 c.setAttribute('type', "EC_Mesh")
02970 c.setAttribute('sync', '1')
02971
02972 a = doc.createElement('attribute'); c.appendChild(a)
02973 a.setAttribute('name', "Mesh ref" )
02974 a.setAttribute('value', meshref)
02975
02976 a = doc.createElement('attribute'); c.appendChild(a)
02977 a.setAttribute('name', "Mesh materials" )
02978
02979
02980
02981 mymaterials = ob.data.materials
02982 if mymaterials is not None and len(mymaterials) > 0:
02983 mymatstring = ''
02984 for mymat in mymaterials:
02985 if mymat is None:
02986 continue
02987
02988 mymatstring += proto + material_name(mymat, True) + '.material;'
02989 mymatstring = mymatstring[:-1]
02990 a.setAttribute('value', mymatstring )
02991 else:
02992
02993 a.setAttribute('value', "" )
02994
02995 if ob.find_armature():
02996 skeletonref = "%s%s.skeleton" % (proto, clean_object_name(ob.data.name))
02997 print (" Skeleton ref:", skeletonref)
02998 a = doc.createElement('attribute'); c.appendChild(a)
02999 a.setAttribute('name', "Skeleton ref" )
03000 a.setAttribute('value', skeletonref)
03001
03002 a = doc.createElement('attribute'); c.appendChild(a)
03003 a.setAttribute('name', "Draw distance" )
03004 if ob.use_draw_distance:
03005 a.setAttribute('value', ob.draw_distance )
03006 else:
03007 a.setAttribute('value', "0" )
03008
03009 a = doc.createElement('attribute'); c.appendChild(a)
03010 a.setAttribute('name', 'Cast shadows' )
03011 if ob.cast_shadows:
03012 a.setAttribute('value', 'true' )
03013 else:
03014 a.setAttribute('value', 'false' )
03015
03016
03017 def tundra_light( self, e, ob ):
03018 '''
03019 <component type="EC_Light" sync="1">
03020 <attribute value="1" name="light type"/>
03021 <attribute value="1 1 1 1" name="diffuse color"/>
03022 <attribute value="1 1 1 1" name="specular color"/>
03023 <attribute value="true" name="cast shadows"/>
03024 <attribute value="29.9999828" name="light range"/>
03025 <attribute value="1" name="brightness"/>
03026 <attribute value="0" name="constant atten"/>
03027 <attribute value="1" name="linear atten"/>
03028 <attribute value="0" name="quadratic atten"/>
03029 <attribute value="30" name="light inner angle"/>
03030 <attribute value="40" name="light outer angle"/>
03031 </component>
03032 '''
03033
03034 if ob.data.type not in 'POINT SPOT'.split():
03035 return
03036
03037 doc = e.document
03038
03039 c = doc.createElement('component'); e.appendChild( c )
03040 c.setAttribute('type', "EC_Light")
03041 c.setAttribute('sync', '1')
03042
03043 a = doc.createElement('attribute'); c.appendChild(a)
03044 a.setAttribute('name', 'light type' )
03045 if ob.data.type=='POINT':
03046 a.setAttribute('value', '0' )
03047 elif ob.data.type=='SPOT':
03048 a.setAttribute('value', '1' )
03049
03050
03051 R,G,B = ob.data.color
03052 a = doc.createElement('attribute'); c.appendChild(a)
03053 a.setAttribute('name', 'diffuse color' )
03054 if ob.data.use_diffuse:
03055 a.setAttribute('value', '%s %s %s 1' %(R,G,B) )
03056 else:
03057 a.setAttribute('value', '0 0 0 1' )
03058
03059 a = doc.createElement('attribute'); c.appendChild(a)
03060 a.setAttribute('name', 'specular color' )
03061 if ob.data.use_specular:
03062 a.setAttribute('value', '%s %s %s 1' %(R,G,B) )
03063 else:
03064 a.setAttribute('value', '0 0 0 1' )
03065
03066 a = doc.createElement('attribute'); c.appendChild(a)
03067 a.setAttribute('name', 'cast shadows' )
03068 if ob.data.type=='HEMI':
03069 a.setAttribute('value', 'false' )
03070 elif ob.data.shadow_method != 'NOSHADOW':
03071 a.setAttribute('value', 'true' )
03072 else:
03073 a.setAttribute('value', 'false' )
03074
03075 a = doc.createElement('attribute'); c.appendChild(a)
03076 a.setAttribute('name', 'light range' )
03077 a.setAttribute('value', ob.data.distance*2 )
03078
03079 a = doc.createElement('attribute'); c.appendChild(a)
03080 a.setAttribute('name', 'brightness' )
03081 a.setAttribute('value', ob.data.energy )
03082
03083 a = doc.createElement('attribute'); c.appendChild(a)
03084 a.setAttribute('name', 'constant atten' )
03085 a.setAttribute('value', '0' )
03086
03087 a = doc.createElement('attribute'); c.appendChild(a)
03088 a.setAttribute('name', 'linear atten' )
03089 energy = ob.data.energy
03090 if energy <= 0.0:
03091 energy = 0.001
03092 a.setAttribute('value', (1.0/energy)*0.25 )
03093
03094 a = doc.createElement('attribute'); c.appendChild(a)
03095 a.setAttribute('name', 'quadratic atten' )
03096 a.setAttribute('value', '0.0' )
03097
03098 if ob.data.type=='SPOT':
03099 outer = math.degrees(ob.data.spot_size) / 2.0
03100 inner = outer * (1.0-ob.data.spot_blend)
03101
03102 a = doc.createElement('attribute'); c.appendChild(a)
03103 a.setAttribute('name', 'light inner angle' )
03104 a.setAttribute('value', '%s'%inner )
03105
03106 a = doc.createElement('attribute'); c.appendChild(a)
03107 a.setAttribute('name', 'light outer angle' )
03108 a.setAttribute('value', '%s' %outer )
03109
03110
03111
03112 invalid_chars = '\/:*?"<>|'
03113
03114 def clean_object_name(value):
03115 global invalid_chars
03116 for invalid_char in invalid_chars:
03117 value = value.replace(invalid_char, '_')
03118 value = value.replace(' ', '_')
03119 return value;
03120
03121 def clean_object_name_with_spaces(value):
03122 global invalid_chars
03123 for invalid_char in invalid_chars:
03124 value = value.replace(invalid_char, '_')
03125 return value;
03126
03127 last_export_filepath = ""
03128
03129 class _OgreCommonExport_(_TXML_):
03130
03131 @classmethod
03132 def poll(cls, context):
03133 if context.active_object and context.mode != 'EDIT_MESH':
03134 return True
03135
03136 def invoke(self, context, event):
03137
03138
03139
03140 global last_export_filepath
03141 if last_export_filepath == "":
03142
03143 if self.filepath == "" and context.blend_data.filepath != "":
03144 path, name = os.path.split(context.blend_data.filepath)
03145 self.filepath = os.path.join(path, name.split('.')[0])
03146 if self.filepath == "":
03147 self.filepath = "blender2ogre-export"
03148 if self.EXPORT_TYPE == "OGRE":
03149 self.filepath += ".scene"
03150 elif self.EXPORT_TYPE == "REX":
03151 self.filepath += ".txml"
03152 else:
03153
03154 self.filepath = last_export_filepath
03155
03156
03157 if self.EXPORT_TYPE == "OGRE":
03158 self.filepath = self.filepath.replace(".txml", ".scene")
03159 elif self.EXPORT_TYPE == "REX":
03160 self.filepath = self.filepath.replace(".scene", ".txml")
03161
03162
03163 self.update_ui()
03164
03165 wm = context.window_manager
03166 fs = wm.fileselect_add(self)
03167 return {'RUNNING_MODAL'}
03168
03169 def execute(self, context):
03170
03171 global last_export_filepath
03172 last_export_filepath = self.filepath
03173
03174
03175 self.ogre_export(self.filepath, context)
03176 return {'FINISHED'}
03177
03178 def update_ui(self):
03179 self.EX_SWAP_AXIS = CONFIG['SWAP_AXIS']
03180 self.EX_SEP_MATS = CONFIG['SEP_MATS']
03181 self.EX_ONLY_DEFORMABLE_BONES = CONFIG['ONLY_DEFORMABLE_BONES']
03182 self.EX_ONLY_KEYFRAMED_BONES = CONFIG['ONLY_KEYFRAMED_BONES']
03183 self.EX_OGRE_INHERIT_SCALE = CONFIG['OGRE_INHERIT_SCALE']
03184 self.EX_SCENE = CONFIG['SCENE']
03185 self.EX_EXPORT_HIDDEN = CONFIG['EXPORT_HIDDEN']
03186 self.EX_SELONLY = CONFIG['SELONLY']
03187 self.EX_FORCE_CAMERA = CONFIG['FORCE_CAMERA']
03188 self.EX_FORCE_LAMPS = CONFIG['FORCE_LAMPS']
03189 self.EX_MESH = CONFIG['MESH']
03190 self.EX_MESH_OVERWRITE = CONFIG['MESH_OVERWRITE']
03191 self.EX_ARM_ANIM = CONFIG['ARM_ANIM']
03192 self.EX_SHAPE_ANIM = CONFIG['SHAPE_ANIM']
03193 self.EX_TRIM_BONE_WEIGHTS = CONFIG['TRIM_BONE_WEIGHTS']
03194 self.EX_ARRAY = CONFIG['ARRAY']
03195 self.EX_MATERIALS = CONFIG['MATERIALS']
03196 self.EX_FORCE_IMAGE_FORMAT = CONFIG['FORCE_IMAGE_FORMAT']
03197 self.EX_DDS_MIPS = CONFIG['DDS_MIPS']
03198 self.EX_COPY_SHADER_PROGRAMS = CONFIG['COPY_SHADER_PROGRAMS']
03199 self.EX_lodLevels = CONFIG['lodLevels']
03200 self.EX_lodDistance = CONFIG['lodDistance']
03201 self.EX_lodPercent = CONFIG['lodPercent']
03202 self.EX_nuextremityPoints = CONFIG['nuextremityPoints']
03203 self.EX_generateEdgeLists = CONFIG['generateEdgeLists']
03204 self.EX_generateTangents = CONFIG['generateTangents']
03205 self.EX_tangentSemantic = CONFIG['tangentSemantic']
03206 self.EX_tangentUseParity = CONFIG['tangentUseParity']
03207 self.EX_tangentSplitMirrored = CONFIG['tangentSplitMirrored']
03208 self.EX_tangentSplitRotated = CONFIG['tangentSplitRotated']
03209 self.EX_reorganiseBuffers = CONFIG['reorganiseBuffers']
03210 self.EX_optimiseAnimations = CONFIG['optimiseAnimations']
03211
03212
03213 EX_SWAP_AXIS = EnumProperty(
03214 items=AXIS_MODES,
03215 name='swap axis',
03216 description='axis swapping mode',
03217 default= CONFIG['SWAP_AXIS'])
03218 EX_SEP_MATS = BoolProperty(
03219 name="Separate Materials",
03220 description="exports a .material for each material (rather than putting all materials in a single .material file)",
03221 default=CONFIG['SEP_MATS'])
03222 EX_ONLY_DEFORMABLE_BONES = BoolProperty(
03223 name="Only Deformable Bones",
03224 description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Note: Any bone with deformable children/descendants will be output as well.",
03225 default=CONFIG['ONLY_DEFORMABLE_BONES'])
03226 EX_ONLY_KEYFRAMED_BONES = BoolProperty(
03227 name="Only Keyframed Bones",
03228 description="only exports bones that have been keyframed for a given animation. Useful to limit the set of bones on a per-animation basis.",
03229 default=CONFIG['ONLY_KEYFRAMED_BONES'])
03230 EX_OGRE_INHERIT_SCALE = BoolProperty(
03231 name="OGRE inherit scale",
03232 description="whether the OGRE bones have the 'inherit scale' flag on. If the animation has scale in it, the exported animation needs to be adjusted to account for the state of the inherit-scale flag in OGRE.",
03233 default=CONFIG['OGRE_INHERIT_SCALE'])
03234 EX_SCENE = BoolProperty(
03235 name="Export Scene",
03236 description="export current scene (OgreDotScene xml)",
03237 default=CONFIG['SCENE'])
03238 EX_SELONLY = BoolProperty(
03239 name="Export Selected Only",
03240 description="export selected",
03241 default=CONFIG['SELONLY'])
03242 EX_EXPORT_HIDDEN = BoolProperty(
03243 name="Export Hidden Also",
03244 description="Export hidden meshes in addition to visible ones. Turn off to avoid exporting hidden stuff.",
03245 default=CONFIG['EXPORT_HIDDEN'])
03246 EX_FORCE_CAMERA = BoolProperty(
03247 name="Force Camera",
03248 description="export active camera",
03249 default=CONFIG['FORCE_CAMERA'])
03250 EX_FORCE_LAMPS = BoolProperty(
03251 name="Force Lamps",
03252 description="export all lamps",
03253 default=CONFIG['FORCE_LAMPS'])
03254 EX_MESH = BoolProperty(
03255 name="Export Meshes",
03256 description="export meshes",
03257 default=CONFIG['MESH'])
03258 EX_MESH_OVERWRITE = BoolProperty(
03259 name="Export Meshes (overwrite)",
03260 description="export meshes (overwrite existing files)",
03261 default=CONFIG['MESH_OVERWRITE'])
03262 EX_ARM_ANIM = BoolProperty(
03263 name="Armature Animation",
03264 description="export armature animations - updates the .skeleton file",
03265 default=CONFIG['ARM_ANIM'])
03266 EX_SHAPE_ANIM = BoolProperty(
03267 name="Shape Animation",
03268 description="export shape animations - updates the .mesh file",
03269 default=CONFIG['SHAPE_ANIM'])
03270 EX_TRIM_BONE_WEIGHTS = FloatProperty(
03271 name="Trim Weights",
03272 description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
03273 min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
03274 EX_ARRAY = BoolProperty(
03275 name="Optimize Arrays",
03276 description="optimize array modifiers as instances (constant offset only)",
03277 default=CONFIG['ARRAY'])
03278 EX_MATERIALS = BoolProperty(
03279 name="Export Materials",
03280 description="exports .material script",
03281 default=CONFIG['MATERIALS'])
03282 EX_FORCE_IMAGE_FORMAT = EnumProperty(
03283 items=_IMAGE_FORMATS,
03284 name='Convert Images',
03285 description='convert all textures to format',
03286 default=CONFIG['FORCE_IMAGE_FORMAT'] )
03287 EX_DDS_MIPS = IntProperty(
03288 name="DDS Mips",
03289 description="number of mip maps (DDS)",
03290 min=0, max=16,
03291 default=CONFIG['DDS_MIPS'])
03292
03293
03294 EX_lodLevels = IntProperty(
03295 name="LOD Levels",
03296 description="MESH number of LOD levels",
03297 min=0, max=32,
03298 default=CONFIG['lodLevels'])
03299 EX_lodDistance = IntProperty(
03300 name="LOD Distance",
03301 description="MESH distance increment to reduce LOD",
03302 min=0, max=2000, default=CONFIG['lodDistance'])
03303 EX_lodPercent = IntProperty(
03304 name="LOD Percentage",
03305 description="LOD percentage reduction",
03306 min=0, max=99,
03307 default=CONFIG['lodPercent'])
03308 EX_nuextremityPoints = IntProperty(
03309 name="Extremity Points",
03310 description="MESH Extremity Points",
03311 min=0, max=65536,
03312 default=CONFIG['nuextremityPoints'])
03313 EX_generateEdgeLists = BoolProperty(
03314 name="Edge Lists",
03315 description="MESH generate edge lists (for stencil shadows)",
03316 default=CONFIG['generateEdgeLists'])
03317 EX_generateTangents = BoolProperty(
03318 name="Tangents",
03319 description="MESH generate tangents",
03320 default=CONFIG['generateTangents'])
03321 EX_tangentSemantic = StringProperty(
03322 name="Tangent Semantic",
03323 description="MESH tangent semantic - can be 'uvw' or 'tangent'",
03324 maxlen=16,
03325 default=CONFIG['tangentSemantic'])
03326 EX_tangentUseParity = IntProperty(
03327 name="Tangent Parity",
03328 description="MESH tangent use parity",
03329 min=0, max=16,
03330 default=CONFIG['tangentUseParity'])
03331 EX_tangentSplitMirrored = BoolProperty(
03332 name="Tangent Split Mirrored",
03333 description="MESH split mirrored tangents",
03334 default=CONFIG['tangentSplitMirrored'])
03335 EX_tangentSplitRotated = BoolProperty(
03336 name="Tangent Split Rotated",
03337 description="MESH split rotated tangents",
03338 default=CONFIG['tangentSplitRotated'])
03339 EX_reorganiseBuffers = BoolProperty(
03340 name="Reorganise Buffers",
03341 description="MESH reorganise vertex buffers",
03342 default=CONFIG['reorganiseBuffers'])
03343 EX_optimiseAnimations = BoolProperty(
03344 name="Optimize Animations",
03345 description="MESH optimize animations",
03346 default=CONFIG['optimiseAnimations'])
03347 EX_COPY_SHADER_PROGRAMS = BoolProperty(
03348 name="copy shader programs",
03349 description="when using script inheritance copy the source shader programs to the output path",
03350 default=CONFIG['COPY_SHADER_PROGRAMS'])
03351
03352 filepath_last = ""
03353 filepath = StringProperty(
03354 name="File Path",
03355 description="Filepath used for exporting file",
03356 maxlen=1024, default="",
03357 subtype='FILE_PATH')
03358
03359 def dot_material( self, meshes, path='/tmp', mat_file_name='SceneMaterial'):
03360 material_files = []
03361 mats = []
03362 for ob in meshes:
03363 if len(ob.data.materials):
03364 for mat in ob.data.materials:
03365 if mat not in mats:
03366 mats.append( mat )
03367
03368 if not mats:
03369 print('WARNING: no materials, not writting .material script'); return []
03370
03371 M = MISSING_MATERIAL + '\n'
03372 for mat in mats:
03373 if mat is None:
03374 continue
03375 Report.materials.append( material_name(mat) )
03376 if CONFIG['COPY_SHADER_PROGRAMS']:
03377 data = generate_material( mat, path=path, copy_programs=True, touch_textures=CONFIG['TOUCH_TEXTURES'] )
03378 else:
03379 data = generate_material( mat, path=path, touch_textures=CONFIG['TOUCH_TEXTURES'] )
03380
03381 M += data
03382
03383 if self.EX_SEP_MATS:
03384 url = self.dot_material_write_separate( mat, data, path )
03385 material_files.append(url)
03386
03387
03388 if not self.EX_SEP_MATS:
03389 try:
03390 url = os.path.join(path, '%s.material' % mat_file_name)
03391 f = open( url, 'wb' ); f.write( bytes(M,'utf-8') ); f.close()
03392 print(' - Created material:', url)
03393 material_files.append( url )
03394 except Exception as e:
03395 show_dialog("Invalid material object name: " + mat_file_name)
03396
03397 return material_files
03398
03399 def dot_material_write_separate( self, mat, data, path = '/tmp' ):
03400 try:
03401 clean_filename = clean_object_name(mat.name);
03402 url = os.path.join(path, '%s.material' % clean_filename)
03403 f = open(url, 'wb'); f.write( bytes(data,'utf-8') ); f.close()
03404 print(' - Exported Material:', url)
03405 return url
03406 except Exception as e:
03407 show_dialog("Invalid material object name: " + clean_filename)
03408 return ""
03409
03410 def dot_mesh( self, ob, path='/tmp', force_name=None, ignore_shape_animation=False ):
03411 dot_mesh( ob, path, force_name, ignore_shape_animation=False )
03412
03413 def ogre_export(self, url, context, force_material_update=[]):
03414 print ("_"*80)
03415
03416
03417 global CONFIG
03418 for name in dir(self):
03419 if name.startswith('EX_'):
03420 CONFIG[ name[3:] ] = getattr(self,name)
03421
03422 Report.reset()
03423
03424 print("Processing Scene")
03425 prefix = url.split('.')[0]
03426 path = os.path.split(url)[0]
03427
03428
03429 objects = []
03430 linkedgroups = []
03431 invalidnamewarnings = []
03432 for ob in bpy.context.scene.objects:
03433 if ob.subcollision:
03434 continue
03435 if not self.EX_EXPORT_HIDDEN and ob.hide:
03436 continue
03437 if self.EX_SELONLY and not ob.select:
03438 if ob.type == 'CAMERA' and self.EX_FORCE_CAMERA:
03439 pass
03440 elif ob.type == 'LAMP' and self.EX_FORCE_LAMPS:
03441 pass
03442 else:
03443 continue
03444 if ob.type == 'EMPTY' and ob.dupli_group and ob.dupli_type == 'GROUP':
03445 linkedgroups.append(ob)
03446 else:
03447
03448
03449 cleanname = clean_object_name(ob.name)
03450 cleannamespaces = clean_object_name_with_spaces(ob.name)
03451 if cleanname != ob.name:
03452 if cleannamespaces != ob.name:
03453 invalidnamewarnings.append(ob.name + " -> " + cleanname)
03454 objects.append(ob)
03455
03456
03457 if len(invalidnamewarnings) > 0:
03458 print ("[Warning]: Following object names have invalid characters for creating files. They will be automatically converted.")
03459 for namewarning in invalidnamewarnings:
03460 Report.warnings.append("Auto correcting object name: " + namewarning)
03461 print (" - ", namewarning)
03462
03463
03464 temps = []
03465 for e in linkedgroups:
03466 grp = e.dupli_group
03467 subs = []
03468 for o in grp.objects:
03469 if o.type=='MESH':
03470 subs.append( o )
03471 elif o.type == 'EMPTY' and o.dupli_group and o.dupli_type == 'GROUP':
03472 ss = []
03473 for oo in o.dupli_group.objects:
03474 if oo.type=='MESH':
03475 ss.append( oo )
03476 elif oo.type == 'EMPTY' and oo.dupli_group and oo.dupli_type == 'GROUP':
03477 sss = []
03478 for ooo in oo.dupli_group.objects:
03479 if ooo.type=='MESH':
03480 sss.append( ooo )
03481 if sss:
03482 m = merge_objects( sss, name=oo.name, transform=oo.matrix_world )
03483 subs.append( m )
03484 temps.append( m )
03485 if ss:
03486 m = merge_objects( ss, name=o.name, transform=o.matrix_world )
03487 subs.append( m )
03488 temps.append( m )
03489 if subs:
03490 m = merge_objects( subs, name=e.name, transform=e.matrix_world )
03491 objects.append( m )
03492 temps.append( m )
03493
03494
03495 mgroups = []
03496 mobjects = []
03497 for ob in objects:
03498 group = get_merge_group( ob )
03499 if group:
03500 for member in group.objects:
03501 if member not in mobjects: mobjects.append( member )
03502 if group not in mgroups: mgroups.append( group )
03503 for rem in mobjects:
03504 if rem in objects: objects.remove( rem )
03505
03506 for group in mgroups:
03507 merged = merge_group( group )
03508 objects.append( merged )
03509 temps.append( merged )
03510
03511
03512 def _flatten( _c, _f ):
03513 if _c.parent in objects: _f.append( _c.parent )
03514 if _c.parent: _flatten( _c.parent, _f )
03515 else: _f.append( _c )
03516
03517 roots = []
03518 meshes = []
03519
03520 for ob in objects:
03521 flat = []
03522 _flatten( ob, flat )
03523 root = flat[-1]
03524 if root not in roots:
03525 roots.append(root)
03526 if ob.type=='MESH':
03527 meshes.append(ob)
03528
03529 mesh_collision_prims = {}
03530 mesh_collision_files = {}
03531
03532
03533 exported_meshes = []
03534
03535 if self.EX_MATERIALS:
03536 print (" Processing Materials")
03537 material_file_name_base = os.path.split(url)[1].replace('.scene', '').replace('.txml', '')
03538 material_files = self.dot_material(meshes + force_material_update, path, material_file_name_base)
03539 else:
03540 material_files = []
03541
03542
03543 if self.EXPORT_TYPE == 'REX':
03544 rex = self.create_tundra_document(context)
03545 proxies = []
03546 for ob in objects:
03547 print(" Processing %s [%s]" % (ob.name, ob.type))
03548
03549
03550
03551
03552 if ob.type == 'MESH':
03553 ob.data.update(calc_tessface=True)
03554
03555
03556 if ob.type == 'LAMP':
03557 TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
03558 self.tundra_light( TE, ob )
03559
03560 elif ob.type == 'SPEAKER':
03561 TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
03562
03563 elif ob.type == 'MESH' and len(ob.data.tessfaces):
03564 if ob.modifiers and ob.modifiers[0].type=='MULTIRES' and ob.use_multires_lod:
03565 mod = ob.modifiers[0]
03566 basename = ob.name
03567 dataname = ob.data.name
03568 ID = uid( ob )
03569 TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
03570
03571 for level in range( mod.total_levels+1 ):
03572 ob.uid += 1
03573 mod.levels = level
03574 ob.name = '%s.LOD%s' %(basename,level)
03575 ob.data.name = '%s.LOD%s' %(dataname,level)
03576 TE = self.tundra_entity(
03577 rex, ob, path=path, collision_proxies=proxies, parent=basename,
03578 matrix=mathutils.Matrix(), visible=False
03579 )
03580 self.tundra_mesh( TE, ob, url, exported_meshes )
03581
03582 ob.uid = ID
03583 ob.name = basename
03584 ob.data.name = dataname
03585 else:
03586 TE = self.tundra_entity( rex, ob, path=path, collision_proxies=proxies )
03587 self.tundra_mesh( TE, ob, url, exported_meshes )
03588
03589
03590 for proxy in proxies:
03591 self.dot_mesh(
03592 proxy,
03593 path=os.path.split(url)[0],
03594 force_name='_collision_%s' %proxy.data.name
03595 )
03596
03597 if self.EX_SCENE:
03598 if not url.endswith('.txml'):
03599 url += '.txml'
03600 data = rex.toprettyxml()
03601 f = open( url, 'wb' ); f.write( bytes(data,'utf-8') ); f.close()
03602 print(' Exported Tundra Scene:', url)
03603
03604
03605 elif self.EXPORT_TYPE == 'OGRE':
03606 doc = self.create_ogre_document( context, material_files )
03607
03608 for root in roots:
03609 print(' - Exporting root node:', root.name)
03610 self._node_export(
03611 root,
03612 url = url,
03613 doc = doc,
03614 exported_meshes = exported_meshes,
03615 meshes = meshes,
03616 mesh_collision_prims = mesh_collision_prims,
03617 mesh_collision_files = mesh_collision_files,
03618 prefix = prefix,
03619 objects = objects,
03620 xmlparent = doc._scene_nodes
03621 )
03622
03623 if self.EX_SCENE:
03624 if not url.endswith('.scene'):
03625 url += '.scene'
03626 data = doc.toprettyxml()
03627 f = open( url, 'wb' ); f.write( bytes(data,'utf-8') ); f.close()
03628 print(' Exported Ogre Scene:', url)
03629
03630 for ob in temps:
03631 context.scene.objects.unlink( ob )
03632 bpy.ops.wm.call_menu(name='MiniReport')
03633
03634
03635
03636
03637 save_config()
03638
03639 def create_ogre_document(self, context, material_files=[] ):
03640 now = time.time()
03641 doc = RDocument()
03642 scn = doc.createElement('scene'); doc.appendChild( scn )
03643 scn.setAttribute('export_time', str(now))
03644 scn.setAttribute('formatVersion', '1.0.1')
03645 bscn = bpy.context.scene
03646
03647 if '_previous_export_time_' in bscn.keys():
03648 scn.setAttribute('previous_export_time', str(bscn['_previous_export_time_']))
03649 else:
03650 scn.setAttribute('previous_export_time', '0')
03651 bscn[ '_previous_export_time_' ] = now
03652 scn.setAttribute('exported_by', getpass.getuser())
03653
03654 nodes = doc.createElement('nodes')
03655 doc._scene_nodes = nodes
03656 extern = doc.createElement('externals')
03657 environ = doc.createElement('environment')
03658 for n in (nodes,extern,environ):
03659 scn.appendChild( n )
03660
03661
03662 for url in material_files:
03663 item = doc.createElement('item'); extern.appendChild( item )
03664 item.setAttribute('type','material')
03665 a = doc.createElement('file'); item.appendChild( a )
03666 a.setAttribute('name', url)
03667
03668
03669 world = context.scene.world
03670 if world:
03671 _c = {'colourAmbient':world.ambient_color, 'colourBackground':world.horizon_color, 'colourDiffuse':world.horizon_color}
03672 for ctag in _c:
03673 a = doc.createElement(ctag); environ.appendChild( a )
03674 color = _c[ctag]
03675 a.setAttribute('r', '%s'%color.r)
03676 a.setAttribute('g', '%s'%color.g)
03677 a.setAttribute('b', '%s'%color.b)
03678
03679 if world and world.mist_settings.use_mist:
03680 a = doc.createElement('fog'); environ.appendChild( a )
03681 a.setAttribute('linearStart', '%s'%world.mist_settings.start )
03682 mist_falloff = world.mist_settings.falloff
03683 if mist_falloff == 'QUADRATIC': a.setAttribute('mode', 'exp')
03684 elif mist_falloff == 'LINEAR': a.setAttribute('mode', 'linear')
03685 else: a.setAttribute('mode', 'exp2')
03686
03687 a.setAttribute('linearEnd', '%s' %(world.mist_settings.start+world.mist_settings.depth))
03688 a.setAttribute('expDensity', world.mist_settings.intensity)
03689 a.setAttribute('colourR', world.horizon_color.r)
03690 a.setAttribute('colourG', world.horizon_color.g)
03691 a.setAttribute('colourB', world.horizon_color.b)
03692
03693 return doc
03694
03695
03696 def _node_export( self, ob, url='', doc=None, rex=None, exported_meshes=[], meshes=[], mesh_collision_prims={}, mesh_collision_files={}, prefix='', objects=[], xmlparent=None ):
03697 o = _ogre_node_helper( doc, ob, objects )
03698 xmlparent.appendChild(o)
03699
03700
03701 for prop in ob.items():
03702 propname, propvalue = prop
03703 if not propname.startswith('_'):
03704 user = doc.createElement('user_data')
03705 o.appendChild( user )
03706 user.setAttribute( 'name', propname )
03707 user.setAttribute( 'value', str(propvalue) )
03708 user.setAttribute( 'type', type(propvalue).__name__ )
03709
03710
03711 for prop in ob.game.properties:
03712 e = doc.createElement( 'user_data' )
03713 o.appendChild( e )
03714 e.setAttribute('name', prop.name)
03715 e.setAttribute('value', str(prop.value))
03716 e.setAttribute('type', type(prop.value).__name__)
03717
03718
03719
03720 game = doc.createElement('game')
03721 o.appendChild( game )
03722 sens = doc.createElement('sensors')
03723 game.appendChild( sens )
03724 acts = doc.createElement('actuators')
03725 game.appendChild( acts )
03726 for sen in ob.game.sensors:
03727 sens.appendChild( WrapSensor(sen).xml(doc) )
03728 for act in ob.game.actuators:
03729 acts.appendChild( WrapActuator(act).xml(doc) )
03730
03731 if ob.type == 'MESH':
03732 ob.data.update(calc_tessface=True)
03733
03734 if ob.type == 'MESH' and len(ob.data.tessfaces):
03735 collisionFile = None
03736 collisionPrim = None
03737 if ob.data.name in mesh_collision_prims:
03738 collisionPrim = mesh_collision_prims[ ob.data.name ]
03739 if ob.data.name in mesh_collision_files:
03740 collisionFile = mesh_collision_files[ ob.data.name ]
03741
03742 e = doc.createElement('entity')
03743 o.appendChild(e); e.setAttribute('name', ob.data.name)
03744 prefix = ''
03745 e.setAttribute('meshFile', '%s%s.mesh' %(prefix,ob.data.name) )
03746
03747 if not collisionPrim and not collisionFile:
03748 if ob.game.use_collision_bounds:
03749 collisionPrim = ob.game.collision_bounds_type.lower()
03750 mesh_collision_prims[ ob.data.name ] = collisionPrim
03751 else:
03752 for child in ob.children:
03753 if child.subcollision and child.name.startswith('DECIMATE'):
03754 collisionFile = '%s_collision_%s.mesh' %(prefix,ob.data.name)
03755 break
03756 if collisionFile:
03757 mesh_collision_files[ ob.data.name ] = collisionFile
03758 self.dot_mesh(
03759 child,
03760 path=os.path.split(url)[0],
03761 force_name='_collision_%s' %ob.data.name
03762 )
03763
03764 if collisionPrim:
03765 e.setAttribute('collisionPrim', collisionPrim )
03766 elif collisionFile:
03767 e.setAttribute('collisionFile', collisionFile )
03768
03769 _mesh_entity_helper( doc, ob, e )
03770
03771 if self.EX_MESH:
03772 murl = os.path.join( os.path.split(url)[0], '%s.mesh'%ob.data.name )
03773 exists = os.path.isfile( murl )
03774 if not exists or (exists and self.EX_MESH_OVERWRITE):
03775 if ob.data.name not in exported_meshes:
03776 exported_meshes.append( ob.data.name )
03777 self.dot_mesh( ob, os.path.split(url)[0] )
03778
03779
03780 vecs = [ ob.matrix_world.to_translation() ]
03781 for mod in ob.modifiers:
03782 if mod.type == 'ARRAY':
03783 if mod.fit_type != 'FIXED_COUNT':
03784 print( 'WARNING: unsupport array-modifier type->', mod.fit_type )
03785 continue
03786 if not mod.use_constant_offset:
03787 print( 'WARNING: unsupport array-modifier mode, must be "constant offset" type' )
03788 continue
03789 else:
03790
03791 newvecs = []
03792 for prev in vecs:
03793 for i in range( mod.count-1 ):
03794 v = prev + mod.constant_offset_displace
03795 newvecs.append( v )
03796 ao = _ogre_node_helper( doc, ob, objects, prefix='_array_%s_'%len(vecs+newvecs), pos=v )
03797 xmlparent.appendChild(ao)
03798
03799 e = doc.createElement('entity')
03800 ao.appendChild(e); e.setAttribute('name', ob.data.name)
03801
03802
03803 e.setAttribute('meshFile', '%s.mesh' %ob.data.name)
03804
03805 if collisionPrim: e.setAttribute('collisionPrim', collisionPrim )
03806 elif collisionFile: e.setAttribute('collisionFile', collisionFile )
03807 vecs += newvecs
03808
03809 elif ob.type == 'CAMERA':
03810 Report.cameras.append( ob.name )
03811 c = doc.createElement('camera')
03812 o.appendChild(c); c.setAttribute('name', ob.data.name)
03813 aspx = bpy.context.scene.render.pixel_aspect_x
03814 aspy = bpy.context.scene.render.pixel_aspect_y
03815 sx = bpy.context.scene.render.resolution_x
03816 sy = bpy.context.scene.render.resolution_y
03817 fovY = 0.0
03818 if (sx*aspx > sy*aspy):
03819 fovY = 2*math.atan(sy*aspy*16.0/(ob.data.lens*sx*aspx))
03820 else:
03821 fovY = 2*math.atan(16.0/ob.data.lens)
03822
03823 fov = math.radians( fovY*180.0/math.pi )
03824 c.setAttribute('fov', '%s'%fov)
03825 c.setAttribute('projectionType', "perspective")
03826 a = doc.createElement('clipping'); c.appendChild( a )
03827 a.setAttribute('nearPlaneDist', '%s' %ob.data.clip_start)
03828 a.setAttribute('farPlaneDist', '%s' %ob.data.clip_end)
03829 a.setAttribute('near', '%s' %ob.data.clip_start)
03830 a.setAttribute('far', '%s' %ob.data.clip_end)
03831
03832 elif ob.type == 'LAMP' and ob.data.type in 'POINT SPOT SUN'.split():
03833 Report.lights.append( ob.name )
03834 l = doc.createElement('light')
03835 o.appendChild(l)
03836
03837 mat = get_parent_matrix(ob, objects).inverted() * ob.matrix_world
03838
03839 p = doc.createElement('position')
03840 l.appendChild(p)
03841 v = swap( ob.matrix_world.to_translation() )
03842 p.setAttribute('x', '%6f'%v.x)
03843 p.setAttribute('y', '%6f'%v.y)
03844 p.setAttribute('z', '%6f'%v.z)
03845
03846 if ob.data.type == 'POINT':
03847 l.setAttribute('type', 'point')
03848 elif ob.data.type == 'SPOT':
03849 l.setAttribute('type', 'spot')
03850 elif ob.data.type == 'SUN':
03851 l.setAttribute('type', 'directional')
03852
03853 l.setAttribute('name', ob.name )
03854 l.setAttribute('powerScale', str(ob.data.energy))
03855
03856 a = doc.createElement('lightAttenuation'); l.appendChild( a )
03857 a.setAttribute('range', '5000' )
03858 a.setAttribute('constant', '1.0')
03859 a.setAttribute('linear', '%s'%(1.0/ob.data.distance))
03860 a.setAttribute('quadratic', '0.0')
03861
03862 if ob.data.type in ('SPOT', 'SUN'):
03863 vector = swap(mathutils.Euler.to_matrix(ob.rotation_euler)[2])
03864 a = doc.createElement('direction')
03865 l.appendChild(a)
03866 a.setAttribute('x',str(round(-vector[0],3)))
03867 a.setAttribute('y',str(round(-vector[1],3)))
03868 a.setAttribute('z',str(round(-vector[2],3)))
03869
03870 if ob.data.type == 'SPOT':
03871 a = doc.createElement('spotLightRange')
03872 l.appendChild(a)
03873 a.setAttribute('inner',str( ob.data.spot_size*(1.0-ob.data.spot_blend) ))
03874 a.setAttribute('outer',str(ob.data.spot_size))
03875 a.setAttribute('falloff','1.0')
03876
03877 if ob.data.use_diffuse:
03878 a = doc.createElement('colourDiffuse'); l.appendChild( a )
03879 a.setAttribute('r', '%s'%ob.data.color.r)
03880 a.setAttribute('g', '%s'%ob.data.color.g)
03881 a.setAttribute('b', '%s'%ob.data.color.b)
03882
03883 if ob.data.use_specular:
03884 a = doc.createElement('colourSpecular'); l.appendChild( a )
03885 a.setAttribute('r', '%s'%ob.data.color.r)
03886 a.setAttribute('g', '%s'%ob.data.color.g)
03887 a.setAttribute('b', '%s'%ob.data.color.b)
03888
03889 if ob.data.type != 'HEMI':
03890 if ob.data.shadow_method != 'NOSHADOW':
03891 a = doc.createElement('colourShadow');l.appendChild( a )
03892 a.setAttribute('r', '%s'%ob.data.color.r)
03893 a.setAttribute('g', '%s'%ob.data.color.g)
03894 a.setAttribute('b', '%s'%ob.data.color.b)
03895 l.setAttribute('shadow','true')
03896
03897 for child in ob.children:
03898 self._node_export( child,
03899 url = url, doc = doc, rex = rex,
03900 exported_meshes = exported_meshes,
03901 meshes = meshes,
03902 mesh_collision_prims = mesh_collision_prims,
03903 mesh_collision_files = mesh_collision_files,
03904 prefix = prefix,
03905 objects=objects,
03906 xmlparent=o
03907 )
03908
03909
03910
03911 class INFO_OT_createOgreExport(bpy.types.Operator, _OgreCommonExport_):
03912 '''Export Ogre Scene'''
03913 bl_idname = "ogre.export"
03914 bl_label = "Export Ogre"
03915 bl_options = {'REGISTER'}
03916
03917
03918 EX_SWAP_AXIS = EnumProperty(
03919 items=AXIS_MODES,
03920 name='swap axis',
03921 description='axis swapping mode',
03922 default= CONFIG['SWAP_AXIS'])
03923 EX_SEP_MATS = BoolProperty(
03924 name="Separate Materials",
03925 description="exports a .material for each material (rather than putting all materials in a single .material file)",
03926 default=CONFIG['SEP_MATS'])
03927 EX_ONLY_DEFORMABLE_BONES = BoolProperty(
03928 name="Only Deformable Bones",
03929 description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Note: Any bone with deformable children/descendants will be output as well.",
03930 default=CONFIG['ONLY_DEFORMABLE_BONES'])
03931 EX_ONLY_KEYFRAMED_BONES = BoolProperty(
03932 name="Only Keyframed Bones",
03933 description="only exports bones that have been keyframed for a given animation. Useful to limit the set of bones on a per-animation basis.",
03934 default=CONFIG['ONLY_KEYFRAMED_BONES'])
03935 EX_OGRE_INHERIT_SCALE = BoolProperty(
03936 name="OGRE inherit scale",
03937 description="whether the OGRE bones have the 'inherit scale' flag on. If the animation has scale in it, the exported animation needs to be adjusted to account for the state of the inherit-scale flag in OGRE.",
03938 default=CONFIG['OGRE_INHERIT_SCALE'])
03939 EX_SCENE = BoolProperty(
03940 name="Export Scene",
03941 description="export current scene (OgreDotScene xml)",
03942 default=CONFIG['SCENE'])
03943 EX_SELONLY = BoolProperty(
03944 name="Export Selected Only",
03945 description="export selected",
03946 default=CONFIG['SELONLY'])
03947 EX_EXPORT_HIDDEN = BoolProperty(
03948 name="Export Hidden Also",
03949 description="Export hidden meshes in addition to visible ones. Turn off to avoid exporting hidden stuff.",
03950 default=CONFIG['EXPORT_HIDDEN'])
03951 EX_FORCE_CAMERA = BoolProperty(
03952 name="Force Camera",
03953 description="export active camera",
03954 default=CONFIG['FORCE_CAMERA'])
03955 EX_FORCE_LAMPS = BoolProperty(
03956 name="Force Lamps",
03957 description="export all lamps",
03958 default=CONFIG['FORCE_LAMPS'])
03959 EX_MESH = BoolProperty(
03960 name="Export Meshes",
03961 description="export meshes",
03962 default=CONFIG['MESH'])
03963 EX_MESH_OVERWRITE = BoolProperty(
03964 name="Export Meshes (overwrite)",
03965 description="export meshes (overwrite existing files)",
03966 default=CONFIG['MESH_OVERWRITE'])
03967 EX_ARM_ANIM = BoolProperty(
03968 name="Armature Animation",
03969 description="export armature animations - updates the .skeleton file",
03970 default=CONFIG['ARM_ANIM'])
03971 EX_SHAPE_ANIM = BoolProperty(
03972 name="Shape Animation",
03973 description="export shape animations - updates the .mesh file",
03974 default=CONFIG['SHAPE_ANIM'])
03975 EX_TRIM_BONE_WEIGHTS = FloatProperty(
03976 name="Trim Weights",
03977 description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
03978 min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
03979 EX_ARRAY = BoolProperty(
03980 name="Optimize Arrays",
03981 description="optimize array modifiers as instances (constant offset only)",
03982 default=CONFIG['ARRAY'])
03983 EX_MATERIALS = BoolProperty(
03984 name="Export Materials",
03985 description="exports .material script",
03986 default=CONFIG['MATERIALS'])
03987 EX_FORCE_IMAGE_FORMAT = EnumProperty(
03988 items=_IMAGE_FORMATS,
03989 name='Convert Images',
03990 description='convert all textures to format',
03991 default=CONFIG['FORCE_IMAGE_FORMAT'] )
03992 EX_DDS_MIPS = IntProperty(
03993 name="DDS Mips",
03994 description="number of mip maps (DDS)",
03995 min=0, max=16,
03996 default=CONFIG['DDS_MIPS'])
03997
03998
03999 EX_lodLevels = IntProperty(
04000 name="LOD Levels",
04001 description="MESH number of LOD levels",
04002 min=0, max=32,
04003 default=CONFIG['lodLevels'])
04004 EX_lodDistance = IntProperty(
04005 name="LOD Distance",
04006 description="MESH distance increment to reduce LOD",
04007 min=0, max=2000,
04008 default=CONFIG['lodDistance'])
04009 EX_lodPercent = IntProperty(
04010 name="LOD Percentage",
04011 description="LOD percentage reduction",
04012 min=0, max=99,
04013 default=CONFIG['lodPercent'])
04014 EX_nuextremityPoints = IntProperty(
04015 name="Extremity Points",
04016 description="MESH Extremity Points",
04017 min=0, max=65536,
04018 default=CONFIG['nuextremityPoints'])
04019 EX_generateEdgeLists = BoolProperty(
04020 name="Edge Lists",
04021 description="MESH generate edge lists (for stencil shadows)",
04022 default=CONFIG['generateEdgeLists'])
04023 EX_generateTangents = BoolProperty(
04024 name="Tangents",
04025 description="MESH generate tangents",
04026 default=CONFIG['generateTangents'])
04027 EX_tangentSemantic = StringProperty(
04028 name="Tangent Semantic",
04029 description="MESH tangent semantic",
04030 maxlen=16,
04031 default=CONFIG['tangentSemantic'])
04032 EX_tangentUseParity = IntProperty(
04033 name="Tangent Parity",
04034 description="MESH tangent use parity",
04035 min=0, max=16,
04036 default=CONFIG['tangentUseParity'])
04037 EX_tangentSplitMirrored = BoolProperty(
04038 name="Tangent Split Mirrored",
04039 description="MESH split mirrored tangents",
04040 default=CONFIG['tangentSplitMirrored'])
04041 EX_tangentSplitRotated = BoolProperty(
04042 name="Tangent Split Rotated",
04043 description="MESH split rotated tangents",
04044 default=CONFIG['tangentSplitRotated'])
04045 EX_reorganiseBuffers = BoolProperty(
04046 name="Reorganise Buffers",
04047 description="MESH reorganise vertex buffers",
04048 default=CONFIG['reorganiseBuffers'])
04049 EX_optimiseAnimations = BoolProperty(
04050 name="Optimize Animations",
04051 description="MESH optimize animations",
04052 default=CONFIG['optimiseAnimations'])
04053
04054 filepath= StringProperty(
04055 name="File Path",
04056 description="Filepath used for exporting Ogre .scene file",
04057 maxlen=1024,
04058 default="",
04059 subtype='FILE_PATH')
04060
04061 EXPORT_TYPE = 'OGRE'
04062
04063
04064
04065 class INFO_OT_createRealxtendExport( bpy.types.Operator, _OgreCommonExport_):
04066 '''Export RealXtend Scene'''
04067 bl_idname = "ogre.export_realxtend"
04068 bl_label = "Export RealXtend"
04069 bl_options = {'REGISTER', 'UNDO'}
04070
04071 EX_SWAP_AXIS = EnumProperty(
04072 items=AXIS_MODES,
04073 name='swap axis',
04074 description='axis swapping mode',
04075 default= CONFIG['SWAP_AXIS']
04076 )
04077
04078
04079 EX_SEP_MATS = BoolProperty(
04080 name="Separate Materials",
04081 description="exports a .material for each material (rather than putting all materials in a single .material file)",
04082 default=CONFIG['SEP_MATS'])
04083 EX_ONLY_DEFORMABLE_BONES = BoolProperty(
04084 name="Only Deformable Bones",
04085 description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Note: Any bone with deformable children/descendants will be output as well.",
04086 default=CONFIG['ONLY_DEFORMABLE_BONES'])
04087 EX_ONLY_KEYFRAMED_BONES = BoolProperty(
04088 name="Only Keyframed Bones",
04089 description="only exports bones that have been keyframed for a given animation. Useful to limit the set of bones on a per-animation basis.",
04090 default=CONFIG['ONLY_KEYFRAMED_BONES'])
04091 EX_OGRE_INHERIT_SCALE = BoolProperty(
04092 name="OGRE inherit scale",
04093 description="whether the OGRE bones have the 'inherit scale' flag on. If the animation has scale in it, the exported animation needs to be adjusted to account for the state of the inherit-scale flag in OGRE.",
04094 default=CONFIG['OGRE_INHERIT_SCALE'])
04095 EX_SCENE = BoolProperty(
04096 name="Export Scene",
04097 description="export current scene (OgreDotScene xml)",
04098 default=CONFIG['SCENE'])
04099 EX_SELONLY = BoolProperty(
04100 name="Export Selected Only",
04101 description="export selected",
04102 default=CONFIG['SELONLY'])
04103 EX_EXPORT_HIDDEN = BoolProperty(
04104 name="Export Hidden Also",
04105 description="Export hidden meshes in addition to visible ones. Turn off to avoid exporting hidden stuff.",
04106 default=CONFIG['EXPORT_HIDDEN'])
04107 EX_FORCE_CAMERA = BoolProperty(
04108 name="Force Camera",
04109 description="export active camera",
04110 default=CONFIG['FORCE_CAMERA'])
04111 EX_FORCE_LAMPS = BoolProperty(
04112 name="Force Lamps",
04113 description="export all lamps",
04114 default=CONFIG['FORCE_LAMPS'])
04115 EX_MESH = BoolProperty(
04116 name="Export Meshes",
04117 description="export meshes",
04118 default=CONFIG['MESH'])
04119 EX_MESH_OVERWRITE = BoolProperty(
04120 name="Export Meshes (overwrite)",
04121 description="export meshes (overwrite existing files)",
04122 default=CONFIG['MESH_OVERWRITE'])
04123 EX_ARM_ANIM = BoolProperty(
04124 name="Armature Animation",
04125 description="export armature animations - updates the .skeleton file",
04126 default=CONFIG['ARM_ANIM'])
04127 EX_SHAPE_ANIM = BoolProperty(
04128 name="Shape Animation",
04129 description="export shape animations - updates the .mesh file",
04130 default=CONFIG['SHAPE_ANIM'])
04131 EX_TRIM_BONE_WEIGHTS = FloatProperty(
04132 name="Trim Weights",
04133 description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
04134 min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
04135 EX_ARRAY = BoolProperty(
04136 name="Optimize Arrays",
04137 description="optimize array modifiers as instances (constant offset only)",
04138 default=CONFIG['ARRAY'])
04139 EX_MATERIALS = BoolProperty(
04140 name="Export Materials",
04141 description="exports .material script",
04142 default=CONFIG['MATERIALS'])
04143 EX_FORCE_IMAGE_FORMAT = EnumProperty(
04144 name='Convert Images',
04145 description='convert all textures to format',
04146 items=_IMAGE_FORMATS,
04147 default=CONFIG['FORCE_IMAGE_FORMAT'])
04148 EX_DDS_MIPS = IntProperty(
04149 name="DDS Mips",
04150 description="number of mip maps (DDS)",
04151 min=0, max=16,
04152 default=CONFIG['DDS_MIPS'])
04153
04154
04155 EX_lodLevels = IntProperty(
04156 name="LOD Levels",
04157 description="MESH number of LOD levels",
04158 min=0, max=32,
04159 default=CONFIG['lodLevels'])
04160 EX_lodDistance = IntProperty(
04161 name="LOD Distance",
04162 description="MESH distance increment to reduce LOD",
04163 min=0, max=2000,
04164 default=CONFIG['lodDistance'])
04165 EX_lodPercent = IntProperty(
04166 name="LOD Percentage",
04167 description="LOD percentage reduction",
04168 min=0, max=99,
04169 default=CONFIG['lodPercent'])
04170 EX_nuextremityPoints = IntProperty(
04171 name="Extremity Points",
04172 description="MESH Extremity Points",
04173 min=0, max=65536,
04174 default=CONFIG['nuextremityPoints'])
04175 EX_generateEdgeLists = BoolProperty(
04176 name="Edge Lists",
04177 description="MESH generate edge lists (for stencil shadows)",
04178 default=CONFIG['generateEdgeLists'])
04179 EX_generateTangents = BoolProperty(
04180 name="Tangents",
04181 description="MESH generate tangents",
04182 default=CONFIG['generateTangents'])
04183 EX_tangentSemantic = StringProperty(
04184 name="Tangent Semantic",
04185 description="MESH tangent semantic",
04186 maxlen=3,
04187 default=CONFIG['tangentSemantic'])
04188 EX_tangentUseParity = IntProperty(
04189 name="Tangent Parity",
04190 description="MESH tangent use parity",
04191 min=0, max=16,
04192 default=CONFIG['tangentUseParity'])
04193 EX_tangentSplitMirrored = BoolProperty(
04194 name="Tangent Split Mirrored",
04195 description="MESH split mirrored tangents",
04196 default=CONFIG['tangentSplitMirrored'])
04197 EX_tangentSplitRotated = BoolProperty(
04198 name="Tangent Split Rotated",
04199 description="MESH split rotated tangents",
04200 default=CONFIG['tangentSplitRotated'])
04201 EX_reorganiseBuffers = BoolProperty(
04202 name="Reorganise Buffers",
04203 description="MESH reorganise vertex buffers",
04204 default=CONFIG['reorganiseBuffers'])
04205 EX_optimiseAnimations = BoolProperty(
04206 name="Optimize Animations",
04207 description="MESH optimize animations",
04208 default=CONFIG['optimiseAnimations'])
04209
04210 filepath = StringProperty(
04211 name="File Path",
04212 description="Filepath used for exporting .txml file",
04213 maxlen=1024,
04214 default="",
04215 subtype='FILE_PATH')
04216
04217 EXPORT_TYPE = 'REX'
04218
04219
04220
04221 def _ogre_node_helper( doc, ob, objects, prefix='', pos=None, rot=None, scl=None ):
04222
04223 mat = get_parent_matrix(ob, objects).inverted() * ob.matrix_world
04224
04225 o = doc.createElement('node')
04226 o.setAttribute('name',prefix+ob.name)
04227 p = doc.createElement('position')
04228 q = doc.createElement('rotation')
04229 s = doc.createElement('scale')
04230 for n in (p,q,s):
04231 o.appendChild(n)
04232
04233 if pos:
04234 v = swap(pos)
04235 else:
04236 v = swap( mat.to_translation() )
04237 p.setAttribute('x', '%6f'%v.x)
04238 p.setAttribute('y', '%6f'%v.y)
04239 p.setAttribute('z', '%6f'%v.z)
04240
04241 if rot:
04242 v = swap(rot)
04243 else:
04244 v = swap( mat.to_quaternion() )
04245 q.setAttribute('qx', '%6f'%v.x)
04246 q.setAttribute('qy', '%6f'%v.y)
04247 q.setAttribute('qz', '%6f'%v.z)
04248 q.setAttribute('qw','%6f'%v.w)
04249
04250 if scl:
04251 v = swap(scl)
04252 x=abs(v.x); y=abs(v.y); z=abs(v.z)
04253 s.setAttribute('x', '%6f'%x)
04254 s.setAttribute('y', '%6f'%y)
04255 s.setAttribute('z', '%6f'%z)
04256 else:
04257 ri = mat.to_quaternion().inverted().to_matrix()
04258 scale = ri.to_4x4() * mat
04259 v = swap( scale.to_scale() )
04260 x=abs(v.x); y=abs(v.y); z=abs(v.z)
04261 s.setAttribute('x', '%6f'%x)
04262 s.setAttribute('y', '%6f'%y)
04263 s.setAttribute('z', '%6f'%z)
04264 return o
04265
04266
04267
04268 class MeshMagick(object):
04269 ''' Usage: MeshMagick [global_options] toolname [tool_options] infile(s) -- [outfile(s)]
04270 Available Tools
04271 ===============
04272 info - print information about the mesh.
04273 meshmerge - Merge multiple submeshes into a single mesh.
04274 optimise - Optimise meshes and skeletons.
04275 rename - Rename different elements of meshes and skeletons.
04276 transform - Scale, rotate or otherwise transform a mesh.
04277 '''
04278
04279 @staticmethod
04280 def get_merge_group( ob ):
04281 return get_merge_group( ob, prefix='magicmerge' )
04282
04283 @staticmethod
04284 def merge( group, path='/tmp', force_name=None ):
04285 print('-'*80)
04286 print(' mesh magick - merge ')
04287 exe = CONFIG['OGRETOOLS_MESH_MAGICK']
04288 if not os.path.isfile( exe ):
04289 print( 'ERROR: can not find MeshMagick.exe' )
04290 print( exe )
04291 return
04292
04293 files = []
04294 for ob in group.objects:
04295 if ob.data.users == 1:
04296 files.append( os.path.join( path, ob.data.name+'.mesh' ) )
04297 print( files[-1] )
04298
04299 opts = 'meshmerge'
04300 if sys.platform == 'linux2': cmd = '/usr/bin/wine %s %s' %(exe, opts)
04301 else: cmd = '%s %s' %(exe, opts)
04302 if force_name: output = force_name + '.mesh'
04303 else: output = '_%s_.mesh' %group.name
04304 cmd = cmd.split() + files + ['--', os.path.join(path,output) ]
04305 subprocess.call( cmd )
04306 print(' mesh magick - complete ')
04307 print('-'*80)
04308
04309
04310
04311 _ogre_command_line_tools_doc = '''
04312 Usage: OgreXMLConverter [options] sourcefile [destfile]
04313
04314 Available options:
04315 -i = interactive mode - prompt for options
04316 (The next 4 options are only applicable when converting XML to Mesh)
04317 -l lodlevels = number of LOD levels
04318 -v lodvalue = value increment to reduce LOD
04319 -s lodstrategy = LOD strategy to use for this mesh
04320 -p lodpercent = Percentage triangle reduction amount per LOD
04321 -f lodnumtris = Fixed vertex reduction per LOD
04322 -e = DON'T generate edge lists (for stencil shadows)
04323 -r = DON'T reorganise vertex buffers to OGRE recommended format.
04324 -t = Generate tangents (for normal mapping)
04325 -td [uvw|tangent]
04326 = Tangent vertex semantic destination (default tangent)
04327 -ts [3|4] = Tangent size (3 or 4 components, 4 includes parity, default 3)
04328 -tm = Split tangent vertices at UV mirror points
04329 -tr = Split tangent vertices where basis is rotated > 90 degrees
04330 -o = DON'T optimise out redundant tracks & keyframes
04331 -d3d = Prefer D3D packed colour formats (default on Windows)
04332 -gl = Prefer GL packed colour formats (default on non-Windows)
04333 -E endian = Set endian mode 'big' 'little' or 'native' (default)
04334 -x num = Generate no more than num eXtremes for every submesh (default 0)
04335 -q = Quiet mode, less output
04336 -log filename = name of the log file (default: 'OgreXMLConverter.log')
04337 sourcefile = name of file to convert
04338 destfile = optional name of file to write to. If you don't
04339 specify this OGRE works it out through the extension
04340 and the XML contents if the source is XML. For example
04341 test.mesh becomes test.xml, test.xml becomes test.mesh
04342 if the XML document root is <mesh> etc.
04343 '''
04344
04345
04346
04347 def OgreXMLConverter( infile, has_uvs=False ):
04348
04349
04350 exe = CONFIG['OGRETOOLS_XML_CONVERTER']
04351 if not os.path.isfile( exe ):
04352 print( 'WARNING: can not find OgreXMLConverter (can not convert XXX.mesh.xml to XXX.mesh' )
04353 return
04354
04355 basicArguments = ''
04356
04357
04358
04359
04360
04361
04362 if CONFIG['nuextremityPoints'] > 0:
04363 basicArguments += ' -x %s' %CONFIG['nuextremityPoints']
04364
04365 if not CONFIG['generateEdgeLists']:
04366 basicArguments += ' -e'
04367
04368
04369 if CONFIG['generateTangents'] and has_uvs:
04370 basicArguments += ' -t'
04371 if CONFIG['tangentSemantic']:
04372 basicArguments += ' -td %s' %CONFIG['tangentSemantic']
04373 if CONFIG['tangentUseParity']:
04374 basicArguments += ' -ts %s' %CONFIG['tangentUseParity']
04375 if CONFIG['tangentSplitMirrored']:
04376 basicArguments += ' -tm'
04377 if CONFIG['tangentSplitRotated']:
04378 basicArguments += ' -tr'
04379 if not CONFIG['reorganiseBuffers']:
04380 basicArguments += ' -r'
04381 if not CONFIG['optimiseAnimations']:
04382 basicArguments += ' -o'
04383
04384
04385 basicArguments += ' -q'
04386
04387 opts = '-log _ogre_debug.txt %s' %basicArguments
04388 path,name = os.path.split( infile )
04389
04390 cmd = '%s %s' %(exe, opts)
04391 cmd = cmd.split() + [infile]
04392 subprocess.call( cmd )
04393
04394
04395
04396 class Bone(object):
04397
04398 def __init__(self, rbone, pbone, skeleton):
04399 if CONFIG['SWAP_AXIS'] == 'xyz':
04400 self.fixUpAxis = False
04401 else:
04402 self.fixUpAxis = True
04403 if CONFIG['SWAP_AXIS'] == '-xzy':
04404 self.flipMat = mathutils.Matrix(((-1,0,0,0),(0,0,1,0),(0,1,0,0),(0,0,0,1)))
04405 elif CONFIG['SWAP_AXIS'] == 'xz-y':
04406
04407 self.flipMat = mathutils.Matrix(((1,0,0,0),(0,0,1,0),(0,-1,0,0),(0,0,0,1)))
04408 else:
04409 print( 'ERROR - TODO: axis swap mode not supported with armature animation' )
04410 assert 0
04411
04412 self.skeleton = skeleton
04413 self.name = pbone.name
04414 self.matrix = rbone.matrix_local.copy()
04415
04416
04417 self.bone = pbone
04418 self.shouldOutput = True
04419 if CONFIG['ONLY_DEFORMABLE_BONES'] and not pbone.bone.use_deform:
04420 self.shouldOutput = False
04421
04422
04423 self.parent = pbone.parent
04424 self.children = []
04425
04426 def update(self):
04427 pbone = self.bone
04428 pose = pbone.matrix.copy()
04429 self._inverse_total_trans_pose = pose.inverted()
04430
04431 if self.parent:
04432 pose = self.parent._inverse_total_trans_pose* pose
04433 elif self.fixUpAxis:
04434 pose = self.flipMat * pose
04435 else:
04436 pass
04437
04438 self.pose_location = pose.to_translation() - self.ogre_rest_matrix.to_translation()
04439 pose = self.inverse_ogre_rest_matrix * pose
04440 self.pose_rotation = pose.to_quaternion()
04441
04442
04443
04444
04445
04446
04447
04448
04449 if CONFIG['OGRE_INHERIT_SCALE']:
04450
04451
04452
04453
04454
04455
04456 self.pose_scale = pose.to_scale()
04457 self.ogreDerivedScale = self.pose_scale.copy()
04458 if self.parent:
04459
04460 self.ogreDerivedScale[0] *= self.parent.ogreDerivedScale[0]
04461 self.ogreDerivedScale[1] *= self.parent.ogreDerivedScale[1]
04462 self.ogreDerivedScale[2] *= self.parent.ogreDerivedScale[2]
04463
04464 if not self.bone.bone.use_inherit_scale:
04465
04466 scl = self.parent.ogreDerivedScale
04467 self.pose_scale = mathutils.Vector((1.0/scl[0], 1.0/scl[1], 1.0/scl[2]))
04468 self.ogreDerivedScale = mathutils.Vector((1.0, 1.0, 1.0))
04469 else:
04470
04471
04472 self.pose_scale = pbone.scale.copy()
04473
04474 if self.parent and self.bone.bone.use_inherit_scale:
04475
04476 self.pose_scale[0] *= self.parent.pose_scale[0]
04477 self.pose_scale[1] *= self.parent.pose_scale[1]
04478 self.pose_scale[2] *= self.parent.pose_scale[2]
04479
04480 for child in self.children:
04481 child.update()
04482
04483 def clear_pose_transform( self ):
04484 self.bone.location.zero()
04485 self.bone.scale.Fill(3, 1.0)
04486 self.bone.rotation_quaternion.identity()
04487 self.bone.rotation_euler.zero()
04488
04489
04490 def save_pose_transform( self ):
04491 self.savedPoseLocation = self.bone.location.copy()
04492 self.savedPoseScale = self.bone.scale.copy()
04493 self.savedPoseRotationQ = self.bone.rotation_quaternion
04494 self.savedPoseRotationE = self.bone.rotation_euler
04495
04496
04497 def restore_pose_transform( self ):
04498 self.bone.location = self.savedPoseLocation
04499 self.bone.scale = self.savedPoseScale
04500 self.bone.rotation_quaternion = self.savedPoseRotationQ
04501 self.bone.rotation_euler = self.savedPoseRotationE
04502
04503
04504 def rebuild_tree( self ):
04505 if self.parent:
04506 self.parent = self.skeleton.get_bone( self.parent.name )
04507 self.parent.children.append( self )
04508 if self.shouldOutput and not self.parent.shouldOutput:
04509
04510 parent = self.parent
04511 while parent:
04512 parent.shouldOutput = True
04513 parent = parent.parent
04514
04515 def compute_rest( self ):
04516 if self.parent:
04517 inverseParentMatrix = self.parent.inverse_total_trans
04518 elif self.fixUpAxis:
04519 inverseParentMatrix = self.flipMat
04520 else:
04521 inverseParentMatrix = mathutils.Matrix(((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)))
04522
04523
04524 self.ogre_rest_matrix = self.matrix.copy()
04525
04526 self.inverse_total_trans = self.ogre_rest_matrix.inverted()
04527
04528 self.ogre_rest_matrix = inverseParentMatrix * self.ogre_rest_matrix
04529 self.inverse_ogre_rest_matrix = self.ogre_rest_matrix.inverted()
04530
04531
04532 for child in self.children:
04533 child.compute_rest()
04534
04535 class Keyframe:
04536 def __init__(self, time, pos, rot, scale):
04537 self.time = time
04538 self.pos = pos.copy()
04539 self.rot = rot.copy()
04540 self.scale = scale.copy()
04541
04542 def isTransIdentity( self ):
04543 return self.pos.length < 0.0001
04544
04545 def isRotIdentity( self ):
04546
04547 if abs(self.rot.angle) < 0.0001 or abs(self.rot.axis.length - 1.0) > 0.001:
04548
04549 return True
04550 return False
04551
04552 def isScaleIdentity( self ):
04553 scaleDiff = mathutils.Vector((1,1,1)) - self.scale
04554 return scaleDiff.length < 0.0001
04555
04556
04557
04558
04559
04560 class Bone_Track:
04561 def __init__(self, bone):
04562 self.bone = bone
04563 self.keyframes = []
04564
04565 def is_pos_animated( self ):
04566
04567 for kf in self.keyframes:
04568 if not kf.isTransIdentity():
04569 return True
04570 return False
04571
04572 def is_rot_animated( self ):
04573
04574 for kf in self.keyframes:
04575 if not kf.isRotIdentity():
04576 return True
04577 return False
04578
04579 def is_scale_animated( self ):
04580
04581 for kf in self.keyframes:
04582 if not kf.isScaleIdentity():
04583 return True
04584 return False
04585
04586 def add_keyframe( self, time ):
04587 bone = self.bone
04588 kf = Keyframe(time, bone.pose_location, bone.pose_rotation, bone.pose_scale)
04589 self.keyframes.append( kf )
04590
04591 def write_track( self, doc, tracks_element ):
04592 isPosAnimated = self.is_pos_animated()
04593 isRotAnimated = self.is_rot_animated()
04594 isScaleAnimated = self.is_scale_animated()
04595 if not isPosAnimated and not isRotAnimated and not isScaleAnimated:
04596 return
04597 track = doc.createElement('track')
04598 track.setAttribute('bone', self.bone.name)
04599 keyframes_element = doc.createElement('keyframes')
04600 track.appendChild( keyframes_element )
04601 for kf in self.keyframes:
04602 keyframe = doc.createElement('keyframe')
04603 keyframe.setAttribute('time', '%6f' % kf.time)
04604 if isPosAnimated:
04605 trans = doc.createElement('translate')
04606 keyframe.appendChild( trans )
04607 trans.setAttribute('x', '%6f' % kf.pos.x)
04608 trans.setAttribute('y', '%6f' % kf.pos.y)
04609 trans.setAttribute('z', '%6f' % kf.pos.z)
04610
04611 if isRotAnimated:
04612 rotElement = doc.createElement( 'rotate' )
04613 keyframe.appendChild( rotElement )
04614 angle = kf.rot.angle
04615 axis = kf.rot.axis
04616
04617 if kf.isRotIdentity():
04618 angle = 0.0
04619 axis = mathutils.Vector((0,0,0))
04620 rotElement.setAttribute('angle', '%6f' %angle )
04621 axisElement = doc.createElement('axis')
04622 rotElement.appendChild( axisElement )
04623 axisElement.setAttribute('x', '%6f' %axis[0])
04624 axisElement.setAttribute('y', '%6f' %axis[1])
04625 axisElement.setAttribute('z', '%6f' %axis[2])
04626
04627 if isScaleAnimated:
04628 scale = doc.createElement('scale')
04629 keyframe.appendChild( scale )
04630 x,y,z = kf.scale
04631 scale.setAttribute('x', '%6f' %x)
04632 scale.setAttribute('y', '%6f' %y)
04633 scale.setAttribute('z', '%6f' %z)
04634 keyframes_element.appendChild( keyframe )
04635 tracks_element.appendChild( track )
04636
04637
04638 def findArmature( ob ):
04639 arm = ob.find_armature()
04640
04641 if not arm.animation_data:
04642
04643 for ob2 in bpy.data.objects:
04644 if ob2.type == 'ARMATURE' and ob2.proxy == arm:
04645 print( "proxy armature %s found" % ob2.name )
04646 return ob2
04647 return arm
04648
04649 class Skeleton(object):
04650 def get_bone( self, name ):
04651 for b in self.bones:
04652 if b.name == name:
04653 return b
04654 return None
04655
04656 def __init__(self, ob ):
04657 if ob.location.x != 0 or ob.location.y != 0 or ob.location.z != 0:
04658 Report.warnings.append('ERROR: Mesh (%s): is offset from Armature - zero transform is required' %ob.name)
04659 if ob.scale.x != 1 or ob.scale.y != 1 or ob.scale.z != 1:
04660 Report.warnings.append('ERROR: Mesh (%s): has been scaled - scale(1,1,1) is required' %ob.name)
04661
04662 self.object = ob
04663 self.bones = []
04664 mats = {}
04665 self.arm = arm = findArmature( ob )
04666 arm.hide = False
04667 self._restore_layers = list(arm.layers)
04668
04669
04670 for pbone in arm.pose.bones:
04671 mybone = Bone( arm.data.bones[pbone.name], pbone, self )
04672 self.bones.append( mybone )
04673
04674 if arm.name not in Report.armatures:
04675 Report.armatures.append( arm.name )
04676
04677
04678
04679
04680
04681
04682 x,y,z = arm.matrix_local.to_euler()
04683 if x != 0 or y != 0 or z != 0:
04684 Report.warnings.append('ERROR: Armature: %s is rotated - (rotation is ignored)' %arm.name)
04685
04686
04687 for b in self.bones:
04688 b.rebuild_tree()
04689
04690 self.roots = []
04691 ep = 0.0001
04692 for b in self.bones:
04693 if not b.parent:
04694 b.compute_rest()
04695 loc,rot,scl = b.ogre_rest_matrix.decompose()
04696
04697
04698
04699
04700 self.roots.append( b )
04701
04702 def write_animation( self, arm, actionName, frameBegin, frameEnd, doc, parentElement ):
04703 _fps = float( bpy.context.scene.render.fps )
04704
04705 bone_tracks = []
04706 for bone in self.bones:
04707
04708 if bone.shouldOutput:
04709 bone_tracks.append( Bone_Track(bone) )
04710 bone.clear_pose_transform()
04711 for frame in range( int(frameBegin), int(frameEnd)+1, bpy.context.scene.frame_step):
04712 bpy.context.scene.frame_set(frame)
04713 for bone in self.roots:
04714 bone.update()
04715 for track in bone_tracks:
04716 track.add_keyframe((frame - frameBegin) / _fps)
04717
04718 animationFound = False
04719 for track in bone_tracks:
04720 if track.is_pos_animated() or track.is_rot_animated() or track.is_scale_animated():
04721 animationFound = True
04722 break
04723 if not animationFound:
04724 return
04725 anim = doc.createElement('animation')
04726 parentElement.appendChild( anim )
04727 tracks = doc.createElement('tracks')
04728 anim.appendChild( tracks )
04729 Report.armature_animations.append( '%s : %s [start frame=%s end frame=%s]' %(arm.name, actionName, frameBegin, frameEnd) )
04730
04731 anim.setAttribute('name', actionName)
04732 anim.setAttribute('length', '%6f' %( (frameEnd - frameBegin)/_fps ) )
04733
04734 for track in bone_tracks:
04735
04736 track.write_track( doc, tracks )
04737
04738 def to_xml( self ):
04739 doc = RDocument()
04740 root = doc.createElement('skeleton'); doc.appendChild( root )
04741 bones = doc.createElement('bones'); root.appendChild( bones )
04742 bh = doc.createElement('bonehierarchy'); root.appendChild( bh )
04743 boneId = 0
04744 for bone in self.bones:
04745 if not bone.shouldOutput:
04746 continue
04747 b = doc.createElement('bone')
04748 b.setAttribute('name', bone.name)
04749 b.setAttribute('id', str(boneId) )
04750 boneId = boneId + 1
04751 bones.appendChild( b )
04752 mat = bone.ogre_rest_matrix.copy()
04753 if bone.parent:
04754 bp = doc.createElement('boneparent')
04755 bp.setAttribute('bone', bone.name)
04756 bp.setAttribute('parent', bone.parent.name)
04757 bh.appendChild( bp )
04758
04759 pos = doc.createElement( 'position' ); b.appendChild( pos )
04760 x,y,z = mat.to_translation()
04761 pos.setAttribute('x', '%6f' %x )
04762 pos.setAttribute('y', '%6f' %y )
04763 pos.setAttribute('z', '%6f' %z )
04764 rot = doc.createElement( 'rotation' )
04765 b.appendChild( rot )
04766
04767 q = mat.to_quaternion()
04768 rot.setAttribute('angle', '%6f' %q.angle )
04769 axis = doc.createElement('axis'); rot.appendChild( axis )
04770 x,y,z = q.axis
04771 axis.setAttribute('x', '%6f' %x )
04772 axis.setAttribute('y', '%6f' %y )
04773 axis.setAttribute('z', '%6f' %z )
04774
04775
04776
04777 arm = self.arm
04778
04779 savedFrame = bpy.context.scene.frame_current
04780
04781 for b in self.bones:
04782 b.save_pose_transform()
04783
04784 anims = doc.createElement('animations')
04785 root.appendChild( anims )
04786 if not arm.animation_data or (arm.animation_data and not arm.animation_data.nla_tracks):
04787
04788 self.write_animation( arm, 'my_animation', bpy.context.scene.frame_start, bpy.context.scene.frame_end, doc, anims )
04789
04790 elif arm.animation_data:
04791 savedUseNla = arm.animation_data.use_nla
04792 savedAction = arm.animation_data.action
04793 arm.animation_data.use_nla = False
04794 if not len( arm.animation_data.nla_tracks ):
04795 Report.warnings.append('you must assign an NLA strip to armature (%s) that defines the start and end frames' %arm.name)
04796
04797 actions = {}
04798
04799
04800 for nla in arm.animation_data.nla_tracks:
04801 print('NLA track:', nla.name)
04802
04803 for strip in nla.strips:
04804 action = strip.action
04805 actions[ action.name ] = action
04806 print(' strip name:', strip.name)
04807 print(' action name:', action.name)
04808
04809 actionNames = sorted( actions.keys() )
04810 for actionName in actionNames:
04811 action = actions[ actionName ]
04812 arm.animation_data.action = action
04813 suppressedBones = []
04814 if CONFIG['ONLY_KEYFRAMED_BONES']:
04815 keyframedBones = {}
04816 for group in action.groups:
04817 keyframedBones[ group.name ] = True
04818 for b in self.bones:
04819 if (not b.name in keyframedBones) and b.shouldOutput:
04820
04821 b.shouldOutput = False
04822 suppressedBones.append( b.name )
04823 self.write_animation( arm, actionName, action.frame_range[0], action.frame_range[1], doc, anims )
04824
04825 for boneName in suppressedBones:
04826 bone = self.get_bone( boneName )
04827 bone.shouldOutput = True
04828
04829 arm.animation_data.action = savedAction
04830 arm.animation_data.use_nla = savedUseNla
04831
04832
04833 bpy.context.scene.frame_set( savedFrame )
04834
04835 for b in self.bones:
04836 b.restore_pose_transform()
04837
04838 return doc.toprettyxml()
04839
04840
04841
04842
04843 class INFO_MT_instances(bpy.types.Menu):
04844 bl_label = "Instances"
04845
04846 def draw(self, context):
04847 layout = self.layout
04848 inst = gather_instances()
04849 for data in inst:
04850 ob = inst[data][0]
04851 op = layout.operator(INFO_MT_instance.bl_idname, text=ob.name)
04852 op.mystring = ob.name
04853 layout.separator()
04854
04855 class INFO_MT_instance(bpy.types.Operator):
04856 '''select instance group'''
04857 bl_idname = "ogre.select_instances"
04858 bl_label = "Select Instance Group"
04859 bl_options = {'REGISTER', 'UNDO'}
04860 mystring= StringProperty(name="MyString", description="hidden string", maxlen=1024, default="my string")
04861
04862 @classmethod
04863 def poll(cls, context):
04864 return True
04865
04866 def invoke(self, context, event):
04867 print( 'invoke select_instances op', event )
04868 select_instances( context, self.mystring )
04869 return {'FINISHED'}
04870
04871 class INFO_MT_groups(bpy.types.Menu):
04872 bl_label = "Groups"
04873
04874 def draw(self, context):
04875 layout = self.layout
04876 for group in bpy.data.groups:
04877 op = layout.operator(INFO_MT_group.bl_idname, text=group.name)
04878 op.mystring = group.name
04879 layout.separator()
04880
04881 class INFO_MT_group(bpy.types.Operator):
04882 '''select group'''
04883 bl_idname = "ogre.select_group"
04884 bl_label = "Select Group"
04885 bl_options = {'REGISTER'}
04886 mystring= StringProperty(name="MyString", description="hidden string", maxlen=1024, default="my string")
04887
04888 @classmethod
04889 def poll(cls, context):
04890 return True
04891
04892 def invoke(self, context, event):
04893 select_group( context, self.mystring )
04894 return {'FINISHED'}
04895
04896
04897
04898 NVDXT_DOC = '''
04899 Version 8.30
04900 NVDXT
04901 This program
04902 compresses images
04903 creates normal maps from color or alpha
04904 creates DuDv map
04905 creates cube maps
04906 writes out .dds file
04907 does batch processing
04908 reads .tga, .bmp, .gif, .ppm, .jpg, .tif, .cel, .dds, .png, .psd, .rgb, *.bw and .rgba
04909 filters MIP maps
04910
04911 Options:
04912 -profile <profile name> : Read a profile created from the Photoshop plugin
04913 -quick : use fast compression method
04914 -quality_normal : normal quality compression
04915 -quality_production : production quality compression
04916 -quality_highest : highest quality compression (this can be very slow)
04917 -rms_threshold <int> : quality RMS error. Above this, an extensive search is performed.
04918 -prescale <int> <int>: rescale image to this size first
04919 -rescale <nearest | hi | lo | next_lo>: rescale image to nearest, next highest or next lowest power of two
04920 -rel_scale <float, float> : relative scale of original image. 0.5 is half size Default 1.0, 1.0
04921
04922 Optional Filtering for rescaling. Default cube filter:
04923 -RescalePoint
04924 -RescaleBox
04925 -RescaleTriangle
04926 -RescaleQuadratic
04927 -RescaleCubic
04928 -RescaleCatrom
04929 -RescaleMitchell
04930 -RescaleGaussian
04931 -RescaleSinc
04932 -RescaleBessel
04933 -RescaleHanning
04934 -RescaleHamming
04935 -RescaleBlackman
04936 -RescaleKaiser
04937 -clamp <int, int> : maximum image size. image width and height are clamped
04938 -clampScale <int, int> : maximum image size. image width and height are scaled
04939 -window <left, top, right, bottom> : window of original window to compress
04940 -nomipmap : don't generate MIP maps
04941 -nmips <int> : specify the number of MIP maps to generate
04942 -rgbe : Image is RGBE format
04943 -dither : add dithering
04944 -sharpenMethod <method>: sharpen method MIP maps
04945 <method> is
04946 None
04947 Negative
04948 Lighter
04949 Darker
04950 ContrastMore
04951 ContrastLess
04952 Smoothen
04953 SharpenSoft
04954 SharpenMedium
04955 SharpenStrong
04956 FindEdges
04957 Contour
04958 EdgeDetect
04959 EdgeDetectSoft
04960 Emboss
04961 MeanRemoval
04962 UnSharp <radius, amount, threshold>
04963 XSharpen <xsharpen_strength, xsharpen_threshold>
04964 Custom
04965 -pause : wait for keyboard on error
04966 -flip : flip top to bottom
04967 -timestamp : Update only changed files
04968 -list <filename> : list of files to convert
04969 -cubeMap : create cube map .
04970 Cube faces specified with individual files with -list option
04971 positive x, negative x, positive y, negative y, positive z, negative z
04972 Use -output option to specify filename
04973 Cube faces specified in one file. Use -file to specify input filename
04974
04975 -volumeMap : create volume texture.
04976 Volume slices specified with individual files with -list option
04977 Use -output option to specify filename
04978 Volume specified in one file. Use -file to specify input filename
04979
04980 -all : all image files in current directory
04981 -outdir <directory>: output directory
04982 -deep [directory]: include all subdirectories
04983 -outsamedir : output directory same as input
04984 -overwrite : if input is .dds file, overwrite old file
04985 -forcewrite : write over readonly files
04986 -file <filename> : input file to process. Accepts wild cards
04987 -output <filename> : filename to write to [-outfile can also be specified]
04988 -append <filename_append> : append this string to output filename
04989 -8 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555 | L8 | A8> : compress 8 bit images with this format
04990 -16 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555 | A8L8> : compress 16 bit images with this format
04991 -24 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555> : compress 24 bit images with this format
04992 -32 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555> : compress 32 bit images with this format
04993
04994 -swapRB : swap rb
04995 -swapRG : swap rg
04996 -gamma <float value>: gamma correcting during filtering
04997 -outputScale <float, float, float, float>: scale the output by this (r,g,b,a)
04998 -outputBias <float, float, float, float>: bias the output by this amount (r,g,b,a)
04999 -outputWrap : wraps overflow values modulo the output format
05000 -inputScale <float, float, float, float>: scale the inpput by this (r,g,b,a)
05001 -inputBias <float, float, float, float>: bias the input by this amount (r,g,b,a)
05002 -binaryalpha : treat alpha as 0 or 1
05003 -alpha_threshold <byte>: [0-255] alpha reference value
05004 -alphaborder : border images with alpha = 0
05005 -alphaborderLeft : border images with alpha (left) = 0
05006 -alphaborderRight : border images with alpha (right)= 0
05007 -alphaborderTop : border images with alpha (top) = 0
05008 -alphaborderBottom : border images with alpha (bottom)= 0
05009 -fadeamount <int>: percentage to fade each MIP level. Default 15
05010
05011 -fadecolor : fade map (color, normal or DuDv) over MIP levels
05012 -fadetocolor <hex color> : color to fade to
05013 -custom_fade <n> <n fadeamounts> : set custom fade amount. n is number number of fade amounts. fadeamount are [0,1]
05014 -fadealpha : fade alpha over MIP levels
05015 -fadetoalpha <byte>: [0-255] alpha to fade to
05016 -border : border images with color
05017 -bordercolor <hex color> : color for border
05018 -force4 : force DXT1c to use always four colors
05019 -weight <float, float, float>: Compression weightings for R G and B
05020 -luminance : convert color values to luminance for L8 formats
05021 -greyScale : Convert to grey scale
05022 -greyScaleWeights <float, float, float, float>: override greyscale conversion weights of (0.3086, 0.6094, 0.0820, 0)
05023 -brightness <float, float, float, float>: per channel brightness. Default 0.0 usual range [0,1]
05024 -contrast <float, float, float, float>: per channel contrast. Default 1.0 usual range [0.5, 1.5]
05025
05026 Texture Format Default DXT3:
05027 -dxt1c : DXT1 (color only)
05028 -dxt1a : DXT1 (one bit alpha)
05029 -dxt3 : DXT3
05030 -dxt5 : DXT5n
05031 -u1555 : uncompressed 1:5:5:5
05032 -u4444 : uncompressed 4:4:4:4
05033 -u565 : uncompressed 5:6:5
05034 -u8888 : uncompressed 8:8:8:8
05035 -u888 : uncompressed 0:8:8:8
05036 -u555 : uncompressed 0:5:5:5
05037 -p8c : paletted 8 bit (256 colors)
05038 -p8a : paletted 8 bit (256 colors with alpha)
05039 -p4c : paletted 4 bit (16 colors)
05040 -p4a : paletted 4 bit (16 colors with alpha)
05041 -a8 : 8 bit alpha channel
05042 -cxv8u8 : normal map format
05043 -v8u8 : EMBM format (8, bit two component signed)
05044 -v16u16 : EMBM format (16 bit, two component signed)
05045 -A8L8 : 8 bit alpha channel, 8 bit luminance
05046 -fp32x4 : fp32 four channels (A32B32G32R32F)
05047 -fp32 : fp32 one channel (R32F)
05048 -fp16x4 : fp16 four channels (A16B16G16R16F)
05049 -dxt5nm : dxt5 style normal map
05050 -3Dc : 3DC
05051 -g16r16 : 16 bit in, two component
05052 -g16r16f : 16 bit float, two components
05053
05054 Mip Map Filtering Options. Default box filter:
05055 -Point
05056 -Box
05057 -Triangle
05058 -Quadratic
05059 -Cubic
05060 -Catrom
05061 -Mitchell
05062 -Gaussian
05063 -Sinc
05064 -Bessel
05065 -Hanning
05066 -Hamming
05067 -Blackman
05068 -Kaiser
05069
05070 ***************************
05071 To make a normal or dudv map, specify one of
05072 -n4 : normal map 4 sample
05073 -n3x3 : normal map 3x3 filter
05074 -n5x5 : normal map 5x5 filter
05075 -n7x7 : normal map 7x7 filter
05076 -n9x9 : normal map 9x9 filter
05077 -dudv : DuDv
05078
05079 and source of height info:
05080 -alpha : alpha channel
05081 -rgb : average rgb
05082 -biased : average rgb biased
05083 -red : red channel
05084 -green : green channel
05085 -blue : blue channel
05086 -max : max of (r,g,b)
05087 -colorspace : mix of r,g,b
05088
05089 -norm : normalize mip maps (source is a normal map)
05090
05091 -toHeight : create a height map (source is a normal map)
05092
05093
05094 Normal/DuDv Map dxt:
05095 -aheight : store calculated height in alpha field
05096 -aclear : clear alpha channel
05097 -awhite : set alpha channel = 1.0
05098 -scale <float> : scale of height map. Default 1.0
05099 -wrap : wrap texture around. Default off
05100 -minz <int> : minimum value for up vector [0-255]. Default 0
05101
05102 ***************************
05103 To make a depth sprite, specify:
05104 -depth
05105
05106 and source of depth info:
05107 -alpha : alpha channel
05108 -rgb : average rgb (default)
05109 -red : red channel
05110 -green : green channel
05111 -blue : blue channel
05112 -max : max of (r,g,b)
05113 -colorspace : mix of r,g,b
05114
05115 Depth Sprite dxt:
05116 -aheight : store calculated depth in alpha channel
05117 -aclear : store 0.0 in alpha channel
05118 -awhite : store 1.0 in alpha channel
05119 -scale <float> : scale of depth sprite (default 1.0)
05120 -alpha_modulate : multiplies color by alpha during filtering
05121 -pre_modulate : multiplies color by alpha before processing
05122
05123 Examples
05124 nvdxt -cubeMap -list cubemapfile.lst -output cubemap.dds
05125 nvdxt -cubeMap -file cubemapfile.tga
05126 nvdxt -file test.tga -dxt1c
05127 nvdxt -file *.tga
05128 nvdxt -file c:\temp\*.tga
05129 nvdxt -file temp\*.tga
05130 nvdxt -file height_field_in_alpha.tga -n3x3 -alpha -scale 10 -wrap
05131 nvdxt -file grey_scale_height_field.tga -n5x5 -rgb -scale 1.3
05132 nvdxt -file normal_map.tga -norm
05133 nvdxt -file image.tga -dudv -fade -fadeamount 10
05134 nvdxt -all -dxt3 -gamma -outdir .\dds_dir -time
05135 nvdxt -file *.tga -depth -max -scale 0.5
05136
05137 '''
05138
05139 try:
05140 import io_export_rogremesh.rogremesh as Rmesh
05141 except:
05142 Rmesh = None
05143 print( 'WARNING: "io_export_rogremesh" is missing' )
05144
05145 if Rmesh and Rmesh.rpy.load():
05146 _USE_RPYTHON_ = True
05147 else:
05148 _USE_RPYTHON_ = False
05149 print( 'Rpython module is not cached, you must exit Blender to compile the module:' )
05150 print( 'cd io_export_rogremesh; python rogremesh.py' )
05151
05152 class VertexNoPos(object):
05153 def __init__(self, ogre_vidx, nx,ny,nz, r,g,b,ra, vert_uvs):
05154 self.ogre_vidx = ogre_vidx
05155 self.nx = nx
05156 self.ny = ny
05157 self.nz = nz
05158 self.r = r
05159 self.g = g
05160 self.b = b
05161 self.ra = ra
05162 self.vert_uvs = vert_uvs
05163
05164 '''does not compare ogre_vidx (and position at the moment) [ no need to compare position ]'''
05165 def __eq__(self, o):
05166 if self.nx != o.nx or self.ny != o.ny or self.nz != o.nz: return False
05167 elif self.r != o.r or self.g != o.g or self.b != o.b or self.ra != o.ra: return False
05168 elif len(self.vert_uvs) != len(o.vert_uvs): return False
05169 elif self.vert_uvs:
05170 for i, uv1 in enumerate( self.vert_uvs ):
05171 uv2 = o.vert_uvs[ i ]
05172 if uv1 != uv2: return False
05173 return True
05174
05175
05176
05177 def dot_mesh( ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True, isLOD=False):
05178 start = time.time()
05179
05180 logging = not isLOD
05181
05182 if not os.path.isdir( path ):
05183 print('>> Creating working directory', path )
05184 os.makedirs( path )
05185
05186 Report.meshes.append( ob.data.name )
05187 Report.faces += len( ob.data.tessfaces )
05188 Report.orig_vertices += len( ob.data.vertices )
05189
05190 cleanup = False
05191 if ob.modifiers:
05192 cleanup = True
05193 copy = ob.copy()
05194
05195 rem = []
05196 for mod in copy.modifiers:
05197 if mod.type in 'ARMATURE ARRAY'.split(): rem.append( mod )
05198 for mod in rem: copy.modifiers.remove( mod )
05199
05200 mesh = copy.to_mesh(bpy.context.scene, True, "PREVIEW")
05201 else:
05202 copy = ob
05203 mesh = ob.data
05204
05205 name = force_name or ob.data.name
05206 name = clean_object_name(name)
05207 xmlfile = os.path.join(path, '%s.mesh.xml' % name )
05208
05209 if logging:
05210 print(' - Generating:', '%s.mesh.xml' % name)
05211
05212 if _USE_RPYTHON_ and False:
05213 Rmesh.save( ob, xmlfile )
05214 else:
05215 f = None
05216 try:
05217 f = open( xmlfile, 'w' )
05218 except Exception as e:
05219 show_dialog("Invalid mesh object name: " + name)
05220 return
05221
05222 doc = SimpleSaxWriter(f, 'mesh', {})
05223
05224
05225 doc.start_tag('sharedgeometry', {'vertexcount' : '__TO_BE_REPLACED_VERTEX_COUNT__'})
05226
05227 if logging:
05228 print(' - Writing shared geometry')
05229
05230 doc.start_tag('vertexbuffer', {
05231 'positions':'true',
05232 'normals':'true',
05233 'colours_diffuse' : str(bool( mesh.vertex_colors )),
05234 'texture_coords' : '%s' % len(mesh.uv_textures) if mesh.uv_textures.active else '0'
05235 })
05236
05237
05238 vcolors = None
05239 vcolors_alpha = None
05240 if len( mesh.tessface_vertex_colors ):
05241 vcolors = mesh.tessface_vertex_colors[0]
05242 for bloc in mesh.tessface_vertex_colors:
05243 if bloc.name.lower().startswith('alpha'):
05244 vcolors_alpha = bloc; break
05245
05246
05247 materials = []
05248 for mat in ob.data.materials:
05249 if mat:
05250 materials.append( mat )
05251 else:
05252 print('[WARNING:] Bad material data in', ob)
05253 materials.append( '_missing_material_' )
05254 if not materials:
05255 materials.append( '_missing_material_' )
05256 _sm_faces_ = []
05257 for matidx, mat in enumerate( materials ):
05258 _sm_faces_.append([])
05259
05260
05261 dotextures = False
05262 uvcache = []
05263 if mesh.tessface_uv_textures.active:
05264 dotextures = True
05265 for layer in mesh.tessface_uv_textures:
05266 uvs = []; uvcache.append( uvs )
05267 for uvface in layer.data:
05268 uvs.append( (uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4) )
05269
05270 _sm_vertices_ = {}
05271 _remap_verts_ = []
05272 numverts = 0
05273
05274 for F in mesh.tessfaces:
05275 smooth = F.use_smooth
05276 faces = _sm_faces_[ F.material_index ]
05277
05278 tris = []
05279 tris.append( (F.vertices[0], F.vertices[1], F.vertices[2]) )
05280 if len(F.vertices) >= 4:
05281 tris.append( (F.vertices[0], F.vertices[2], F.vertices[3]) )
05282 if dotextures:
05283 a = []; b = []
05284 uvtris = [ a, b ]
05285 for layer in uvcache:
05286 uv1, uv2, uv3, uv4 = layer[ F.index ]
05287 a.append( (uv1, uv2, uv3) )
05288 b.append( (uv1, uv3, uv4) )
05289
05290 for tidx, tri in enumerate(tris):
05291 face = []
05292 for vidx, idx in enumerate(tri):
05293 v = mesh.vertices[ idx ]
05294
05295 if smooth:
05296 nx,ny,nz = swap( v.normal )
05297 else:
05298 nx,ny,nz = swap( F.normal )
05299
05300 r = 1.0
05301 g = 1.0
05302 b = 1.0
05303 ra = 1.0
05304 if vcolors:
05305 k = list(F.vertices).index(idx)
05306 r,g,b = getattr( vcolors.data[ F.index ], 'color%s'%(k+1) )
05307 if vcolors_alpha:
05308 ra,ga,ba = getattr( vcolors_alpha.data[ F.index ], 'color%s'%(k+1) )
05309 else:
05310 ra = 1.0
05311
05312
05313 vert_uvs = []
05314 if dotextures:
05315 for layer in uvtris[ tidx ]:
05316 vert_uvs.append(layer[ vidx ])
05317
05318 ''' Check if we already exported that vertex with same normal, do not export in that case,
05319 (flat shading in blender seems to work with face normals, so we copy each flat face'
05320 vertices, if this vertex with same normals was already exported,
05321 todo: maybe not best solution, check other ways (let blender do all the work, or only
05322 support smooth shading, what about seems, smoothing groups, materials, ...)
05323 '''
05324 vert = VertexNoPos(numverts, nx, ny, nz, r, g, b, ra, vert_uvs)
05325 alreadyExported = False
05326 if idx in _sm_vertices_:
05327 for vert2 in _sm_vertices_[idx]:
05328
05329 if vert == vert2:
05330 face.append(vert2.ogre_vidx)
05331 alreadyExported = True
05332
05333 break
05334 if not alreadyExported:
05335 face.append(vert.ogre_vidx)
05336 _sm_vertices_[idx].append(vert)
05337
05338 else:
05339 face.append(vert.ogre_vidx)
05340 _sm_vertices_[idx] = [vert]
05341
05342
05343 if alreadyExported:
05344 continue
05345
05346 numverts += 1
05347 _remap_verts_.append( v )
05348
05349 x,y,z = swap(v.co)
05350
05351 doc.start_tag('vertex', {})
05352 doc.leaf_tag('position', {
05353 'x' : '%6f' % x,
05354 'y' : '%6f' % y,
05355 'z' : '%6f' % z
05356 })
05357
05358 doc.leaf_tag('normal', {
05359 'x' : '%6f' % nx,
05360 'y' : '%6f' % ny,
05361 'z' : '%6f' % nz
05362 })
05363
05364 if vcolors:
05365 doc.leaf_tag('colour_diffuse', {'value' : '%6f %6f %6f %6f' % (r,g,b,ra)})
05366
05367
05368 if dotextures:
05369 for uv in vert_uvs:
05370 doc.leaf_tag('texcoord', {
05371 'u' : '%6f' % uv[0],
05372 'v' : '%6f' % (1.0-uv[1])
05373 })
05374
05375 doc.end_tag('vertex')
05376
05377 faces.append( (face[0], face[1], face[2]) )
05378
05379 Report.vertices += numverts
05380
05381 doc.end_tag('vertexbuffer')
05382 doc.end_tag('sharedgeometry')
05383
05384 if logging:
05385 print(' Done at', timer_diff_str(start), "seconds")
05386 print(' - Writing submeshes')
05387
05388 doc.start_tag('submeshes', {})
05389 for matidx, mat in enumerate( materials ):
05390 if not len(_sm_faces_[matidx]):
05391 if not isinstance(mat, str):
05392 mat_name = mat.name
05393 else:
05394 mat_name = mat
05395 Report.warnings.append( 'BAD SUBMESH "%s": material %r, has not been applied to any faces - not exporting as submesh.' % (ob.name, mat_name) )
05396 continue
05397
05398 submesh_attributes = {
05399 'usesharedvertices' : 'true',
05400
05401
05402 "use32bitindexes" : str(bool(numverts > 65535)),
05403 "operationtype" : "triangle_list"
05404 }
05405 if material_name(mat, False) != "_missing_material_":
05406 submesh_attributes['material'] = material_name(mat, False)
05407
05408 doc.start_tag('submesh', submesh_attributes)
05409 doc.start_tag('faces', {
05410 'count' : str(len(_sm_faces_[matidx]))
05411 })
05412 for fidx, (v1, v2, v3) in enumerate(_sm_faces_[matidx]):
05413 doc.leaf_tag('face', {
05414 'v1' : str(v1),
05415 'v2' : str(v2),
05416 'v3' : str(v3)
05417 })
05418 doc.end_tag('faces')
05419 doc.end_tag('submesh')
05420 Report.triangles += len(_sm_faces_[matidx])
05421
05422 del(_sm_faces_)
05423 del(_sm_vertices_)
05424 doc.end_tag('submeshes')
05425
05426
05427
05428
05429 doc.start_tag('submeshnames', {})
05430 for matidx, mat in enumerate( materials ):
05431 doc.leaf_tag('submesh', {
05432 'name' : material_name(mat, False),
05433 'index' : str(matidx)
05434 })
05435 doc.end_tag('submeshnames')
05436
05437 if logging:
05438 print(' Done at', timer_diff_str(start), "seconds")
05439
05440
05441 if isLOD == False and ob.type == 'MESH' and CONFIG['lodLevels'] > 0:
05442 lod_levels = CONFIG['lodLevels']
05443 lod_distance = CONFIG['lodDistance']
05444 lod_ratio = CONFIG['lodPercent'] / 100.0
05445 lod_pre_mesh_count = len(bpy.data.meshes)
05446
05447
05448 if lod_levels > 10:
05449 lod_levels = 10
05450
05451 def activate_object(obj):
05452 bpy.ops.object.select_all(action = 'DESELECT')
05453 bpy.context.scene.objects.active = obj
05454 obj.select = True
05455
05456 def duplicate_object(scene, name, copyobj):
05457
05458
05459 mesh = bpy.data.meshes.new(name)
05460
05461
05462 ob_new = bpy.data.objects.new(name, mesh)
05463
05464
05465 ob_new.data = copyobj.data.copy()
05466 ob_new.location = copyobj.location
05467 ob_new.rotation_euler = copyobj.rotation_euler
05468 ob_new.scale = copyobj.scale
05469
05470
05471 scene.objects.link(ob_new)
05472 ob_new.select = True
05473
05474 return ob_new, mesh
05475
05476 def delete_object(obj):
05477 activate_object(obj)
05478 bpy.ops.object.delete()
05479
05480
05481 def get_or_create_modifier(obj, modifier_name):
05482 if obj.type != 'MESH':
05483 return None
05484
05485 for mod_iter in obj.modifiers:
05486 if mod_iter.type == modifier_name:
05487 return mod_iter
05488
05489 activate_object(obj)
05490 bpy.ops.object.modifier_add(type=modifier_name)
05491 return get_or_create_modifier(obj, modifier_name)
05492
05493
05494 ob_copy, ob_copy_mesh = duplicate_object(bpy.context.scene, ob.name + "_LOD_TEMP_COPY", ob)
05495 ob_copy_meshes = [ ob_copy.data, ob_copy_mesh ]
05496
05497
05498 decimate = get_or_create_modifier(ob_copy, 'DECIMATE')
05499 if decimate is not None:
05500 decimate.decimate_type = 'COLLAPSE'
05501 decimate.show_viewport = True
05502 decimate.show_render = True
05503
05504 lod_generated = []
05505 lod_ratio_multiplier = 1.0 - lod_ratio
05506 lod_current_ratio = 1.0 * lod_ratio_multiplier
05507 lod_current_distance = lod_distance
05508 lod_current_vertice_count = len(mesh.vertices)
05509 lod_min_vertice_count = 12
05510
05511 for level in range(lod_levels+1)[1:]:
05512 decimate.ratio = lod_current_ratio
05513 lod_mesh = ob_copy.to_mesh(scene = bpy.context.scene, apply_modifiers = True, settings = 'PREVIEW')
05514 ob_copy_meshes.append(lod_mesh)
05515
05516
05517 lod_mesh_vertices = len(lod_mesh.vertices)
05518 if lod_mesh_vertices < lod_min_vertice_count:
05519 print(' - LOD', level, 'vertice count', lod_mesh_vertices, 'too small. Ignoring LOD.')
05520 break
05521 if lod_mesh_vertices >= lod_current_vertice_count:
05522 print(' - LOD', level-1, 'vertice count', lod_mesh_vertices, 'cannot be decimated any longer. Ignoring LOD.')
05523 break
05524
05525
05526 lod_generated.append({ 'level': level, 'distance': lod_current_distance, 'ratio': lod_current_ratio, 'mesh': lod_mesh })
05527 lod_current_distance += lod_distance
05528 lod_current_vertice_count = lod_mesh_vertices
05529 lod_current_ratio *= lod_ratio_multiplier
05530
05531
05532 if len(lod_generated) > 0:
05533
05534
05535
05536
05537
05538 doc.start_tag('levelofdetail', {
05539 'strategy' : 'default',
05540 'numlevels' : str(len(lod_generated) + 1),
05541 'manual' : "true"
05542 })
05543
05544 print(' - Generating', len(lod_generated), 'LOD meshes. Original: vertices', len(mesh.vertices), "faces", len(mesh.tessfaces))
05545 for lod in lod_generated:
05546 ratio_percent = round(lod['ratio'] * 100.0, 0)
05547 print(' > Writing LOD', lod['level'], 'for distance', lod['distance'], 'and ratio', str(ratio_percent) + "%", 'with', len(lod['mesh'].vertices), 'vertices', len(lod['mesh'].tessfaces), 'faces')
05548 lod_ob_temp = bpy.data.objects.new(name, lod['mesh'])
05549 lod_ob_temp.data.name = name + '_LOD_' + str(lod['level'])
05550 dot_mesh(lod_ob_temp, path, lod_ob_temp.data.name, ignore_shape_animation, normals, isLOD=True)
05551
05552
05553 doc.leaf_tag('lodmanual', {
05554 'value' : str(lod['distance']),
05555 'meshname' : lod_ob_temp.data.name + ".mesh"
05556 })
05557
05558
05559
05560 lod_ob_temp.user_clear()
05561 delete_object(lod_ob_temp)
05562 del lod_ob_temp
05563
05564 doc.end_tag('levelofdetail')
05565
05566
05567 delete_object(ob_copy)
05568 del ob_copy
05569
05570
05571 for mesh_iter in ob_copy_meshes:
05572 mesh_iter.user_clear()
05573 bpy.data.meshes.remove(mesh_iter)
05574 del mesh_iter
05575 ob_copy_meshes = []
05576
05577 if lod_pre_mesh_count != len(bpy.data.meshes):
05578 print(' - WARNING: After LOD generation, cleanup failed to erase all temporary data!')
05579
05580 arm = ob.find_armature()
05581 if arm:
05582 doc.leaf_tag('skeletonlink', {
05583 'name' : '%s.skeleton' % name
05584 })
05585 doc.start_tag('boneassignments', {})
05586 boneOutputEnableFromName = {}
05587 boneIndexFromName = {}
05588 for bone in arm.pose.bones:
05589 boneOutputEnableFromName[ bone.name ] = True
05590 if CONFIG['ONLY_DEFORMABLE_BONES']:
05591
05592 if bone.bone.use_deform:
05593
05594 parBone = bone.parent
05595 while parBone:
05596 boneOutputEnableFromName[ parBone.name ] = True
05597 parBone = parBone.parent
05598 else:
05599
05600 boneOutputEnableFromName[ bone.name ] = False
05601 boneIndex = 0
05602 for bone in arm.pose.bones:
05603 boneIndexFromName[ bone.name ] = boneIndex
05604 if boneOutputEnableFromName[ bone.name ]:
05605 boneIndex += 1
05606 badverts = 0
05607 for vidx, v in enumerate(_remap_verts_):
05608 check = 0
05609 for vgroup in v.groups:
05610 if vgroup.weight > CONFIG['TRIM_BONE_WEIGHTS']:
05611 groupIndex = vgroup.group
05612 if groupIndex < len(copy.vertex_groups):
05613 vg = copy.vertex_groups[ groupIndex ]
05614 if vg.name in boneIndexFromName:
05615 bnidx = boneIndexFromName[ vg.name ]
05616 doc.leaf_tag('vertexboneassignment', {
05617 'vertexindex' : str(vidx),
05618 'boneindex' : str(bnidx),
05619 'weight' : '%6f' % vgroup.weight
05620 })
05621 check += 1
05622 else:
05623 print('WARNING: object vertex groups not in sync with armature', copy, arm, groupIndex)
05624 if check > 4:
05625 badverts += 1
05626 print('WARNING: vertex %s is in more than 4 vertex groups (bone weights)\n(this maybe Ogre incompatible)' %vidx)
05627 if badverts:
05628 Report.warnings.append( '%s has %s vertices weighted to too many bones (Ogre limits a vertex to 4 bones)\n[try increaseing the Trim-Weights threshold option]' %(mesh.name, badverts) )
05629 doc.end_tag('boneassignments')
05630
05631
05632 if CONFIG['SHAPE_ANIM'] and ob.data.shape_keys and len(ob.data.shape_keys.key_blocks):
05633 print(' - Writing shape keys')
05634
05635 doc.start_tag('poses', {})
05636 for sidx, skey in enumerate(ob.data.shape_keys.key_blocks):
05637 if sidx == 0: continue
05638 if len(skey.data) != len( mesh.vertices ):
05639 failure = 'FAILED to save shape animation - you can not use a modifier that changes the vertex count! '
05640 failure += '[ mesh : %s ]' %mesh.name
05641 Report.warnings.append( failure )
05642 print( failure )
05643 break
05644
05645 doc.start_tag('pose', {
05646 'name' : skey.name,
05647
05648
05649
05650 'target' : 'mesh'
05651 })
05652
05653 for vidx, v in enumerate(_remap_verts_):
05654 pv = skey.data[ v.index ]
05655 x,y,z = swap( pv.co - v.co )
05656
05657
05658
05659
05660 doc.leaf_tag('poseoffset', {
05661 'x' : '%6f' % x,
05662 'y' : '%6f' % y,
05663 'z' : '%6f' % z,
05664 'index' : str(vidx)
05665 })
05666 doc.end_tag('pose')
05667 doc.end_tag('poses')
05668
05669
05670 if logging:
05671 print(' Done at', timer_diff_str(start), "seconds")
05672
05673 if ob.data.shape_keys.animation_data and len(ob.data.shape_keys.animation_data.nla_tracks):
05674 print(' - Writing shape animations')
05675 doc.start_tag('animations', {})
05676 _fps = float( bpy.context.scene.render.fps )
05677 for nla in ob.data.shape_keys.animation_data.nla_tracks:
05678 for idx, strip in enumerate(nla.strips):
05679 doc.start_tag('animation', {
05680 'name' : strip.name,
05681 'length' : str((strip.frame_end-strip.frame_start)/_fps)
05682 })
05683 doc.start_tag('tracks', {})
05684 doc.start_tag('track', {
05685 'type' : 'pose',
05686 'target' : 'mesh'
05687
05688
05689
05690 })
05691 doc.start_tag('keyframes', {})
05692 for frame in range( int(strip.frame_start), int(strip.frame_end)+1, bpy.context.scene.frame_step):
05693 bpy.context.scene.frame_set(frame)
05694 doc.start_tag('keyframe', {
05695 'time' : str((frame-strip.frame_start)/_fps)
05696 })
05697 for sidx, skey in enumerate( ob.data.shape_keys.key_blocks ):
05698 if sidx == 0: continue
05699 doc.leaf_tag('poseref', {
05700 'poseindex' : str(sidx-1),
05701 'influence' : str(skey.value)
05702 })
05703 doc.end_tag('keyframe')
05704 doc.end_tag('keyframes')
05705 doc.end_tag('track')
05706 doc.end_tag('tracks')
05707 doc.end_tag('animation')
05708 doc.end_tag('animations')
05709 print(' Done at', timer_diff_str(start), "seconds")
05710
05711
05712
05713 if cleanup:
05714
05715 copy.user_clear()
05716 bpy.data.objects.remove(copy)
05717 mesh.user_clear()
05718 bpy.data.meshes.remove(mesh)
05719 del copy
05720 del mesh
05721 del _remap_verts_
05722 del uvcache
05723 doc.close()
05724 f.close()
05725
05726 if logging:
05727 print(' - Created .mesh.xml at', timer_diff_str(start), "seconds")
05728
05729
05730 def replaceInplace(f,searchExp,replaceExp):
05731 import fileinput
05732 for line in fileinput.input(f, inplace=1):
05733 if searchExp in line:
05734 line = line.replace(searchExp,replaceExp)
05735 sys.stdout.write(line)
05736 fileinput.close()
05737
05738 replaceInplace(xmlfile, '__TO_BE_REPLACED_VERTEX_COUNT__' + '"', str(numverts) + '"' )
05739 del(replaceInplace)
05740
05741
05742 OgreXMLConverter(xmlfile, has_uvs=dotextures)
05743
05744 if arm and CONFIG['ARM_ANIM']:
05745 skel = Skeleton( ob )
05746 data = skel.to_xml()
05747 name = force_name or ob.data.name
05748 name = clean_object_name(name)
05749 xmlfile = os.path.join(path, '%s.skeleton.xml' % name)
05750 f = open( xmlfile, 'wb' )
05751 f.write( bytes(data,'utf-8') )
05752 f.close()
05753 OgreXMLConverter( xmlfile )
05754
05755 mats = []
05756 for mat in materials:
05757 if mat != '_missing_material_':
05758 mats.append(mat)
05759
05760 if logging:
05761 print(' - Created .mesh in total time', timer_diff_str(start), 'seconds')
05762 return mats
05763
05764
05765
05766
05767 class JmonkeyPreviewOp( _OgreCommonExport_, bpy.types.Operator ):
05768 '''helper to open jMonkey (JME)'''
05769 bl_idname = 'jmonkey.preview'
05770 bl_label = "opens JMonkeyEngine in a non-blocking subprocess"
05771 bl_options = {'REGISTER'}
05772
05773 filepath= StringProperty(name="File Path", description="Filepath used for exporting Jmonkey .scene file", maxlen=1024, default="/tmp/preview.txml", subtype='FILE_PATH')
05774 EXPORT_TYPE = 'OGRE'
05775
05776 @classmethod
05777 def poll(cls, context):
05778 if context.active_object: return True
05779
05780 def invoke(self, context, event):
05781 global TundraSingleton
05782 path = '/tmp/preview.scene'
05783 self.ogre_export( path, context )
05784 JmonkeyPipe( path )
05785 return {'FINISHED'}
05786
05787 def JmonkeyPipe( path ):
05788 root = CONFIG[ 'JMONKEY_ROOT']
05789 if sys.platform.startswith('win'):
05790 cmd = [ os.path.join( os.path.join( root, 'bin' ), 'jmonkeyplatform.exe' ) ]
05791 else:
05792 cmd = [ os.path.join( os.path.join( root, 'bin' ), 'jmonkeyplatform' ) ]
05793 cmd.append( '--nosplash' )
05794 cmd.append( '--open' )
05795 cmd.append( path )
05796 proc = subprocess.Popen(cmd)
05797 return proc
05798
05799
05800
05801
05802
05803
05804 class TundraPreviewOp( _OgreCommonExport_, bpy.types.Operator ):
05805 '''helper to open Tundra2 (realXtend)'''
05806 bl_idname = 'tundra.preview'
05807 bl_label = "opens Tundra2 in a non-blocking subprocess"
05808 bl_options = {'REGISTER'}
05809 EXPORT_TYPE = 'REX'
05810
05811 filepath= StringProperty(
05812 name="File Path",
05813 description="Filepath used for exporting Tundra .txml file",
05814 maxlen=1024,
05815 default="/tmp/preview.txml",
05816 subtype='FILE_PATH')
05817 EX_FORCE_CAMERA = BoolProperty(
05818 name="Force Camera",
05819 description="export active camera",
05820 default=False)
05821 EX_FORCE_LAMPS = BoolProperty(
05822 name="Force Lamps",
05823 description="export all lamps",
05824 default=False)
05825
05826 @classmethod
05827 def poll(cls, context):
05828 if context.active_object and context.mode != 'EDIT_MESH':
05829 return True
05830
05831 def invoke(self, context, event):
05832 global TundraSingleton
05833 syncmats = []
05834 obs = []
05835 if TundraSingleton:
05836 actob = context.active_object
05837 obs = TundraSingleton.deselect_previously_updated(context)
05838 for ob in obs:
05839 if ob.type=='MESH':
05840 syncmats.append( ob )
05841 if ob.name == actob.name:
05842 export_mesh( ob, path='/tmp/rex' )
05843
05844 if not os.path.isdir( '/tmp/rex' ): os.makedirs( '/tmp/rex' )
05845 path = '/tmp/rex/preview.txml'
05846 self.ogre_export( path, context, force_material_update=syncmats )
05847 if not TundraSingleton:
05848 TundraSingleton = TundraPipe( context )
05849 elif self.EX_SCENE:
05850 TundraSingleton.load( context, path )
05851
05852 for ob in obs:
05853 ob.select = True
05854 return {'FINISHED'}
05855
05856 TundraSingleton = None
05857
05858 class Tundra_StartPhysicsOp(bpy.types.Operator):
05859 '''TundraSingleton helper'''
05860 bl_idname = 'tundra.start_physics'
05861 bl_label = "start physics"
05862 bl_options = {'REGISTER'}
05863
05864 @classmethod
05865 def poll(cls, context):
05866 if TundraSingleton: return True
05867 def invoke(self, context, event):
05868 TundraSingleton.start()
05869 return {'FINISHED'}
05870
05871 class Tundra_StopPhysicsOp(bpy.types.Operator):
05872 '''TundraSingleton helper'''
05873 bl_idname = 'tundra.stop_physics'
05874 bl_label = "stop physics"
05875 bl_options = {'REGISTER'}
05876
05877 @classmethod
05878 def poll(cls, context):
05879 if TundraSingleton: return True
05880 def invoke(self, context, event):
05881 TundraSingleton.stop()
05882 return {'FINISHED'}
05883
05884 class Tundra_PhysicsDebugOp(bpy.types.Operator):
05885 '''TundraSingleton helper'''
05886 bl_idname = 'tundra.toggle_physics_debug'
05887 bl_label = "stop physics"
05888 bl_options = {'REGISTER'}
05889
05890 @classmethod
05891 def poll(cls, context):
05892 if TundraSingleton: return True
05893 def invoke(self, context, event):
05894 TundraSingleton.toggle_physics_debug()
05895 return {'FINISHED'}
05896
05897 class Tundra_ExitOp(bpy.types.Operator):
05898 '''TundraSingleton helper'''
05899 bl_idname = 'tundra.exit'
05900 bl_label = "quit tundra"
05901 bl_options = {'REGISTER'}
05902
05903 @classmethod
05904 def poll(cls, context):
05905 if TundraSingleton: return True
05906 def invoke(self, context, event):
05907 TundraSingleton.exit()
05908 return {'FINISHED'}
05909
05910
05911
05912
05913 class Server(object):
05914 def stream( self, o ):
05915 b = pickle.dumps( o, protocol=2 )
05916
05917 n = len( b ); d = STREAM_BUFFER_SIZE - n -4
05918 if n > STREAM_BUFFER_SIZE:
05919 print( 'ERROR: STREAM OVERFLOW:', n )
05920 return
05921 padding = b'#' * d
05922 if n < 10: header = '000%s' %n
05923 elif n < 100: header = '00%s' %n
05924 elif n < 1000: header = '0%s' %n
05925 else: header = '%s' %n
05926 header = bytes( header, 'utf-8' )
05927 assert len(header) == 4
05928 w = header + b + padding
05929 assert len(w) == STREAM_BUFFER_SIZE
05930 self.buffer.insert(0, w )
05931 return w
05932
05933 def multires_lod( self ):
05934 '''
05935 Ogre builtin LOD sucks for character animation
05936 '''
05937 ob = bpy.context.active_object
05938 cam = bpy.context.scene.camera
05939
05940 if ob and cam and ob.type=='MESH' and ob.use_multires_lod:
05941 delta = bpy.context.active_object.matrix_world.to_translation() - cam.matrix_world.to_translation()
05942 dist = delta.length
05943
05944 if ob.modifiers and ob.modifiers[0].type == 'MULTIRES' and ob.modifiers[0].total_levels > 1:
05945 mod = ob.modifiers[0]
05946 step = ob.multires_lod_range / mod.total_levels
05947 level = mod.total_levels - int( dist / step )
05948 if mod.levels != level: mod.levels = level
05949 return level
05950
05951 def sync( self ):
05952 LOD = self.multires_lod()
05953
05954 p = STREAM_PROTO
05955 i = 0; msg = []
05956 for ob in bpy.context.selected_objects:
05957 if ob.type not in ('MESH','LAMP','SPEAKER'): continue
05958 loc, rot, scale = ob.matrix_world.decompose()
05959 loc = swap(loc).to_tuple()
05960 x,y,z = swap( rot.to_euler() )
05961 rot = (x,y,z)
05962 x,y,z = swap( scale )
05963 scale = ( abs(x), abs(y), abs(z) )
05964 d = { p['ID']:uid(ob), p['POSITION']:loc, p['ROTATION']:rot, p['SCALE']:scale, p['TYPE']:p[ob.type] }
05965 msg.append( d )
05966
05967 if ob.name == bpy.context.active_object.name and LOD is not None:
05968 d[ p['LOD'] ] = LOD
05969
05970 if ob.type == 'MESH':
05971 arm = ob.find_armature()
05972 if arm and arm.animation_data and arm.animation_data.nla_tracks:
05973 anim = None
05974 d[ p['ANIMATIONS'] ] = state = {}
05975 for nla in arm.animation_data.nla_tracks:
05976 for strip in nla.strips:
05977 if strip.active: state[ strip.name ] = strip.influence
05978 else: pass
05979 elif ob.type == 'LAMP':
05980 d[ p['ENERGY'] ] = ob.data.energy
05981 d[ p['DISTANCE'] ] = ob.data.distance
05982 elif ob.type == 'SPEAKER':
05983 d[ p['VOLUME'] ] = ob.data.volume
05984 d[ p['MUTE'] ] = ob.data.muted
05985
05986 if i >= 10: break
05987 return msg
05988
05989 def __init__(self):
05990 import socket
05991 self.buffer = []
05992 self.socket = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
05993 host='localhost'; port = 9420
05994 sock.connect((host, port))
05995 print('SERVER: socket connected', sock)
05996 self._handle = None
05997 self.setup_callback( bpy.context )
05998 import threading
05999 self.ready = threading._allocate_lock()
06000 self.ID = threading._start_new_thread(
06001 self.loop, (None,)
06002 )
06003 print( 'SERVER: thread started')
06004
06005 def loop(self, none):
06006 self.active = True
06007 prev = time.time()
06008 while self.active:
06009 if not self.ready.locked(): time.sleep(0.001)
06010 else:
06011 now = time.time()
06012 if now - prev > 0.066:
06013 actob = None
06014 try: actob = bpy.context.active_object
06015 except: pass
06016 if not actob: continue
06017 prev = now
06018 sel = bpy.context.active_object
06019 msg = self.sync()
06020 self.ready.release()
06021 self.stream( msg )
06022 if self.buffer:
06023 bin = self.buffer.pop()
06024 try:
06025 self.socket.sendall( bin )
06026 except:
06027 print('SERVER: send data error')
06028 time.sleep(0.5)
06029 pass
06030 else: print( 'SERVER: empty buffer' )
06031 else:
06032 self.ready.release()
06033 print('SERVER: thread exit')
06034
06035 def threadsafe( self, reg ):
06036 if not TundraSingleton: return
06037 if not self.ready.locked():
06038 self.ready.acquire()
06039 time.sleep(0.0001)
06040 while self.ready.locked():
06041 time.sleep(0.0001)
06042 else: pass
06043
06044 _handle = None
06045 def setup_callback( self, context ):
06046 print('SERVER: setup frame update callback')
06047 if self._handle: return self._handle
06048 for area in bpy.context.window.screen.areas:
06049 if area.type == 'VIEW_3D':
06050 for reg in area.regions:
06051 if reg.type == 'WINDOW':
06052
06053 self._handle = reg.callback_add(self.threadsafe, (reg,), 'PRE_VIEW' )
06054 self._area = area
06055 self._region = reg
06056 break
06057 if not self._handle:
06058 print('SERVER: FAILED to setup frame update callback')
06059
06060 def _create_stream_proto():
06061 proto = {}
06062 tags = 'ID NAME POSITION ROTATION SCALE DATA SELECTED TYPE MESH LAMP CAMERA SPEAKER ANIMATIONS DISTANCE ENERGY VOLUME MUTE LOD'.split()
06063 for i,tag in enumerate( tags ):
06064 proto[ tag ] = chr(i)
06065 return proto
06066
06067 STREAM_PROTO = _create_stream_proto()
06068 STREAM_BUFFER_SIZE = 2048
06069
06070 TUNDRA_SCRIPT = '''
06071 # this file was generated by blender2ogre #
06072 import tundra, socket, select, pickle
06073 STREAM_BUFFER_SIZE = 2048
06074 globals().update( %s )
06075 E = {} # this is just for debugging from the pyconsole
06076
06077 def get_entity(ID):
06078 scn = tundra.Scene().MainCameraScene()
06079 return scn.GetEntityRaw( ID )
06080
06081 class Client(object):
06082 def __init__(self):
06083 self.socket = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
06084 host='localhost'; port = 9420
06085 sock.bind((host, port))
06086 self._animated = {} # entity ID : { anim-name : weight }
06087
06088 def update(self, delay):
06089 global E
06090 sock = self.socket
06091 poll = select.select( [ sock ], [], [], 0.01 )
06092 if not poll[0]: return True
06093 data = sock.recv( STREAM_BUFFER_SIZE )
06094 assert len(data) == STREAM_BUFFER_SIZE
06095 if not data:
06096 print( 'blender crashed?' )
06097 return
06098 header = data[ : 4]
06099 s = data[ 4 : int(header)+4 ]
06100 objects = pickle.loads( s )
06101 scn = tundra.Scene().MainCameraScene() # replaces GetDefaultScene()
06102 for ob in objects:
06103 e = scn.GetEntityRaw( ob[ID] )
06104 if not e: continue
06105 x,y,z = ob[POSITION]
06106 e.placeable.SetPosition( x,y,z )
06107 x,y,z = ob[SCALE]
06108 e.placeable.SetScale( x,y,z )
06109 #e.placeable.SetOrientation( ob[ROTATION] )
06110
06111 if ob[TYPE] == LAMP:
06112 e.light.range = ob[ DISTANCE ]
06113 e.light.brightness = ob[ ENERGY ]
06114 #e.light.diffColor = !! not wrapped !!
06115 #e.light.specColor = !! not wrapped !!
06116 elif ob[TYPE] == SPEAKER:
06117 e.sound.soundGain = ob[VOLUME]
06118 #e.sound.soundInnerRadius =
06119 #e.sound.soundOuterRadius =
06120 if ob[MUTE]: e.sound.StopSound()
06121 else: e.sound.PlaySound() # tundra API needs sound.IsPlaying()
06122
06123 if ANIMATIONS in ob:
06124 self.update_animation( e, ob )
06125
06126 if LOD in ob:
06127 #print( 'LOD', ob[LOD] )
06128 index = e.id + ob[LOD] + 1
06129 for i in range(1,9):
06130 elod = get_entity( e.id + i )
06131 if elod:
06132 if elod.id == index and not elod.placeable.visible:
06133 elod.placeable.visible = True
06134 elif elod.id != index and elod.placeable.visible:
06135 elod.placeable.visible = False
06136
06137 if ob[ID] not in E: E[ ob[ID] ] = e
06138
06139 def update_animation( self, e, ob ):
06140 if ob[ID] not in self._animated:
06141 self._animated[ ob[ID] ] = {}
06142 state = self._animated[ ob[ID] ]
06143 ac = e.animationcontroller
06144 for aname in ob[ ANIMATIONS ]:
06145 if aname not in state: # save weight of new animation
06146 state[ aname ] = ob[ANIMATIONS][aname] # weight
06147 for aname in state:
06148 if aname not in ob[ANIMATIONS] and ac.IsAnimationActive( aname ):
06149 ac.StopAnim( aname, '0.0' )
06150 elif aname in ob[ANIMATIONS]:
06151 weight = ob[ANIMATIONS][aname]
06152 if ac.HasAnimationFinished( aname ):
06153 ac.PlayLoopedAnim( aname, '1.0', 'false' ) # PlayAnim(...) TODO single playback
06154 ok = ac.SetAnimationWeight( aname, weight )
06155 state[ aname ] = weight
06156
06157 if weight != state[ aname ]:
06158 ok = ac.SetAnimationWeight( aname, weight )
06159 state[ aname ] = weight
06160
06161 client = Client()
06162 tundra.Frame().connect( 'Updated(float)', client.update )
06163 print('blender2ogre plugin ok')
06164 ''' %STREAM_PROTO
06165
06166 class TundraPipe(object):
06167 CONFIG_PATH = '/tmp/rex/plugins.xml'
06168 TUNDRA_SCRIPT_PATH = '/tmp/rex/b2ogre_plugin.py'
06169 CONFIG_XML = '''<?xml version="1.0"?>
06170 <Tundra>
06171 <!-- plugins.xml is hardcoded to be the default configuration file to load if another file is not specified on the command line with the "config filename.xml" parameter. -->
06172 <plugin path="OgreRenderingModule" />
06173 <plugin path="EnvironmentModule" /> <!-- EnvironmentModule depends on OgreRenderingModule -->
06174 <plugin path="PhysicsModule" /> <!-- PhysicsModule depends on OgreRenderingModule and EnvironmentModule -->
06175 <plugin path="TundraProtocolModule" /> <!-- TundraProtocolModule depends on OgreRenderingModule -->
06176 <plugin path="JavascriptModule" /> <!-- JavascriptModule depends on TundraProtocolModule -->
06177 <plugin path="AssetModule" /> <!-- AssetModule depends on TundraProtocolModule -->
06178 <plugin path="AvatarModule" /> <!-- AvatarModule depends on AssetModule and OgreRenderingModule -->
06179 <plugin path="ECEditorModule" /> <!-- ECEditorModule depends on OgreRenderingModule, TundraProtocolModule, OgreRenderingModule and AssetModule -->
06180 <plugin path="SkyXHydrax" /> <!-- SkyXHydrax depends on OgreRenderingModule -->
06181 <plugin path="OgreAssetEditorModule" /> <!-- OgreAssetEditorModule depends on OgreRenderingModule -->
06182 <plugin path="DebugStatsModule" /> <!-- DebugStatsModule depends on OgreRenderingModule, EnvironmentModule and AssetModule -->
06183 <plugin path="SceneWidgetComponents" /> <!-- SceneWidgetComponents depends on OgreRenderingModule and TundraProtocolModule -->
06184 <plugin path="PythonScriptModule" />
06185
06186 <!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
06187 <!-- NOTE: The startup .js scripts are specified only by base name of the file. Don's specify a path here. Place the startup .js scripts to /bin/jsmodules/startup/. -->
06188 <!-- Important: The file names specified here are case sensitive even on Windows! -->
06189 <jsplugin path="cameraapplication.js" />
06190 <jsplugin path="FirstPersonMouseLook.js" />
06191 <jsplugin path="MenuBar.js" />
06192
06193 <!-- Python plugins -->
06194 <!-- <pyplugin path="lib/apitests.py" /> --> <!-- Runs framework api tests -->
06195 <pyplugin path="%s" /> <!-- shows qt py console. could enable by default when add to menu etc. for controls, now just shows directly when is enabled here -->
06196
06197 <option name="--accept_unknown_http_sources" />
06198 <option name="--accept_unknown_local_sources" />
06199 <option name="--fpslimit" value="60" />
06200 <!-- AssetAPI's file system watcher currently disabled because LocalAssetProvider implements
06201 the same functionality for LocalAssetStorages and HTTPAssetProviders do not yet support live-update. -->
06202 <option name="--nofilewatcher" />
06203
06204 </Tundra>''' %TUNDRA_SCRIPT_PATH
06205
06206 def __init__(self, context, debug=False):
06207 self._physics_debug = True
06208 self._objects = []
06209 self.proc = None
06210 exe = None
06211 if 'Tundra.exe' in os.listdir( CONFIG['TUNDRA_ROOT'] ):
06212 exe = os.path.join( CONFIG['TUNDRA_ROOT'], 'Tundra.exe' )
06213 elif 'Tundra' in os.listdir( CONFIG['TUNDRA_ROOT'] ):
06214 exe = os.path.join( CONFIG['TUNDRA_ROOT'], 'Tundra' )
06215
06216 cmd = []
06217 if not exe:
06218 print('ERROR: failed to find Tundra executable')
06219 assert 0
06220 elif sys.platform.startswith('win'):
06221 cmd.append(exe)
06222 else:
06223 if exe.endswith('.exe'): cmd.append('wine')
06224 cmd.append( exe )
06225 if debug:
06226 cmd.append('--loglevel')
06227 cmd.append('debug')
06228
06229 if CONFIG['TUNDRA_STREAMING']:
06230 cmd.append( '--config' )
06231 cmd.append( self.CONFIG_PATH )
06232 with open( self.CONFIG_PATH, 'wb' ) as fp: fp.write( bytes(self.CONFIG_XML,'utf-8') )
06233 with open( self.TUNDRA_SCRIPT_PATH, 'wb' ) as fp: fp.write( bytes(TUNDRA_SCRIPT,'utf-8') )
06234 self.server = Server()
06235
06236
06237 cmd.append( '--storage' )
06238 if sys.platform.startswith('win'): cmd.append( 'C:\\tmp\\rex' )
06239 else: cmd.append( '/tmp/rex' )
06240 self.proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, cwd=CONFIG['TUNDRA_ROOT'])
06241
06242 self.physics = True
06243 if self.proc:
06244 time.sleep(0.1)
06245 self.load( context, '/tmp/rex/preview.txml' )
06246 self.stop()
06247
06248 def deselect_previously_updated(self, context):
06249 r = []
06250 for ob in context.selected_objects:
06251 if ob.name in self._objects: ob.select = False; r.append( ob )
06252 return r
06253
06254 def load( self, context, url, clear=False ):
06255 self._objects += [ob.name for ob in context.selected_objects]
06256 if clear:
06257 self.proc.stdin.write( b'loadscene(/tmp/rex/preview.txml,true,true)\n')
06258 else:
06259 self.proc.stdin.write( b'loadscene(/tmp/rex/preview.txml,false,true)\n')
06260 try:
06261 self.proc.stdin.flush()
06262 except:
06263 global TundraSingleton
06264 TundraSingleton = None
06265
06266 def start( self ):
06267 self.physics = True
06268 self.proc.stdin.write( b'startphysics\n' )
06269 try: self.proc.stdin.flush()
06270 except:
06271 global TundraSingleton
06272 TundraSingleton = None
06273
06274 def stop( self ):
06275 self.physics = False
06276 self.proc.stdin.write( b'stopphysics\n' )
06277 try: self.proc.stdin.flush()
06278 except:
06279 global TundraSingleton
06280 TundraSingleton = None
06281
06282 def toggle_physics_debug( self ):
06283 self._physics_debug = not self._physics_debug
06284 self.proc.stdin.write( b'physicsdebug\n' )
06285 try: self.proc.stdin.flush()
06286 except:
06287 global TundraSingleton
06288 TundraSingleton = None
06289
06290 def exit(self):
06291 self.proc.stdin.write( b'exit\n' )
06292 self.proc.stdin.flush()
06293 global TundraSingleton
06294 TundraSingleton = None
06295
06296
06297
06298
06299 class MENU_preview_material_text(bpy.types.Menu):
06300 bl_label = 'preview'
06301
06302 @classmethod
06303 def poll(self,context):
06304 if context.active_object and context.active_object.active_material:
06305 return True
06306
06307 def draw(self, context):
06308 layout = self.layout
06309 mat = context.active_object.active_material
06310 if mat:
06311
06312 preview = generate_material( mat )
06313 for line in preview.splitlines():
06314 if line.strip():
06315 for ww in wordwrap( line ):
06316 layout.label(text=ww)
06317
06318 @UI
06319 class INFO_HT_myheader(bpy.types.Header):
06320 bl_space_type = 'INFO'
06321 def draw(self, context):
06322 layout = self.layout
06323 wm = context.window_manager
06324 window = context.window
06325 scene = context.scene
06326 rd = scene.render
06327 ob = context.active_object
06328 screen = context.screen
06329
06330
06331
06332 if _USE_JMONKEY_:
06333 row = layout.row(align=True)
06334 op = row.operator( 'jmonkey.preview', text='', icon='MONKEY' )
06335
06336 if _USE_TUNDRA_:
06337 row = layout.row(align=True)
06338 op = row.operator( 'tundra.preview', text='', icon='WORLD' )
06339 if TundraSingleton:
06340 op = row.operator( 'tundra.preview', text='', icon='META_CUBE' )
06341 op.EX_SCENE = False
06342 if not TundraSingleton.physics:
06343 op = row.operator( 'tundra.start_physics', text='', icon='PLAY' )
06344 else:
06345 op = row.operator( 'tundra.stop_physics', text='', icon='PAUSE' )
06346 op = row.operator( 'tundra.toggle_physics_debug', text='', icon='MOD_PHYSICS' )
06347 op = row.operator( 'tundra.exit', text='', icon='CANCEL' )
06348
06349 op = layout.operator( 'ogremeshy.preview', text='', icon='PLUGIN' ); op.mesh = True
06350
06351 row = layout.row(align=True)
06352 sub = row.row(align=True)
06353 sub.menu("INFO_MT_file")
06354 sub.menu("INFO_MT_add")
06355 if rd.use_game_engine: sub.menu("INFO_MT_game")
06356 else: sub.menu("INFO_MT_render")
06357
06358 row = layout.row(align=False); row.scale_x = 1.25
06359 row.menu("INFO_MT_instances", icon='NODETREE', text='')
06360 row.menu("INFO_MT_groups", icon='GROUP', text='')
06361
06362 layout.template_header()
06363 if not context.area.show_menus:
06364 if window.screen.show_fullscreen: layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")
06365 else: layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
06366 layout.template_ID(context.screen, "scene", new="scene.new", unlink="scene.delete")
06367
06368 layout.separator()
06369 layout.template_running_jobs()
06370 layout.template_reports_banner()
06371 layout.separator()
06372 if rd.has_multiple_engines: layout.prop(rd, "engine", text="")
06373
06374 layout.label(text=scene.statistics())
06375 layout.menu( "INFO_MT_help" )
06376 else:
06377 layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
06378
06379 if ob:
06380 row = layout.row(align=True)
06381 row.prop( ob, 'name', text='' )
06382 row.prop( ob, 'draw_type', text='' )
06383 row.prop( ob, 'show_x_ray', text='' )
06384 row = layout.row()
06385 row.scale_y = 0.75; row.scale_x = 0.9
06386 row.prop( ob, 'layers', text='' )
06387
06388 layout.separator()
06389 row = layout.row(align=True); row.scale_x = 1.1
06390 row.prop(scene.game_settings, 'material_mode', text='')
06391 row.prop(scene, 'camera', text='')
06392
06393 layout.menu( 'MENU_preview_material_text', icon='TEXT', text='' )
06394
06395 layout.menu( "INFO_MT_ogre_docs" )
06396 layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER', text="")
06397 if OgreToggleInterfaceOp.TOGGLE: layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_DEHLT')
06398 else: layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_HLT')
06399
06400 def export_menu_func_ogre(self, context):
06401 op = self.layout.operator(INFO_OT_createOgreExport.bl_idname, text="Ogre3D (.scene and .mesh)")
06402
06403 def export_menu_func_realxtend(self, context):
06404 op = self.layout.operator(INFO_OT_createRealxtendExport.bl_idname, text="realXtend Tundra (.txml and .mesh)")
06405
06406 try:
06407 _header_ = bpy.types.INFO_HT_header
06408 except:
06409 print('---blender2ogre addon enable---')
06410
06411
06412
06413 class OgreToggleInterfaceOp(bpy.types.Operator):
06414 '''Toggle Ogre UI'''
06415 bl_idname = "ogre.toggle_interface"
06416 bl_label = "Ogre UI"
06417 bl_options = {'REGISTER'}
06418 TOGGLE = True
06419 BLENDER_DEFAULT_HEADER = _header_
06420
06421 @classmethod
06422 def poll(cls, context):
06423 return True
06424
06425 def invoke(self, context, event):
06426
06427 if OgreToggleInterfaceOp.TOGGLE:
06428 print( 'ogre.toggle_interface ENABLE' )
06429 bpy.utils.register_module(__name__)
06430
06431 try: bpy.utils.unregister_class(_header_)
06432 except: pass
06433 bpy.utils.unregister_class( INFO_HT_microheader )
06434 OgreToggleInterfaceOp.TOGGLE = False
06435 else:
06436 print( 'ogre.toggle_interface DISABLE' )
06437
06438 hide_user_interface()
06439 bpy.utils.register_class(_header_)
06440 restore_minimal_interface()
06441 OgreToggleInterfaceOp.TOGGLE = True
06442 return {'FINISHED'}
06443
06444 class INFO_HT_microheader(bpy.types.Header):
06445 bl_space_type = 'INFO'
06446 def draw(self, context):
06447 layout = self.layout
06448 try:
06449 if OgreToggleInterfaceOp.TOGGLE:
06450 layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_DEHLT')
06451 else:
06452 layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_HLT')
06453 except: pass
06454
06455 def get_minimal_interface_classes():
06456 return INFO_OT_createOgreExport, INFO_OT_createRealxtendExport, OgreToggleInterfaceOp, MiniReport, INFO_HT_microheader
06457
06458 _USE_TUNDRA_ = False
06459 _USE_JMONKEY_ = False
06460
06461 def restore_minimal_interface():
06462
06463 for cls in get_minimal_interface_classes():
06464 try: bpy.utils.register_class( cls )
06465 except: pass
06466 return False
06467
06468 try:
06469 bpy.utils.register_class( INFO_HT_microheader )
06470 for op in get_minimal_interface_classes(): bpy.utils.register_class( op )
06471 return False
06472 except:
06473 print( 'b2ogre minimal UI already setup' )
06474 return True
06475
06476 MyShaders = None
06477
06478 def register():
06479 print('Starting blender2ogre', VERSION)
06480 global MyShaders, _header_, _USE_TUNDRA_, _USE_JMONKEY_
06481
06482
06483
06484 restore_minimal_interface()
06485
06486
06487 if os.path.isdir( CONFIG['TUNDRA_ROOT'] ): _USE_TUNDRA_ = True
06488 else: _USE_TUNDRA_ = False
06489
06490
06491
06492 bpy.types.INFO_MT_file_export.append(export_menu_func_ogre)
06493 bpy.types.INFO_MT_file_export.append(export_menu_func_realxtend)
06494
06495 bpy.utils.register_class(PopUpDialogOperator)
06496
06497 if os.path.isdir( CONFIG['USER_MATERIALS'] ):
06498 scripts,progs = update_parent_material_path( CONFIG['USER_MATERIALS'] )
06499 for prog in progs:
06500 print('Ogre shader program', prog.name)
06501 else:
06502 print('[WARNING]: Invalid my-shaders path %s' % CONFIG['USER_MATERIALS'])
06503
06504 def unregister():
06505 print('Unloading blender2ogre', VERSION)
06506 bpy.utils.unregister_module(__name__)
06507 try: bpy.utils.register_class(_header_)
06508 except: pass
06509
06510
06511
06512 OgreToggleInterfaceOp.TOGGLE = True
06513 bpy.types.INFO_MT_file_export.remove(export_menu_func_ogre)
06514 bpy.types.INFO_MT_file_export.remove(export_menu_func_realxtend)
06515
06516
06517
06518
06519
06520
06521
06522
06523
06524 bpy.types.World.ogre_skyX = BoolProperty(
06525 name="enable sky", description="ogre sky",
06526 default=False
06527 )
06528 bpy.types.World.ogre_skyX_time = FloatProperty(
06529 name="Time Multiplier",
06530 description="change speed of day/night cycle",
06531 default=0.3,
06532 min=0.0, max=5.0
06533 )
06534 bpy.types.World.ogre_skyX_wind = FloatProperty(
06535 name="Wind Direction",
06536 description="change direction of wind",
06537 default=33.0,
06538 min=0.0, max=360.0
06539 )
06540 bpy.types.World.ogre_skyX_volumetric_clouds = BoolProperty(
06541 name="volumetric clouds", description="toggle ogre volumetric clouds",
06542 default=True
06543 )
06544 bpy.types.World.ogre_skyX_cloud_density_x = FloatProperty(
06545 name="Cloud Density X",
06546 description="change density of volumetric clouds on X",
06547 default=0.1,
06548 min=0.0, max=5.0
06549 )
06550 bpy.types.World.ogre_skyX_cloud_density_y = FloatProperty(
06551 name="Cloud Density Y",
06552 description="change density of volumetric clouds on Y",
06553 default=1.0,
06554 min=0.0, max=5.0
06555 )
06556
06557
06558
06559 @UI
06560 class OgreSkyPanel(bpy.types.Panel):
06561 bl_space_type = 'PROPERTIES'
06562 bl_region_type = 'WINDOW'
06563 bl_context = "world"
06564 bl_label = "Ogre Sky Settings"
06565
06566 @classmethod
06567 def poll(cls, context):
06568 return True
06569
06570 def draw(self, context):
06571 layout = self.layout
06572 box = layout.box()
06573 box.prop( context.world, 'ogre_skyX' )
06574 if context.world.ogre_skyX:
06575 box.prop( context.world, 'ogre_skyX_time' )
06576 box.prop( context.world, 'ogre_skyX_wind' )
06577 box.prop( context.world, 'ogre_skyX_volumetric_clouds' )
06578 if context.world.ogre_skyX_volumetric_clouds:
06579 box.prop( context.world, 'ogre_skyX_cloud_density_x' )
06580 box.prop( context.world, 'ogre_skyX_cloud_density_y' )
06581
06582
06583 class OgreProgram(object):
06584 '''
06585 parses .program scripts
06586 saves bytes to copy later
06587
06588 self.name = name of program reference
06589 self.source = name of shader program (.cg, .glsl)
06590 '''
06591
06592 def save( self, path ):
06593 print('saving program to', path)
06594 f = open( os.path.join(path,self.source), 'wb' )
06595 f.write(self.source_bytes )
06596 f.close()
06597 for name in self.includes:
06598 f = open( os.path.join(path,name), 'wb' )
06599 f.write( self.includes[name] )
06600 f.close()
06601
06602 PROGRAMS = {}
06603
06604 def reload(self):
06605 if self.source not in os.listdir( CONFIG['SHADER_PROGRAMS'] ):
06606 print( 'ERROR: ogre material %s is missing source: %s' %(self.name,self.source) )
06607 print( CONFIG['SHADER_PROGRAMS'] )
06608 return False
06609 url = os.path.join( CONFIG['SHADER_PROGRAMS'], self.source )
06610 print('shader source:', url)
06611 self.source_bytes = open( url, 'rb' ).read()
06612 print('shader source num bytes:', len(self.source_bytes))
06613 data = self.source_bytes.decode('utf-8')
06614
06615 for line in data.splitlines():
06616 if line.startswith('#include') and line.count('"')==2:
06617 name = line.split()[-1].replace('"','').strip()
06618 print('shader includes:', name)
06619 url = os.path.join( CONFIG['SHADER_PROGRAMS'], name )
06620 self.includes[ name ] = open( url, 'rb' ).read()
06621 return True
06622
06623 def __init__(self, name='', data=''):
06624 self.name=name
06625 self.data = data.strip()
06626 self.source = None
06627 self.includes = {}
06628
06629 if self.name in OgreProgram.PROGRAMS:
06630 print('---copy ogreprogram---', self.name)
06631 other = OgreProgram.PROGRAMS
06632 self.source = other.source
06633 self.data = other.data
06634 self.entry_point = other.entry_point
06635 self.profiles = other.profiles
06636
06637 if data: self.parse( self.data )
06638 if self.name: OgreProgram.PROGRAMS[ self.name ] = self
06639
06640 def parse( self, txt ):
06641 self.data = txt
06642 print('--parsing ogre shader program--' )
06643 for line in self.data.splitlines():
06644 print(line)
06645 line = line.split('//')[0]
06646 line = line.strip()
06647 if line.startswith('vertex_program') or line.startswith('fragment_program'):
06648 a, self.name, self.type = line.split()
06649
06650 elif line.startswith('source'): self.source = line.split()[-1]
06651 elif line.startswith('entry_point'): self.entry_point = line.split()[-1]
06652 elif line.startswith('profiles'): self.profiles = line.split()[1:]
06653
06654
06655
06656 class OgreMaterialScript(object):
06657 def get_programs(self):
06658 progs = []
06659 for name in list(self.vertex_programs.keys()) + list(self.fragment_programs.keys()):
06660 p = get_shader_program( name )
06661 if p: progs.append( p )
06662 return progs
06663
06664 def __init__(self, txt, url):
06665 self.url = url
06666 self.data = txt.strip()
06667 self.parent = None
06668 self.vertex_programs = {}
06669 self.fragment_programs = {}
06670 self.texture_units = {}
06671 self.texture_units_order = []
06672 self.passes = []
06673
06674 line = self.data.splitlines()[0]
06675 assert line.startswith('material')
06676 if ':' in line:
06677 line, self.parent = line.split(':')
06678 self.name = line.split()[-1]
06679 print( 'new ogre material: %s' %self.name )
06680
06681 brace = 0
06682 self.techniques = techs = []
06683 prog = None
06684 tex = None
06685 for line in self.data.splitlines():
06686
06687 rawline = line
06688 line = line.split('//')[0]
06689 line = line.strip()
06690 if not line: continue
06691
06692 if line == '{': brace += 1
06693 elif line == '}': brace -= 1; prog = None; tex = None
06694
06695 if line.startswith( 'technique' ):
06696 tech = {'passes':[]}; techs.append( tech )
06697 if len(line.split()) > 1: tech['technique-name'] = line.split()[-1]
06698 elif techs:
06699 if line.startswith('pass'):
06700 P = {'texture_units':[], 'vprogram':None, 'fprogram':None, 'body':[]}
06701 tech['passes'].append( P )
06702 self.passes.append( P )
06703
06704 elif tech['passes']:
06705 P = tech['passes'][-1]
06706 P['body'].append( rawline )
06707
06708 if line == '{' or line == '}': continue
06709
06710 if line.startswith('vertex_program_ref'):
06711 prog = P['vprogram'] = {'name':line.split()[-1], 'params':{}}
06712 self.vertex_programs[ prog['name'] ] = prog
06713 elif line.startswith('fragment_program_ref'):
06714 prog = P['fprogram'] = {'name':line.split()[-1], 'params':{}}
06715 self.fragment_programs[ prog['name'] ] = prog
06716
06717 elif line.startswith('texture_unit'):
06718 prog = None
06719 tex = {'name':line.split()[-1], 'params':{}}
06720 if tex['name'] == 'texture_unit':
06721 print('WARNING: material %s contains unnamed texture_units' %self.name)
06722 print('---unnamed texture units will be ignored---')
06723 else:
06724 P['texture_units'].append( tex )
06725 self.texture_units[ tex['name'] ] = tex
06726 self.texture_units_order.append( tex['name'] )
06727
06728 elif prog:
06729 p = line.split()[0]
06730 if p=='param_named':
06731 items = line.split()
06732 if len(items) == 4: p, o, t, v = items
06733 elif len(items) == 3:
06734 p, o, v = items
06735 t = 'class'
06736 elif len(items) > 4:
06737 o = items[1]; t = items[2]
06738 v = items[3:]
06739
06740 opt = { 'name': o, 'type':t, 'raw_value':v }
06741 prog['params'][ o ] = opt
06742 if t=='float': opt['value'] = float(v)
06743 elif t in 'float2 float3 float4'.split(): opt['value'] = [ float(a) for a in v ]
06744 else: print('unknown type:', t)
06745
06746 elif tex:
06747 tex['params'][ line.split()[0] ] = line.split()[ 1 : ]
06748
06749 for P in self.passes:
06750 lines = P['body']
06751 while lines and ''.join(lines).count('{')!=''.join(lines).count('}'):
06752 if lines[-1].strip() == '}': lines.pop()
06753 else: break
06754 P['body'] = '\n'.join( lines )
06755 assert P['body'].count('{') == P['body'].count('}')
06756
06757
06758 self.hidden_texture_units = rem = []
06759 for tex in self.texture_units.values():
06760 if 'texture' not in tex['params']:
06761 rem.append( tex )
06762 for tex in rem:
06763 print('WARNING: not using texture_unit because it lacks a "texture" parameter', tex['name'])
06764 self.texture_units.pop( tex['name'] )
06765
06766 if len(self.techniques)>1:
06767 print('WARNING: user material %s has more than one technique' %self.url)
06768
06769 def as_abstract_passes( self ):
06770 r = []
06771 for i,P in enumerate(self.passes):
06772 head = 'abstract pass %s/PASS%s' %(self.name,i)
06773 r.append( head + '\n' + P['body'] )
06774 return r
06775
06776 class MaterialScripts(object):
06777 ALL_MATERIALS = {}
06778 ENUM_ITEMS = []
06779
06780 def __init__(self, url):
06781 self.url = url
06782 self.data = ''
06783 data = open( url, 'rb' ).read()
06784 try:
06785 self.data = data.decode('utf-8')
06786 except:
06787 self.data = data.decode('latin-1')
06788
06789 self.materials = {}
06790
06791 mats = []
06792 mat = []
06793 skip = False
06794 for line in self.data.splitlines():
06795 if not line.strip(): continue
06796 a = line.split()[0]
06797 if a == 'material':
06798 mat = []; mats.append( mat )
06799 mat.append( line )
06800 elif a in ('vertex_program', 'fragment_program', 'abstract'):
06801 skip = True
06802 elif mat and not skip:
06803 mat.append( line )
06804 elif skip and line=='}':
06805 skip = False
06806
06807
06808 for mat in mats:
06809 omat = OgreMaterialScript( '\n'.join( mat ), url )
06810 if omat.name in self.ALL_MATERIALS:
06811 print( 'WARNING: material %s redefined' %omat.name )
06812
06813
06814
06815
06816 self.materials[ omat.name ] = omat
06817 self.ALL_MATERIALS[ omat.name ] = omat
06818 if omat.vertex_programs or omat.fragment_programs:
06819 self.ENUM_ITEMS.append( (omat.name, omat.name, url) )
06820
06821 @classmethod
06822 def reset_rna(self, callback=None):
06823 bpy.types.Material.ogre_parent_material = EnumProperty(
06824 name="Script Inheritence",
06825 description='ogre parent material class',
06826 items=self.ENUM_ITEMS,
06827
06828 )
06829
06830
06831
06832 def is_image_postprocessed( image ):
06833 if CONFIG['FORCE_IMAGE_FORMAT'] != 'NONE' or image.use_resize_half or image.use_resize_absolute or image.use_color_quantize or image.use_convert_format:
06834 return True
06835 else:
06836 return False
06837
06838 class _image_processing_( object ):
06839 def _reformat( self, name, image ):
06840 if image.convert_format != 'NONE':
06841 name = '%s.%s' %(name[:name.rindex('.')], image.convert_format)
06842 if image.convert_format == 'dds': name = '_DDS_.%s' %name
06843 elif image.use_resize_half or image.use_resize_absolute or image.use_color_quantize or image.use_convert_format:
06844 name = '_magick_.%s' %name
06845 if CONFIG['FORCE_IMAGE_FORMAT'] != 'NONE' and not name.endswith('.dds'):
06846 name = '%s.%s' %(name[:name.rindex('.')], CONFIG['FORCE_IMAGE_FORMAT'])
06847 if CONFIG['FORCE_IMAGE_FORMAT'] == 'dds':
06848 name = '_DDS_.%s' %name
06849 return name
06850
06851 def image_magick( self, texture, infile ):
06852 print('IMAGE MAGICK', infile )
06853 exe = CONFIG['IMAGE_MAGICK_CONVERT']
06854 if not os.path.isfile( exe ):
06855 Report.warnings.append( 'ImageMagick not installed!' )
06856 print( 'ERROR: can not find Image Magick - convert', exe ); return
06857 cmd = [ exe, infile ]
06858
06859 x,y = texture.image.size
06860 ax = texture.image.resize_x
06861 ay = texture.image.resize_y
06862
06863 if texture.image.use_convert_format and texture.image.convert_format == 'jpg':
06864 cmd.append( '-quality' )
06865 cmd.append( '%s' %texture.image.jpeg_quality )
06866
06867 if texture.image.use_resize_half:
06868 cmd.append( '-resize' )
06869 cmd.append( '%sx%s' %(x/2, y/2) )
06870 elif texture.image.use_resize_absolute and (x>ax or y>ay):
06871 cmd.append( '-resize' )
06872 cmd.append( '%sx%s' %(ax,ay) )
06873
06874 elif x > CONFIG['MAX_TEXTURE_SIZE'] or y > CONFIG['MAX_TEXTURE_SIZE']:
06875 cmd.append( '-resize' )
06876 cmd.append( str(CONFIG['MAX_TEXTURE_SIZE']) )
06877
06878 if texture.image.use_color_quantize:
06879 if texture.image.use_color_quantize_dither:
06880 cmd.append( '+dither' )
06881 cmd.append( '-colors' )
06882 cmd.append( str(texture.image.color_quantize) )
06883
06884 path,name = os.path.split( infile )
06885
06886 outfile = os.path.join( path, self._reformat(name,texture.image) )
06887 if outfile.endswith('.dds'):
06888 temp = os.path.join( path, '_temp_.png' )
06889 cmd.append( temp )
06890 print( 'IMAGE MAGICK: %s' %cmd )
06891 subprocess.call( cmd )
06892 self.nvcompress( texture, temp, outfile=outfile )
06893
06894 else:
06895 cmd.append( outfile )
06896 print( 'IMAGE MAGICK: %s' %cmd )
06897 subprocess.call( cmd )
06898
06899 def nvcompress(self, texture, infile, outfile=None, version=1, fast=False, blocking=True):
06900 print('[NVCompress DDS Wrapper]', infile )
06901 assert version in (1,2,3,4,5)
06902 exe = CONFIG['NVCOMPRESS']
06903 cmd = [ exe ]
06904
06905 if texture.image.use_alpha and texture.image.depth==32:
06906 cmd.append( '-alpha' )
06907 if not texture.use_mipmap:
06908 cmd.append( '-nomips' )
06909
06910 if texture.use_normal_map:
06911 cmd.append( '-normal' )
06912 if version in (1,3):
06913 cmd.append( '-bc%sn' %version )
06914 else:
06915 cmd.append( '-bc%s' %version )
06916 else:
06917 cmd.append( '-bc%s' %version )
06918
06919 if fast:
06920 cmd.append( '-fast' )
06921 cmd.append( infile )
06922
06923 if outfile: cmd.append( outfile )
06924
06925 print( cmd )
06926 if blocking:
06927 subprocess.call( cmd )
06928 else:
06929 subprocess.Popen( cmd )
06930
06931
06932
06933 _nvcompress_doc = '''
06934 usage: nvcompress [options] infile [outfile]
06935
06936 Input options:
06937 -color The input image is a color map (default).
06938 -alpha The input image has an alpha channel used for transparency.
06939 -normal The input image is a normal map.
06940 -tonormal Convert input to normal map.
06941 -clamp Clamp wrapping mode (default).
06942 -repeat Repeat wrapping mode.
06943 -nomips Disable mipmap generation.
06944
06945 Compression options:
06946 -fast Fast compression.
06947 -nocuda Do not use cuda compressor.
06948 -rgb RGBA format
06949 -bc1 BC1 format (DXT1)
06950 -bc1n BC1 normal map format (DXT1nm)
06951 -bc1a BC1 format with binary alpha (DXT1a)
06952 -bc2 BC2 format (DXT3)
06953 -bc3 BC3 format (DXT5)
06954 -bc3n BC3 normal map format (DXT5nm)
06955 -bc4 BC4 format (ATI1)
06956 -bc5 BC5 format (3Dc/ATI2)
06957 '''
06958
06959 class OgreMaterialGenerator( _image_processing_ ):
06960 def __init__(self, material, path='/tmp', touch_textures=False ):
06961 self.material = material
06962 self.path = path
06963 self.passes = []
06964 self.touch_textures = touch_textures
06965 if material.node_tree:
06966 nodes = bpyShaders.get_subnodes( self.material.node_tree, type='MATERIAL_EXT' )
06967 for node in nodes:
06968 if node.material:
06969 self.passes.append( node.material )
06970
06971 def get_active_programs(self):
06972 r = []
06973 for mat in self.passes:
06974 if mat.use_ogre_parent_material and mat.ogre_parent_material:
06975 usermat = get_ogre_user_material( mat.ogre_parent_material )
06976 for prog in usermat.get_programs(): r.append( prog )
06977 return r
06978
06979 def get_header(self):
06980 r = []
06981 for mat in self.passes:
06982 if mat.use_ogre_parent_material and mat.ogre_parent_material:
06983 usermat = get_ogre_user_material( mat.ogre_parent_material )
06984 r.append( '// user material: %s' %usermat.name )
06985 for prog in usermat.get_programs():
06986 r.append( prog.data )
06987 r.append( '// abstract passes //' )
06988 r += usermat.as_abstract_passes()
06989 return '\n'.join( r )
06990
06991 def get_passes(self):
06992 r = []
06993 r.append( self.generate_pass(self.material) )
06994 for mat in self.passes:
06995 if mat.use_in_ogre_material_pass:
06996 r.append( self.generate_pass(mat) )
06997 return r
06998
06999 def generate_pass( self, mat, pass_name=None ):
07000 usermat = texnodes = None
07001 if mat.use_ogre_parent_material and mat.ogre_parent_material:
07002 usermat = get_ogre_user_material( mat.ogre_parent_material )
07003 texnodes = bpyShaders.get_texture_subnodes( self.material, mat )
07004
07005 M = ''
07006 if not pass_name: pass_name = mat.name
07007 if usermat:
07008 M += indent(2, 'pass %s : %s/PASS0' %(pass_name,usermat.name), '{' )
07009 else:
07010 M += indent(2, 'pass %s'%pass_name, '{' )
07011
07012 color = mat.diffuse_color
07013 alpha = 1.0
07014 if mat.use_transparency:
07015 alpha = mat.alpha
07016
07017 slots = get_image_textures( mat )
07018 usealpha = False
07019 for slot in slots:
07020
07021 if (slot.texture.image is not None) and (slot.texture.image.use_alpha): usealpha = True; break
07022
07023
07024
07025
07026 if mat.use_fixed_pipeline:
07027 f = mat.ambient
07028 if mat.use_vertex_color_paint:
07029 M += indent(3, 'ambient vertexcolour' )
07030 else:
07031 M += indent(3, 'ambient %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
07032
07033 f = mat.diffuse_intensity
07034 if mat.use_vertex_color_paint:
07035 M += indent(3, 'diffuse vertexcolour' )
07036 else:
07037 M += indent(3, 'diffuse %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
07038
07039 f = mat.specular_intensity
07040 s = mat.specular_color
07041 M += indent(3, 'specular %s %s %s %s %s' %(s.r*f, s.g*f, s.b*f, alpha, mat.specular_hardness/4.0) )
07042
07043 f = mat.emit
07044 if mat.use_shadeless:
07045 M += indent(3, 'emissive %s %s %s 1.0' %(color.r, color.g, color.b) )
07046 elif mat.use_vertex_color_light:
07047 M += indent(3, 'emissive vertexcolour' )
07048 else:
07049 M += indent(3, 'emissive %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
07050 M += '\n'
07051
07052 if mat.offset_z:
07053 M += indent(3, 'depth_bias %s'%mat.offset_z )
07054
07055 for name in dir(mat):
07056 if name.startswith('ogre_') and name != 'ogre_parent_material':
07057 var = getattr(mat,name)
07058 op = name.replace('ogre_', '')
07059 val = var
07060 if type(var) == bool:
07061 if var: val = 'on'
07062 else: val = 'off'
07063 M += indent( 3, '%s %s' %(op,val) )
07064 M += '\n'
07065
07066 if texnodes and usermat.texture_units:
07067 for i,name in enumerate(usermat.texture_units_order):
07068 if i<len(texnodes):
07069 node = texnodes[i]
07070 if node.texture:
07071 geo = bpyShaders.get_connected_input_nodes( self.material, node )[0]
07072 M += self.generate_texture_unit( node.texture, name=name, uv_layer=geo.uv_layer )
07073 elif slots:
07074 for slot in slots:
07075 M += self.generate_texture_unit( slot.texture, slot=slot )
07076
07077 M += indent(2, '}' )
07078 return M
07079
07080 def generate_texture_unit(self, texture, slot=None, name=None, uv_layer=None):
07081 if not hasattr(texture, 'image'):
07082 print('WARNING: texture must be of type IMAGE->', texture)
07083 return ''
07084 if not texture.image:
07085 print('WARNING: texture has no image assigned->', texture)
07086 return ''
07087
07088 if slot and not slot.use: return ''
07089
07090 path = self.path
07091
07092 M = ''; _alphahack = None
07093 if not name: name = ''
07094 M += indent(3, 'texture_unit %s' %name, '{' )
07095
07096 if texture.library:
07097 libpath = os.path.split( bpy.path.abspath(texture.library.filepath) )[0]
07098 iurl = bpy.path.abspath( texture.image.filepath, libpath )
07099 else:
07100 iurl = bpy.path.abspath( texture.image.filepath )
07101
07102 postname = texname = os.path.split(iurl)[-1]
07103 destpath = path
07104
07105 if texture.image.packed_file:
07106 orig = texture.image.filepath
07107 iurl = os.path.join(path, texname)
07108 if '.' not in iurl:
07109 print('WARNING: packed image is of unknown type - assuming PNG format')
07110 iurl += '.png'
07111 texname = postname = os.path.split(iurl)[-1]
07112
07113 if not os.path.isfile( iurl ):
07114 if self.touch_textures:
07115 print('MESSAGE: unpacking image: ', iurl)
07116 texture.image.filepath = iurl
07117 texture.image.save()
07118 texture.image.filepath = orig
07119 else:
07120 print('MESSAGE: packed image already in temp, not updating', iurl)
07121
07122 if is_image_postprocessed( texture.image ):
07123 postname = self._reformat( texname, texture.image )
07124 print('MESSAGE: image postproc',postname)
07125
07126 M += indent(4, 'texture %s' %postname )
07127
07128 exmode = texture.extension
07129 if exmode in TextureUnit.tex_address_mode:
07130 M += indent(4, 'tex_address_mode %s' %TextureUnit.tex_address_mode[exmode] )
07131
07132
07133
07134 if slot:
07135 if exmode == 'CLIP': M += indent(4, 'tex_border_colour %s %s %s' %(slot.color.r, slot.color.g, slot.color.b) )
07136 M += indent(4, 'scale %s %s' %(1.0/slot.scale.x, 1.0/slot.scale.y) )
07137 if slot.texture_coords == 'REFLECTION':
07138 if slot.mapping == 'SPHERE':
07139 M += indent(4, 'env_map spherical' )
07140 elif slot.mapping == 'FLAT':
07141 M += indent(4, 'env_map planar' )
07142 else: print('WARNING: <%s> has a non-UV mapping type (%s) and not picked a proper projection type of: Sphere or Flat' %(texture.name, slot.mapping))
07143
07144 ox,oy,oz = slot.offset
07145 if ox or oy:
07146 M += indent(4, 'scroll %s %s' %(ox,oy) )
07147 if oz:
07148 M += indent(4, 'rotate %s' %oz )
07149
07150
07151 if slot.use_from_dupli:
07152 M += indent(4, 'rotate_anim %s' %slot.emission_color_factor )
07153 if slot.use_map_scatter:
07154 M += indent(4, 'scroll_anim %s %s ' %(slot.density_factor, slot.emission_factor) )
07155
07156 if slot.uv_layer:
07157 idx = find_uv_layer_index( slot.uv_layer, self.material )
07158 M += indent(4, 'tex_coord_set %s' %idx)
07159
07160 rgba = False
07161 if texture.image.depth == 32: rgba = True
07162 btype = slot.blend_type
07163 ex = False; texop = None
07164 if btype in TextureUnit.colour_op:
07165 if btype=='MIX' and slot.use_map_alpha and not slot.use_stencil:
07166 if slot.diffuse_color_factor >= 1.0: texop = 'alpha_blend'
07167 else:
07168 texop = TextureUnit.colour_op[ btype ]
07169 ex = True
07170 elif btype=='MIX' and slot.use_map_alpha and slot.use_stencil:
07171 texop = 'blend_current_alpha'; ex=True
07172 elif btype=='MIX' and not slot.use_map_alpha and slot.use_stencil:
07173 texop = 'blend_texture_alpha'; ex=True
07174 else:
07175 texop = TextureUnit.colour_op[ btype ]
07176 elif btype in TextureUnit.colour_op_ex:
07177 texop = TextureUnit.colour_op_ex[ btype ]
07178 ex = True
07179
07180 if texop and ex:
07181 if texop == 'blend_manual':
07182 factor = 1.0 - slot.diffuse_color_factor
07183 M += indent(4, 'colour_op_ex %s src_texture src_current %s' %(texop, factor) )
07184 else:
07185 M += indent(4, 'colour_op_ex %s src_texture src_current' %texop )
07186 elif texop:
07187 M += indent(4, 'colour_op %s' %texop )
07188
07189 else:
07190 if uv_layer:
07191 idx = find_uv_layer_index( uv_layer )
07192 M += indent(4, 'tex_coord_set %s' %idx)
07193
07194 M += indent(3, '}' )
07195
07196 if self.touch_textures:
07197
07198 if not os.path.isfile(iurl):
07199 Report.warnings.append('Missing texture: %s' %iurl )
07200 else:
07201 desturl = os.path.join( destpath, texname )
07202 updated = False
07203 if not os.path.isfile( desturl ) or os.stat( desturl ).st_mtime < os.stat( iurl ).st_mtime:
07204 f = open( desturl, 'wb' )
07205 f.write( open(iurl,'rb').read() )
07206 f.close()
07207 updated = True
07208 posturl = os.path.join(destpath,postname)
07209 if is_image_postprocessed( texture.image ):
07210 if not os.path.isfile( posturl ) or updated:
07211 self.image_magick( texture, desturl )
07212
07213 return M
07214
07215 class TextureUnit(object):
07216 colour_op = {
07217 'MIX' : 'modulate',
07218 'ADD' : 'add',
07219 'MULTIPLY' : 'modulate',
07220
07221 }
07222 colour_op_ex = {
07223 'MIX' : 'blend_manual',
07224 'SCREEN': 'modulate_x2',
07225 'LIGHTEN': 'modulate_x4',
07226 'SUBTRACT': 'subtract',
07227 'OVERLAY': 'add_signed',
07228 'DIFFERENCE': 'dotproduct',
07229 'VALUE': 'blend_diffuse_colour',
07230 }
07231
07232 tex_address_mode = {
07233 'REPEAT': 'wrap',
07234 'EXTEND': 'clamp',
07235 'CLIP' : 'border',
07236 'CHECKER' : 'mirror'
07237 }
07238
07239
07240 @UI
07241 class PANEL_Object(bpy.types.Panel):
07242 bl_space_type = 'PROPERTIES'
07243 bl_region_type = 'WINDOW'
07244 bl_context = "object"
07245 bl_label = "Object+"
07246
07247 @classmethod
07248 def poll(cls, context):
07249 if _USE_TUNDRA_ and context.active_object:
07250 return True
07251
07252 def draw(self, context):
07253 ob = context.active_object
07254 layout = self.layout
07255 box = layout.box()
07256 box.prop( ob, 'cast_shadows' )
07257
07258 box.prop( ob, 'use_draw_distance' )
07259 if ob.use_draw_distance:
07260 box.prop( ob, 'draw_distance' )
07261
07262 if ob.type == 'EMPTY':
07263 box.prop( ob, 'use_avatar' )
07264 box.prop( ob, 'avatar_reference' )
07265
07266 @UI
07267 class PANEL_Speaker(bpy.types.Panel):
07268 bl_space_type = 'PROPERTIES'
07269 bl_region_type = 'WINDOW'
07270 bl_context = "data"
07271 bl_label = "Sound+"
07272 @classmethod
07273 def poll(cls, context):
07274 if context.active_object and context.active_object.type=='SPEAKER': return True
07275 def draw(self, context):
07276 layout = self.layout
07277 box = layout.box()
07278 box.prop( context.active_object.data, 'play_on_load' )
07279 box.prop( context.active_object.data, 'loop' )
07280 box.prop( context.active_object.data, 'use_spatial' )
07281
07282 @UI
07283 class PANEL_MultiResLOD(bpy.types.Panel):
07284 bl_space_type = 'PROPERTIES'
07285 bl_region_type = 'WINDOW'
07286 bl_context = "modifier"
07287 bl_label = "Multi-Resolution LOD"
07288 @classmethod
07289 def poll(cls, context):
07290 if context.active_object and context.active_object.type=='MESH':
07291 ob = context.active_object
07292 if ob.modifiers and ob.modifiers[0].type=='MULTIRES':
07293 return True
07294 def draw(self, context):
07295 ob = context.active_object
07296 layout = self.layout
07297 box = layout.box()
07298 box.prop( ob, 'use_multires_lod' )
07299 if ob.use_multires_lod:
07300 box.prop( ob, 'multires_lod_range' )
07301
07302
07303
07304 def material_name( mat, clean = False ):
07305 if type(mat) is str:
07306 return mat
07307 elif not mat.library:
07308 return mat.name
07309 else:
07310 if clean:
07311 return clean_object_name(mat.name + mat.library.filepath.replace('/','_'))
07312
07313 def export_mesh(ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True):
07314 ''' returns materials used by the mesh '''
07315 return dot_mesh( ob, path, force_name, ignore_shape_animation, normals )
07316
07317 def generate_material(mat, path='/tmp', copy_programs=False, touch_textures=False):
07318 ''' returns generated material string '''
07319
07320 safename = material_name(mat)
07321 M = '// %s generated by blender2ogre %s\n\n' % (mat.name, VERSION)
07322
07323 M += 'material %s \n{\n' % safename
07324 if mat.use_shadows:
07325 M += indent(1, 'receive_shadows on \n')
07326 else:
07327 M += indent(1, 'receive_shadows off \n')
07328
07329 M += indent(1, 'technique', '{' )
07330 w = OgreMaterialGenerator(mat, path=path, touch_textures=touch_textures)
07331
07332 if copy_programs:
07333 progs = w.get_active_programs()
07334 for prog in progs:
07335 if prog.source:
07336 prog.save(path)
07337 else:
07338 print( '[WARNING}: material %s uses program %s which has no source' % (mat.name, prog.name) )
07339
07340 header = w.get_header()
07341 passes = w.get_passes()
07342
07343 M += '\n'.join(passes)
07344 M += indent(1, '}' )
07345 M += '}\n'
07346
07347 if len(header) > 0:
07348 return header + '\n' + M
07349 else:
07350 return M
07351
07352 def get_ogre_user_material( name ):
07353 if name in MaterialScripts.ALL_MATERIALS:
07354 return MaterialScripts.ALL_MATERIALS[ name ]
07355
07356 def get_shader_program( name ):
07357 if name in OgreProgram.PROGRAMS:
07358 return OgreProgram.PROGRAMS[ name ]
07359 else:
07360 print('WARNING: no shader program named: %s' %name)
07361
07362 def get_shader_programs():
07363 return OgreProgram.PROGRAMS.values()
07364
07365 def parse_material_and_program_scripts( path, scripts, progs, missing ):
07366 for name in os.listdir(path):
07367 url = os.path.join(path,name)
07368 if os.path.isdir( url ):
07369 parse_material_and_program_scripts( url, scripts, progs, missing )
07370
07371 elif os.path.isfile( url ):
07372 if name.endswith( '.material' ):
07373 print( '<found material>', url )
07374 scripts.append( MaterialScripts( url ) )
07375
07376 if name.endswith('.program'):
07377 print( '<found program>', url )
07378 data = open( url, 'rb' ).read().decode('utf-8')
07379
07380 chk = []; chunks = [ chk ]
07381 for line in data.splitlines():
07382 line = line.split('//')[0]
07383 if line.startswith('}'):
07384 chk.append( line )
07385 chk = []; chunks.append( chk )
07386 elif line.strip():
07387 chk.append( line )
07388
07389 for chk in chunks:
07390 if not chk: continue
07391 p = OgreProgram( data='\n'.join(chk) )
07392 if p.source:
07393 ok = p.reload()
07394 if not ok: missing.append( p )
07395 else: progs.append( p )
07396
07397 def update_parent_material_path( path ):
07398 ''' updates RNA '''
07399 print( '>>SEARCHING FOR OGRE MATERIALS: %s' %path )
07400 scripts = []
07401 progs = []
07402 missing = []
07403 parse_material_and_program_scripts( path, scripts, progs, missing )
07404
07405 if missing:
07406 print('WARNING: missing shader programs:')
07407 for p in missing: print(p.name)
07408 if missing and not progs:
07409 print('WARNING: no shader programs were found - set "SHADER_PROGRAMS" to your path')
07410
07411 MaterialScripts.reset_rna( callback=bpyShaders.on_change_parent_material )
07412 return scripts, progs
07413
07414 def get_subcollision_meshes():
07415 ''' returns all collision meshes found in the scene '''
07416 r = []
07417 for ob in bpy.context.scene.objects:
07418 if ob.type=='MESH' and ob.subcollision: r.append( ob )
07419 return r
07420
07421 def get_objects_with_subcollision():
07422 ''' returns objects that have active sub-collisions '''
07423 r = []
07424 for ob in bpy.context.scene.objects:
07425 if ob.type=='MESH' and ob.collision_mode not in ('NONE', 'PRIMITIVE'):
07426 r.append( ob )
07427 return r
07428
07429 def get_subcollisions(ob):
07430 prefix = '%s.' %ob.collision_mode
07431 r = []
07432 for child in ob.children:
07433 if child.subcollision and child.name.startswith( prefix ):
07434 r.append( child )
07435 return r
07436
07437 class bpyShaders(bpy.types.Operator):
07438 '''operator: enables material nodes (workaround for not having IDPointers in pyRNA)'''
07439 bl_idname = "ogre.force_setup_material_passes"
07440 bl_label = "force bpyShaders"
07441 bl_options = {'REGISTER'}
07442
07443 @classmethod
07444 def poll(cls, context):
07445 if context.active_object and context.active_object.active_material: return True
07446 def invoke(self, context, event):
07447 mat = context.active_object.active_material
07448 mat.use_material_passes = True
07449 bpyShaders.create_material_passes( mat )
07450 return {'FINISHED'}
07451
07452
07453 @staticmethod
07454 def on_change_parent_material(mat,context):
07455 print(mat,context)
07456 print('callback', mat.ogre_parent_material)
07457
07458 @staticmethod
07459 def get_subnodes(mat, type='TEXTURE'):
07460 d = {}
07461 for node in mat.nodes:
07462 if node.type==type: d[node.name] = node
07463 keys = list(d.keys())
07464 keys.sort()
07465 r = []
07466 for key in keys: r.append( d[key] )
07467 return r
07468
07469
07470 @staticmethod
07471 def get_texture_subnodes( parent, submaterial=None ):
07472 if not submaterial: submaterial = parent.active_node_material
07473 d = {}
07474 for link in parent.node_tree.links:
07475 if link.from_node and link.from_node.type=='TEXTURE':
07476 if link.to_node and link.to_node.type == 'MATERIAL_EXT':
07477 if link.to_node.material:
07478 if link.to_node.material.name == submaterial.name:
07479 node = link.from_node
07480 d[node.name] = node
07481 keys = list(d.keys())
07482 keys.sort()
07483 r = []
07484 for key in keys: r.append( d[key] )
07485 return r
07486
07487 @staticmethod
07488 def get_connected_input_nodes( material, node ):
07489 r = []
07490 for link in material.node_tree.links:
07491 if link.to_node and link.to_node.name == node.name:
07492 r.append( link.from_node )
07493 return r
07494
07495 @staticmethod
07496 def get_or_create_material_passes( mat, n=8 ):
07497 if not mat.node_tree:
07498 print('CREATING MATERIAL PASSES', n)
07499 bpyShaders.create_material_passes( mat, n )
07500
07501 d = {}
07502 for node in mat.node_tree.nodes:
07503 if node.type == 'MATERIAL_EXT' and node.name.startswith('GEN.'):
07504 d[node.name] = node
07505 keys = list(d.keys())
07506 keys.sort()
07507 r = []
07508 for key in keys: r.append( d[key] )
07509 return r
07510
07511 @staticmethod
07512 def get_or_create_texture_nodes( mat, n=6 ):
07513
07514 assert mat.node_tree
07515 m = []
07516 for node in mat.node_tree.nodes:
07517 if node.type == 'MATERIAL_EXT' and node.name.startswith('GEN.'):
07518 m.append( node )
07519 if not m:
07520 m = bpyShaders.get_or_create_material_passes(mat)
07521 print(m)
07522 r = []
07523 for link in mat.node_tree.links:
07524 print(link, link.to_node, link.from_node)
07525 if link.to_node and link.to_node.name.startswith('GEN.') and link.from_node.type=='TEXTURE':
07526 r.append( link.from_node )
07527 if not r:
07528 print('--missing texture nodes--')
07529 r = bpyShaders.create_texture_nodes( mat, n )
07530 return r
07531
07532 @staticmethod
07533 def create_material_passes( mat, n=8, textures=True ):
07534
07535 mat.use_nodes = True
07536 tree = mat.node_tree
07537
07538 nodes = bpyShaders.get_subnodes( tree, 'MATERIAL' )
07539 if nodes and not nodes[0].material:
07540 nodes[0].material = mat
07541
07542 r = []
07543 x = 680
07544 for i in range( n ):
07545 node = tree.nodes.new( type='MATERIAL_EXT' )
07546 node.name = 'GEN.%s' %i
07547 node.location.x = x; node.location.y = 640
07548 r.append( node )
07549 x += 220
07550
07551 if textures:
07552 texnodes = bpyShaders.create_texture_nodes( mat )
07553 print( texnodes )
07554 return r
07555
07556 @staticmethod
07557 def create_texture_nodes( mat, n=6, geoms=True ):
07558
07559 assert mat.node_tree
07560 mats = bpyShaders.get_or_create_material_passes( mat )
07561 r = {}; x = 400
07562 for i,m in enumerate(mats):
07563 r['material'] = m; r['textures'] = []; r['geoms'] = []
07564 inputs = []
07565 for tag in ['Mirror', 'Ambient', 'Emit', 'SpecTra', 'Ray Mirror', 'Translucency']:
07566 inputs.append( m.inputs[ tag ] )
07567 for j in range(n):
07568 tex = mat.node_tree.nodes.new( type='TEXTURE' )
07569 tex.name = 'TEX.%s.%s' %(j, m.name)
07570 tex.location.x = x - (j*16)
07571 tex.location.y = -(j*230)
07572 input = inputs[j]; output = tex.outputs['Color']
07573 link = mat.node_tree.links.new( input, output )
07574 r['textures'].append( tex )
07575 if geoms:
07576 geo = mat.node_tree.nodes.new( type='GEOMETRY' )
07577 link = mat.node_tree.links.new( tex.inputs['Vector'], geo.outputs['UV'] )
07578 geo.location.x = x - (j*16) - 250
07579 geo.location.y = -(j*250) - 1500
07580 r['geoms'].append( geo )
07581 x += 220
07582 return r
07583
07584 @UI
07585 class PANEL_node_editor_ui( bpy.types.Panel ):
07586 bl_space_type = 'NODE_EDITOR'
07587 bl_region_type = 'UI'
07588 bl_label = "Ogre Material"
07589
07590 @classmethod
07591 def poll(self,context):
07592 if context.space_data.id:
07593 return True
07594
07595 def draw(self, context):
07596 layout = self.layout
07597 topmat = context.space_data.id
07598 mat = topmat.active_node_material
07599 if not mat or topmat.name == mat.name:
07600 self.bl_label = topmat.name
07601 if not topmat.use_material_passes:
07602 layout.operator(
07603 'ogre.force_setup_material_passes',
07604 text="Ogre Material Layers",
07605 icon='SCENE_DATA'
07606 )
07607 ogre_material_panel( layout, topmat, show_programs=False )
07608 elif mat:
07609 self.bl_label = mat.name
07610 ogre_material_panel( layout, mat, topmat, show_programs=False )
07611
07612 @UI
07613 class PANEL_node_editor_ui_extra( bpy.types.Panel ):
07614 bl_space_type = 'NODE_EDITOR'
07615 bl_region_type = 'UI'
07616 bl_label = "Ogre Material Advanced"
07617 bl_options = {'DEFAULT_CLOSED'}
07618 @classmethod
07619 def poll(self,context):
07620 if context.space_data.id: return True
07621 def draw(self, context):
07622 layout = self.layout
07623 topmat = context.space_data.id
07624 mat = topmat.active_node_material
07625 if mat:
07626 self.bl_label = mat.name + ' (advanced)'
07627 ogre_material_panel_extra( layout, mat )
07628 else:
07629 self.bl_label = topmat.name + ' (advanced)'
07630 ogre_material_panel_extra( layout, topmat )
07631
07632 def ogre_material_panel_extra( parent, mat ):
07633 box = parent.box()
07634 header = box.row()
07635
07636 if mat.use_fixed_pipeline:
07637 header.prop( mat, 'use_fixed_pipeline', text='Fixed Pipeline', icon='LAMP_SUN' )
07638 row = box.row()
07639 row.prop(mat, "use_vertex_color_paint", text="Vertex Colors")
07640 row.prop(mat, "use_shadeless")
07641 if mat.use_shadeless and not mat.use_vertex_color_paint:
07642 row = box.row()
07643 row.prop(mat, "diffuse_color", text='')
07644 elif not mat.use_shadeless:
07645 if not mat.use_vertex_color_paint:
07646 row = box.row()
07647 row.prop(mat, "diffuse_color", text='')
07648 row.prop(mat, "diffuse_intensity", text='intensity')
07649 row = box.row()
07650 row.prop(mat, "specular_color", text='')
07651 row.prop(mat, "specular_intensity", text='intensity')
07652 row = box.row()
07653 row.prop(mat, "specular_hardness")
07654 row = box.row()
07655 row.prop(mat, "ambient")
07656
07657 row.prop(mat, "emit")
07658 box.prop(mat, 'use_ogre_advanced_options', text='---guru options---' )
07659 else:
07660 header.prop( mat, 'use_fixed_pipeline', text='', icon='LAMP_SUN' )
07661 header.prop(mat, 'use_ogre_advanced_options', text='---guru options---' )
07662
07663 if mat.use_ogre_advanced_options:
07664 box.prop(mat, 'offset_z')
07665 box.prop(mat, "use_shadows")
07666 box.prop(mat, 'ogre_depth_write' )
07667 for tag in 'ogre_colour_write ogre_lighting ogre_normalise_normals ogre_light_clip_planes ogre_light_scissor ogre_alpha_to_coverage ogre_depth_check'.split():
07668 box.prop(mat, tag)
07669 for tag in 'ogre_polygon_mode ogre_shading ogre_cull_hardware ogre_transparent_sorting ogre_illumination_stage ogre_depth_func ogre_scene_blend_op'.split():
07670 box.prop(mat, tag)
07671
07672 def ogre_material_panel( layout, mat, parent=None, show_programs=True ):
07673 box = layout.box()
07674 header = box.row()
07675 header.prop(mat, 'ogre_scene_blend', text='')
07676 if mat.ogre_scene_blend and 'alpha' in mat.ogre_scene_blend:
07677 row = box.row()
07678 if mat.use_transparency:
07679 row.prop(mat, "use_transparency", text='')
07680 row.prop(mat, "alpha")
07681 else:
07682 row.prop(mat, "use_transparency", text='Transparent')
07683 if not parent:
07684 return
07685
07686 header.prop(mat, 'use_ogre_parent_material', icon='FILE_SCRIPT', text='')
07687
07688 if mat.use_ogre_parent_material:
07689 row = box.row()
07690 row.prop(mat, 'ogre_parent_material', text='')
07691
07692 s = get_ogre_user_material( mat.ogre_parent_material )
07693 if s and (s.vertex_programs or s.fragment_programs):
07694 progs = s.get_programs()
07695 split = box.row()
07696 texnodes = None
07697
07698 if parent:
07699 texnodes = bpyShaders.get_texture_subnodes( parent, submaterial=mat )
07700 elif mat.node_tree:
07701 texnodes = bpyShaders.get_texture_subnodes( mat )
07702
07703 if not progs:
07704 bx = split.box()
07705 bx.label( text='(missing shader programs)', icon='ERROR' )
07706 elif s.texture_units and texnodes:
07707 bx = split.box()
07708 for i,name in enumerate(s.texture_units_order):
07709 if i<len(texnodes):
07710 row = bx.row()
07711
07712 tex = texnodes[i]
07713 row.prop( tex, 'texture', text=name )
07714 if parent:
07715 inputs = bpyShaders.get_connected_input_nodes( parent, tex )
07716 if inputs:
07717 geo = inputs[0]
07718 assert geo.type == 'GEOMETRY'
07719 row.prop( geo, 'uv_layer', text='UV' )
07720 else:
07721 print('WARNING: no slot for texture unit:', name)
07722
07723 if show_programs and (s.vertex_programs or s.fragment_programs):
07724 bx = box.box()
07725 for name in s.vertex_programs:
07726 bx.label( text=name )
07727 for name in s.fragment_programs:
07728 bx.label( text=name )
07729
07730
07731
07732
07733 if __name__ == "__main__":
07734 register()