# How to cut a mesh into smaller pieces with python?

I have a big terrain object, I'm trying to write a script that cut it in smaller pieces and then I will export the pieces to other formats. This is required by a game engine because the mesh, if not subdivided at export time, is not properly culled, resulting in slow rendering.

I tried to use bpy.ops.view3d.select_border() but it fails with message:

RuntimeError: Operator bpy.ops.view3d.select_border.poll() expected a view3d region


A search revealed that select_border must be called from 3d view, probably by clicking a button. But this is not a generalized script, it is specific to this model, I will rewrite parts of it if I need it for another model. I want to run it from text editor directly.

The only solution I can see for now is to iterate every vertex of the selected object and if they are inside the defined tile size, then duplicate, separate, export, delete, and repeat moving one tile to the left, starting at object dimensions[0] / 2 * -1, dimensions[1] / 2 * -1. Iterate vertices and select them can be done from text editor.

Is there a more convenient way of doing this?

• You could try overriding the context for the select_border operator – gandalf3 Oct 31 '13 at 19:46
• This must be the way. If I override the context, no more exceptions, but nothing get selected. Maybe the coordinate system used by the select_border operator is not what I think. – Hatoru Hansou Oct 31 '13 at 21:15
• I did a test with an absurd high number for xmax and ymax and I get something selected. This question is solved. If you post as an answer I will select your answer. – Hatoru Hansou Oct 31 '13 at 21:39
• I tried Gandalf3's script, and I cannot get it to select anything at all, even trying it with the view3d.render_border, it will not create a box. Am I doing something wrong? I tried making my own question, but was redirected to here... – rioforce Dec 23 '13 at 20:05
• @rioforce, you can use select_border operator with the correct context overriden, but you will have to fight another problem. The values of xmax and y ymax, related to the view and its zoom, are not explained anywhere. Create a plane from -10 to 10 in x/y axes. Subdivide 10 or more times. Zoom out until you can see all vertices inside the view. Experiment with xmax and ymax until you figure out its scale. I don't remember now if they change with zoom. To get something selected I used values above 1000. For my terrain mesh I decided to use bisect_plane (CoDEmanX's way). – Hatoru Hansou Dec 24 '13 at 21:20

You can override the context for the operator so you can still run it from the text editor.

From the API docs:

When calling an operator you may want to pass the execution context.

This determines the context thats given to the operator to run in, and weather invoke() is called or execute().

‘EXEC_DEFAULT’ is used by default but you may want the operator to take user interaction with ‘INVOKE_DEFAULT’.

The execution context is as a non keyword, string argument in: (‘INVOKE_DEFAULT’, ‘INVOKE_REGION_WIN’, ‘INVOKE_REGION_CHANNELS’, ‘INVOKE_REGION_PREVIEW’, ‘INVOKE_AREA’, ‘INVOKE_SCREEN’, ‘EXEC_DEFAULT’, ‘EXEC_REGION_WIN’, ‘EXEC_REGION_CHANNELS’, ‘EXEC_REGION_PREVIEW’, ‘EXEC_AREA’, ‘EXEC_SCREEN’)

# group add popup import bpy


It is also possible to run an operator in a particular part of the user interface. For this we need to pass the window, screen, area and sometimes a region.

# maximize 3d view in all windows
import bpy

for window in bpy.context.window_manager.windows:
screen = window.screen

for area in screen.areas:
if area.type == 'VIEW_3D':
override = {'window': window, 'screen': screen, 'area': area}
bpy.ops.screen.screen_full_area(override)
break


E.g.:

import bpy

screen = bpy.context.window.screen
for area in screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
override = {'window': bpy.context.window, 'screen': screen, 'area': area, 'region': region}
bpy.ops.view3d.select_border(override, xmin=0, xmax=0, ymin=0, ymax=0, extend=False)
break

• I tried this, and I still cannot get something selected. I tried changing the xmin, xmax, ymin, and ymax values but still nothing selected. Any help? – rioforce Dec 14 '13 at 21:16

I don't think hijacking select_border() is a good approach. You could use the new bisect_plane() bmesh operator to slice your mesh.

Here's a straightforward script that slices a mesh into an aligned grid:

import bpy, bmesh
from bpy import context as C

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

bm = bmesh.from_edit_mesh(C.object.data)

edges = []

for i in range(-10, 10, 2):
ret = bmesh.ops.bisect_plane(bm, geom=bm.verts[:]+bm.edges[:]+bm.faces[:], plane_co=(i,0,0), plane_no=(-1,0,0))
bmesh.ops.split_edges(bm, edges=[e for e in ret['geom_cut'] if isinstance(e, bmesh.types.BMEdge)])

for i in range(-10, 10, 2):
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')

• Indeed, this is more efficient that what I was doing before. – Hatoru Hansou Nov 1 '13 at 9:42
• Just tested it, coincidentally my terrain mesh start at -10 and ends at 10 in x and y axes. So the script works without needing any change. – Hatoru Hansou Nov 1 '13 at 10:02
• CoDEmanX, thanks for the tip of using "isinstance" to sort the elements returned from bmesh.ops operators! I've been using "hasattr" and trying to find a unique attribute of faces, edges and verts. But it always felt hacky. – patmo141 Jun 19 '15 at 15:56
• That sounds very hacky indeed. Another workaround is to do a string comparison on the type, like str(type(bm.verts[0])) == "<class 'BMVert'>", but that's not less ugly. – CodeManX Jul 1 '15 at 11:54