2
$\begingroup$

I am trying to execute a line of code, such as: bpy.ops.object.modifier_add(type='SUBSURF') from a string of text.

My code is:

import bpy
import math

print("~~~Separator~~~")

obj_list = []
task_list = []

class ExportSomeData(bpy.types.Operator):
"""Test exporter which just writes hello world"""
bl_idname = "export.some_data"
bl_label = "Export Some Data"

filepath = bpy.props.StringProperty(subtype="FILE_PATH")

@classmethod
def poll(cls, context):
    if not obj_list:
        return False
    else:
        return True

def callMethod(o, name):
    getattr(o, name)()

def execute(self, context):
    global task_list
    file = open(self.filepath, 'r+')
    for line in file:
        task_list.append(line)
        callMethod("I don't know what to put here", line)
    print(str(task_list))
    file.close()

    return {'FINISHED'}

def invoke(self, context, event):
    context.window_manager.fileselect_add(self)
    return {'RUNNING_MODAL'}

class SelectOperator(bpy.types.Operator):
"""Adds """
bl_idname = "scene.select_option"
bl_label = "Multi Select Operator"

@classmethod
def poll(cls, context):
    if not bpy.context.selected_objects:
        return False
    else:
        return True

def execute(self, context):
    global obj_list
    obj_list = []
    for obj in bpy.context.selected_objects:
        obj_list.append(obj)
        print(str(obj))
    print("Objects in the active list: " + str(obj_list))
    return {'FINISHED'}

class TaskOperator(bpy.types.Operator):
"""Subdivide and scale a mesh."""
bl_idname = "scene.task_option"
bl_label = "Multi Task Operator"

@classmethod
def poll(cls, context):
    global obj_list
    if not obj_list:
        return False
    else:
        return True

def execute(self, context):
    global obj_list

    return {'FINISHED'}

class SelectionPanel(bpy.types.Panel):

bl_label = "Multi Selection Options"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'

def draw(self, context):
    layout = self.layout

    row = layout.row()

    split = layout.split()

    col = split.column(align=True)

    'row.operator("mesh.primitive_uv_sphere_add")'

    col.operator("scene.select_option", text = "Set Objects", icon = "MESH_CUBE")

    'row.operator("scene.task_option", text = "Do Task", icon = "LONGDISPLAY")'

    col.operator("export.some_data", text = "Open And Do Task", icon = "FILE")
def register():
bpy.utils.register_class(SelectionPanel)
bpy.utils.register_class(SelectOperator)
bpy.utils.register_class(TaskOperator)
bpy.utils.register_class(ExportSomeData)

def unregister():
bpy.utils.unregister_class(SelectionPanel)
bpy.utils.unregister_class(SelectOperator)
bpy.utils.unregister_class(TaskOperator)
bpy.utils.unregister_class(ExportSomeData)

if __name__ == "__main__":
register()

Below is what is causing me trouble:

def callMethod(o, name):
    getattr(o, name)()

def execute(self, context):
    global task_list
    file = open(self.filepath, 'r+')
    for line in file:
        task_list.append(line)
        callMethod("I don't know what to put here", line)
    print(str(task_list))
    file.close()

    return {'FINISHED'}

line is bpy.ops.object.modifier_add(type='SUBSURF'), but I don't know the object to put for the first parameter. If I am going the wrong direction with this, please correct me.

$\endgroup$
2
$\begingroup$

It looks like you are passing a whole function call to the getattr() function as a string.

getattr() takes an object as first argument and a string a second argument.

What you should do is isolate the object, the function and the params

Once you isolated your object string (ie. str="bpy.ops.object") you can isolate the object with a quick but unsafe myObj = eval(str) or use a safer

import sys

def str_to_class(str):
    return getattr(sys.modules[__name__], str)

myObj = str_to_class("bpy.ops.object") #line added from original code

(Code from https://stackoverflow.com/questions/1176136/convert-string-to-python-class-object)

After that you should call getattr in this fashion:
getattr(myObj, 'myFunction')(params)

In the specific case you presented it would look something like this in explicit form
getattr(bpy.ops.object, "modifier_add")(type='SUBSURF')

Note: eval() is a very unsafe to use if there's some kind of user input, since the user could inject malicious code in eval()'s parameter string.

Edit: if security is not a concern, for any reason, you could also just eval() the whole string. Just make sure not to share your code without a proper warning.
If security is a concern, in both of the cases described above, you might want to apply a filter on the strings you pass to the converting function. Something like if str[:3]=="bpy": str_to_class(str) #or eval(). It makes things a little safer.

$\endgroup$
  • $\begingroup$ I apologize, but I am still having an error. getattr(bpy.ops.object, "modifier_add")(type='SUBSURF') works fine. However, import sys def str_to_class(str): return getattr(sys.modules[__name__], str) myObj = str_to_class("bpy.ops.object") #line added from original code gives an error of "AttributeError: 'module' object has no attribute 'bpy.ops.object'" $\endgroup$ – Tritofic Aug 31 '15 at 14:17
  • $\begingroup$ Yup: Didn't test the code coming from the page I linked, assuming it was working, but it isn't for me either. I've been playing around a bit and worked out something like this: m = __import__("bpy") \ ops = getattr(m, "ops") \ object = getattr(ops, "object") You can easily loop that process with the aid of "my.test.string".split(".") Give a shout out if you need any more help :) $\endgroup$ – torels Sep 1 '15 at 15:33
  • $\begingroup$ Maybe m = locals().get("bpy", None) , fair to assume bpy would be in locals for a blender script. $\endgroup$ – batFINGER Jan 1 '17 at 11:41

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.