Join Blender Studio for just €11.50/month and get instant access to all of our training and film assets!
Learn about Collections in this video. Not only do we look more at lists, we also use dictionaries in order to automatically mass-rename objects in the scene, and tuples to assign to multiple names at the same time.
Slides are only visible to Blender Cloud susbcribers
This is a good example of how coding principles can lead to problems. The first code produced is functional and easy to read. But, the programmer doesn't like the repetition, so he proceeds to obfuscate (restructure) the simple code into complex code. I understand that the goal is to demonstrate code restructuring, but the final code is not superior to the original code; it took more time, has more lines, and must be translated (decoded) in the programmer's mind to be understood. The "don't repeat yourself" principle is a good guideline, but it should only be applied if it saves you work and makes your code easier to read.
@Michael Bonar It's not so much the repetition itself, it's the separation of concepts, and making sure that there is only one bit of code responsible for that particular functionality.
This is just a demo of how to refactor code into a function. Of course this is going to be demonstrated with simple code first, that's how teaching works. I don't see the problem here.
You're right in that the new code has more lines. I still think it's often better to avoid mixing too many levels of abstraction in one function. Either it's composed of high-level operations, or doing low-level work. Having a clear separation between those can help a lot in trying to understand what the code is doing, and how it's separated into steps. Shorter code doesn't always mean "easier to read", and neither does "more split up into functions". It's always a balancing act.
You might be interested in Chapter 16: Readability & Understandability, where I look more at these topics.
I love how with every video, your beard gets bushier
thank you, Sybren, for the suggestion (around 16:50) of using continue
to avoid extra conditions and indenting. as an inexperienced python coder, this sounds like something that should be followed in all one's code.
to this end, for anyone else struggling with the meaning of continue
, what it really means in plain English is that the code will stop executing the current iteration of the loop and go back to the top of the loop process and move on to the next iteration, so continue
means continue with the whole loop operation, rather than continue with this iteration. i found this very confusing at first.
Can someone help me out with why this code isn't working? I have a loop creating 20 Suzannes:
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
for index in range(20):
x = index % 4
y = index // 4
bpy.ops.mesh.primitive_monkey_add(size=0.2, location=(x,y,1))
ob = bpy.context.active_object
ob.name = 'Suzanne.R.' + str(f'{index+1:03}')
ob.data.name = ob.name
Okay, markdown doesn't seem to like the { character in the code above (at least in the preview window) and is adding
*@Peter Smith* Well at least use f 'Suzanne.R.{index+1:03}'
;-)
bpy.ops.mesh.primitive_monkey_add()
creates a new monkey every time, so Blender will just keep making new ones. Since they are all in the same blend file, they have to have a unique name.
*@Sybren A. Stüvel*
Thanks for the reply, but I don't think I was clear in my original question about what my issue is. The script deletes every object in the scene before then creating 20 monkey objects. The monkeys are renamed at object level (ob.name = str(f...
) and that part works fine.
But when I then try to set the mesh data name to be the same as the object name (ob.data.name = ob.name
) it doesn't work. It works the first time I run the script when I open Blender (i.e. in a new Blender session). But every time I run it after that the mesh data doesn't take the name of the object data. Instead, the mesh numbers are incremented, so by the time I've run the script a few times I end up with objects named e.g. Suzanne.R.001 but with mesh name Suzanne.R.240.
I don't get why that should be the case when the script deletes all the objects before recreating them? Is Blender somehow not "flushing out" the mesh data names when objects are deleted?
*@Peter Smith* You're right, take a look at bpy.data.meshes
. It does not remove those when you remove the objects. If you want to remove a mesh, use bpy.data.meshes.remove(the_mesh)
.
BTW, the str()
call is unnecessary, as f"..."
is already a string.
*@Sybren A. Stüvel* Cool, thanks for the reply, and thanks for the Python tip!
Thanks for those amazing videos! I have a question about bones. I'd like to get the info of all currently selected bones and use an array to store their names, so I can then change them. Is there a way to do that using Python? Thanks!
my rename and flip script, even after copy pasting from the slides does not seem to work, it changes the name but one of the objects out of two has a .001 at the end of of it, meaning that it changed the name of .l object into .R first but since .R object already existed blender added a .001 next to it, then it moves on the .R into .L and that seems to work. But the objects dont flip at all! and somehow this is working in your demonstration!.
@Pranavjit Virdi: ohkay sorry, so the script that i was refferring to at 13:02 works only if the names of the objects are not the same, meaning that it would work if you have a cube_L and sphere_R but it would not work if you have Cube_L and Cube_R, huh interesting, since the flip name solutions before that in the video seemed to work without error on just that scenario.
@Pranavjit Virdi: wow i just realized you addressed this issue in the video completely, i had paused the video before that point and didn't bother to see the whole thing through, hehe.
@Pranavjit Virdi: k it works... here is the code if anyone wants to see it.
import bpy
suffix_translations = {
'_R': '_E',
'_L': '_W',
}
suflen = 2 # all suffixes should be this length.
for name, ob in bpy.data.objects.items():
suffix = name[-suflen:]
if suffix in suffix_translations:
new_suffix = suffix_translations[suffix]
ob.name = name[:-suflen] + new_suffix
ob.location.x *= -1
suffix_translations_b = {
'_E': '_L',
'_W': '_R',
}
for name, ob in bpy.data.objects.items():
suffix_b = name[-suflen:]
if suffix_b in suffix_translations_b:
new_suffix_b = suffix_translations_b[suffix_b]
ob.name = name[:-suflen] + new_suffix_b
@Pranavjit Virdi: alright the flipping does work now(i am often amazed at my stupidity, the objects i tested were spread along the y axis instead of the x axis.) the rest of the prob still stands.
@Sybren A. Stüvel: _L_becomes_R is way more characters than suflen 2 which is already predefined, so i made it _E and _W (for east and west) but now it changes them to _E and _W only and i have to run the script another time for the _E to change to _L(which is what it was in the first place). another thing, even if i change the other object to have a different name the flipping of location doesn't work.
@Pranavjit Virdi: In such cases having an intermediary name can help out. For example, first you rename Cube_L
→ Cube_L_becomes_R
and Cube_R
→ Cube_R_becomes_L
. Then rename them again, Cube_L_becomes_R
→ Cube_R
and Cube_R_becomes_L
→ Cube_L
. Then you're sure that you don't get any naming collissions.
I load some objs, then hope to add "test" for each cube name, in the scene, I code like that.
for ob in bpy.data.objects: ob.name = 'test' + ob.name
but it not work, eg cube001 change name 'testtesttesttestcube001" etc it added multiple "test" about each obj. I understand, something wrong, but could you teach me clear?
@fun2tax: This is quite subtle. It happens because you're modifying the bpy.data.objects
collection while you're looping over it. Internally Blender uses the object name to put the object into that collection. Since you're changing the keys, you're changing the collection itself, which causes objects to be visited more than once.
You can solve this by first creating a dictionary of new names, and then loop over that to give each object its new name:
new_names =
for ob in bpy.data.objects:
new_names[ob] = 'test' + ob.name
for ob, new_name in new_names.items():
ob.name = new_name
This way you do not change bpy.data.objects
in the first loop, and you do not change new_names
in the second loop.
@sybren: I could understand why it may cause problem by your answer. I just need to make it as habit, dividing 2 loop. ;-)
@fun2tax: Doing something the correct way is always preferrable to "it seems to work in this particular version of Blender" ;-)
Have blender 2.79 current build change about it? because, now it seems work , even though I use
for obj in bpy.data.objects:
obj.name = "aaa_" + obj.name
when I test same thing in 2.79 rc2, it actually added many "aaa_" about same objects . but I may prefer your recommend way, which make new dictionary for new name, then use two loop without change collection in one loop. thanks.
or I can use values(), and keys() in spite of items()
for ob in bpy.data.objects.values():
ob.name = "aaa_" + ob.name
for name in bpy.data.objects.keys():
new_name = "aaa_" + name
bpy.data.objects[name].name = new_name
but
for obj in bpy.data.objects:
obj.name = "aaa_" + obj.name
directly change bpy.data.objects collection keys , in this loop, then it can not work?
@sybren: Thanks, one thing I still can not understand is, why I can change key values when I use "items()"? <quote>for ob_name, ob in bpy.data.objects.items(): ob.name = "aaa_" + ob.name</quote>
this code seems work for me. but it not recommended?
@fun2tax: My point is that you do not want to use one loop. Loop 1: given the object, figure out its new name. Loop 2: given the new names, assign them to the objects. Combining those in a single loop is not possible, as you should not modify the collection you're looping over.
@sybren: Thanks I could understand better. so to chane obj.name in same loop need to use items()?
<code>for name, obj in bpy.data.objects.items(): if obj.location.x < 0: obj.name = 'Left_' + obj.name else: obj.name = 'Right_' +obj.name</code>
@sybren: thanks to take your time. I will try again your discribed code.
So I check list, in python doc, then change , as same as you did.
for name, ob in bpy.data.objects.items():
ob.name = 'test_' + ob.name
it worked but the 'name' should be ob.name, is not it? actually even though I simply use name = 'test_' + name, all cube change name .
Even after check python doc about tuples, I'm confuse about this line:
base_name, suffix = name[:-suflen], name[-suflen:]
I don't understand how the hell base_name
find its value, and where is stored name[-suflen:]
.
In fact I just understand the part with suffix = name[:-suflen]
:)
@vinc3r: Erf OK I got it, after relistening! (not native english speaker here).
I was thinking that suffix = name[:-suflen]
but not!
For whose wondering:
base_name, suffix = name[:-suflen], name[-suflen:]
is equal to
base_name = name[:-suflen]
and suffix = name[-suflen:]
Am i right ?
@vinc3r: You're right, a, b = c, d
is the same as a = c
and b = d
.
The added benefit of the former is that it also works when (c, d)
is returned from a function, so then you can do a, b = function_returning_c_and_d()
.
Hmm, at 7.11'' I am confused by 'suflen'. How was this defined, I see that it was hard coded to 2 chars earlier. Is it a python function like 'len'?
@3pointedit: its defined in the code, suflen = len(suffix) so it should contain the length of what is in suffix. eg if suffix contain '_R' the suflen will equal 2 Hope I'm getting you question correct :)
@Stefan59: Thank you, I guess I am getting lost on the order of operations/definitions?
@3pointedit: (5 minutes into tutorial) the function is defined def rename(some_name, suffix):
and gets it value when called ob.name = rename(name, '_R')
so name
is passed to some_name
and '_R'
is passed to suffix
@3pointedit: There is less magic involved than that, the underscore is nothing special. This type of programming is called "imperative programming", which means that you tell the computer "do this, then do this, then do this". This means that len(suffix)
is executed when the computer runs that bit of code. It doesn't matter that suffix
can have different lengths at different times. The result of len(suffix)
now is the length of suffix
now. Try toying around with it in the interactive Python console.
@Stefan59: Thank you for your patience :-) I think that I will have to rewatch some more times. How does the script calculate that the suffix (which is only a variable with no fixed length) is 2 characters long? Does it read the underscore or are you defining the suffix in ob.name?
@3pointedit: some_name
is defined, it's the name of the first parameter of the rename
function.
Whatever you pass as first parameter, it'll get the name some_name
. For example, if you would call rename("steve", "L_")
, it would act as if you had said some_name="steve"
and suffix="L_"
.
@3pointedit: Nope, i'm not great at explaining things so I hope you can understand. The suffix is a variable the script defines, it can be anything. In the tutorial suffix is computered to be either R_ or L_ as thats what we want to move, but really it can be any name or length. len(suffix) command just mean grab the length of suffix, adding suflen = len(suffix) defines suflen for the first time and gives it the value of the length of suffix, tutorial it is always 2 because R_ and L_ have the length of 2. You may need to do some python tutorials before try to understand python in blender, it will help. Steve
@Stefan59: Oh I see, does this mean that anything in a string after an underscore "_" is recognized as a suffix function? So "suffix" is a python tool? And is "some_name" also a python function as it doesn't get defined anywhere? I guess that it is a wildcard?
Join to leave a comment.