io_export_ogreDotScene.py
Go to the documentation of this file.
00001 # Copyright (C) 2010 Brett Hartshorn
00002 #
00003 # This library is free software; you can redistribute it and/or
00004 # modify it under the terms of the GNU Lesser General Public
00005 # License as published by the Free Software Foundation; either
00006 # version 2.1 of the License, or (at your option) any later version.
00007 #
00008 # This library is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011 # Lesser General Public License for more details.
00012 #
00013 # You should have received a copy of the GNU Lesser General Public
00014 # License along with this library; if not, write to the Free Software
00015 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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 ## Public API
00097 ## Search for "Public API" to find more
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   # start at 100
00120         ob.uid = high
00121     return ob.uid
00122 
00123 ## Imports
00124 
00125 import os, sys, time, array, ctypes, math
00126 
00127 try:
00128     # Inside blender
00129     import bpy, mathutils
00130     from bpy.props import *
00131 except ImportError:
00132     # If we end up here we are outside blender (compile optional C module)
00133     assert __name__ == '__main__'
00134     print('Trying to compile Rpython C-library')
00135     assert sys.version_info.major == 2  # rpython only works from Python2
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, # [str] is too tricky to convert py-list to rpy-list
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 ) # face vertex indices
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 ] ) # ctypes.c_bool > char > int
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                 #if smooth:
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)#, matidx)
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,    # funny that tuple is valid here
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        # free memory
00235 
00236         SMS = ['<submeshes>']
00237         #for matidx, matname in ...:
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             #x,y,z = tri    # rpython bug, when in a new 'block' need to unpack/repack tuple
00244             #s = '<face v1="%s" v2="%s" v3="%s" />' %(x,y,z)
00245             assert isinstance(tri,tuple) #and len(tri)==3        # this also works
00246             s = '<face v1="%s" v2="%s" v3="%s" />' %tri        # but tuple is not valid here
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 ## More imports now that Blender is imported
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             # no colors_raw !!?
00292             self.vcolors1 = (ctypes.c_float * (n * 3))()  # face1
00293             vc.data.foreach_get( 'color1', self.vcolors1 )
00294             self.vertex_colors.append( self.vcolors1 )
00295 
00296             self.vcolors2 = (ctypes.c_float * (n * 3))()  # face2
00297             vc.data.foreach_get( 'color2', self.vcolors2 )
00298             self.vertex_colors.append( self.vcolors2 )
00299 
00300             self.vcolors3 = (ctypes.c_float * (n * 3))()  # face3
00301             vc.data.foreach_get( 'color3', self.vcolors3 )
00302             self.vertex_colors.append( self.vcolors3 )
00303 
00304             self.vcolors4 = (ctypes.c_float * (n * 3))()  # face4
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 )   # 4 faces
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 ## Make sure we can import from same directory
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 ## Avatar
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) # todo: is this used?
00348 
00349 # Tundra IDs
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 # Rendering
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 ## Physics
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 ## Sound
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 ## ImageMagick
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 # Materials
00489 
00490 bpy.types.Material.ogre_depth_write = BoolProperty(
00491     # Material.ogre_depth_write = AUTO|ON|OFF
00492     name='depth write',
00493     default=True)
00494 bpy.types.Material.ogre_depth_check = BoolProperty(
00495     # If depth-buffer checking is on, whenever a pixel is about to be written to
00496     # the frame buffer the depth buffer is checked to see if the pixel is in front
00497     # of all other pixels written at that point. If not, the pixel is not written.
00498     # If depth checking is off, pixels are written no matter what has been rendered before.
00499     name='depth check',
00500     default=True)
00501 bpy.types.Material.ogre_alpha_to_coverage = BoolProperty(
00502     # Sets whether this pass will use 'alpha to coverage', a way to multisample alpha
00503     # texture edges so they blend more seamlessly with the background. This facility
00504     # is typically only available on cards from around 2006 onwards, but it is safe to
00505     # enable it anyway - Ogre will just ignore it if the hardware does not support it.
00506     # The common use for alpha to coverage is foliage rendering and chain-link fence style textures.
00507     name='multisample alpha edges',
00508     default=False)
00509 bpy.types.Material.ogre_light_scissor = BoolProperty(
00510     # This option is usually only useful if this pass is an additive lighting pass, and is
00511     # at least the second one in the technique. Ie areas which are not affected by the current
00512     # light(s) will never need to be rendered. If there is more than one light being passed to
00513     # the pass, then the scissor is defined to be the rectangle which covers all lights in screen-space.
00514     # Directional lights are ignored since they are infinite. This option does not need to be specified
00515     # if you are using a standard additive shadow mode, i.e. SHADOWTYPE_STENCIL_ADDITIVE or
00516     # SHADOWTYPE_TEXTURE_ADDITIVE, since it is the default behaviour to use a scissor for each additive
00517     # shadow pass. However, if you're not using shadows, or you're using Integrated Texture Shadows
00518     # where passes are specified in a custom manner, then this could be of use to you.
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     # Sets whether or not dynamic lighting is turned on for this pass or not. If lighting is turned off,
00530     # all objects rendered using the pass will be fully lit. This attribute has no effect if a vertex program is used.
00531     name='dynamic lighting',
00532     default=True)
00533 bpy.types.Material.ogre_colour_write = BoolProperty(
00534     # If colour writing is off no visible pixels are written to the screen during this pass. You might think
00535     # this is useless, but if you render with colour writing off, and with very minimal other settings,
00536     # you can use this pass to initialise the depth buffer before subsequently rendering other passes which
00537     # fill in the colour data. This can give you significant performance boosts on some newer cards, especially
00538     # when using complex fragment programs, because if the depth check fails then the fragment program is never run.
00539     name='color-write',
00540     default=True)
00541 bpy.types.Material.use_fixed_pipeline = BoolProperty(
00542     # Fixed pipeline is oldschool
00543     # todo: whats the meaning of this?
00544     name='fixed pipeline',
00545     default=True)
00546 bpy.types.Material.use_material_passes = BoolProperty(
00547     # hidden option - gets turned on by operator
00548     # todo: What is a hidden option, is this needed?
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', #default='NONE',
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 ## FAQ
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 ## DOCUMENTATION
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 ## Options
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 ## Config
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', # ogre standard
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, # this is now safe - ignored if mesh is missing UVs
00777     'tangentSemantic' : 'tangent', # used to default to "uvw" but that doesn't seem to work with anything and breaks shaders
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', # source build is better
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     #'USER_MATERIALS' : '~/ogre_src_v1-7-3/Samples/Media/materials',
00813     #'SHADER_PROGRAMS' : '~/ogre_src_v1-7-3/Samples/Media/materials/programs',
00814     'NVCOMPRESS' : '/usr/local/bin/nvcompress'
00815 }
00816 
00817 # Unix: Replace ~ with absolute home dir path
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 ## PUBLIC API continues
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             # Find the blender2ogre install path from windows registry
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                 # OgreXmlConverter
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                 # Run auto updater as silent. Notifies user if there is a new version out.
00869                 # This will not show any UI if there are no update and will go to network
00870                 # only once per 2 days so it wont be spending much resources either.
00871                 # todo: Move this to a more appropriate place than load_config()
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     # Setup temp hidden RNA to expose the file paths
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     #for key in CONFIG: print( '%s =   %s' %(key, CONFIG[key]) )
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 # Make default material for missing materials:
00926 # * Red flags for users so they can quickly see what they forgot to assign a material to.
00927 # * Do not crash if no material on object - thats annoying for the user.
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 ## Helper functions
00947 
00948 def timer_diff_str(start):
00949     return "%0.2f" % (time.time()-start)
00950 
00951 def find_bone_index( ob, arm, groupidx): # sometimes the groups are out of order, this finds the right index.
00952     if groupidx < len(ob.vertex_groups): # reported by Slacker
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 # if we skip bones we need to adjust the id
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     # This breaks if users have uv layers with same name with different indices over different objects
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 # should we check all objects using material and enforce the same index?
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     # A default plane, with simple-subsurf and displace modifier on Z
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 # disallow NORMAL and other modes
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             # context.scene.objects.active = grp.objects
01042             # Note that the context is read-only. These values cannot be modified directly,
01043             # though they may be changed by running API functions or by using the data API.
01044             # So bpy.context.object = obj will raise an error. But bpy.context.scene.objects.active = obj
01045             # will work as expected. - http://wiki.blender.org/index.php?title=Dev:2.5/Py/API/Intro&useskin=monobook
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)))   # Requiered for Blender SVN > 2.56
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") # collaspe modifiers
01089             while o2.modifiers:
01090                 o2.modifiers.remove( o2.modifiers[0] )
01091             bpy.context.scene.objects.link( o2 ) #; o2.select = True
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") # collaspe modifiers
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 ) #; o2.select = True
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         #if ob.data.users != 1:
01133         #    print( 'WARNING: an instance can not be in a merge group' )
01134         #    return
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(' '): # do not split on tabs
01143         word = word.replace('\t', ' '*3)
01144         r[-1] += word + ' '
01145         if len(r[-1]) > 90:
01146             r.append( '' )
01147     return r
01148 
01149 ## RPython xml dom
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         # sorted attributes -- don't want attributes output in random order, which is what the XMLGenerator class does
01209         self.output.write(" " * self.indent)
01210         self.output.write("<%s" % name)
01211         sortedNames = sorted( attrs.keys() )  # sorted list of attribute names
01212         for name in sortedNames:
01213             value = attrs[ name ]
01214             # if not of type string,
01215             if not isinstance(value, str):
01216                 # turn it into a string
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 ## Report Hack
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         #if not bpy.context.selected_objects:
01274         #    self.warnings.append('YOU DID NOT SELECT ANYTHING TO EXPORT')
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             ## TODO report file sizes, meshes and textures
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 )        # console only - extended report
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 ## RPython xml dom ends
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() # blender grid prim zig-zags
01359         rows.append( row )
01360     return {'data':rows, 'min':Zmin, 'max':Zmax, 'depth':depth}
01361 
01362 def save_terrain_as_NTF( path, ob ): # Tundra format - hardcoded 16x16 patch format
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     # Header
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     # Body
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         #child.select = False
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         #pos = ob.matrix_world.to_translation()
01438         bpy.ops.mesh.primitive_grid_add(
01439             x_subdivisions=x,
01440             y_subdivisions=y,
01441             size=1.0 )      #, location=pos )
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         #grid.select = False
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             ##BLENDERBUG##ob.game.collision_bounds_type = subtype   # BUG this can not come before
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  # BLENDERBUG - this must come after draw_bounds_type assignment
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  # proxy
01522             mod = _get_proxy_decimate_mod( ob )
01523             mod.show_viewport = True
01524             if not proxy.select:    # ugly (but works)
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 # unknown mode
01543 
01544         return {'FINISHED'}
01545 
01546 # UI panels
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  # decimate modifier is missing
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                 #box.prop( terrain.modifiers[0], 'offset' ) # gets normalized away
01698                 box.prop( terrain.modifiers[0], 'cull_face', text='Cull' )
01699                 box.prop( terrain, 'location' )     # TODO hide X and Y
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 ## Pop up dialog for various info/error messages
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         # todo: Make this bigger and center on screen.
01745         # Blender UI stuff seems quite complex, would
01746         # think that showing a dialog with a message thath
01747         # does not hide when mouse is moved would be simpler!
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         # Close
01761         if event.type == 'LEFTMOUSE':
01762             print ("Left mouse")
01763             return {'FINISHED'}
01764         # Close
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 ## Game Logic Documentation
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' } # custom name hacks
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', '')        # None case
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'],        #skipping body_type
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(),        # note .sound contains .filepath
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         #layout.label(text=str(self.INDEX))
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     # nudge each pass by 2
01938         return {'FINISHED'}
01939 
01940 # UI panels continues
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         #if not hasattr(context, "texture_slot"):
02000         #    return False
02001         layout = self.layout
02002         #idblock = context_tex_datablock(context)
02003         slot = context.texture_slot
02004         if not slot or not slot.texture:
02005             return
02006 
02007         btype = slot.blend_type  # todo: fix this hack if/when slots support pyRNA
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         # Animation and offset options
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         # Can't use if its enabled by default row.prop(slot, "use_map_density", text="")
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         ## Image magick
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 ## OgreMeshy
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             # If OgreMeshy ends with .exe, set the path for preview meshes to
02139             # the user's wine directory, otherwise to /tmp.
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': # assume group
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             #CONFIG['TOUCH_TEXTURES'] = True
02202             #CONFIG['PATH'] = path   # TODO deprecate
02203             data = ''
02204             for umat in umaterials:
02205                 data += generate_material( umat, path=path, copy_programs=True, touch_textures=True ) # copies shader programs to path
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             #subprocess.call(cmd)
02218             subprocess.Popen(cmd)
02219         else:
02220             #subprocess.call([CONFIG_OGRE_MESHY, 'C:\\tmp\\preview.mesh'])
02221             subprocess.Popen( [CONFIG['OGRE_MESHY'], 'C:\\tmp\\preview.mesh'] )
02222 
02223         Report.show()
02224         return {'FINISHED'}
02225 
02226 ## Ogre Documentation to UI
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         #row = self.layout.box().split(percentage=0.05)
02241         #col = row.column(align=False)
02242         #print(dir(col))
02243         #row.scale_x = 0.1
02244         #row.alignment = 'RIGHT'
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 # Ogre v1.7 Doc
02352 
02353 def _mesh_entity_helper( doc, ob, o ):
02354     ## extended format - BGE Physics ##
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     # custom user props
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 #class _type(bpy.types.IDPropertyGroup):
02389 #    name = StringProperty(name="jpeg format", description="", maxlen=64, default="")
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         # todo: Make a way in the gui to give prefix for the refs
02411         # This can be very useful if you want to give deployment URL
02412         # eg. "http://www.myassets.com/myscene/". By default this needs
02413         # to be an empty string, it will operate best for local preview
02414         # and importing the scene content to existing scenes with relative refs.
02415         proto = ''
02416 
02417         doc = RDocument()
02418         scn = doc.createElement('scene')
02419         doc.appendChild( scn )
02420 
02421         # EC_Script
02422         if 0: # todo: tundra bug (what does this mean?)
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             #a.setAttribute('value', "%s%s"%(proto,TUNDRA_GEN_SCRIPT_PATH) )
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         # Check lighting settings
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         # Environment
02456         if bpy.context.scene.world.mist_settings.use_mist or sun or hemi:
02457             # Entity for environment components
02458             e = doc.createElement( 'entity' )
02459             doc.documentElement.appendChild( e )
02460             e.setAttribute('id', len(doc.documentElement.childNodes)+1 )
02461 
02462             # EC_Fog
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             # EC_EnvironmentLight
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') # brightness of sunlight
02508             if sun:
02509                 a.setAttribute('value', sun.data.energy*10) # 10=magic
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')   # TODO, get the sun rotation from blender
02527 
02528             a = doc.createElement('attribute'); c.appendChild( a )
02529             a.setAttribute('name', 'Sunlight cast shadows')
02530             a.setAttribute('value', 'true')
02531 
02532         # EC_SkyX
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     # Creates new Tundra entity
02562     def tundra_entity( self, doc, ob, path='/tmp', collision_proxies=[], parent=None, matrix=None,visible=True ):
02563         assert not ob.subcollision
02564 
02565         #  Tundra TRANSFORM
02566         if not matrix:
02567             matrix = ob.matrix_world.copy()
02568 
02569         # todo: Make a way in the gui to give prefix for the refs
02570         # This can be very useful if you want to give deployment URL
02571         # eg. "http://www.myassets.com/myscene/". By default this needs
02572         # to be an empty string, it will operate best for local preview
02573         # and importing the scene content to existing scenes with relative refs.
02574         proto = ''
02575 
02576         # Entity
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         # EC_Name
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         # EC_Placeable
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)) # Tundra2 clamps any negative to zero
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         # Don't mark bounding boxes to show in Tundra!
02623         #if ob.show_bounds or ob.type != 'MESH':
02624         #    a.setAttribute('value', "true" )
02625         #else:
02626         #    a.setAttribute('value', "false" )
02627 
02628         a = doc.createElement('attribute'); c.appendChild(a)
02629         a.setAttribute('name', "Visible" )
02630         if visible:
02631             a.setAttribute('value', 'true') # overrides children's setting - not good!
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         # Tundra parenting to EC_Placeable.
02640         # todo: Verify this inserts correct ent name or id here.
02641         #   <attribute value="" name="Parent entity ref"/>
02642         #   <attribute value="" name="Parent bone name"/>
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         # EC_Sound: Supports wav and ogg
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         # EC_Camera
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         # EC_Rigidbody
02738         # Any object can have physics, although it needs
02739         # EC_Placeable to have position etc.
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, # Missing in Tundra
02746                 'CAPSULE' : 3,
02747                 'TRIANGLE_MESH' : 4,
02748                 #'HEIGHT_FIELD': 5, # Missing in Blender
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             # Mass
02757             # * Does not affect static collision types (TriMesh and ConvexHull)
02758             # * You can have working collisions with mass 0
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                 #x,y,z = swap(ob.matrix_world.to_scale())
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             #if ob.game.use_collision_compound:
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' ) # HEIGHT_FIELD
02801             elif ob.type == 'MESH':
02802                 # todo: Remove this. There is no need to set mesh collision ref
02803                 # if TriMesh or ConvexHull is used, it will be auto picked from EC_Mesh
02804                 # in the same Entity.
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             #avg = sum( ob.game.friction_coefficients ) / 3.0
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             # todo: Find out what Phantom actually means and if this
02838             # needs to be set for NONE collision rigids. I don't actually
02839             # see any reason to make EC_RigidBody if collision is NONE
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             # Never mark rigids to have draw debug, it can
02852             # be toggled in tundra for visual debugging.
02853             #if ob.collision_mode == 'NONE':
02854             #    a.setAttribute('value', 'false' )
02855             #else:
02856             #    a.setAttribute('value', 'true' )
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         # EC_Terrain
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             #trans = '%s,%s,%s,' %(-xp/4, -z/2, -yp/4)
02893             trans = '%s,%s,%s,' %(-xp/4, -depth, -yp/4)
02894             # scaling in Tundra happens after translation
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             # todo: Check that NTF['name'] is the actual valid asset ref
02926             # and not the disk path.
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         # Enitity XML generation done, return the element.
02934         return e
02935 
02936     # EC_Mesh
02937     def tundra_mesh( self, e, ob, url, exported_meshes ):
02938         # todo: Make a way in the gui to give prefix for the refs
02939         # This can be very useful if you want to give deployment URL
02940         # eg. "http://www.myassets.com/myscene/". By default this needs
02941         # to be an empty string, it will operate best for local preview
02942         # and importing the scene content to existing scenes with relative refs.
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         # Query object its materials and make a proper material ref string of it.
02980         # note: We assume blindly here that the 'submesh' indexes are correct in the material list.
02981         mymaterials = ob.data.materials
02982         if mymaterials is not None and len(mymaterials) > 0:
02983             mymatstring = '' # generate ; separated material list
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]  # strip ending ;
02990             a.setAttribute('value', mymatstring )
02991         else:
02992             # default to nothing to avoid error prints in .txml import
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     # EC_Light
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         #2 = directional light.  blender has no directional light?
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' ) # HEMI no .shadow_method
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 ## UI export panel
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         # Resolve path from opened .blend if available. It's not if
03138         # blender normally was opened with "last open scene".
03139         # After export is done once, remember that path when re-exporting.
03140         global last_export_filepath
03141         if last_export_filepath == "":
03142             # First export during this blender run
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             # Sequential export, use the previous path
03154             self.filepath = last_export_filepath
03155 
03156         # Replace file extension if we have swapped dialogs.
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         # Update ui setting from the last export, or file config.
03163         self.update_ui()
03164 
03165         wm = context.window_manager
03166         fs = wm.fileselect_add(self) # writes to filepath
03167         return {'RUNNING_MODAL'}
03168 
03169     def execute(self, context):
03170         # Store this path for later re-export
03171         global last_export_filepath
03172         last_export_filepath = self.filepath
03173 
03174         # Run the .scene or .txml export
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     # Basic options
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     # Mesh options
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             # Write own .material file per material
03383             if self.EX_SEP_MATS:
03384                 url = self.dot_material_write_separate( mat, data, path )
03385                 material_files.append(url)
03386 
03387         # Write one .material file for everything
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         # Updating config to latest values?
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         # Nodes (objects) - gather because macros will change selection state
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                 # Gather data of invalid names. Don't bother user with warnings on names
03448                 # that only get spaces converted to _, just do that automatically.
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         # Print invalid obj names so user can go and fix them.
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         # Linked groups - allows 3 levels of nested blender library linking
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 )     # TOP-LEVEL
03471                 elif o.type == 'EMPTY' and o.dupli_group and o.dupli_type == 'GROUP':
03472                     ss = []     # LEVEL2
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 = []    # LEVEL3
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         # Find merge groups
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         # Gather roots because ogredotscene supports parents and children
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         # Track that we don't export same data multiple times
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         # realXtend Tundra .txml scene description export
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                 # This seemingly needs to be done as its done in .scene
03550                 # export. Fixed a bug that no .meshes were exported when doing
03551                 # a Tundra export.
03552                 if ob.type == 'MESH':
03553                     ob.data.update(calc_tessface=True)
03554 
03555                 # EC_Light
03556                 if ob.type == 'LAMP':
03557                     TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
03558                     self.tundra_light( TE, ob )
03559                 # EC_Sound
03560                 elif ob.type == 'SPEAKER':
03561                     TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
03562                 # EC_Mesh
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 ) # ensure uid
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             # EC_RigidBody separate collision meshes
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         # Ogre .scene scene description export
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         # Always save?
03635         # todo: This does not seem to stick! It might save to disk
03636         # but the old config defaults are read when this panel is opened!
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         # Extern files
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         # Environ settings
03669         world = context.scene.world
03670         if world: # multiple scenes - other scenes may not have a 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')    # on DTD spec (none | exp | exp2 | linear)
03684             elif mist_falloff == 'LINEAR': a.setAttribute('mode', 'linear')
03685             else: a.setAttribute('mode', 'exp2')
03686             #a.setAttribute('mode', world.mist_settings.falloff.lower() )    # not on DTD spec
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     # Recursive Node export
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         # Custom user props
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         # Custom user props from BGE props by Mind Calamity
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         # -- end of Mind Calamity patch
03718 
03719         # BGE subset
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             # Deal with Array modifier
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                         #v = ob.matrix_world.to_translation()
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                                 #if self.EX_MESH_SUBDIR: e.setAttribute('meshFile', 'meshes/%s.mesh' %ob.data.name)
03802                                 #else:
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             # fov in radians - like OgreMax - requested by cyrfer
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)    # requested by cyrfer
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')   # just to make sure we conform with the DTD
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' )            # is this an Ogre constant?
03858             a.setAttribute('constant', '1.0')        # TODO support quadratic light
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':  # colourShadow is extra, not part of Ogre DTD
03890                 if ob.data.shadow_method != 'NOSHADOW': # Hemi light has no shadow_method
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 ## UI panel Ogre export - Subclasses _OgreCommonExport_
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     # Basic options
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     # Mesh options
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 ## UI panel Tundra export - Subclasses _OgreCommonExport_
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     # Basic options
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     # Mesh options
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 ## Ogre node helper
04220 
04221 def _ogre_node_helper( doc, ob, objects, prefix='', pos=None, rot=None, scl=None ):
04222     # shouldn't this be matrix_local?
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')       #('quaternion')
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:        # this should not be used
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:        # scale is different in Ogre from blender - rotation is removed
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 ## MeshMagick
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:    # single users only
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 ## Ogre Command Line Tools Documentation
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 ## Ogre Command Line Tools
04346 
04347 def OgreXMLConverter( infile, has_uvs=False ):
04348     # todo: Show a UI dialog to show this error. It's pretty fatal for normal usage.
04349     # We should show how to configure the converter location in config panel or tell the default path.
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     # LOD generation with OgreXMLConverter tool does not work. Currently the mesh files are generated
04358     # manually and referenced in the main mesh file.
04359     #if CONFIG['lodLevels']:
04360     #    basicArguments += ' -l %s -v %s -p %s' %(CONFIG['lodLevels'], CONFIG['lodDistance'], CONFIG['lodPercent'])
04361 
04362     if CONFIG['nuextremityPoints'] > 0:
04363         basicArguments += ' -x %s' %CONFIG['nuextremityPoints']
04364 
04365     if not CONFIG['generateEdgeLists']:
04366         basicArguments += ' -e'
04367 
04368     # note: OgreXmlConverter fails to convert meshes without UVs
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     # Make xml converter print less stuff, comment this if you want more debug info out
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 ## Bone
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':      # Tundra 1.x
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':    # Tundra 2.x current generation
04406                 #self.flipMat = mathutils.Matrix(((1,0,0,0),(0,0,1,0),(0,1,0,0),(0,0,0,1)))
04407                 self.flipMat = mathutils.Matrix(((1,0,0,0),(0,0,1,0),(0,-1,0,0),(0,0,0,1))) # thanks to Waruck
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() # armature space
04415         #self.matrix_local = rbone.matrix.copy() # space?
04416 
04417         self.bone = pbone        # safe to hold pointer to pose bone, not edit bone!
04418         self.shouldOutput = True
04419         if CONFIG['ONLY_DEFORMABLE_BONES'] and not pbone.bone.use_deform:
04420             self.shouldOutput = False
04421 
04422         # todo: Test -> #if pbone.bone.use_inherit_scale: print('warning: bone <%s> is using inherit scaling, Ogre has no support for this' %self.name)
04423         self.parent = pbone.parent
04424         self.children = []
04425 
04426     def update(self):        # called on frame update
04427         pbone = self.bone
04428         pose =  pbone.matrix.copy()
04429         self._inverse_total_trans_pose = pose.inverted()
04430         # calculate difference to parent bone
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         #self.pose_location = pbone.location.copy()
04443         #self.pose_scale = pbone.scale.copy()
04444         #if pbone.rotation_mode == 'QUATERNION':
04445         #    self.pose_rotation = pbone.rotation_quaternion.copy()
04446         #else:
04447         #    self.pose_rotation = pbone.rotation_euler.to_quaternion()
04448             
04449         if CONFIG['OGRE_INHERIT_SCALE']:
04450             # special case workaround for broken Ogre nonuniform scaling:
04451             # Ogre can't deal with arbitrary nonuniform scaling, but it can handle certain special cases
04452             # The special case we are trying to handle here is when a bone has a nonuniform scale and it's
04453             # child bones are not inheriting the scale.  We should be able to do this without having to
04454             # do any extra setup in Ogre (like turning off "inherit scale" on the Ogre bones)
04455             # if Ogre is inheriting scale, we just output the scale relative to the parent
04456             self.pose_scale = pose.to_scale()
04457             self.ogreDerivedScale = self.pose_scale.copy()
04458             if self.parent:
04459                 # this is how Ogre handles inheritance of scale
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                 # if we don't want inherited scale,
04464                 if not self.bone.bone.use_inherit_scale:
04465                     # cancel out the scale that Ogre will calculate
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             # if Ogre is not inheriting the scale,
04471             # just output the scale directly
04472             self.pose_scale = pbone.scale.copy()
04473             # however, if Blender is inheriting the scale,
04474             if self.parent and self.bone.bone.use_inherit_scale:
04475                 # apply parent's scale (only works for uniform scaling)
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         #self.bone.rotation_axis_angle  #ignore axis angle mode
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         #self.bone.rotation_axis_angle  #ignore axis angle mode
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         #self.bone.rotation_axis_angle  #ignore axis angle mode
04503 
04504     def rebuild_tree( self ):        # called first on all bones
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                 # mark all ancestor bones as shouldOutput
04510                 parent = self.parent
04511                 while parent:
04512                     parent.shouldOutput = True
04513                     parent = parent.parent
04514 
04515     def compute_rest( self ):    # called after rebuild_tree, recursive roots to leaves
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         #self.ogre_rest_matrix = self.skeleton.object_space_transformation * self.matrix    # ALLOW ROTATION?
04524         self.ogre_rest_matrix = self.matrix.copy()
04525         # store total inverse transformation
04526         self.inverse_total_trans = self.ogre_rest_matrix.inverted()
04527         # relative to OGRE parent bone origin
04528         self.ogre_rest_matrix = inverseParentMatrix * self.ogre_rest_matrix
04529         self.inverse_ogre_rest_matrix = self.ogre_rest_matrix.inverted()
04530 
04531         # recursion
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         # if the angle is very close to zero, or the axis is not unit length,
04547         if abs(self.rot.angle) < 0.0001 or abs(self.rot.axis.length - 1.0) > 0.001:
04548             # treat it as a zero rotation
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 # Bone_Track
04558 # Encapsulates all of the key information for an individual bone within a single animation,
04559 # and srores that information as XML.
04560 class Bone_Track:
04561     def __init__(self, bone):
04562         self.bone = bone
04563         self.keyframes = []
04564 
04565     def is_pos_animated( self ):
04566         # take note if any keyframe is anything other than the IDENTITY transform
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         # take note if any keyframe is anything other than the IDENTITY transform
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         # take note if any keyframe is anything other than the IDENTITY transform
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                 # if angle is near zero or axis is not unit magnitude,
04617                 if kf.isRotIdentity():
04618                     angle = 0.0  # avoid outputs like "-0.00000"
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 # Skeleton
04638 def findArmature( ob ):
04639     arm = ob.find_armature()
04640     # if this armature has no animation,
04641     if not arm.animation_data:
04642         # search for another armature that is a proxy for it
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         #arm.layers = [True]*20      # can not have anything hidden - REQUIRED?
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         ## bad idea - allowing rotation of armature, means vertices must also be rotated,
04678         ## also a bug with applying the rotation, the Z rotation is lost
04679         #x,y,z = arm.matrix_local.copy().inverted().to_euler()
04680         #e = mathutils.Euler( (x,z,y) )
04681         #self.object_space_transformation = e.to_matrix().to_4x4()
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         ## setup bones for Ogre format ##
04687         for b in self.bones:
04688             b.rebuild_tree()
04689         ## walk bones, convert them ##
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                 #if loc.x or loc.y or loc.z:
04697                 #    Report.warnings.append('ERROR: root bone has non-zero transform (location offset)')
04698                 #if rot.w > ep or rot.x > ep or rot.y > ep or rot.z < 1.0-ep:
04699                 #    Report.warnings.append('ERROR: root bone has non-zero transform (rotation offset)')
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         #boneNames = sorted( [bone.name for bone in arm.pose.bones] )
04705         bone_tracks = []
04706         for bone in self.bones:
04707             #bone = self.get_bone(boneName)
04708             if bone.shouldOutput:
04709                 bone_tracks.append( Bone_Track(bone) )
04710             bone.clear_pose_transform()  # clear out any leftover pose transforms in case this bone isn't keyframed
04711         for frame in range( int(frameBegin), int(frameEnd)+1, bpy.context.scene.frame_step):#thanks to Vesa
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         # check to see if any animation tracks would be output
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)                       # USE the action name
04732         anim.setAttribute('length', '%6f' %( (frameEnd - frameBegin)/_fps ) )
04733 
04734         for track in bone_tracks:
04735             # will only write a track if there is some kind of animation there
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' ) # "rotation", not "rotate"
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             # Ogre bones do not have initial scaling
04776 
04777         arm = self.arm
04778         # remember some things so we can put them back later
04779         savedFrame = bpy.context.scene.frame_current
04780         # save the current pose
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             # write a single animation from the blender timeline
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 = {}  # actions by name
04798             # the only thing NLA is used for is to gather the names of the actions
04799             # it doesn't matter if the actions are all in the same NLA track or in different tracks
04800             for nla in arm.animation_data.nla_tracks:        # NLA required, lone actions not supported
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() )  # output actions in alphabetical order
04810             for actionName in actionNames:
04811                 action = actions[ actionName ]
04812                 arm.animation_data.action = action  # set as the current 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                             # suppress this bone's output
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                 # restore suppressed bones
04825                 for boneName in suppressedBones:
04826                     bone = self.get_bone( boneName )
04827                     bone.shouldOutput = True
04828             # restore these to what they originally were
04829             arm.animation_data.action = savedAction
04830             arm.animation_data.use_nla = savedUseNla
04831 
04832         # restore
04833         bpy.context.scene.frame_set( savedFrame )
04834         # restore the current pose
04835         for b in self.bones:
04836             b.restore_pose_transform()
04837 
04838         return doc.toprettyxml()
04839 
04840 
04841 ## Selector extras
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) # operator has no variable for button 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'} # Options for this panel type
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)    # operator no variable for button 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'}                              # Options for this panel type
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 ## NVIDIA texture tool documentation
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 ## Creating .mesh
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         #bpy.context.scene.objects.link(copy)
05195         rem = []
05196         for mod in copy.modifiers:        # remove armature and array modifiers before collaspe
05197             if mod.type in 'ARMATURE ARRAY'.split(): rem.append( mod )
05198         for mod in rem: copy.modifiers.remove( mod )
05199         # bake mesh
05200         mesh = copy.to_mesh(bpy.context.scene, True, "PREVIEW")    # collaspe
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         # Very ugly, have to replace number of vertices later
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         # Vertex colors
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         # Materials
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_' ) # fixed dec22, keep proper index
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         # Textures
05261         dotextures = False
05262         uvcache = [] # should get a little speed boost by this cache
05263         if mesh.tessface_uv_textures.active:
05264             dotextures = True
05265             for layer in mesh.tessface_uv_textures:
05266                 uvs = []; uvcache.append( uvs ) # layer contains: name, active, data
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             # Ogre only supports triangles
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 ) # fixed june 17th 2011
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                     # Texture maps
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                             #does not compare ogre_vidx (and position at the moment)
05329                             if vert == vert2:
05330                                 face.append(vert2.ogre_vidx)
05331                                 alreadyExported = True
05332                                 #print(idx,numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "already exported")
05333                                 break
05334                         if not alreadyExported:
05335                             face.append(vert.ogre_vidx)
05336                             _sm_vertices_[idx].append(vert)
05337                             #print(numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "appended")
05338                     else:
05339                         face.append(vert.ogre_vidx)
05340                         _sm_vertices_[idx] = [vert]
05341                         #print(idx, numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "created")
05342 
05343                     if alreadyExported:
05344                         continue
05345 
05346                     numverts += 1
05347                     _remap_verts_.append( v )
05348 
05349                     x,y,z = swap(v.co)        # xz-y is correct!
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                     # Texture maps
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 # fixes corrupt unused materials
05397 
05398             submesh_attributes = {
05399                 'usesharedvertices' : 'true',
05400                 # Maybe better look at index of all faces, if one over 65535 set to true;
05401                 # Problem: we know it too late, postprocessing of file needed
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         # Submesh names
05427         # todo: why is the submesh name taken from the material
05428         # when we have the blender object name available?
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         # Generate lod levels
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             # Cap lod levels to something sensible (what is it?)
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                 # Create new mesh
05459                 mesh = bpy.data.meshes.new(name)
05460 
05461                 # Create new object associated with the mesh
05462                 ob_new = bpy.data.objects.new(name, mesh)
05463 
05464                 # Copy data block from the old object into the new object
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                 # Link new object to the given scene and select it
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             # todo: Potential infinite recursion creation fails?
05481             def get_or_create_modifier(obj, modifier_name):
05482                 if obj.type != 'MESH':
05483                     return None
05484                 # Find modifier
05485                 for mod_iter in obj.modifiers:
05486                     if mod_iter.type == modifier_name:
05487                         return mod_iter
05488                 # Not found? Create it and call recurse
05489                 activate_object(obj)
05490                 bpy.ops.object.modifier_add(type=modifier_name)
05491                 return get_or_create_modifier(obj, modifier_name)
05492 
05493             # Create a temporary duplicate
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             # Activate clone for modifier manipulation
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                     # Check min vertice count and that the vertice count got reduced from last iteration
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                     # todo: should we check if the ratio gets too small? although its up to the user to configure from the export panel
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                 # Create lod .mesh files and generate LOD XML to the original .mesh.xml
05532                 if len(lod_generated) > 0:
05533                     # 'manual' means if the geometry gets loaded from a
05534                     # different file that this LOD list references
05535                     # NOTE: This is the approach at the moment. Another option would be to
05536                     # references to the same vertex indexes in the shared geometry. But the
05537                     # decimate approach wont work with this as it generates a fresh geometry.
05538                     doc.start_tag('levelofdetail', {
05539                         'strategy'  : 'default',
05540                         'numlevels' : str(len(lod_generated) + 1), # The main mesh is + 1 (kind of weird Ogre logic)
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                         # 'value' is the distance this LOD kicks in for the 'Distance' strategy.
05553                         doc.leaf_tag('lodmanual', {
05554                             'value'    : str(lod['distance']),
05555                             'meshname' : lod_ob_temp.data.name + ".mesh"
05556                         })
05557 
05558                         # Delete temporary LOD object.
05559                         # The clone meshes will be deleted later.
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             # Delete temporary LOD object
05567             delete_object(ob_copy)
05568             del ob_copy
05569 
05570             # Delete temporary data/mesh objects
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                     # if we found a deformable bone,
05592                     if bone.bone.use_deform:
05593                         # visit all ancestor bones and mark them "output enabled"
05594                         parBone = bone.parent
05595                         while parBone:
05596                             boneOutputEnableFromName[ parBone.name ] = True
05597                             parBone = parBone.parent
05598                     else:
05599                         # non-deformable bone, no output
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: # allows other vertex groups, not just armature vertex groups
05615                                 bnidx = boneIndexFromName[ vg.name ] # find_bone_index(copy,arm,vgroup.group)
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         # Updated June3 2011 - shape animation works
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                         # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
05648                         #'index' : str(sidx-1),
05649                         #'index' : '0',
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                     #for i,p in enumerate( skey.data ):
05657                     #x,y,z = p.co - ob.data.vertices[i].co
05658                     #x,y,z = swap( ob.data.vertices[i].co - p.co )
05659                     #if x==.0 and y==.0 and z==.0: continue        # the older exporter optimized this way, is it safe?
05660                     doc.leaf_tag('poseoffset', {
05661                             'x' : '%6f' % x,
05662                             'y' : '%6f' % y,
05663                             'z' : '%6f' % z,
05664                             'index' : str(vidx)     # is this required?
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                                 # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
05688                                 #'index' : str(idx)
05689                                 #'index' : '0'
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):#thanks to Vesa
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         ## Clean up and save
05712         #bpy.context.scene.meshes.unlink(mesh)
05713         if cleanup:
05714             #bpy.context.scene.objects.unlink(copy)
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() # reported by Reyn
05724         f.close()
05725 
05726         if logging:
05727             print('      - Created .mesh.xml at', timer_diff_str(start), "seconds")
05728 
05729     # todo: Very ugly, find better way
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() # reported by jakob
05737 
05738     replaceInplace(xmlfile, '__TO_BE_REPLACED_VERTEX_COUNT__' + '"', str(numverts) + '"' )#+ ' ' * (ls - lr))
05739     del(replaceInplace)
05740 
05741     # Start .mesh.xml to .mesh convertion tool
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 ## Jmonkey preview
05765 ## todo: remove jmonkey
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)#, stdin=subprocess.PIPE)
05797     return proc
05798 
05799 ## realXtend Tundra preview
05800 ## todo: This only work if the custom py script is enabled in Tundra
05801 ## It's nice when it works but PythonScriptModule is not part of the
05802 ## default Tundra distro anymore, so this is atm kind of dead.
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 # restore selection
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 ## Server object to talk with realXtend Tundra with UDP
05911 ## Requires Tundra to be running a py script.
05912 
05913 class Server(object):
05914     def stream( self, o ):
05915         b = pickle.dumps( o, protocol=2 ) #protocol2 is python2 compatible
05916         #print( 'streaming bytes', len(b) )
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             #print( 'Distance', dist )
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 ):   # 153 bytes per object + n bytes for animation names and weights
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 = {}    # animation-name : weight
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      # armature without proper NLA setup
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    # max is 13 objects to stay under 2048 bytes
05987         return msg
05988 
05989     def __init__(self):
05990         import socket
05991         self.buffer = []    # cmd buffer
05992         self.socket = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)   # UDP
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)    # not threadsafe
06010             else:    # threadsafe start
06011                 now = time.time()
06012                 if now - prev > 0.066:            # don't flood Tundra
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()          # thread release
06021                     self.stream( msg )            # releases GIL?
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():    # must block to be safe
06041                 time.sleep(0.0001)            # wait for unlock
06042         else: pass #time.sleep(0.033) dont block
06043 
06044     _handle = None
06045     def setup_callback( self, context ):        # TODO replace with a proper frame update callback
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                         # PRE_VIEW, POST_VIEW, POST_PIXEL
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) # up to 256
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')     # assume user has 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         #cmd += ['--file', '/tmp/rex/preview.txml']     # tundra2.1.2 bug loading from --file ignores entity ID's
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 ## More UI
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             #CONFIG['TOUCH_TEXTURES'] = False
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         #layout.separator()
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 ## Toggle button for blender2ogre UI panels
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  #restore_minimal_interface()
06419     BLENDER_DEFAULT_HEADER = _header_
06420 
06421     @classmethod
06422     def poll(cls, context):
06423         return True
06424 
06425     def invoke(self, context, event):
06426         #global _header_
06427         if OgreToggleInterfaceOp.TOGGLE: #_header_:
06428             print( 'ogre.toggle_interface ENABLE' )
06429             bpy.utils.register_module(__name__)
06430             #_header_ = bpy.types.INFO_HT_header
06431             try: bpy.utils.unregister_class(_header_)
06432             except: pass
06433             bpy.utils.unregister_class( INFO_HT_microheader )   # moved to custom header
06434             OgreToggleInterfaceOp.TOGGLE = False
06435         else:
06436             print( 'ogre.toggle_interface DISABLE' )
06437             #bpy.utils.unregister_module(__name__); # this is not safe, can segfault blender, why?
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    # STILL REQUIRED?
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     #if not hasattr( bpy.ops.ogre..   #always true
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     #bpy.utils.register_module(__name__)    ## do not load all the ogre panels by default
06482     #_header_ = bpy.types.INFO_HT_header
06483     #bpy.utils.unregister_class(_header_)
06484     restore_minimal_interface()
06485 
06486     # only test for Tundra2 once - do not do this every panel redraw ##
06487     if os.path.isdir( CONFIG['TUNDRA_ROOT'] ): _USE_TUNDRA_ = True
06488     else: _USE_TUNDRA_ = False
06489     #if os.path.isdir( CONFIG['JMONKEY_ROOT'] ): _USE_JMONKEY_ = True
06490     #else: _USE_JMONKEY_ = False
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     # If the addon is disabled while the UI is toggled, reset it for next time.
06511     # "Untoggling" it by setting the value to True seems a bit counter-intuitive.
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     # This seems to be not registered by the time this function is called.
06516     #bpy.utils.unregister_class(PopUpDialogOperator)
06517 
06518 
06519 ## Blender world panel options for EC_SkyX creation
06520 ## todo: EC_SkyX has changes a bit lately, see that
06521 ## all these options are still correct and valid
06522 ## old todo (?): Move to tundra.py
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 ## Sky UI panel
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): # only one directory is allowed to hold shader programs
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()#.decode('utf-8')
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():  # only cg shaders use the include macro?
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 = {} # cg files may use #include something.cg
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 ## Ogre Material object(s) that is utilized during export stages
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 )  # OgreProgram.PROGRAMS
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  # pick up program params
06684         tex = None  # pick up texture_unit options, require "texture" ?
06685         for line in self.data.splitlines():
06686             #print( line )
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': # ignore unnamed texture units
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:   # (not used)
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('}')     # if this fails, the parser choked
06756 
06757         #print( self.techniques )
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         ## chop up .material file, find all material defs ####
06791         mats = []
06792         mat = []
06793         skip = False    # for now - programs must be defined in .program files, not in the .material
06794         for line in self.data.splitlines():
06795             if not line.strip(): continue
06796             a = line.split()[0]             #NOTE ".split()" strips white space
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                 #print( '--OLD MATERIAL--')
06813                 #print( self.ALL_MATERIALS[ omat.name ].data )
06814                 #print( '--NEW MATERIAL--')
06815                 #print( omat.data )
06816             self.materials[ omat.name ] = omat
06817             self.ALL_MATERIALS[ omat.name ] = omat
06818             if omat.vertex_programs or omat.fragment_programs:  # ignore materials without programs
06819                 self.ENUM_ITEMS.append( (omat.name, omat.name, url) )
06820 
06821     @classmethod # only call after parsing all material scripts
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             #update=callback
06828         )
06829 
06830 ## Image/texture proecssing
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         ## enforce max size ##
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         #if (texture.image.use_convert_format and texture.image.convert_format == 'dds') or CONFIG['FORCE_IMAGE_FORMAT'] == 'dds':
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 ## NVIDIA texture compress documentation
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 # top level material
06962         self.path = path         # copy textures to 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: # submaterials
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 )        # returns texture_slot objects (CLASSIC MATERIAL)
07018         usealpha = False #mat.ogre_depth_write
07019         for slot in slots:
07020             #if slot.use_map_alpha and slot.texture.use_alpha: usealpha = True; break
07021             if (slot.texture.image is not None) and (slot.texture.image.use_alpha): usealpha = True; break
07022 
07023         ## force material alpha to 1.0 if textures use_alpha?
07024         #if usealpha: alpha = 1.0    # let the alpha of the texture control material alpha?
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:        # fall back to basic material
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:        # fall back to basic material
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:     # requested by Borris
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' # pretty printing
07051 
07052         if mat.offset_z:
07053             M += indent(3, 'depth_bias %s'%mat.offset_z )
07054 
07055         for name in dir(mat):   #mat.items() - items returns custom props not pyRNA:
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' # pretty printing
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, '}' )    # end pass
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         #if slot: print(dir(slot))
07088         if slot and not slot.use: return ''
07089 
07090         path = self.path    #CONFIG['PATH']
07091 
07092         M = ''; _alphahack = None
07093         if not name: name = ''      #texture.name   # (its unsafe to use texture block name)
07094         M += indent(3, 'texture_unit %s' %name, '{' )
07095 
07096         if texture.library: # support library linked textures
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         # TODO - hijack nodes for better control?
07134         if slot:        # classic blender material slot options
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             #if slot.use_map_emission:    # problem, user will want to use emission sometimes
07151             if slot.use_from_dupli:    # hijacked again - june7th
07152                 M += indent(4, 'rotate_anim %s' %slot.emission_color_factor )
07153             if slot.use_map_scatter:    # hijacked from volume shaders
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     # TODO - fix this hack if/when slots support pyRNA
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             # Copy texture only if newer
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 )   # calls nvconvert if required
07212 
07213         return M
07214 
07215 class TextureUnit(object):
07216     colour_op = {
07217         'MIX'       :   'modulate',        # Ogre Default - was "replace" but that kills lighting
07218         'ADD'     :   'add',
07219         'MULTIPLY' : 'modulate',
07220         #'alpha_blend' : '',
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',        # best match?
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         #if ob.find_armature():
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 ## Public API (continued)
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) # supports blender library linking
07321     M = '// %s generated by blender2ogre %s\n\n' % (mat.name, VERSION)
07322 
07323     M += 'material %s \n{\n' % safename # start material
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', '{' ) # technique GLSL, CG
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, '}' )      # end technique
07345     M += '}\n'                # end material
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 ):   # recursive
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     ## setup from MaterialScripts.reset_rna( callback=bpyShaders.on_change_parent_material )
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())           # this breaks if the user renames the node - TODO improve me
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 = {}      # funky, blender259 had this in order, now blender260 has random order
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 ):    # currently not used
07513         #print('bpyShaders.get_or_create_texture_nodes( %s, %s )' %(mat,n))
07514         assert mat.node_tree    # must call create_material_passes first
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         #print('bpyShaders.create_material_passes( %s, %s )' %(mat,n))
07535         mat.use_nodes = True
07536         tree = mat.node_tree    # valid pointer now
07537 
07538         nodes = bpyShaders.get_subnodes( tree, 'MATERIAL' )  # assign base 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         #mat.use_nodes = False  # TODO set user material to default output
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         #print('bpyShaders.create_texture_nodes( %s )' %mat)
07559         assert mat.node_tree    # must call create_material_passes first
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 = []     # other inputs mess up material preview #
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             # the top level node_tree
07598         mat = topmat.active_node_material        # the currently selected sub-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             # the top level node_tree
07624         mat = topmat.active_node_material        # the currently selected sub-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             #row = box.row()
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 # only allow on pass1 and higher
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 )  # gets by name
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 )   # assume toplevel
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                         #row.label( text=name )
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 ## Blender addon main entry point.
07731 ## Allows directly running by "blender --python blender2ogre.py"
07732 
07733 if __name__ == "__main__":
07734     register()


naoqi_tools
Author(s): Mikael Arguedas
autogenerated on Sat Jun 8 2019 20:30:21