0
$\begingroup$

Here's my mesh. It has some interior faces (it's made out of merged cubes) that I want to remove. I've highlighted two of them for example:

I want to remove

When I run Select Interior Faces, it selects them, but also erroneously selects the large middle face:

wrong interior

I believe I understand why this is happening, due to the nature of the vertices at the edge of this face and how the "interior" faces algorithm works.

Is there an automatable way using Blender scripting to ignore faces that are visible to the camera, or achieve a result similar to that? I'm writing a Blender script to do some automatic mesh cleanup. Removing interior faces is a required part of the process, but I'm not sure how to achieve it in a script.

$\endgroup$
  • $\begingroup$ Use limit selection to visible related: blender.stackexchange.com/questions/14764/… $\endgroup$ – cegaton Jul 9 '16 at 19:28
  • $\begingroup$ @cegaton I'm trying to do this in a script. Can bounding box selection be scripted? I can't find any resources on it. $\endgroup$ – Andy Ray Jul 9 '16 at 19:28
  • $\begingroup$ select_interior_faces() from the docs: Select faces where all edges have more than 2 face users. also the "use_occlude_geometry" is situated in bpy.types.spaceView3D If you want to see how they are done just right click on the icons button and select "edit source", it will open the code in the text editor, but without enough knowledge of bpy it might be really hard to understand. did you get to this link ? blender.org/api/blender_python_api_2_77a_release $\endgroup$ – Yvain Jul 10 '16 at 9:08
2
$\begingroup$

Here's the solution I came up with:

  1. Run Blender's built in "select interior face" algorithm
  2. Note which faces are selected
  3. Loop over every face, and cast a ray from the "camera" position to the face.
  4. If the ray hits (the center of) the face, we know it's visible to the camera, so don't delete it.
  5. 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:

no hit example

hit example

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 ) > 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 ) > 0:
        mesh.delete( type = 'FACE' )

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.