Last active
August 2, 2025 18:41
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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