Automated way to make "Select interior faces" ignore select faces that are visible to the camera?
Here's the solution I came up with:


Run Blender's built in "select interior face" algorithm
Note which faces are selected
Loop over every face, and cast a ray from the "camera" position to the face.
If the ray hits (the center of) the face, we know it's visible to the camera, so don't delete it.
If the ray doesn't hit the face (hits another face) it means it's probably interior.


This is a bit confusing, so I made two images to show more detail:





Commented code is below. Please note this is taken from a large file so I might have forgot to declare some variable references. Happy to clarify if anyone has questions in comments.

import bpy
import bmesh
from mathutils import Vector

cameraOrigin = Vector( ( 0, 0, 10 ) )

ops = bpy.ops
scene = bpy.context.scene
mesh = bpy.ops.mesh

current_mesh = scene.objects[ 0 ]
current_mesh_data = current_mesh.data
scene.objects.active = current_mesh

# Blender's "select interior faces" doesn't actually select interior faces, it
# selects faces that share edges with 2 or more other faces. In my case this
# erroneously selects some faces that are visible but connect to multiple other
# faces. The solution is to select interior faces, then ignore any faces that
# the camera (positioned at 0,0,10) can see, and delete the remaining ones
# See http//blender.stackexchange.com/questions/57540/automated-way-to-make-select-interior-faces-ignore-select-faces-that-are-visib
def removeInteriorFaces( mesh_data ):

    # First do the built in selection
    mesh.select_interior_faces()

    # And store all faces inside that selection
    indices = []
    bm = bmesh.from_edit_mesh( mesh_data )
    for index, face in enumerate( bm.faces ):
        if face.select:
            indices.append( ( index, face.calc_center_median_weighted() ) )

    # Deselect everything...
    mesh.select_all()

    # Switch to object mode to do scene raycasting (doesn't work in edit mode
    # I don't think, got error "has no mesh data to be used for ray casting"
    ops.object.mode_set( mode = 'OBJECT' )
    outside = []
    for index_data in indices:
        index = index_data[ 0 ]
        center = index_data[ 1 ]
        direction = center - cameraOrigin;
        direction.normalize()

        # Cast a ray from the "camera" position to the face we think is interior
        result, location, normal, faceIndex, object, matrix = scene.ray_cast( cameraOrigin, direction )

        # If the ray actually hit the face, as in the face index from the
        # selection matches the face index from the raycast, then this face
        # *is* visible to the camera, so don't remove it!
        if faceIndex == index:
            outside.append( faceIndex )

    # Build a list of the "true" interior face indices, which is the original
    # indices from Blender's built in "select interior faces", but without the
    # faces we know the camera can see
    invisible_interior_faces = [ data[ 0 ] for data in indices if data[ 0 ] not in outside ]

    print( 'Removing ',len( invisible_interior_faces ),'invisible faces' )

    # Select the faces (in object mode this is easy, strangely)...
    if len( invisible_interior_faces ) &gt; 0:

        for index in invisible_interior_faces:
            mesh_data.polygons[ index ].select = True

    ops.object.mode_set( mode = 'EDIT' )

    # Then delete them
    if len( invisible_interior_faces ) &gt; 0:
        mesh.delete( type = 'FACE' )

ops.object.mode_set( mode = 'EDIT' )
removeInteriorFaces( current_mesh_data )