1
$\begingroup$

I'm to test a programming idea I have in Blender. I need to combine multiple cubes. I want to create lots of duplicate cubes, and then delete all faces that are doubled.

I know there is the remove double faces, but that doesn't do what I want. It leaves a single face where two faces were.

Blender two cubes In this example, I want to join the two cubes, but where they join, I want both faces deleted since they can't be seen.

$\endgroup$
1
  • $\begingroup$ I marked the answer on this question as complete, since it solves my initial question and provides some information at a basic level. Since it didn't solve my problem yet, I reposted a new question which adds additional info about this question. $\endgroup$
    – Lazetti
    May 21 '19 at 17:57
2
$\begingroup$
  • 'Remove Doubles' (that refers to vertices, actually, not faces) as you already have..
  • In the header Select menu: Select All by Trait > Interior Faces
  • X > Delete Faces
$\endgroup$
2
  • $\begingroup$ .. but maybe expand on your programming idea.. there may be other approaches.. $\endgroup$ May 15 '19 at 23:52
  • $\begingroup$ That works pretty close for what I wanted, and it answers the question I posed. I'm going to open a new question linking back to this for the full question. I'm trying to create a voxel map like what is done with Minecraft. In a voxel-map like that, faces are only drawn if they are exposed to a non-solid block. I'm trying to make a model based on blocks and then delete the duplicate faces because that's the same procedure. The answer works, but when I try to scale it up, it doesn't. $\endgroup$
    – Lazetti
    May 21 '19 at 17:32
5
$\begingroup$

BMesh script.

enter image description here

Wrote the script for another robloxic type question, buggered if I can find it maybe didn't post an answer. Also note script is for blender 2.8, whereas your question image is clearly 2.79. If you are just starting out scripting in blender, my advice is to start using 2.8

Any way, here is a script to run in object mode with all the cubes you want to join selected.

Loads all selected objects into a global bmesh. Make sure only cubes are selected, haven't tested for it as script expects selected objects to only be 8 vertex cubes. Also added commented out code to select cubes based on their name starting with "Cube"

Tests if two faces have same center using a bvh tree loaded with the bmesh. The find_nearest_range method finds each face twice, hence 4 matches being a pair, and 2 matches being no match

A further more robust test would be checking opposite facing normals, and to be sure to be sure the vert coordinates of the face themselves.

If remove_originals is set to True then the original cube objects are removed.

If remove_doubles is set to True it will remove doubles.

import bpy
import bmesh
from mathutils import Vector
from mathutils.bvhtree import BVHTree

remove_originals = True
remove_doubles = True

context = bpy.context
scene = context.scene
# by selection
cubes = context.selected_objects
# by name
#cubes = [o for o in scene.objects if o.name.startswith("Cube")]

bm = bmesh.new()
for o in cubes:
    bm.from_mesh(o.data)
    bmesh.ops.transform(bm,
            verts=bm.verts[-8:],
            matrix=o.matrix_world
            )
if remove_originals:
    bpy.ops.object.delete()
    
bvhtree = BVHTree().FromBMesh(bm, epsilon=1e-7)
faces = bm.faces[:]

remove = list()
while faces:        
    f = faces.pop()        
    pair = bvhtree.find_nearest_range(f.calc_center_median(), 1e-4)
    if len(pair) > 2:
        # mark face for removal
        remove.extend(p[2] for p in pair)

bm.faces.ensure_lookup_table()
bmesh.ops.delete(bm,
        geom=[bm.faces[i] for i in set(remove)],
        context='FACES_KEEP_BOUNDARY',
        )
if remove_doubles:
    bmesh.ops.remove_doubles(bm,
            verts=bm.verts,
            dist=1e-7)

me = bpy.data.meshes.new("CubeBoundaryFaces")
bm.to_mesh(me)
ob = bpy.data.objects.new("CubeBoundaryFaces", me)

scene.collection.objects.link(ob)
context.view_layer.objects.active = ob
ob.select_set(True)

Note, would be for quicker, as pointed out by subd guru @RobinBetts, to simply join, remove doubles then remove interior faces, done via a script using the appropriate operators and object mode for them.

Another take would be to use the set of face indices to remove the faces from the original cubes without joining them. Since the compound bmesh faces will be in same order as the originals.

for i in set(remove):
    ob = cubes[i // 6] # 6 faces per cube
    f = ob.data.polygons[i % 6]

EDIT

As pointed out by @MarkusVonBroady in comments this will remove any cube that overlaps another. test blend

Here is a numpy test to only use cubes with unique matrix worlds. In this case translations (locations) would suffice.

>>> cubes = np.array(C.selected_objects)

>>> A = np.array([o.matrix_world for o in cubes])

Return indices of unique matrix worlds to 2 decimal places

>>> B, indices = np.unique(A.round(decimals=2), axis=0, return_index=True)

10 cubes in, 9 uniquely positioned.

>>> len(cubes)
10

>>> len(indices) # or len(B)
9

Iterate over

>>> cubes[indices]

The dupes being

>>> set(range(10)) - set(indices)
{3}

then it occurs the same could be used to look for and remove dupe faces using Mesh.polygons[i].center

$\endgroup$
6
  • $\begingroup$ I think this answers my question, so +1, but I would like to be able to do it without scripting. $\endgroup$
    – Lazetti
    May 21 '19 at 17:36
  • $\begingroup$ Did I find a bug or am I using the script incorrectly? .GIF The file is saved just before hitting the "play" button: .BLEND $\endgroup$ Sep 17 at 8:36
  • $\begingroup$ @MarkusvonBroady finally got around to taking a look, the hole is created where there are two overlapping cubes. Not sure it is worth fixing for this case, although as mentioned in answer testing face normals would help somewhat since will have same face center, same face normal. Or look if there is another match same as first.... or run a test for exactly overlapping cubes. 3) $\endgroup$
    – batFINGER
    Sep 18 at 15:22
  • $\begingroup$ @batFINGER ohh, it's my fault then, the script pretty much by design removes double faces. Thanks for looking into it! $\endgroup$ Sep 18 at 15:24
  • 1
    $\begingroup$ @MarkusvonBroady recommend use pasteall.org/blend for temp demo blends $\endgroup$
    – batFINGER
    Sep 18 at 16:50
2
$\begingroup$

(moving from Extracting surface from mesh with interior geometry)

Problems

Interesting question, I played for a while and just couldn't find a scriptless solution: I'm bad with Remesh modifier, so I'd love to see this problem solved with it by someone else, selecting everything from all orthographic view presets will not select faces of holes that are perpendicular to the view. Even drawing selection with C Selection Circle will select internal faces due to how selecting non-manifold geometry works in Blender. When selecting faces one by one with default selection tool, sometimes I would end up with no new face being selected after a click, making me paranoid and undoing a few steps to really make sure nothing is selected inside! 😅

Eventually I started to doubt this could be solved without Python, considering, that some external vertices and edges can have maximum connections, so the number of connected geometry isn't useful here:

Python

define a grid on which the voxels sit, detect which cells of the grid are taken, then detect which faces point towards those cells - those are the internal faces.

I reproduced the problem by 3 Array modifiers on the default cube, applying them, removing some vertices for a more interesting shape, merging doubled vertices, and (eventually, when debugging my code) recalculating all normals outside. The script assumes the voxels are aligned to axes, but it's forgiving enough, that you can align the mesh manually:

import bpy, bmesh
from mathutils import Vector
from math import floor


def main():
    me = bpy.context.active_object.data
    bm = bmesh.from_edit_mesh(me)

    ruler = bm.edges[0].verts
    edge_len = (ruler[0].co - ruler[1].co).length
    half_len = edge_len / 2
    mins = Vector([float("inf")]*3)
    maxs = mins * -1
    for v in bm.verts:
        mins = Vector(map(min, zip(mins, v.co)))
        maxs = Vector(map(max, zip(maxs, v.co)))
    
    cols = round((maxs.x - mins.x) / edge_len)
    dims = [round((b-a) / edge_len) for a, b in zip(mins, maxs)]
    print(f"{mins=}, {maxs=}, {dims=}")
    grid = [
        [
            [False] * dims[0]        # x
            for y in range(dims[1])  # y
        ] for z in range(dims[2])    # z
    ]
    to_grid_co = lambda co: map(floor, (co - mins) / edge_len)
    cache = [(f, f.calc_center_median()) for f in bm.faces]
    
    '''check which grid cells are taken:
    perhaps for axis aligned voxels this could be optimized and process
    only faces with a single normal, e.g. only top face of each voxel'''
    for f, face_center in cache:
        voxel_center = face_center + f.normal * -1 * half_len
        x, y, z = to_grid_co(voxel_center)
        grid[z][y][x] = True
# for debugging
#        if (z, y, x) == (2, 3, 4):
#            f.select_set(True)
#            bmesh.update_edit_mesh(me)
#            return
#    for z, layer in enumerate(grid):
#        print(f"\n{z=}", end="")
#        for y, column in enumerate(layer):
#            print(f"\n{y=}", end=":   ")
#            for x, value in enumerate(column):
#                print(x, value, end="; ")
    
    # now select faces which point outside taken grid cells
    for f, face_center in cache:
        outside = face_center + f.normal * half_len
        x, y, z = to_grid_co(outside)
        try:
            if min(x, y, z) < 0 or not grid[z][y][x]:
                # outside entire grid on negative end, or no voxel
                f.select_set(True)
            else:
                f.select_set(False)
        except IndexError:  # outside the grid on the positive end
            f.select_set(True)
                
    bmesh.update_edit_mesh(me)


main()

This was my 2nd episode with voxels on BSE, here's the first one: How can I close holes in hexahedral meshes?

$\endgroup$
0
$\begingroup$

To do that, turn on all vertex visibility,

Select all the Vertex at the point of contact, then look for "Loop Tool" under the editing panel in the "Tool Shelf"

Use either "Bridge" or "Loft" in the "Loop Tool" option.

It will join them together

$\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.