I am randomly generating organic shapes that I want to 3D print lightweight and thus with 0% infill, a single perimeter, and zero top and bottom layers. To make this work properly I need the mesh to contain no local minima (no vertices lower than all their connected vertices) other than at the base of the mesh, and all walls (edges/faces) must be minimally sloped. Vertices at local minima in the z direction will cause slicers to begin extrusions in mid-air and thus the print will fail.
My first idea is to find any vertex which is below all of it's connected vertices (neighbors) and move it up in the z direction until it is sufficiently above its neighbors. "Sufficiently above" would be determined by the minimum angle (~30°) of the edges between the vertex and its neighbors compared to the XY plane. A better solution might be to move the vertex along its normal vector.
Once a vertex has been adjusted, its neighboring vertices could be checked and iterated through until the entire model has been checked and "fixed".
Since there will always be vertices at the base of the model, either the algorithm could ignore those, or the algorithm could limit itself to only iterate on a vertex group (or on the currently selected vertices). Or maybe the algorithm could work by starting with the base vertices, and then traveling up the mesh adjusting each vertex as it goes.
I have started coding the script and I'm to the point where I need to calculate where to move an unsupported vertex to. I'm stuck here. I should probably use some sort of vector math that I forgot 25 years ago. :)
I can visualize the solution, but I have no idea how to calculate it. Comments are in the code.
import bpy
import bmesh
import math
from mathutils import Vector, Matrix
def slope(v,t):
rise = (v.co.z - t.co.z)
run = math.sqrt((v.co.x-t.co.x)**2+(v.co.y-t.co.y)**2)
if run:
return rise/run
else:
return 100*rise
#EDIT mode#
me = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(me)
vertices = sorted([v for v in bm.verts], key= lambda v : v.co.z) # sort by z height
# Find the lowest Z value amongst the object's verts, this is the base. Ignore these vertices
minZ = min( [ v.co.z for v in bm.verts ] )
for v in vertices:
v.tag = False
allTagged = False
while allTagged = False: # once every vertex is tagged, we're finished
allTagged = True
for v in vertices:
if v.co.z > minZ and not v.tag: # found an untagged, non-base vertex...
allTagged = False # ...Need to keep looping.
v.tag = True # tag this vertex as analyzed/fixed
neighbors = [e.other_vert(v) for e in v.link_edges] # create a list of connected neighbors
print("target: " + str(v.co) + " Neighbors: " + str([v.co for v in neighbors])) # debugging
unsupported = True #default each vertex as unsupported unless...
for n in neighbors:
print(slope(v,n)) # debugging
if slope(v, n) > 0.3: # ...a connected vertex is sloped down enough...
unsupported = False # ...in which case this vertex is supported.
# future optimization - could create a list of "supporting vertices" for each vertex. If a vertex is verified as supported, and at least one "supporting vertex" isn't moved, no need to recheck this vertex.
if unsupported:
for n in neighbors:
n.tag = False # need to recheck neighbors after moving vertex (NOTE: this isn't optimal)
print("Target is unsupported.") # debugging
#TODO - how to calculate where to move the unsupported vertex in order to be supported?
# Should move the vertex along its normal.
# Imagine an inverted cone originating at each neighboring vertex. The slope of the cone is our target slope.
# The point where the normal vector intersects the first cone is where the vertex should move to.
# How to calculate that?
So my code correctly identifies the two vertices in the below sample blend file that are unsupported. All that is left is to move the vertices to the closest supported point along their normal vector. Sounds so simple...
Here is a blend file with a sample object. The vertex in the middle of the cube (local minima) needs to be moved so there is no local minima and so that at least one connected edge is at least ~20 degrees from horizontal. The vertex at the center/top should be moved until at least one connected edge is more than ~20 degrees from horizontal too.
I found this document (Pages 10-15) that provides some pseudo-code that should help. Since I don't have any experience doing anything like this, I could really use some help. I can't copy/paste the pseudo code here because the pdf file shows spaces in between most of the letters. Ugh. I'm not sure the whole thing is required, but maybe?