3
$\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 Answer 1

2
$\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$
2
  • $\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, 2016 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, 2016 at 13:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .