Yet Another Blender Egg Exporter (YABEE)

Documentation for people wanting to work on the plugin.

It is going to take more than code comments if you want contributors to get up to speed quickly.

This is a too vague concept.
Docstrins/API Reference - is “Documentation for people wanting to work on the plugin.”
TODO - is “Documentation for people wanting to work on the plugin.”
Bugtracker - is “Documentation for people wanting to work on the plugin.”
Flowchart - is “Documentation for people wanting to work on the plugin.”
Even versions history - is “Documentation for people wanting to work on the plugin.”

Ok, if you don’t like the word “coments” let name it “docstrngs” and use sphinx. :wink: M?

Docstrings/API Reference is what this plugin could use.

Ok. So how about using sphinx + autodoc? It much better than *.docx imho
For example this html is what I have got after a few quick experiments

I’m attempting to create an object that includes a collision object (modelled in Blender, and having the relevant logic tag, I believe) that is a animated by an armature. However, when I export the project the collision object seems to be exported as normal geometry, not collision geometry.

Am I doing something incorrectly, is this a limitation, either in YABEE or Panda, or is there some other issue?

I’m not sure of my version of YABEE, but it’s one that works with Blender 2.67b, I believe.

If I understand, you trying to make animated collision geometry. If so, then it’s Panda’s restriction. Possible to create a Collision solid out of actor?

Ah, fair enough, and thank you for the clarification.

Last time I participated in PyWeek, I found a number of features missing, which I added in my local version. I finally got around to cleaning up my patch today, and I’d like to submit it for inclusion.

diff -r 8a5e858c3262 yabee_libs/
--- a/yabee_libs/	Sun Mar 03 16:29:05 2013 +0400
+++ b/yabee_libs/	Sun Feb 09 23:59:22 2014 +0100
@@ -75,9 +75,14 @@
         egg_str = ''
         if self.object:
             for prop in
-                if in ('Collide', 'ObjectType'):
+                normalized ='_', '-')
+                if normalized in ('collide', 'objecttype'):
                     vals = ('  ' * level,, prop.value)
                     egg_str += '%s<%s> { %s }\n' % vals
+                elif normalized in ('collide-mask', 'from-collide-mask', 'into-collide-mask', 'bin', 'draw-order'):
+                    vals = ('  ' * level,, prop.value)
+                    egg_str += '%s<Scalar> %s { %s }\n' % vals
                     vals = ('  ' * level, eggSafeName(, eggSafeName(prop.value))
                     egg_str += '%s<Tag> %s { %s }\n' % vals
@@ -509,7 +514,7 @@
                 mat =[face.material_index]
                 tex_idx = 0
                 for tex in [tex for tex in mat.texture_slots if tex]:
-                    if ((tex.texture_coords == 'UV') 
+                    if (tex.texture_coords in ('UV', 'GLOBAL')
                          and (not tex.texture.use_nodes)
                          and (mat.use_textures[tex_idx])):
                             if tex.texture.type == 'IMAGE' and tex.texture.image and tex.texture.image.source == 'FILE':
@@ -932,6 +937,13 @@
         mat_str += '  "' + convertFileNameToPanda(params['path']) + '"\n'
         for scalar in params['scalars']:
             mat_str += ('  <Scalar> %s { %s }\n' % scalar)
+        if 'transform' in params and len(params['transform']) > 0:
+            mat_str += '  <Transform> {\n'
+            for ttype, transform in params['transform']:
+                transform = ' '.join(map(str, transform))
+                mat_str += '    <%s> { %s }\n' % (ttype, transform)
+            mat_str += '  }\n'
         mat_str += '}\n\n'
     return mat_str
diff -r 8a5e858c3262 yabee_libs/
--- a/yabee_libs/	Sun Mar 03 16:29:05 2013 +0400
+++ b/yabee_libs/	Sun Feb 09 23:59:22 2014 +0100
@@ -24,10 +24,10 @@
     def is_slot_valid(self, tex):
         if ((tex) and (not tex.texture.use_nodes)):
-            if tex.texture_coords == 'UV':
+            if tex.texture_coords in ('UV', 'GLOBAL'):
                 if tex.texture.type == 'IMAGE' and tex.texture.image and tex.texture.image.source == 'FILE':
                     return True
         return False
     def get_valid_slots(self, slots):
@@ -48,7 +48,8 @@
         """ Collect images from the UV images and Material texture slots 
         tex_list structure:
             image_name: { 'scalars': [(name, val), (name, val), ...],
-                          'path': 'path/to/texture'
+                          'path': 'path/to/texture',
+                          'transform': [(type, val), (type, val), ...]
         tex_list = {}
@@ -77,22 +78,35 @@
                                     tex_list[f.image.yabee_name]['scalars'].append(('envtype', 'MODULATE'))
                                     if name:
                                         tex_list[f.image.yabee_name]['scalars'].append(('uv-name', name))
                 # General textures
                 for f in
                     if and f.material_index < len(
                         valid_slots = self.get_valid_slots([f.material_index].texture_slots)
                         alpha_tex = self.get_alpha_slot(valid_slots)
                         alpha_map_assigned = False
                         for tex in valid_slots:
-                            if tex.uv_layer:
-                                uv_name = tex.uv_layer
-                                if not [ for uv in].index(uv_name):
-                                    uv_name = ''
-                            else:
-                                uv_name = ''[0].name
+                            scalars = []
+                            transform = []
+                            if tex.texture_coords == 'UV':
+                                if tex.uv_layer:
+                                    uv_name = tex.uv_layer
+                                    if not [ for uv in].index(uv_name):
+                                        uv_name = ''
+                                else:
+                                    uv_name = ''[0].name
+                                if uv_name:
+                                    scalars.append(('uv-name', uv_name))
+                            elif tex.texture_coords == 'GLOBAL':
+                                scalars.append(('tex-gen', 'WORLD_POSITION'))
+                                # Scale to Panda's coordinate system
+                                transform.append(('Translate', (1, 1, 1)))
+                                transform.append(('Scale', (0.5, 0.5, 0.5)))
                             #if not in list(tex_list.keys()):
                             if not tex.texture.yabee_name in list(tex_list.keys()):
@@ -104,47 +118,79 @@
                                     if tex.use_map_specular:
                                         envtype = 'GLOSS'
                                     if tex.use_map_alpha and not tex.use_map_color_diffuse:
-                                        continue;
-                                    t_path = bpy.path.abspath(tex.texture.image.filepath)
+                                        continue
+                                    scalars.append(('envtype', envtype))
+                                    t_path = tex.texture.image.filepath
                                     if self.copy_tex:
                                         t_path = save_image(tex.texture.image, self.file_path, self.tex_path)
                                     #tex_list[] = {'path': t_path,
-                                    #                              'scalars': [] }
-                                    #tex_list[]['scalars'].append(('envtype', envtype))
+                                    #                              'scalars': scalars, 'transform': transform }
                                     tex_list[tex.texture.yabee_name] = {'path': t_path,
-                                                                  'scalars': [] }
-                                    tex_list[tex.texture.yabee_name]['scalars'].append(('envtype', envtype))
+                                                                        'scalars': scalars, 'transform': transform }
-                                        #tex_list[]['scalars'].append(('minfilter', 'LINEAR_MIPMAP_LINEAR'))
-                                        #tex_list[]['scalars'].append(('magfilter', 'LINEAR_MIPMAP_LINEAR'))
-                                        tex_list[tex.texture.yabee_name]['scalars'].append(('minfilter', 'LINEAR_MIPMAP_LINEAR'))
-                                        tex_list[tex.texture.yabee_name]['scalars'].append(('magfilter', 'LINEAR_MIPMAP_LINEAR'))
-                                    wrap_mode = 'REPEAT'
-                                    if(tex.texture.extension == 'CLIP'):
-                                        wrap_mode = 'CLAMP'
-                                    #tex_list[]['scalars'].append(('wrap', wrap_mode))     
-                                    tex_list[tex.texture.yabee_name]['scalars'].append(('wrap', wrap_mode))     
+                                        scalars.append(('minfilter', 'LINEAR_MIPMAP_LINEAR'))
+                                        scalars.append(('magfilter', 'LINEAR_MIPMAP_LINEAR'))
+                                    # Process wrap modes.
+                                    if(tex.texture.extension == 'EXTEND'):
+                                        scalars.append(('wrap', 'CLAMP'))
+                                    elif(tex.texture.extension in ('CLIP', 'CLIP_CUBE')):
+                                        scalars.append(('wrap', 'BORDER_COLOR'))
+                                        scalars.append(('borderr', '1'))
+                                        scalars.append(('borderg', '1'))
+                                        scalars.append(('borderb', '1'))
+                                        scalars.append(('bordera', '1'))
+                                    elif(tex.texture.extension in ('REPEAT', 'CHECKER')):
+                                        scalars.append(('wrap', 'REPEAT'))
+                                    # Process coordinate mapping using a matrix.
+                                    mappings = (tex.mapping_x, tex.mapping_y, tex.mapping_z)
+                                    if mappings != ('X', 'Y', 'Z'):
+                                        matrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
+                                        for col, mapping in enumerate(mappings):
+                                            if mapping == 'Z' and tex.texture_coords == 'UV':
+                                                # Z is not present when using UV coordinates.
+                                                mapping = 'NONE'
+                                            if mapping == 'NONE':
+                                                # It seems that Blender sets Z to 0.5 when it is not present.
+                                                matrix[4 * 3 + col] = 0.5
+                                            else:
+                                                row = ord(mapping) - ord('X')
+                                                matrix[4 * row + col] = 1
+                                        transform.append(('Matrix4', matrix))
+                                    # Process texture transformations.
+                                    if(tuple(tex.scale) != (1.0, 1.0, 1.0)):
+                                        # Blender scales from the centre, so shift it before scaling and then shift it back.
+                                        transform.append(('Translate', (-0.5, -0.5, -0.5)))
+                                        transform.append(('Scale', tex.scale))
+                                        transform.append(('Translate', (0.5, 0.5, 0.5)))
+                                    if(tuple(tex.offset) != (0.0, 0.0, 0.0)):
+                                        transform.append(('Translate', tex.offset))
                                     if(envtype == 'MODULATE'):
                                         if(alpha_tex and not alpha_map_assigned):
                                             alpha_map_assigned = True
-                                            alpha_path = bpy.path.abspath(alpha_tex.texture.image.filepath)
+                                            alpha_path = alpha_tex.texture.image.filepath
                                             if self.copy_tex:
                                                 alpha_path = save_image(alpha_tex.texture.image, self.file_path, self.tex_path)
-                                            #tex_list[]['scalars'].append(('alpha-file', '\"%s\"' % alpha_path))
-                                            tex_list[tex.texture.yabee_name]['scalars'].append(('alpha-file', '\"%s\"' % alpha_path))
-                                            tex_list[tex.texture.yabee_name]['scalars'].append(('alpha-file-channel', '4'))
+                                            scalars.append(('alpha-file', '\"%s\"' % alpha_path))
+                                            scalars.append(('alpha-file-channel', '4'))
                                             if([f.material_index].game_settings.alpha_blend == 'CLIP'):
-                                                #tex_list[]['scalars'].append(('alpha', 'BINARY'))
-                                                tex_list[tex.texture.yabee_name]['scalars'].append(('alpha', 'BINARY'))
-                                    if uv_name:
-                                        #tex_list[]['scalars'].append(('uv-name', uv_name))
-                                        tex_list[tex.texture.yabee_name]['scalars'].append(('uv-name', uv_name))
+                                                scalars.append(('alpha', 'BINARY'))
+                                            elif([f.material_index].game_settings.alpha_blend == 'ADD'):
+                                                scalars.append(('blend', 'add'))
                                 #    print('ERROR: can\'t get texture image on %s.' %
         return tex_list
@@ -289,7 +335,8 @@
                         #tex_list[key] = (uv_name, img_path, envtype)
                         # Texture information dict
                         tex_list[key] = {'path': img_path,
-                                         'scalars': [] }
+                                         'scalars': [],
+                                         'transform': [] }
                         tex_list[key]['scalars'].append(('envtype', envtype))
                         if uv_name:
                             tex_list[key]['scalars'].append(('uv-name', uv_name))

The patch changes the following things:

  1. Exports some properties as scalar: collide-mask, into-collide-mask, from-collide-mask, bin, draw-order.
  2. No longer writes out absolute paths for texture references. This behaviour must have been a mistake. There are more calls to abspath in the yabee source - not sure which are appropriate and which should be removed as well.
  3. Supports texture offset and size. This is particularly useful because it allows scaling layered texture octaves without requiring multiple UV sets.
  4. Supports the ‘Global’ texture mapping mode to allow texture coordinate generation without the use of UVs.
  5. Supports changing the X/Y/Z mapping (particularly useful with ‘Global’ but works in ‘UV’ mode nonetheless)
  6. Supports the ‘Extend’ and ‘Clip’ extension modes.
  7. Supports the ‘Add’ alpha-blend mode, useful for effects like the rays in this shot.

I’ve used some pretty esoteric combinations of transformations, and I made sure that every case looked identical to Blender Render (NB. not all of these options are actually supported by Blender GLSL), though there’s always a possibility that I missed something.

I’d appreciate it if you could incorporate the patch into the next release. You might be seeing another instancing-related patch from me later before the next PyWeek.

Thanks for the patch! I merged it with my current version. As soon as I solve some troubles with animation which appeared after changes in the armature exporting, I’ll make new revision.

HI ninth,

Here is a small patch that corrects saving of TextureStage names as they are called in Blender. Currently the first stage is renamed to default.

diff --git a/yabee_libs/ b/yabee_libs/
index 73572f3..e18317e 100644
--- a/yabee_libs/
+++ b/yabee_libs/
@@ -120,6 +120,7 @@ class SimpleTextures():
                                     if tex.use_map_alpha and not tex.use_map_color_diffuse:
                                     scalars.append(('envtype', envtype))
+                                    scalars.append(('saved-result', '1'))
                                     t_path = tex.texture.image.filepath
                                     if self.copy_tex:

Do you have any plans for supporting vertex groups and automatically separation to different vertex groups when more than 1 material is applied?

Best Regards,


About groups. Panda splits model to the different geoms, which based on the materials/textures applied to it. For example, if you have a model with the two materials applied to the different faces, then you get something like this in Panda:

PandaNode models
  ModelRoot untitled.egg
    GeomNode Cube (2 geoms: S:(MaterialAttrib))

Hi ninth,

Just to note - there is nothing wrong with two(or more) materials over 1 mesh.
The problem is when you need(want) to manipulate complex meshes with more materials. Replacing, hiding or whatever you want of different vertex groups is easier, than manipulating of geoms. And combining this with automatically separating of mesh to vertex groups by their materials(when more than 1 is applied), made some advanced tasks just a few lines of code - instead of huge an complex one.

Best Regards,

Hmm. Not sure that it’s possible in the EGG format. Can you give a link to the appropriate EGG syntax if I am wrong?

Hi ninth,

It is not something new. It is just -ing of polygons by their materials. When more than one material is applied - all polygons with same material are grouped in group with name for example -> ParentGroup_Material. After that you have easy access to these polygons.

Best Regards,

If you decide to implement that, please make that an optional feature that’s off by default. It doesn’t seem to be a straightforward way to map Blender vertex groups to Panda; the point of vertex groups is that parts of the model can have different materials, not that they should be considered as separate meshes or manipulatable as such. In my case, it could have negative effects on my performance since I have models with quite a number of vertex groups that really should go into the same GeomNode.

By the way, ninth, do you happen to know why Blender duplicates my scene whenever Yabee gets an exception?

HI rdb,

Yes, I agree with you in almost all points. But there are also specific tasks that are possible easily only in this way. ninth, please let me know if you plan or not to add this feature to yabee.

Best Regards,

2 deflected
If we’ll be use , then actually we’ll be split the mesh to the several geom nodes. It can affect performance I think. But, well if make it optionaly it can be useful. I’ll see how it can be implemented.

2 rdb
Yabee duplicates scene before exporting. It’s works like in the Chicken and provides safety applying modifiers. If Yabee gets exception then the scene copy stays unremoved, but original scene stays untouched.


I want to create a set of reusable character model assets using Blender and I want to include some eye animations in my model.

However, in addition to using armatures, my eye animations uses a lattice modifier because the eyes of my model are not a spherical eyes.

This video demonstrates a non-spherical cartoon eye in action

The problem I’m concerned about is that YABEE has the export option of applying modifiers.

If it applies the lattice modifiers before writing armature animation, then the animation would be wrong. If it applied the lattice modifier after writing animation, the animation would likely be wrong as well.

If I do not apply modifiers, there is still the possibility that my animation would be affected.

ninth, I need to know if YABEE can properly export the type of animation found in that video. I haven’t tried exporting such an animation yet because I prefer not to do a leap of faith just to find out it was indeed a pitfall.

I think it not possible - YABEE recognize only two type of animation: armature and shapekeys.
You can try to combine shapekeys (shape of the eye) and moving of the eye texture to provide effects like on your video, or may be pure shapekeys if you need only a few types of the eye positions.

Hmm, if you are working on a new version of YABEE, I would like to see the ability to properly export an eye controlled by an armature but has its shape maintained by that of a lattice.

If you are not working on a new version of YABEE, there is now at least one reason why you should.