3
$\begingroup$

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:

36 objects instead of 10

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') 
$\endgroup$
4
  • $\begingroup$ Might find this answer useful. Instead of separating into new objects returns a dictionary of the islands. $\endgroup$ Commented Sep 21, 2018 at 13:02
  • $\begingroup$ Thought there was something familiar about this. After running your bisects, get the list of islands using code from comment above. Each island will belong to a bisect based on coords. There may be none, one or more islands per bisect. From there could split by selection, or (possibly simpler-ish) join the split objects that share verts in a bisect. $\endgroup$ Commented Sep 21, 2018 at 13:32
  • $\begingroup$ This is interesting! Code from first comment finds 36 islands. So how do I group these into 10 objects? I'd like to name them bisect-1, bisect-2 etc. Do you think I need this answer to make separate objects? $\endgroup$ Commented Sep 21, 2018 at 14:01
  • $\begingroup$ Yes that would be one way. Or use something like left - right test to see if (say centroid) point of each split is between planes. Also there is a clear inner (or outer) on the bisect plane operator. Might be easier to step thru this way with each set of two planes. ie copy bmesh bisect clear inner on plane i, and outer on plane i+1 write to new mesh... for each section. example of clear outer $\endgroup$ Commented Sep 21, 2018 at 14:25

2 Answers 2

6
$\begingroup$

Using clear inner and outer on bisect plane operator.

Further to my comment, think this may be the simplest way.
Test script, which I've tested on Suzanne (who has seperate eye objects) and appears to work correctly.

Test script uses bmesh of original object, then for each set of two planes bisects and clears inner on left hand plane, and outer on right hand plane on a copy of the original bmesh.

Run in object mode with object to split selected. Creates a new object for each split. Doesn't remove original.

(for example sake splits in along local z axis in units from -10 to 10)

import bpy, bmesh from bpy import context as C from mathutils import Vector def newobj(bm): me = bpy.data.meshes.new("split") bm.to_mesh(me) ob = bpy.data.objects.new("split", me) C.scene.objects.link(ob) bmo = bmesh.new() bmo.from_mesh(C.object.data) planes = [Vector((0, 0, i)) for i in range(-10, 10, 1)] p0 = planes.pop(0) while planes: bm = bmo.copy() p1 = planes.pop(0) bmesh.ops.bisect_plane(bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], plane_co=p0, plane_no=(0,0,1), clear_inner=True) bmesh.ops.bisect_plane(bm, geom=bm.verts[:] + bm.edges[:] + bm.faces[:], plane_co=p1, plane_no=(0,0,1), clear_outer=True) if len(bm.verts): newobj(bm) p0 = p1 

Naming the bisects based on each plane pair should be trivial after determining the correct splits as you have in question.

$\endgroup$
5
$\begingroup$

Thanks to the comments of batFINGER I came up with a nearly identical solution. Main difference is that I cut again from the remains of the previous iteration, gradually reducing the mesh to bisect. I guess this performs better than bisecting from the original bmesh each iteration.

import bpy, bmesh from bpy import context as C from mathutils import Vector def newobj(bm, name): me = bpy.data.meshes.new(name) bm.to_mesh(me) ob = bpy.data.objects.new(name,me) C.scene.objects.link(ob) return ob # 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='OBJECT') cut_index = 0 step_size = 10 bisection_outer = bmesh.new() bisection_outer.from_mesh(C.object.data) # 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+step_size)), step_size): bisection_inner = bisection_outer.copy() bmesh.ops.bisect_plane(bisection_outer, geom=bisection_outer.verts[:]+bisection_outer.edges[:]+bisection_outer.faces[:], plane_co=(0,i,0), plane_no=(0,1,0), clear_inner=True) bmesh.ops.bisect_plane(bisection_inner, geom=bisection_inner.verts[:]+bisection_inner.edges[:]+bisection_inner.faces[:], plane_co=(0,i,0), plane_no=(0,1,0), clear_outer=True) newobj(bisection_inner, "bisect-"+str(cut_index)) bisection_inner.free() # free and prevent further access cut_index+=1 bisection_outer.free() # free and prevent further access bpy.data.objects.remove(C.object, True) 
$\endgroup$
3
  • 1
    $\begingroup$ Nice one, agree using cut down mesh each time more efficient, didn't bother for example sake. Prob. no need for edit mode also. Please consider upvoting too if you find questions and answers useful. $\endgroup$ Commented Sep 21, 2018 at 15:47
  • $\begingroup$ Thanks you're right. Works in object mode like your solution. Upvoted :) $\endgroup$ Commented Sep 21, 2018 at 15:50
  • $\begingroup$ It removes materials.. $\endgroup$ Commented Jan 18, 2019 at 18:19

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.