2

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=((xcell_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=((ycell_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 &lt;= 0:
    delete = True
if delete &gt; 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?

1 Answers1

2

I can't tell all of what is going on with your code, there seems to be missing sections.

Also you didn't post the error code so I can't really tell exactly how to fix your one specific problem.

Your question however seems more about philosophical "How to Write better Python" type question, so maybe I can offer a few pointers.

I think overall you should stop polling every 5 lines in your script. Theres a rule of thumb in Python known as Easier to Ask Forgiveness than Permission which in your context just means: stop asking Blender if you CAN do a thing before doing it, just do it and if an error occurs, either write an exception or correct the code to handle that case.

Like here:

def select(obj):
    # just call this method by itself, you don't need to poll.
    # if you're in edit mode, it does what you want, if not it does nothing.
    # ask forgiveness, not permission.
    bpy.ops.object.mode_set(mode='OBJECT')
    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
    # then, again just call the method you want.
    # if it does what you expect, great.
    # if not, it should do nothing.
    # you can write a try:, except block to handle it.
    # stop using plain except clauses.
    # always find the exact error, like AttributeError, or IndexError
    # and cite that in the exception.
    # a plain except will also bypass on NameError if you forgot to import something.
    bpy.ops.object.mode_set(mode='EDIT')

Generally speaking your code is also just way too full of if statements and between that and using poll() every 5 seconds it's no wonder half your code isn't running. Try to find ways to simplify the code, find a way to get one "final answer" as to whether you should go through this or that execution statement.

Like in this function:

# extract certain functionality to a separate function that is easier to 
# debug if there are issues, and easier to reuse. Clean code is easier to 
# troubleshoot.

def invalid_dimensions(dims, threshold=.05):

# Read the book `Python Cookbook by David Beazley` 
# you can find out alot of ways like this to use Python to 
# it's strengths and get the same data quicker and easier.
# returns the number of valid dimensions for an object

return sum([v &lt;= threshold for v in dims])

def cleanup():

use a list comprehension to get all the objects you want in one place,

THEN loop through

rather than checking every time if it's the correct object

with some `if object.type == "CAMERA" or object.type == "EMPTY":

branching statement.

objs = [obj for obj in bpy.data.objects if obj.type == &quot;MESH&quot;]
for obj in objs:
    bpy.context.view_layer.objects.active = obj
    print(obj.name,obj.dimensions)
    # stop doing all the weird addition stuff and find the minimum amount
    # of info to accomplish what you need. 
    if invalid_dimensions(obj.dimensions) &gt; 1 or len(obj.data.polygons) &lt; 1:
        obj.select_set(True)
        bpy.data.objects.remove(obj)
    else:
        bpy.ops.object.modifier_add(type='SOLIDIFY')

That takes something that was 35 lines of code down to 14, including whitespace between functions. Loads easier to read and debug.

EDIT:

I wrote an addon to do this. It can be found on my GitHub page. You can use it entirely if you want, or just look at it to compare.

enter image description here

Jakemoyo
  • 4,375
  • 10
  • 20
  • I've tried this but now Blender just shuts down whenever I run it. – Cerealmarrow100 Jul 04 '22 at 21:34
  • 1
    I'm not sure what to tell you, that probably doesn't have to do with my code, that might be a bug. If you have specific problems with your script that you want solved then you should post the error Traceback and specific problems. Right now all you've done is posted almost your entire script and just asked "How do i make it work?". We can only do so much with that. – Jakemoyo Jul 05 '22 at 09:56
  • That does sound reasonable and I don't think it is an error with your code, you seem way more advanced than me so I don't believe your code would be the problem. Blender just closes, no errors, it closes the system console window too. I ironed out the bugs but then it just closed Blender and I'm not sure how to find the causation of Blender closing upon running. – Cerealmarrow100 Jul 05 '22 at 18:32
  • I've tried with multiple windows and with live edit but it still just closes all the tabs without warning. I believe the problem may be caused by Blender no longer responding because when I first reopened Blender it showed the no longer responding message and then closed but, I cannot see an error in the code that may cause this. I could just be plain wrong however. I'm new to stack exchange so I don't know how to show the whole code without going over the character limit or editing the post itself. – Cerealmarrow100 Jul 05 '22 at 18:37
  • I would just edit the post and share a blend file with the script packed into the project text editor. That way anyone testing can basically have a perfect copy of your test environment . – Jakemoyo Jul 06 '22 at 10:03
  • I have shared the file. – Cerealmarrow100 Jul 06 '22 at 19:02
  • It works for me, although it does crash sometimes. I think your problem is that your script is adding a shitload of solidify modifiers to each object created. When I set the cell size to something lower I get a huge amount of solidify's and I think that is causing the script to run out of memory and crash. – Jakemoyo Jul 06 '22 at 20:39
  • Understood, I'll try adding them separately – Cerealmarrow100 Jul 06 '22 at 20:46
  • I've commented out the cleanup calls and it doesn't crash now but the result is not as expected, I expected 8 objects to come out like cubic chunks cut out. I'm not asking for someone to do all the work for me but looking at the script do you have any idea why that may be the case. I will be very appreciative of any help please. – Cerealmarrow100 Jul 06 '22 at 20:51
  • I edited my post. I started looking at your script, then just decided to restart from scratch and wrote an addon to do the same thing. Let me know if you have any questions about it. – Jakemoyo Jul 08 '22 at 14:58
  • 1
    Oh my God, I just really wanted to thank you. I'm so new to blender python scripting and python scripting in general and I'm so so grateful that you've been so patient over the course of a week to answer my questions and look at bugs in my code that may seem simple to you. I'm just so glad that someone like you took time out of your day to rewrite my terrible code to make an addon and I'm just so happy that you've helped me and taught me. I will most certainly use your logic and philosophy in all my future programming. So again I say, thank you. – Cerealmarrow100 Jul 09 '22 at 10:58
  • 1
    I have installed the zip of the My Pie Menus add on but it's getting an error from your init.py file saying that Traceback (most recent call last): File "C:\Program Files\Blender Foundation\Blender 3.2\3.2\scripts\modules\addon_utils.py", line 335, in enable mod = __import__(module_name) File "C:\Users\Godwin\AppData\Roaming\Blender Foundation\Blender\3.2\scripts\addons\my_pie_menus-master\__init__.py", line 33, in <module> from my_pie_menus.resources import utils ModuleNotFoundError: No module named 'my_pie_menus' – Cerealmarrow100 Jul 09 '22 at 11:03
  • That's because the parent file is named my_pie_menus-master/ it should be my_pie_menus, something GitHub does I guess. This repo is just for storage of my own scripts online so I haven't full configured the whole thing for someone else to use just yet. I'm no addon dev lol, Here, I made an standalone repo for the script by itself. Just go with that for now and try installing that through your preferences menu. – Jakemoyo Jul 09 '22 at 16:24
  • 1
    Dude you've so helpful thank you so much! – Cerealmarrow100 Jul 09 '22 at 18:37
  • I'm sorry for pestering you again, I really appreciate the help but could you please explain how to install and use the addon because I've used the built-in addon installer and located the zip file and extracted the files into the addons folder for blender C:\Program Files\Blender Foundation\Blender 3.2\3.2\scripts\addons but neither methods work for either addons. Thank you very much! – Cerealmarrow100 Jul 09 '22 at 21:22
  • I got it corrected, also added a few extras, you should be able to download the zip, double click the zipfile from the install window and it will work. The default hotkey to run it is Alt+Shift+/ – Jakemoyo Jul 10 '22 at 10:52
  • We're both learning :D – Jakemoyo Jul 10 '22 at 10:53
  • 1
    :D I'll make sure to give you credit – Cerealmarrow100 Jul 10 '22 at 14:53
  • Sorry, I know it's been a while I've been quite busy, but I would like to ask how do I make a mesh a manifold mesh or get the add on to work on non manifold meshes because I've been getting an error saying that the slice function can only work on manifold meshes. I have edited the post to link the mesh and type of meshes I would like the add on to work on. I know what a manifold mesh is but I don't know how to automatically or manually make a non manifold mesh a manifolds mesh. Any help would be appreciated. – Cerealmarrow100 Jul 17 '22 at 11:43
  • If you click "Force Non-Manifold" in the addon popup it will override that. Manifold mesh means "clean" or "solid", "realistic geometry" in that there aren't any holes, flat planes sticking off or anything like that. You can run this on a non-manifold mesh by enabling Force Non-Manifold but the results might not be what you expect. Probably will not create perfect blocky chunks like you want, hence why I made it only work with manifold meshes by default. – Jakemoyo Jul 17 '22 at 12:34
  • Okay I understand now how to use the modifier but for some reason it only doesn't work on the meshes that I've attached. Is there a problem with the meshes that blender stops responding and then finishes with a bunch of empty mesh copies and no effect to any mesh. The meshes are in the second .blend file attached, it's imported from a .obj file but now I don't understand why these meshes in particular are so problematic. I would just like to know how to get it to work on 126 with a chunk size of 20 so I can use it for the rest of the meshes. – Cerealmarrow100 Jul 18 '22 at 19:25
  • We should move this to chat. – Jakemoyo Jul 19 '22 at 08:24