Wednesday, 29 June 2016

Meshing about in Blender with Python

Part 2 of my adventures in Blender add-on land

A short one tonight as I've been busy with RL annoyances.

In the last post on my Blender Journey when I stopped I had built my first ever Blender add-on, taught it to iterate through a list of selected items and for each one create duplicates that we can then mangle into lower LOD models.

At the moment the results of running it are as follows:-
In this example the High LOD model is shown first (it is two separate objects), the "Make LOD models from selection" button has been clicked, and the associated operator (blender term for function) has been executed. The result as shown is that the first five layers are populated. Layer zero (top left) has the original model, which we are treating as the High LOD. Next to this are layers one and two which contain lower detail models for Medium and Low. In this example, I had already made the models and by naming them appropriately, I allowed the script to find them and move them to the correct layer and not have it generate ones I did not need. Layer three contains a copy of the original renamed to Original_LOWEST and finally layer four includes another copy Original_PHYS. 

This immediately raises a few points. If I have a model for LOW then perhaps I should derive LOWEST and PHYS from that one. Perhaps I do not want a physics model at all.

Also, while my workflow would normally be to start with the High and work down there is often a good case for starting with the Medium and establishing the basic shape before adding smaller details to the High model. So maybe I should allow this too.

The image below is a quick mockup (from Gliffy) of how it could look

Each LOD/model has a row with a set of radio buttons to determine which LOD the selected object(s) should be treated as, and a set of tick boxes for the ones we want to be generated/found and moved. In this example the selected objects would be renamed with an _MED suffix. A copy would be made and any suffix removed, another copy with the _LOW suffix would be made. 

Having thought about the inheriting of LOD models I think it makes the most sense to do this automatically, so I will update the code to derive LOW from MED, LOWEST from LOW. This automatically means that models will inherit from pre-existing LOD models.

Updating the panel

It looks as if my visualisation was somewhat fanciful as Blender does not easily support radio buttons as a set (know better? Please tell me). It does, however, have an enumeration property type and this gives us a similar functionality.

Adding a properties class is a bit fiddly, or at least it seemed so. The result is quite simple, but the journey to getting there took me on more messy routes.
Here, however, is the final UI, which allows me to specify the source model and the targets to create from it.

import bpy

class LODModelProperties(bpy.types.PropertyGroup):
# This is how you make a static enum prop for the scene
    enum_items = (('0','High',''),('1','Medium',''), ('2','Low',''), ('3','Lowest',''), ('4','Physics',''))
    LOD_model_source = bpy.props.EnumProperty(items = enum_items)
    LOD_model_target = bpy.props.EnumProperty(
            name = "LOD Models required",
            description = "LOD Models to be generated",
            items = enum_items,
            options = {"ENUM_FLAG"}

bpy.types.Scene.sl_lod = bpy.props.PointerProperty(type=LODModelProperties)

class CreateLODModelsPanel(bpy.types.Panel):
    bl_idname = "create_lod_models_panel"
    bl_label = "Create Lod Models Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "SecondLife"
    def draw(self, context):
        layout = self.layout
        layout.label("Use selected model as...")
        layout.prop(bpy.context.scene.sl_lod, 'LOD_model_source', expand=True)
        layout.label("Create LOD (shift click multi)")
        layout.prop(bpy.context.scene.sl_lod, 'LOD_model_target', expand=True)

Right now, however, there is no linkage between them. In an ideal world, the target models would automatically exclude the source LOD selected.

We can achieve this using the get/set methods. It took me quite a while to figure this out.
It appears that the property itself is read only. You cannot apply a remove('1') to the set that represents that LODS to generate for example.
The solution is to maintain a parallel data value as a map entry on the property class.

def getter(self):
    return self['value']
def setter(self, value):
    if (value & self['src_value']):
        value ^= self['src_value']
    self['value'] = value

def source_getter(self):
    return self['src_value']
def source_setter(self, value):
    self['src_value'] = value
    if (value & self['src_value']):
        self['value'] ^= value

Now when we select a value in the source radio control, it is automatically removed from the target set if it is set, likewise trying to set it in the target will fail if it corresponds to the source.

That's it for tonight.
Next step is to use out new properties to control the generation because at the moment it still generates all 5 models.

No comments:

Post a Comment