Mesh Silhouette: My first Bmesh Operator


This function takes a mesh as an input, and outputs a silhouette of the object using simple face testing. It will also add a smooth modifier and shrinkwrap modifier to make it look a little nicer optionally. This happens in local coords, and gives back an object unscaled, rotated or translated.

This is a problem I’ve been wanting to tackle for a while. Some googling, revisiting some old threads and a small lightbulb went off that said this would be very easy to do with bmesh. I’ve left it in function form so it’s up to you to package it in an operator because you might want to do it from the view or by specific directions.

Questions?
Comments?
Suggestions for speed improvements?

-Patrick


import bpy
import bmesh
import time
def silouette_brute_force(context, ob, view, smooth = True, debug = False):
    '''
    args:
      ob - mesh object
      view - Mathutils Vector
      
    return:
       new mesh of type Mesh (not BMesh)
    '''
    if debug:
        start = time.time()
        
    #careful, this can get expensive with multires
    me = ob.to_mesh(context.scene, True, 'RENDER')    
    bme = bmesh.new()
    bme.from_mesh(me)
    bme.normal_update()
    
    if debug:
        face_time = time.time()
        print("took %f to initialze the bmesh" % (face_time - start))
        
    face_directions = [[0]] * len(bme.faces)
    
    for f in bme.faces:
        if debug > 1:
            print(f.normal)
        
        face_directions[f.index] = f.normal.dot(view)
    
    
    if debug:
        edge_time = time.time()
        print("%f seconds to test the faces" % (edge_time - face_time))
        
        if debug > 2:
            print(face_directions)
            
    delete_edges = []
    keep_verts = set()
    
    for ed in bme.edges:
        if len(ed.link_faces) == 2:
            silhouette = face_directions[ed.link_faces[0].index] * face_directions[ed.link_faces[1].index]
            if silhouette < 0:
                keep_verts.add(ed.verts[0])
                keep_verts.add(ed.verts[1])
            else:
                delete_edges.append(ed)
    if debug > 1:
        print("%i edges to be delted" % len(delete_edges))
        print("%i verts to be deleted" % (len(bme.verts) - len(keep_verts)))
    if debug:
        delete_time = time.time()
        print("%f seconds to test the edges" % (delete_time - edge_time))
        
    delete_verts = set(bme.verts) - keep_verts
    delete_verts = list(delete_verts)
    
    
    #https://svn.blender.org/svnroot/bf-blender/trunk/blender/source/blender/bmesh/intern/bmesh_operator_api.h
#presuming the delte enum is 0 = verts, 1 = edges, 2 = faces?  who knows.
    bmesh.ops.delete(bme, geom = bme.faces, context = 3)
    bmesh.ops.delete(bme, geom = delete_verts, context = 1)
    #bmesh.ops.delete(bme, geom = delete_edges, context = 2)  
    
    new_me = bpy.data.meshes.new(ob.name + '_silhouette')
    bme.to_mesh(new_me)
    bme.free()
    
    obj = bpy.data.objects.new(new_me.name, new_me)
    context.scene.objects.link(obj)
    
    obj.select = True
    context.scene.objects.active = obj
    
    if smooth:
        mod = obj.modifiers.new('Smooth', 'SMOOTH')
        mod.iterations = 10
    
        mod2 = obj.modifiers.new('Wrap','SHRINKWRAP')
        mod2.target = ob
    
    if debug:
        print("finished in %f seconds" % (time.time() - start))
    
    return

1 Like

here is a comparison of the smoothed to not smoothed result. The smoothing clearly makes the precision a little lower, but it’s close enough for my work. It leaves the smooth modifier un-applied so one could easily adjust the amount of smoothing and therefore the amount of precision lost.


thankyou!

I will use for sure!

This looks really interesting, but how does it work exacly? Am I supposed to select a mesh and run the script in the python window? Or is this a plugin?

Paste into Text Editor and click Run Script. So. That’s not a addon this time :slight_smile:

can you make it interactive?.. as in, if i rotate the camera it keeps on showing those sihlouettes, so we can make a “toon outline” but geometry based (it’d be super cool).

Yes, but performance would depend on your system. I’m fairly busy right now but if you wanted to take a stab at it, I would be happy to review code and make suggestions.

I can’t code sadly,
An other good use for this would be making an onion skinning tool for animators like this one, if i have time to investigate i’ll try to do it myself =)
http://www.graphite9.com/MayaDownloads.html

Akh, that looks awesome but I can’t get it to output anything. Do I need to do something… ? I tried selecting the object, the mesh, but it won’t create the silhouette.

Hadrien

Noted. Pretty cool implementation :slight_smile:

Damn this could be really nice, I’ll take a look at onion skinning with it. some problems need to be solved, like maybe working with linked chars/groups/proxies, and using greasepencil instead of mesh.

Excellent! =)

Messed with this a bit and turned it into an operator: http://www.pasteall.org/47767/python (had to use pasteall since the formatting gets all messed up for some odd reason, chrome thing maybe?)

Some more playing, got it working with grease pencil --> http://www.pasteall.org/47834/python

How does it work with greasepencil?

Like this…


@^ wow cool… I imagine that is very cool when objects are animated and change shape.

whoa can you do this on every keyframe? Or +/- 10 keyframes?

edit: DOH! There is an onion skin checkbox already built into the grease pencil…and that is exactly what you are showing there. Really brilliant way to take this to the next level. All it needs now is a litte button right there next to the grease pencil panel.

So how do you install this and use it. Just put it in the scripts/addon folder?

It is amazing! I have to draw the silhouette by hand when i need the ghost, so this is perfect. But, i cant make it work… i run the script and nothing happens… its for a specific version of Blender?
Thanks!