Yet Another Blender Egg Exporter (YABEE)

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/egg_writer.py
--- a/yabee_libs/egg_writer.py	Sun Mar 03 16:29:05 2013 +0400
+++ b/yabee_libs/egg_writer.py	Sun Feb 09 23:59:22 2014 +0100
@@ -75,9 +75,14 @@
         egg_str = ''
         if self.object:
             for prop in self.object.game.properties:
-                if prop.name in ('Collide', 'ObjectType'):
+                normalized = prop.name.lower().replace('_', '-')
+
+                if normalized in ('collide', 'objecttype'):
                     vals = ('  ' * level, prop.name, 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.name, prop.value)
+                    egg_str += '%s<Scalar> %s { %s }\n' % vals
                 else:
                     vals = ('  ' * level, eggSafeName(prop.name), eggSafeName(prop.value))
                     egg_str += '%s<Tag> %s { %s }\n' % vals
@@ -509,7 +514,7 @@
                 mat = self.obj_ref.data.materials[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/texture_processor.py
--- a/yabee_libs/texture_processor.py	Sun Mar 03 16:29:05 2013 +0400
+++ b/yabee_libs/texture_processor.py	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 obj.data.polygons:
                     if obj.data.uv_textures and f.material_index < len(obj.data.materials):
                         valid_slots = self.get_valid_slots(obj.data.materials[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 [uv.name for uv in obj.data.uv_textures].index(uv_name):
-                                    uv_name = ''
-                            else:
-                                uv_name = '' #obj.data.uv_textures[0].name
-                    
-                            
+                            scalars = []
+                            transform = []
+
+                            if tex.texture_coords == 'UV':
+                                if tex.uv_layer:
+                                    uv_name = tex.uv_layer
+                                    if not [uv.name for uv in obj.data.uv_textures].index(uv_name):
+                                        uv_name = ''
+                                else:
+                                    uv_name = '' #obj.data.uv_textures[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 tex.texture.name in list(tex_list.keys()):
                             if not tex.texture.yabee_name in list(tex_list.keys()):
                                 #try:
@@ -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[tex.texture.name] = {'path': t_path,
-                                    #                              'scalars': [] }
-                                    #tex_list[tex.texture.name]['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 }
+
                                     if(tex.texture.use_mipmap):
-                                        #tex_list[tex.texture.name]['scalars'].append(('minfilter', 'LINEAR_MIPMAP_LINEAR'))
-                                        #tex_list[tex.texture.name]['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[tex.texture.name]['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[tex.texture.name]['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(obj.data.materials[f.material_index].game_settings.alpha_blend == 'CLIP'):
-                                                #tex_list[tex.texture.name]['scalars'].append(('alpha', 'BINARY'))
-                                                tex_list[tex.texture.yabee_name]['scalars'].append(('alpha', 'BINARY'))
-                                    if uv_name:
-                                        #tex_list[tex.texture.name]['scalars'].append(('uv-name', uv_name))
-                                        tex_list[tex.texture.yabee_name]['scalars'].append(('uv-name', uv_name))
+                                                scalars.append(('alpha', 'BINARY'))
+                                            elif(obj.data.materials[f.material_index].game_settings.alpha_blend == 'ADD'):
+                                                scalars.append(('blend', 'add'))
                                 #except:
                                 #    print('ERROR: can\'t get texture image on %s.' % tex.texture.name)
         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/texture_processor.py b/yabee_libs/texture_processor.py
index 73572f3..e18317e 100644
--- a/yabee_libs/texture_processor.py
+++ b/yabee_libs/texture_processor.py
@@ -120,6 +120,7 @@ class SimpleTextures():
                                     if tex.use_map_alpha and not tex.use_map_color_diffuse:
                                         continue
                                     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,
deflected

Thanks!

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,
deflected

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,
deflected

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,
deflected

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.

Ninth

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.

Problem is not only in the YABEE - I don’t know how to realize such ability in the EGG syntax and not sure that it’s possible at all.

Isn’t possible to write the mesh data that results from having a live modifier?

As I understand, It the same thing as the “Applying modifiers”. You will have the modified mesh at static position, and when you trying to animate it with the bone(armature) you get the different result from what you expected.

What you might be able to do is implement the lattice-modifier functionality in Panda (perhaps better in C++ than in Python), then, in Blender, either use dummy objects to indicate the modifier or create a custom exporter (a modification to YABEE, perhaps?) to export the data for use by your Panda lattice-modifier code.

Perhaps it would be a good idea to add a {1} tag to every exported Empty, so that the Empty’s position can’t be flattened away later? Just an idea.

(solved, see last line of post)

Hi,

It appears YABEE is broken for me.

YABEE github/latest dev code:

I tried the version from github (by downloading the tree as zip and installing that as blender addon). It appears to be broken:

http://i.imgur.com/k1NhYt1.png
(doesn’t even finish the export)

YABEE r13.1:

However, YABEE r13.1 is broken for me too. It worked perfectly as long as I didn’t have any shape keys and animations (armature itself is fine), but as soon as I added a simple skeletal animation + an eye blink and mouth shape key (both relative) I only got an empty screen in the panda3d viewer which I have always open up to check on the result. So all I can do is export emptiness from my 3d model now. Feel free to test for yourself: http://www.homeofjones.de/temp/blendpacked.blend

Since that leaves me unable to get any of my humanoids from blender into panda3d, it would be awesome if someone could look into this!

Edit: I was told by the elders of panda3d to use this magic summoning word to attract the yabee-knowledgeable folk: водка

Edit 2: I found out what the problem with 13.1 is: space characters in shape key names. However, YABEE 1.) totally fails to display the error properly, 2.) leaves a stale copy of the scene around on failed export. Those two things should be fixed apart from the actual problem itself.