4
$\begingroup$

I was writing a script and discover weird thing. I have code like this:

import bpy
obj = bpy.context.active_object
for s in obj.scale:
    s *= 2.0
    print(s)

and it doesn't change scale of the object although print(s) shows me correct values. I thought I get a pointer to the object's property as I get pointer to the object in case with:

for obj in bpy.context.selected_objects:
    # do something
$\endgroup$
5
$\begingroup$

The issue here is that its not always clear when values are wrapped or not, that is to say, when modifying a value will apply changes back to the Blender data the values originally came from.

The rule of thumb here is that array/vector/matrix/quaternion/euler/color types wrap blender internal data, but when you get a single number from any of these types, changes to it won't be applied back to the original data.

Taking Object.scale as an example...


This will modifiy the objects scale:

obj.scale[0] = obj.scale[0] * 2.0

# or...
obj.scale[0] *= 2.0

# or...
scale = obj.scale
scale[0] *= 2.0

This won't modify the objects scale:

# s is a float and holds no reference to the original data
s = obj.scale[0]
s *= 2.0

# or...
# this does nothing other then to do a multiplication and ignore the result
obj.scale[0] * 2.0

# or...
# by making a copy of the scale, the reference to blender data is dropped,
# this is often useful when you *want* to get some data and manipulate it
# without touching your data in Blender.
scale = obj.scale.copy()
scale[0] *= 2.0

# or...
# by decomposing the scale value by axis,
# we get a list of floats which won't wrap original data,
# this is like '.copy()' but gives us a list rather then a Vector.
scale = [obj.scale[0], obj.scale[1], obj.scale[2]]
scale[0] *= 2.0

# or...
# this also has the effect of returning a new, modified copy.
scale = [s * 2.0 for s in obj.scale]

Note:

While this is a Vector, the same thing applies to color, eulers... etc as mentioned before.

In the example given in the question, its looping over numbers, so you would have to do something like this...

for i, s in enumerate(obj.scale):
    obj.scale[i] = s * 2.0

Note that in this case Vectors can be multiplied by numbers so its best to do:

obj.scale *= 2.0

Note:

if you have a mathutils class - Vector/Matrix/Color/Quaternion/Euler, you can check to see if they reference some other data with the readonly is_wrapped attribute. You can even get the data they came from using owner

$\endgroup$
1
$\begingroup$

summary

namebinding - does not affect b

b=a
a=42

modifying attributes or items - does affect b

b=a
a.attr=42
a[item]=360
>>>b.attr
#42
>>>b[item]
#360

some more details

What happens when you call (see for loop)

for elem in obj:

is, that an iterator will be retrieved from the obj like this

obj_iter=iter(obj)

This iterator has a method __next__ which can be called by

next(obj_iter)

and will return the next element in the collection. The for-loop repeately calls __next__, assigns the returned value to the variable elem

elem=next(obj_iter)

and executes the body on each turn until the iterator raises the StopIteration exception signalizing that there are'nt any elements left.

If the elements in obj are mutable you can modify the elements .attributes or [items] with the value given to elem.

In your example you are rebinding a new value to the iteration variable 's name. (see assignment)

$\endgroup$

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.