How do I work with User Ops Nicely? How do I manage selection state? 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)  # &lt;function bpy.ops.object.name at 0x117028470'&gt;


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?
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..