2
$\begingroup$

Based on all the helpful information in the answers to my previous question I have a new question. I will be generating a large number of meshes and animating them using just a script. In the following code, I use bpy.ops.mesh.primitive_uv_sphere_add to make a sphere, and bpy.data.objects.new to make a custom mesh. In both cases, it is sometimes necessary to access the object through bpy.context.active_object and also through bpy.ops.object. After the whole script runs, the fourth sphere is unintentionally smooth, because it is still active.

My question is partly how to fix this (make only the purple mesh smooth), but I want to check that these are really the most correct and safest and foolproof ways to do this. How do I "know" if bpy.ops.object really refers to the same object as bpy.context.active_object? Assume this is in a large script generating and deleting a large number/variety of meshes and objects and materials, vertices are being adjusted, keyframes and shapeframes are doing their thing, etc.

Last question didn't have enough code, so maybe I've overcompensated with too much this time:

import bpy
import numpy as np

nx, ny = 20, 30
x = np.linspace(0, 3, nx)
y = np.linspace(0, 3, ny)
X, Y = np.meshgrid(x, y)
Z = np.cos(X**2 + Y**2)

verts = [tuple(thing) for thing in zip(X.flatten(), Y.flatten(), Z.flatten())]
corner_verts = [verts[0], verts[nx-1], verts[-nx], verts[-1]]

faces = []
for iy in range(ny-1):
    for ix in range(nx-1):
        v1 = iy*nx + ix
        v2 = v1 + 1
        v3 = v2 + nx
        v4 = v3 - 1
        faces.append((v1, v2, v3, v4))

purp = bpy.data.materials.new("PKHG")
cy   = bpy.data.materials.new("PKHG")

purp.diffuse_color = (1, 0, 1)
cy.diffuse_color   = (0, 1, 1)

puv_add = bpy.ops.mesh.primitive_uv_sphere_add
for vert in corner_verts:
    puv_add(segments=8, ring_count=4, size=1, location=vert)
    ao = bpy.context.active_object
    ao.active_material = cy

me  = bpy.data.meshes.new('timmesh')
ob  = bpy.data.objects.new('timobj', me)
sco = bpy.context.scene.objects
sco.link(ob)

me.from_pydata(verts, [], faces)

ob.active_material = purp

# the above script leaves the mesh unselected for some reason
ob.select = True
boo = bpy.ops.object
boo.shade_smooth()

ao = bpy.context.active_object
print("ao: ", ao.name)    # Sphere.003
print("ob: ", ob.name)    # timobj
print("boo: ", boo.name)  # <function bpy.ops.object.name at 0x117028470'>

edit (a question from the comments)

since a script does not use a mouse, why does it have to worry about selecting and deselecting and what is or isn't active. I am wondering how I can just give them a reference such as ob and then just do these things without having to constantly manage selection in the script?

$\endgroup$
  • $\begingroup$ I think you're asking several questions here, hopefully i've explained how to deal with active objects and the operator that sets selected objects to smoothi. You vaguely mention other interesting things like keyframing and updating mesh data every frame, those are questions that can be asked separately (after a search of course) and they'll get dedicated answers. $\endgroup$ – zeffii May 28 '15 at 12:44
  • $\begingroup$ Your answers are extremely helpful @zeffii. I appreciate the wide coverage. Yep keyframes are next. $\endgroup$ – uhoh May 28 '15 at 13:11
1
$\begingroup$

My personal philosophy has been to avoid bpy.ops as much as possible, because so much of it relies on undocumented (although usually straightforward) manipulation of the editor context. I consider the editor context to be an environment variable, and I like to avoid global variables, environment variables, and singletons (although I don't elevate this principle to a religion).

Instead of the primitive_uv_sphere_add, I would probably manually create the sphere in the UI, and just access its mesh datablock to create new objects. (although you could say that is a global variable).

These are the changes I made:

--- /tmp/se0.py 2015-05-28 11:17:48.715945835 -0400
+++ /tmp/se.py  2015-05-28 11:17:20.040779317 -0400
@@ -25,11 +25,10 @@
 purp.diffuse_color = (1, 0, 1)
 cy.diffuse_color   = (0, 1, 1)

-puv_add = bpy.ops.mesh.primitive_uv_sphere_add
 for vert in corner_verts:
-    puv_add(segments=8, ring_count=4, size=1, location=vert)
-    ao = bpy.context.active_object
-    ao.active_material = cy
+    ao = bpy.data.objects.new("sphere", bpy.data.meshes['Sphere'])
+    bpy.context.scene.objects.link(ao)
+    ao.location = vert

 me  = bpy.data.meshes.new('timmesh')
 ob  = bpy.data.objects.new('timobj', me)
@@ -40,12 +39,5 @@

 ob.active_material = purp

-# the above script leaves the mesh unselected for some reason
-ob.select = True
-boo = bpy.ops.object
-boo.shade_smooth()
-
-ao = bpy.context.active_object
-print("ao: ", ao.name)    # Sphere.003
-print("ob: ", ob.name)    # timobj
-print("boo: ", boo.name)  # <function bpy.ops.object.name at 0x117028470'>
+for p in ob.data.polygons:
+    p.use_smooth = True

A great majority of the functions in bpy.ops can be accomplished in other, less state-y ways, but it requires a certain level of dedication to acquire the experience necessary to accomplish some of the more advanced operations.

$\endgroup$
  • $\begingroup$ object instantiation (objects.new) gets progressively slower with more objects, so reusing the mesh still isn't the best solution. $\endgroup$ – zeffii May 31 '15 at 7:14
  • $\begingroup$ Thanks @Mutant Bob for the clear alternate scripting! I'll work on this now and see what I can come up with. I'm trying to find a safe, clean way to add and manipulate mesh objects in a self-contained way, so that it might take place inside a larger script. I like this! I think I can just use the primitives once or a few times (once per unique number of facets) and hang on to those meshes as reference. Maybe then just me.copy()? Nah, not so simple! Probably there is a make_mesh_copy_from method. I will look. $\endgroup$ – uhoh May 31 '15 at 7:16
  • $\begingroup$ Actually, the icosphere is the only primitive that isn't straightforward to just create on the spot using trig. (oh, and Suzanne) $\endgroup$ – uhoh May 31 '15 at 7:33
3
$\begingroup$

I'm not certain there is a 'most correct' way, answers rarely exist in a vacuum. Settle for 'optimal' approach given a scenario.

A few take-aways:

  • The "Active object" is always selected, its .select attribute will return True.
  • Only one object can be Active in the scene at any given time.
  • it is possible that no object is active, yet several are selected.
  • A call to bpy.ops.object.shade_smooth() will shade all selected objects, not just active. (bpy.ops.object isn't a reference to an object, like bpy.context.active_object is )
  • bpy.ops.object.shade_smooth() doesn't take parameters, so it operates blindly on the current data.objects in your scene.
  • Adding objects using ops.mesh places that object in the active state
  • bpy.context.scene.objects.active = None, will remove the active state of an object, and it will become just 'selected'
  • bpy.context.scene.objects.active = bpy.data.objects[your_object], will set that object active. (it will not clear the select state of any previously active object)
  • bpy.context.scene.objects.link() does not place the object in an active or selected state, you must set that explicitly yourself.

A solution to the potential chaos?

Most flexible is probably an ID property, each object can have several ID properties. You can pool objects by giving them each the same ID prop.

my_obj = bpy.data.objects['some_object']
my_obj['UHOH_ID'] = 'DONTSHADEME'  # attaches this ID property

A fast way to 1) un-select all objects, and 2) make sure none are active is the following two lines:

bpy.context.scene.objects.active = None  # turns active object to selected
bpy.ops.object.select_all(action='DESELECT') # properly unselects all.

Once you get your head around adding ID properties to objects, here's a list comprehension that returns the list of object references that have MY_ID.

objects = bpy.data.objects
objs = [o for o in objects if o.get('MY_ID') == 'SOME_VALUE']

Then to select just those objects with MY_ID

for o in objs:
    o.select = True

Why worry about selecting and deselecting and what is or isn't active?

In essence because the kinds of function you are calling are wrapped functionality intended to be triggered by the UI, and therefore don't need to supply return values, but do assume some state of the scene.

You've already encountered from_pydata, there is also bm.to_mesh(obj.data) which lets you assign the content of a bm into the mesh of an object. Getting away from the bpy.ops is a natural progression for someone who is learning bpy, they are great first steps.. but as soon as you need more control and complexity -- well.. a different approach is needed, and is available.

for instance, to make a uv sphere without bpy.ops

import bpy
import bmesh

bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=20, v_segments=20, diameter=1.2)
my_mesh = bpy.data.meshes.new("my_mesh")
bm.to_mesh(my_mesh)
bm.free()
ob = bpy.data.objects.new("mySphere", my_mesh)
bpy.context.scene.objects.link(ob)

You could wrap that in a function that returns the reference of the new object..

$\endgroup$
  • $\begingroup$ So ao.select = False helps, but only if you remember to put it everywhere, always. My question is not just how to fix this one example, but how to say "I am working on THIS object now - please ignore anything else that might be active or might be selected" instead of relying on all other processes to always unselect when they are done. $\endgroup$ – uhoh May 28 '15 at 10:17
  • $\begingroup$ How? I think we're converging on the essence of my question. $\endgroup$ – uhoh May 28 '15 at 10:28
  • $\begingroup$ These are incredibly helpful! I used select_all(action='DESELECT') and bpy.ops.object.shade_smooth() just returns "CANCELED" which is good, but then bpy.context.active_object.active_material=cy will still turn the most recently selected object cyan, even though it is not selected. I didn't realize active object status persists when nothing is selected. I will chew on this more. Thanks very much for your persistence! $\endgroup$ – uhoh May 28 '15 at 13:03
  • 1
    $\begingroup$ to make sure no object is active use the bpy.context.scene.objects.active = None, then deselect everything. $\endgroup$ – zeffii May 28 '15 at 13:06
  • $\begingroup$ Consider also groups. You might profit if you took the viewpoint that the Script (your script) has the responsibility of directly keeping track of the selection. Saving and Restoring for robustness. Those mechanisms have been demonstrated above. Your script can usefully create a container list [Z], ensure its existence, perform various actions, and finally later restore the selection to [Z]. Some environments have explicit means to PUSH the current selection and POP it from a stack. You might consider that approach even in a simplified sense. $\endgroup$ – atomicbezierslinger May 28 '15 at 13:09

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.