changing modifier attributes globally

I want to create a button that loops through all subsurf modifiers in a scene and changes their “show_only_control_edge” to True. I’m newer to blender python scripting and can’t figure out how to setup the function via the API docs…any help is much appreciated!

Ok, I probably shouldn’t attempt this while I am away from a Blender instance to test it on, but maybe it will give you a starting point to debug:


for obj in bpy.data.objects:
    for mod in obj.modifiers:
        if 'SUBSURF' == mod.type:
            mod.show_only_control_edge = True

I am now excited to see if it works. It looks right to me from here.

-rking

without access to blender rking? you’re brilliant =) thanks, it works!

Woo-hoo!

Thanks for letting me know the results.

By the way, my rule in Blender is: Five Times.

I will allow the by-hand approach exactly five repetitions. Like, click one object, change a setting, repeat. Any more than that I’ll switch to the Scripting screen layout and write a loop. Through a use of Ctrl+Space in the Python Console and through Help > Python API Reference, it is faster than one might think.

-rking

Thanks rking! I’ll use that approach from now on.

So now I’ve made a working function that adds a subsurf modifier to all mesh objects in the scene with the name “MySubsurf”.



import bpy


for ob in bpy.data.objects:
    if ob.type == "MESH":
        ob.modifiers.new('MySubsurf','SUBSURF')

Next, I want to write a function to delete all subsurf modifiers in the scene named “MySubsurf”. This is what I’ve come up with:


import bpy


for ob in bpy.data.objects:
    if ob.type == "MESH":
            ob.modifiers.remove(bpy.types.SubsurfModifier)

This remove function doesn’t work. I get the error “function.modifier expected a Modifier type, not type”. The API docs say the parameter is “modifier” and I can’t figure out what the syntax for that parameter is supposed to be…


import bpy
for ob in bpy.data.objects:
    for mod in ob.modifiers:
        if 'MySubsurf' == mod.name:
            ob.modifiers.remove(mod)

(But this time I cheated and used Blender to test it. =) )

By the way - bpy.types.SubsurfModifier is the type, whereas “mod” in the example above is an instance of that type. It’s like if you had class full of children, and you wanted to punish one. The principal wouldn’t come over the Public Announcement System and say, “Mankind, come to my office.” He’d say, “Fred, come to my office.”

-rking

rking, you’re shedding a magnificently bright light on hours of frustrating techno-babble-docs. I LOVE analogies like this - for this I’m truly grateful!

So these “type instances” (and I guess “ob” is one as well) - I’m having trouble locating them in the docs…where can I reference them. And I don’t mean to annoy…now I’m just trying to pick your brain =)

No annoyance whatsoever. We can keep going on with questions and answers as long as my knowledge holds out - but we’ll soon find that my knowledge of Blender + Python is shallow.

In Python, in general, you instantiate by putting ()'s after the type name. For example, look at what the Python Console prints if you evaluate “list” by itself or with parens:


>>> list
<class 'list'>

>>> list()
[]

The first is a category (or class) of things, but the second is an actual thing, an instance.

For some reason, the things I have seen in Blender do not use the bare parens syntax, nor do they even get invoked on a class directly, but instead use somecollection.new(). I am sure there are good reasons for this, but it is unfamiliar to me.

Object creation in Blender gets further weirdified by the need to link objects to Scenes - or else they are floating out in the middle of nowhere. So let’s say you wanted to make a duplicate of the Default Cube, but rotated a bit, the full sequence would be:


import bpy
mesh = bpy.data.objects['Cube'].data.copy()
ob = bpy.data.objects.new('RotCube', mesh)
ob.location.y = ob.location.x = 2
ob.rotation_euler.z = 45
bpy.context.scene.objects.link(ob)

But really, most of the time you are not creating objects - you are simply passing them around from one part of the API to another.

So when I saw your code fail because it was looking for an instance of “a Modifier type, not type”, I knew all I had to do was find a handle to the Modifier itself. That error message is a bit confusing, but perhaps this snippet will clear it up some:


>>> type(bpy.data.objects['Cube'].modifiers[0])
<class 'bpy.types.SubsurfModifier'>

>>> type(bpy.types.SubsurfModifier)
<class 'type'>

You see there that using the type() function we can examine what class an instance is. bpy.types.SubsurfModifier - being a class - is actually an instance of type “type”. Hehe… we are sort of in a terminology vortex, but it is actually elegant. You will find that type(type) == <class ‘type’>. Even a type has methods (you can see them by running dir(bpy.types.SubsurfModifier), for example).

Now that I get to this point I feel like I’ve said everything I have planned, but I am not sure it is clear at all.

Please continue asking questions to steer me back on course.
-rking

This code will actually only remove a single subsurf modifier.


import bpy
for ob in bpy.data.objects:
     for mod in ob.modifiers:
         if 'MySubsurf' == mod.name:
             ob.modifiers.remove(mod)

Because datablock names are always unique, the IF statement will reliably detect only a single modifier named MySubsurf. But other modifiers named like MySubsurf.000 or .xxx will not be removed. So you must do clever string comparison to get them all.


import bpy
myName = "MySubsurf"
l = len (myName)
for ob in bpy.data.objects:
     for mod in ob.modifiers:
         if myName == mod.name[:l]:
             ob.modifiers.remove(mod)

An assumption was made by theLuthier in the following code.


import bpy  
for ob in bpy.data.objects:
    if ob.type == "MESH":
        ob.modifiers.new('MySubsurf','SUBSURF')

The assumption is that Blender will actually name your object, modifier or datablock the name you supply the new() function. This is not the case. The first time through the loop you will create a new modifier called MySubsurf but the second time through the loop you will get a new modifier called MySubsurf.000. To prevent name collision Blender automatically and behind the scenes, within the API itself, renames your object. No error is thrown, of course, but if you try to access the second modifier by the original name, later on, you will actually be fetching the first one.

@rking: wonderful explanation - thanks! I’ll be reading over your post a few more times to understand it fully (my brain hitting the ceiling, not confusion from your end of course). I really appreciate you being open to more questions - as I continue with this current script you can definitely count on a few more Q’s from me.

@Atom: awesome modification - implementing it now! thanks a TON for the help

I’ve been working on another function for a one-button-click “wireframe-on-shaded” for all meshes in the scene. This is the semi-working operator:

#ALL EDGES TOGGLE             
class OBJECT_OT_allEdgesToggle(bpy.types.Operator):
    #connecting this class to the button of the same name
    bl_idname = "object.all_edges_toggle"
    bl_label = "All Edges"
    bl_description = "Toggles wireframe-on-shaded for all mesh objects in the viewport"
           
    #function that's executed when button is pushed
    def execute(self, context):       
        for ob in context.scene.objects:
            if ob.type == "MESH":
              mesh = ob.data
              if mesh.show_all_edges == True:
                mesh.show_all_edges = False
                ob.show_wire = False
              else:
                mesh.show_all_edges = True
                ob.show_wire = True
            
        return {'FINISHED'} # operator worked

The only problem I’m running into is that if some objects have wireframe-on-shaded on and others have it off, they simply toggle back and forth individually instead of globally. I’m not 100% sure what the syntax is, but I think it should function something like this:

First, Reset: For all mesh objects with show_all_edges == True, change to False.
Second, Toggle: If a mesh object has show_all_edges == False, change to True; else leave as False.

Any help with syntax and/or a more efficient function is always appreciated!

I like your choice if semantics for it - basically making it behave like the select All key.

I would create a variable named toggle and initialize it with True. Then loop over all meshes looking for show_all_meshes == True. If you find one then assign False to “toggle”. Then loop again and do the assignment.

By the way, you mentioned efficiency. An old programming adage is, “Premature optimization is the root of all evil.” Pretty much the only efficiency I focus on is programmer efficiency. It keeps the code simple and bug-free. In those (surprisingly rare) occasions where the thing is too slow, then use hard data to determine what optimizations to make. Learn about profiling and benchmarking. If a theorized speedup complicates the code but doesn’t provide any gain, revert to a previous version and try a new approach. Usually it is only a matter of cacheing some data or something equally simple. Once in a while you will run into the need to do something serious, like call C/Asm bits, but you will have saved yourself so much time by skipping needless tricks that you can afford it.

-rking

Wow, I guess I’ve always assumed there are a hundred ways to write the same bit of code and each way differed in efficiency…good to know @rking! Helpful as always good sir.

I see you’re in Texas (as am I), are you working in the industry anywhere?