Mesh Silhouette: My first Bmesh Operator

Hey guys, not sure if you still need a silhouette, there is several solutions out there, however I wrote an add-on that displays silhouettes in the view port with a simple toggle in the display panel :slight_smile:

Check it out here

Hope this is alright, I updated the script to work with blender 2.8 and included an example use at the bottom. Simply paste this into the text editor, select an object, and run the script.

Note: it works better with a higher resolution, so add a subsurf modifier to suzanne to get the same silhouette as in the pictures at the beginning of the thread.

Note 2: Blender 2.8 works with the preview/viewport settings of modifiers not the render settings.

Note 3: It seems the object you have selected must be located at the world origin for this to generate the silhouette correctly.

import bpy
import bmesh
import time
import mathutils

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
    dg = context.evaluated_depsgraph_get()
    me = ob.evaluated_get(dg).to_mesh()
    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 deleted" % 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 = delete_verts, context = 'VERTS')
    bmesh.ops.delete(bme, geom = bme.faces, context = 'FACES')
    #bmesh.ops.delete(bme, geom = delete_edges, context = 'EDGES')

    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.collection.objects.link(obj)

    obj.select_set(True)
    context.view_layer.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


# example usage
front_view = mathutils.Vector((0.0, 1.0, 0.0))
side_view = mathutils.Vector((1.0, 0.0, 0.0))
top_view = mathutils.Vector((0.0, 0.0, 1.0))

silouette_brute_force(bpy.context, bpy.context.object, front_view)
1 Like

Found out that if you combine front, side, and top view silhouettes, convert them to curves, and bevel them, you can create some really cool wireframe like statuary.
silhouette_wireframe_suzanne

3 Likes