Skip to content

Instantly share code, notes, and snippets.

@marcosbitetti
Last active August 2, 2025 18:41
Show Gist options
  • Select an option

  • Save marcosbitetti/c3e02a6c868b99e343dda28e0c896f55 to your computer and use it in GitHub Desktop.

Select an option

Save marcosbitetti/c3e02a6c868b99e343dda28e0c896f55 to your computer and use it in GitHub Desktop.
This tool get a large terrain on Blender, and create thin objects from it. Slicing original object. Useful to make game levels, spliting a huge scene and optimize each slice for LOD
import bpy
import bmesh
from mathutils import Vector
def slice_object_by_grid(obj_name, cell_size_x=5.0, cell_size_y=5.0):
"""
Slices a mesh object into new objects based on a grid.
Considers faces if *any* of their vertices fall within the grid cell.
Args:
obj_name (str): The name of the object to slice.
cell_size_x (float): The size of each grid cell along the X-axis.
cell_size_y (float): The size of each grid cell along the Y-axis.
"""
obj = bpy.context.scene.objects.get(obj_name)
if not obj or obj.type != 'MESH':
print(f"Error: Object '{obj_name}' not found or not a mesh.")
return
# Ensure we are in Object Mode initially for selection operations
bpy.ops.object.mode_set(mode='OBJECT')
# Deselect all objects first
bpy.ops.object.select_all(action='DESELECT')
# Select and make the target object active
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') # Go to Edit Mode to access mesh data efficiently
bm = bmesh.from_edit_mesh(obj.data)
# Get the bounding box of the object to determine grid limits
# We'll use the world coordinates for the bounding box
bbox_corners_world = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
min_x = min(v.x for v in bbox_corners_world)
max_x = max(v.x for v in bbox_corners_world)
min_y = min(v.y for v in bbox_corners_world)
max_y = max(v.y for v in bbox_corners_world)
# Calculate grid dimensions
# Add a small epsilon to max_x/max_y to ensure the last cell covers the full extent
epsilon = 0.0001 # Small value to ensure floating point precision issues don't cut off the last cell
num_cells_x = int((max_x - min_x + epsilon) / cell_size_x) + (1 if (max_x - min_x + epsilon) % cell_size_x != 0 else 0)
num_cells_y = int((max_y - min_y + epsilon) / cell_size_y) + (1 if (max_y - min_y + epsilon) % cell_size_y != 0 else 0)
# A simpler way to get num_cells, ensuring at least one cell for small objects
num_cells_x = max(1, int((max_x - min_x + epsilon) / cell_size_x) + 1)
num_cells_y = max(1, int((max_y - min_y + epsilon) / cell_size_y) + 1)
original_object_name = obj.name
new_object_counter = 0
# Loop through each cell in the grid
for i in range(num_cells_x):
for j in range(num_cells_y):
cell_min_x = min_x + i * cell_size_x
cell_max_x = cell_min_x + cell_size_x
cell_min_y = min_y + j * cell_size_y
cell_max_y = cell_min_y + cell_size_y
print(f"Processing cell: X[{cell_min_x:.2f}, {cell_max_x:.2f}], Y[{cell_min_y:.2f}, {cell_max_y:.2f}]")
# Deselect all faces within BMesh for the current iteration
for face in bm.faces:
face.select = False
faces_to_select = []
for face in bm.faces:
if not face.hide and not face.select: # Only consider unhidden and unselected faces
# *** MODIFICATION START ***
# Check if ANY vertex of the face is within the current cell's X and Y bounds
any_vertex_in_cell = False
for vert in face.verts:
world_vert_coord = obj.matrix_world @ vert.co
if (cell_min_x <= world_vert_coord.x <= cell_max_x and
cell_min_y <= world_vert_coord.y <= cell_max_y):
any_vertex_in_cell = True
break # Found one, no need to check other vertices of this face
if any_vertex_in_cell:
faces_to_select.append(face)
# *** MODIFICATION END ***
if faces_to_select:
for face in faces_to_select:
face.select = True
# Update the mesh in edit mode to reflect selections
bmesh.update_edit_mesh(obj.data)
# Separate selected faces into a new object
bpy.ops.mesh.separate(type='SELECTED')
# The newly created object will be the active one.
new_obj = bpy.context.view_layer.objects.active
if new_obj and new_obj.name != original_object_name:
new_obj.name = f"{original_object_name}_slice_{new_object_counter:03d}"
print(f"Created new object: {new_obj.name}")
new_object_counter += 1
# Now, switch back to Object Mode, select the ORIGINAL object,
# make it active, and then switch back to Edit Mode.
bpy.ops.object.mode_set(mode='OBJECT') # IMPORTANT: Switch to Object Mode
bpy.ops.object.select_all(action='DESELECT') # Deselect the new object
obj.select_set(True) # Select the original object
bpy.context.view_layer.objects.active = obj # Make it active
bpy.ops.object.mode_set(mode='EDIT') # Go back to Edit Mode for the original object
bm.free() # Free the old bmesh
bm = bmesh.from_edit_mesh(obj.data) # Re-get bmesh for the updated original object
else:
print("No faces found in this cell.")
# If no faces were found in a cell, ensure we're still on the original object
# and in edit mode for the next iteration
bpy.ops.object.mode_set(mode='OBJECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bm.free() # Free old bmesh if present (important to avoid issues)
bm = bmesh.from_edit_mesh(obj.data) # Re-get bmesh (even if no change, good practice)
# Important: Free the bmesh at the end when done with it
bm.free()
bpy.ops.object.mode_set(mode='OBJECT') # Go back to Object Mode
# Remove the original object if it has no faces left
if obj.data.polygons: # Check if there are still polygons
print(f"Original object '{original_object_name}' still has faces left ({len(obj.data.polygons)}).")
else:
print(f"Original object '{original_object_name}' has no faces left, removing it.")
bpy.data.objects.remove(obj, do_unlink=True)
# --- How to use the script ---
if __name__ == "__main__":
# Replace 'Cube' with the name of your object
# Make sure your object is visible and not hidden
object_to_slice = "Cube" # e.g., 'Plane', 'Suzanne', etc.
slice_object_by_grid(object_to_slice, cell_size_x=5.0, cell_size_y=5.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment