EDIT: Here is the .blend file:
EDIT 2: Here is the mesh I want it to work on if you're interested.
It's being used for a game I'm making but there is a maximum mesh size which is 20.48 Blender Units (The cube is just the region showing the mesh size limit)
I've been working on an algorithm that takes a mesh which has its origin to its geometry and bisects it into cubic chunks of a specified size, selects the faces of these chunks and then separates them. After each separation, it cleans up the scene by removing meshes with dimensions of more than 2 axes of 0, and adding a solidify modifier to meshes where only one axis has a dimension of zero.
However, when assembling each of these 5 steps an error occurs and I don't understand how to debug these errors.
I have imported bpy, time and math.
Here is the code for step 1 where the mesh is bisected into cubic chunks:
select function:
def select(obj):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
else:
return
verts = obj.data.vertices
edges = obj.data.edges
faces = obj.data.polygons
for v in verts:
v.select = True
for e in edges:
e.select = True
for f in faces:
f.select = True
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
Bisection:
bpy.context.view_layer.objects.active = obj
so = obj
#so means 'selected object'
name = so.name
start = [0,0,0]
dim = so.dimensions
loc = so.location
cell_size = size
for i in range(3):
start[i] = math.floor((loc[i] - (dim[i]/2))/cell_size)*cell_size
if output:
print(start, end)
x_segments = (math.ceil((end[0]-start[0]) / cell_size))
y_segments = (math.ceil((end[1]-start[1]) / cell_size))
z_segments = (math.ceil((end[2]-start[2]) / cell_size))
if output:
print(x_segments,y_segments,z_segments)
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
for x in range(x_segments+1):
select(so)
if bpy.ops.mesh.bisect.poll():
bpy.ops.mesh.bisect(plane_co=((x*cell_size) + start[0],0,0),plane_no=(1,0,0))
for y in range(y_segments+1):
select(so)
if bpy.ops.mesh.bisect.poll():
bpy.ops.mesh.bisect(0,plane_co=((y*cell_size) + start[1],0),plane_no=(0,1,0))
for z in range(z_segments+1):
select(so)
if bpy.ops.mesh.bisect.poll():
bpy.ops.mesh.bisect(0,0,plane_co=((z*cell_size) + start[2]),plane_no=(0,0,1))
if output:
print("Bisected")
For the separation I used the centre of the polygons which, due the bisection, should never be overlapping two chunks. I also utilised a dictionary of lists containing the faces to separate into a mesh.
Vector Hash function:
def VectorHash(*args):
x,y,z = 0,0,0
x = args[0]
y = args[1]
x = args[2]
return x * 314159 + y * 232357 + z * 998873
Separation:
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
global cells
cells = {}
vertices = so.data.vertices
polygons = so.data.polygons
for face in polygons:
#Get global face center coordinates
local_co = face.center
co = so.matrix_world @ local_co
x = co[0] // cell_size
y = co[1] // cell_size
z = co[2] // cell_size
print(VectorHash(x, y, z))
try:
cells[str(VectorHash(x, y, z))].append(face)
except:
cells[str(VectorHash(x, y, z))] = list((face))
print("Cells are: ",cells)
for cell in cells.copy().values():
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
for face in polygons:
face.select = False
for face in cell:
face.select = True
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
try:
selected = False
for face in polygons:
if face.select:
selected = True
if selected:
if bpy.ops.mesh.separate.poll():
bpy.ops.mesh.separate(type='SELECTED')
except:
pass
if output:
print("Done")
#Get the largest object to apply the separation on again
for obj in bpy.context.view_layer.objects:
bpy.context.view_layer.objects.active = obj
obj.select_set(False)
if name in obj.name:
chunk = True
for dimension in obj.dimensions:
if dimension > cell_size:
chunk = False
if not chunk:
so = obj
bpy.context.view_layer.objects.active = so
cleanup()
And here is the cleanup function that deletes useless meshes
def cleanup():
for obj in bpy.data.objects:
if obj.name == "Camera" or obj.name == "Light":
continue
print(obj.name,obj.dimensions)
delete = 0
for d in obj.dimensions:
if d <= 0.05:
delete = delete + 1
verts = 0
edges = 0
faces = 0
for v in obj.data.vertices:
verts += 1
for e in obj.data.edges:
edges += 1
for f in obj.data.polygons:
faces += 1
if faces <= 0:
delete = True
if delete > 1 or delete == True:
#Get the name so as to rename the objects
name = obj.name
bpy.data.objects.remove(obj)
for object in bpy.data.objects:
if name in object.name:
#Blender to readjust the name
object.name = name
elif delete == 1:
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
if bpy.ops.object.modifier_add.poll():
bpy.ops.object.modifier_add(type='SOLIDIFY')
The problem is that sometimes things such as poll functions don't return true so important functions aren't executed, or collections like so.data.polygons
is empty because it selected an empty mesh or didn't select one at all and I don't know what makes it work and what doesn't.
How do I assemble these in a full proof way that works without confusing to debug problems?