Is there a better way than dupliverts to make these scripted monkeys always face "out"? In another question I ask How to make these scripted duplivert monkeys always face “out” using duplicates? Here I would like to ask if there is a better (or at least alternate) way to do this besides using duplicates or "dupliverts".

Here's an example using dupliverts - the same as in the other question and so far I haven't figured out there how to get the monkeys placed on the icosphere's vertices or it's faces to face "out".

Is there a different way to do this?

note: I will not always use monkeys on icospheres - this is an abstraction. I'm working towards correctly animating this constellation of 4,425 Earth-facing satellites where I'll have roughly eighty rotating circles with sixty "monkeys" each. However I will also be using 2D meshes in a second project (rather than circles), so I really want to understand the underlying logic/math of the orientations, both on 1D (circles) and 2D meshes.

edit: Here is one of the images from the linked satellite question to help illustrate why I want to create "monkeys" (satellites) in groups, and not create and keyframe four thousand individuals:









Potentially adjustable things:

# monkey.rotation_euler = 0, 0, pi   # ??

bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) # keep_transform ??

bpy.context.object.use_dupli_vertices_rotation = True  # ??


Ok here is a basic script:

import bpy, math

pi = math.pi
loco_f, loco_v = (-5, 0, 5), (+5, 0, 5)

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

bpy.ops.mesh.primitive_ico_sphere_add(location=loco_f, size=4, subdivisions=2)
shape_mesh_f = bpy.context.active_object

bpy.ops.mesh.primitive_ico_sphere_add(location=loco_v, size=4, subdivisions=2)
shape_mesh_v = bpy.context.active_object

bpy.ops.mesh.primitive_monkey_add(radius=1, location=loco_f)
monkey_f = bpy.context.active_object
# monkey.rotation_euler = 0, 0, pi

bpy.ops.mesh.primitive_monkey_add(radius=1, location=loco_v)
monkey_v = bpy.context.active_object
# monkey.rotation_euler = 0, 0, pi

bpy.ops.object.select_all(action='DESELECT')

monkey_f.select = True 
bpy.context.scene.objects.active = shape_mesh_f

bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
bpy.context.object.dupli_type = 'FACES'
bpy.context.object.use_dupli_vertices_rotation = True

bpy.ops.object.select_all(action='DESELECT')

monkey_v.select = True 
bpy.context.scene.objects.active = shape_mesh_v

bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) 
bpy.context.object.dupli_type = 'VERTS'
bpy.context.object.use_dupli_vertices_rotation = True
Instead of dupliverts, we can create it all in one mesh using bmesh.ops.create_monkey().

import bpy
import bmesh
import mathutils

loco_f, loco_v = (-5, 0, 5), (+5, 0, 5)

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

bpy.ops.mesh.primitive_ico_sphere_add(location=loco_v, size=4, subdivisions=2)
shape_mesh_v = bpy.context.active_object

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete(type='EDGE_FACE')
bpy.ops.object.mode_set(mode='OBJECT')

bm = bmesh.new()
bm.from_mesh(shape_mesh_v.data)

src = [v for v in bm.verts]

for v in src:
    loc = mathutils.Matrix.Translation(v.co)
    rot = v.normal.to_track_quat('-Y','Z')
    mat = mathutils.Matrix.Identity(3)
    mat.rotate(rot)
    mat = loc * mat.to_4x4()
    bmesh.ops.create_monkey(bm, matrix=mat)

bm.to_mesh(shape_mesh_v.data)
bm.free()


So the key point you are looking for is v.normal.to_track_quat('-Y','Z') to get a quaternion rotation matrix from the vertex normal.

For another way to approach this, you could try animation nodes.



Here we make a list of the location and normal of each vert in an icosphere, we use that list to replicate the suzanne mesh.



EDIT:
After some experimenting with the aim of creating your final goal, I found creating one orbital plane at a time as a unique object seems to be the fastest way, seems to find a balance between object and vertex count. Once they are all created they can all be merged into one mesh for simplicity. While creating each plane, an armature bone can be added and aligned with each orbit allowing easy animation. This is the script I came up with, a simple proof of concept that needs better positioning and timing to be complete.