1
$\begingroup$

I studied a lot of examples but with no success. The script I'm working on is calculating the area of the selected object faces (or the whole area), and then I would like to call the operator to store the area. The area should be stored in a list of simple floats, which then I would like to save by another operator. However, I can't force python to create a list of values. I analyzed UIList "simple" example for materials list, but is was not too simple at all. Also, I analyzed solutions found here: http://blenderartists.org/forum/showthread.php?343863-List-of-strings-as-scene-variable&highlight=UI+list+from+python http://blenderartists.org/forum/showthread.php?349417-How-to-add-custom-list-property&highlight=UI+list+from+python http://blenderartists.org/forum/showthread.php?376910-UI-list-for-actions&highlight=UI+list+from+python How can I store and retrieve a custom list in a blend file? What ui would work for choosing from a long, long list? and few more. No solution works for me. Obviously I miss something. The question is: how to do a list of float values added to it by pressing a button. The list should expand to for example at most 10 values and then a scrollbar should appear, similar to other lists in Blender UI. I don't need any searching, no clicking on the list, no selecting items, nothing. The list of values should act as a history of measurements, that's it. And I would like to have data in memory in a list form, to save it on demand to a text file (but this is not important here).

So far I created script like this:

import bpy, bmesh
from bpy.types import ( Operator, Panel, UIList )
from bpy.props import ( IntProperty, CollectionProperty )

# returns -1 if object has no area property
def calcArea(self,obj):
    area = 0.0
    #.... calculating the area, unimportant
    return(area)

# this was supposed to be a list, but it is not working
class AreaItemList(UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.prop(item, "name", text="", emboss=False, icon_value=icon)
        elif self.layout_type in {'GRID'}:
            pass

# this class should actually store area in the list
# but right now I stored only one area in custom data
# I should get rid of this when I manage to store data in a list    
class AreaStorage(Operator):
    bl_idname = "area.storage"
    bl_label  = "Area Storage"
    def execute(self,context):
        ob = context.active_object  
        area = calcArea(self,ob)
        ob['area'] = area
        return {'FINISHED'}

class AreaCalculator(Panel):
    """Some text"""
    bl_idname = "area.calculator"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "aaa"
    bl_label    = "bbb"

# I've put calculating area code also in draw panel class
# because then simple selecting the object is showing a result
# in the panel. Dunno how to make this better.
    def draw(self, context):
        ob = context.active_object  
        if ob is not None and ob.type == 'MESH' and (ob.mode == 'OBJECT' or ob.mode == 'EDIT'):
            area = calcArea(self,ob)
            layout = self.layout
            if area >= 0.0:
                r = layout.row(align=True)
                b = r.box()
                b.label(str(context.active_object.name)+":")
                b.label(str(round(area,6))+'m²')
                if ob.scale[0] != 1.000 or ob.scale[1] != 1.000 or ob.scale[2] != 1.000:
                    b.label('Set scale to 1.0!!!',icon='ERROR')
            else:
                if area < 0.0:
                    r = layout.row(align=True)
                    b = r.box()
                    b.label(str(context.active_object.name))
                    b.label("has no polygons!")
            r = layout.row(align=False)
            r.operator("area.storage",text="Store Area",icon='FACESEL')

  # and here is the list, which DOES NOT WORK
  # simply I have no idea how to generate proper data for it
            s = context.scene
            layout.template_list("AreaItemList", "", s, "areas", s, "area_index")
        else:
            layout = self.layout
            r = layout.row(align=False)
            r.label("Select a mesh.")

# this was written as one of the examples from network, did not
# work either.
class Area(bpy.types.PropertyGroup):
    id = IntProperty()

def register():
# here is a mess
    bpy.utils.register_class(Area)
    bpy.types.Scene.areas = CollectionProperty(type=Area)
    bpy.types.Scene.area_index = IntProperty()
# those two classess are ok so far
    bpy.utils.register_class(AreaStorage)
    bpy.utils.register_class(AreaCalculator)

def unregister():
    bpy.utils.unregister_class(AreaStorage)
    bpy.utils.unregister_class(AreaCalculator)
    del bpy.types.Scene.area_index
    del bpy.types.Scene.areas
    bpy.utils.unregister_class(Area)


if __name__ == "__main__":
    register()

So it seems that I did not understand how to create lists. I do not need any custom listing, nothing fancy, and I did not found working example of this very simple case. Please help.

$\endgroup$
1
$\begingroup$

scene.areas is defined but not populated.

The following will add to the areas list.

class AreaStorage(Operator):
    bl_idname = "area.storage"
    bl_label  = "Area Storage"
    def execute(self,context):
        ob = context.active_object  
        scene = context.scene
        area = calcArea(self,ob)
        ob['area'] = area
        ar = scene.areas.get(ob.name)
        # make sure it's only stored once.
        if ar is None:
            ar = scene.areas.add()
            ar.name = ob.name
        ar.id = 3 # what should id be?
        # make it the active area
        scene.area_index = scene.areas.find(ob.name)
        return {'FINISHED'}

and remember to register AreaItemList

Could also just repopulate the entire list

import bpy
context = bpy.context
scene = context.scene

# list of all objects in scene with area custom prop.
aobs = [o for o in scene.objects if 'area' in o.keys()]

# clear and populate scene.areas
scene.areas.clear()
for o in aobs:
    ao = scene.areas.add()
    ao.name = o.name
    ao.id = 3 # some id.    
$\endgroup$
  • $\begingroup$ Thank you. This solution works for me. I could later add Clear button which clears the list, etc. I changed the line ar.name = ob.name to something like this: ar.name = str(round(area,4))+' ('+ob.name+')' which shows the area of the object on the list. Small improvement is still needed: how to put elements in that list in reversed order? I can't reverse it like and array and the collection seems to have tuples in the list: first my string, then something: '0.5615 (Plane.001)', bpy.data.scenes['Scene'].areas[0]. How to build this list in reversed order? $\endgroup$ – piotao Apr 19 '16 at 12:35
  • 1
    $\begingroup$ Could put layout.label("%.4f" % context.scene.objects[item.name]["area"]) in the list UI layout rather than concatenating area and name. Have a look at the def filter_items in UIList template (text editor) to reorder the list. $\endgroup$ – batFINGER Apr 19 '16 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.