Using Python I want to split a mesh into tiles. All the solutions to this question rely on bpy.ops.mesh.separate(type='LOOSE')
. But this leads to problems in the following mesh:
I need 10 objects, named bisect-1, bisect-2 etc. But after bisecting with the code below the overhanging cubes on top become disconnected from the surface, and will end up in their own object as a result of bpy.ops.mesh.separate(type='LOOSE')
. I end up with 36 objects instead of 10.
How to bisect a mesh into n parts, without separate 'LOOSE'?
import bpy, bmesh
from bpy import context as C
from mathutils import Vector
def bounds(obj, local=False):
local_coords = obj.bound_box[:]
om = obj.matrix_world
if not local:
worldify = lambda p: om * Vector(p[:])
coords = [worldify(p).to_tuple() for p in local_coords]
else:
coords = [p[:] for p in local_coords]
rotated = zip(*coords[::-1])
push_axis = []
for (axis, _list) in zip('xyz', rotated):
info = lambda: None
info.max = max(_list)
info.min = min(_list)
info.distance = info.max - info.min
push_axis.append(info)
import collections
originals = dict(zip(['x', 'y', 'z'], push_axis))
o_details = collections.namedtuple('object_details', 'x y z')
return o_details(**originals)
object_details = bounds(C.object, True)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(C.object.data)
edges = []
# split the model into parts along y-axis, with steps of 10 in local coordinate space (this ignores the scale property, so make sure to apply it before)
for i in range(int(round(object_details.y.min)), int(round(object_details.y.max)), 10):
ret = bmesh.ops.bisect_plane(bm, geom=bm.verts[:]+bm.edges[:]+bm.faces[:], plane_co=(0,i,0), plane_no=(0,1,0))
bmesh.ops.split_edges(bm, edges=[e for e in ret['geom_cut'] if isinstance(e, bmesh.types.BMEdge)])
bmesh.update_edit_mesh(C.object.data)
bpy.ops.mesh.separate(type='LOOSE')
bpy.ops.object.mode_set(mode='OBJECT')
Edit: code changes after comment from batFINGER. Object isn't yet split after bisecting. But the 36 separate islands are identified. Now I need to group the islands based on the bisection coordinates, make separate objects and name them appropriately. But I'm not sure how to approach this.
import bpy, bmesh
from bpy import context as C
from mathutils import Vector
# https://blender.stackexchange.com/questions/75332/how-to-find-the-number-of-loose-parts-with-python/105142#105142
def walk_island(vert):
''' walk all un-tagged linked verts '''
vert.tag = True
yield(vert)
linked_verts = [e.other_vert(vert) for e in vert.link_edges
if not e.other_vert(vert).tag]
for v in linked_verts:
if v.tag:
continue
yield from walk_island(v)
def get_islands(bm, verts=[]):
def tag(verts, switch):
for v in verts:
v.tag = switch
tag(bm.verts, True)
tag(verts, False)
ret = {"islands" : []}
verts = set(verts)
while verts:
v = verts.pop()
verts.add(v)
island = set(walk_island(v))
ret["islands"].append(list(island))
tag(island, False) # remove tag = True
verts -= island
return ret
# https://blender.stackexchange.com/questions/32283/what-are-all-values-in-bound-box
def bounds(obj, local=False):
local_coords = obj.bound_box[:]
om = obj.matrix_world
if not local:
worldify = lambda p: om * Vector(p[:])
coords = [worldify(p).to_tuple() for p in local_coords]
else:
coords = [p[:] for p in local_coords]
rotated = zip(*coords[::-1])
push_axis = []
for (axis, _list) in zip('xyz', rotated):
info = lambda: None
info.max = max(_list)
info.min = min(_list)
info.distance = info.max - info.min
push_axis.append(info)
import collections
originals = dict(zip(['x', 'y', 'z'], push_axis))
o_details = collections.namedtuple('object_details', 'x y z')
return o_details(**originals)
object_details = bounds(C.object, True)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(C.object.data)
edges = []
# split the model into parts along y-axis, with steps of 10 in local coordinate space (this ignores the scale property, so make sure to apply it before)
for i in range(int(round(object_details.y.min)), int(round(object_details.y.max)), 10):
ret = bmesh.ops.bisect_plane(bm, geom=bm.verts[:]+bm.edges[:]+bm.faces[:], plane_co=(0,i,0), plane_no=(0,1,0))
bmesh.ops.split_edges(bm, edges=[e for e in ret['geom_cut'] if isinstance(e, bmesh.types.BMEdge)])
bmesh.update_edit_mesh(C.object.data)
# bpy.ops.mesh.separate(type='LOOSE')
islands = [island for island in get_islands(bm, verts=bm.verts)["islands"]]
print("Islands:", len(islands))
print([len(i) for i in islands])
bpy.ops.object.mode_set(mode='OBJECT')