I made a Blender addon that thickens surfaces like this:

You assign the inner surface to a vertex group named InnerGroupTriangle and the outer surface to OuterGroup. The inner surface must be made of triangles. Then you run the script and the outer surface scales away from the inner surface. The script works but is very slow. I can't figure out why.
Here is the code:
import bpy
def is_edge_in_group(edge,object,group_id):
for v_index in edge.vertices:
is_in_group=False
for g in object.data.vertices[v_index].groups:
if g.group==group_id:
is_in_group=True
break
if not is_in_group:
return False
return True
def check_if_face_in_vertex_group(vertices,group_index):
for face_vert in vertices:
face_vert_in_group=False
for g in face_vert.groups:
if g.group == group_index:
face_vert_in_group=True
break
if not face_vert_in_group:
return False
return True
def is_vertex_in_group(vertex,group_id):
for g in vertex.groups:
if g.group == group_id:
return True
return False
def change_vector_basis_from_orthogonal(rootv1,rootv2,rootv3,vector_to_transform):
px=vector_to_transform.x
py=vector_to_transform.y
pz=vector_to_transform.z
x1=rootv1.x
y1=rootv1.y
z1=rootv1.z
x2=rootv2.x
y2=rootv2.y
z2=rootv2.z
x3=rootv3.x
y3=rootv3.y
z3=rootv3.z
#print(rootv1,rootv2,rootv3,vector_to_transform)
try:
a=(pz*(-(x3*y2) + x2*y3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (py*(x3*z2 - x2*z3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (px*(-(y3*z2) + y2*z3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3)
b=(pz*(x3*y1 - x1*y3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (py*(-(x3*z1) + x1*z3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (px*(y3*z1 - y1*z3))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3)
c= (pz*(-(x2*y1) + x1*y2))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (py*(x2*z1 - x1*z2))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3) + (px*(-(y2*z1) + y1*z2))/(-(x3*y2*z1) + x2*y3*z1 + x3*y1*z2 - x1*y3*z2 - x2*y1*z3 + x1*y2*z3)
except ZeroDivisionError:
#print (rootv1,rootv2,rootv3,vector_to_transform)
return (-1,-1,-1)
return (a,b,c)
def calculate_root_vectors_and_transform_vertex(point,edge,ob,ig_polygons):
vertices_in_edge=[ob.data.vertices[x] for x in edge.vertices]
#print(list(vertices_in_edge))
polygons=ig_polygons
polygons_next_to_edge=[]
for polygon in polygons:#finds the faces next to the edge
edge_in_polygon=True
for v_index in edge.vertices:
if not v_index in polygon.vertices:
edge_in_polygon=False
break
if edge_in_polygon:
polygons_next_to_edge.append(polygon)
if len(polygons_next_to_edge)==2:
break
edge_vector=(vertices_in_edge[1].co-vertices_in_edge[0].co)
if len(polygons_next_to_edge)==2:#edge next to two faces
second_face_normal=polygons_next_to_edge[1].normal
if len(polygons_next_to_edge)==1:#if edge is next to only one face, condider the normal of the other face to be perpendicular to the edge and the only face's normal, away from the face.
normal = polygons_next_to_edge[0].normal
for edge_face_vertex_index in polygons_next_to_edge[0].vertices:#finds the vertex on the face next to the edge that isn't in the edge
if not edge_face_vertex_index in edge.vertices:
face_vertex_not_in_edge=ob.data.vertices[edge_face_vertex_index]
break
cross=edge_vector.cross(face_vertex_not_in_edge.co-vertices_in_edge[0].co)
direction=1
if normal.x !=0:
if cross.x/normal.x <0:
direction = -1
elif normal.y !=0:
if cross.y/normal.y <0:
direction = -1
else:
if cross.z/normal.z <0:
direction = -1
second_face_normal=direction*edge_vector.cross(polygons_next_to_edge[0].normal)
(a,b,c)=change_vector_basis_from_orthogonal(edge_vector,polygons_next_to_edge[0].normal,second_face_normal,point)
return (edge_vector,polygons_next_to_edge[0].normal,second_face_normal,a,b,c)
def try_to_move_relative_to_edge(point,ob,ig_edges,multiplier,ig_polygons):
adequete_edge_and_distance=()
for edge in ig_edges:#lets go through edges and find the one that is closest to the point
(edge_vector,normal1,normal2,a,b,c)=calculate_root_vectors_and_transform_vertex(point.co-ob.data.vertices[edge.vertices[0]].co,edge,ob,ig_polygons)
#print (edge_vector,normal1,normal2,a,b,c)
distance=(b*normal1+c*normal2).length
if (len(adequete_edge_and_distance)==0 or distance<adequete_edge_and_distance[1])and 0<=a<=1 and 0<=b and 0<=c:
adequete_edge_and_distance=(edge,distance)
if not len(adequete_edge_and_distance)==0:
adequete_edge=adequete_edge_and_distance[0]
(edge_vector,normal1,normal2,a,b,c)=calculate_root_vectors_and_transform_vertex(point.co-ob.data.vertices[adequete_edge_and_distance[0].vertices[0]].co,adequete_edge,ob,ig_polygons)
point.co=a*edge_vector+multiplier*b*normal1+multiplier*c*normal2+ob.data.vertices[adequete_edge.vertices[0]].co
return True
return False
ob = bpy.context.object
multiplier = 2
print ("start")
ogi = ob.vertex_groups["OuterGroup"].index # get group index
igi= ob.vertex_groups["InnerGroupTriangle"].index
ig_edges=[x for x in ob.data.edges if is_edge_in_group(x,ob,igi)]
ig_polygons=[x for x in ob.data.polygons if check_if_face_in_vertex_group([ob.data.vertices[i] for i in x.vertices],igi)]
ig_vertices=[x for x in ob.data.vertices if is_vertex_in_group(x,igi)]
i=0
to_go_trough=len(ob.data.vertices)
for v in ob.data.vertices:
i+=1
print(i,"/",to_go_trough)
#input("Press Enter to continue...")
if is_vertex_in_group(v,ogi):#go through only vertices that are in outer group
projection_was_made=False
adequate_polygon_and_distance=()
#print("############# vertex:",v,v.co,"*************")
for polygon in ig_polygons:
verts_in_face = polygon.vertices[:]
#print(verts_in_face," checking")
#print(verts_in_face,"in inner group")
(a,b,c)=change_vector_basis_from_orthogonal(ob.data.vertices[verts_in_face[1]].co-ob.data.vertices[verts_in_face[0]].co ,ob.data.vertices[verts_in_face[2]].co-ob.data.vertices[verts_in_face[0]].co,polygon.normal, v.co-ob.data.vertices[verts_in_face[0]].co)
#print("a:",a,"b:",b,"c:",c)
if 0<=a and 0<=b and a+b<=1 and 0<=c:
#print("abc in range")
if len(adequate_polygon_and_distance)==0 or c < adequate_polygon_and_distance[1]:
#print("cccccccccccccc",c)
adequate_polygon_and_distance=(polygon,c)
if len(adequate_polygon_and_distance)>0:
#print(adequate_polygon_and_distance[0].vertices[:],"//////////////////////////////////")
verts_in_face = adequate_polygon_and_distance[0].vertices[:]
(a,b,c)=change_vector_basis_from_orthogonal(ob.data.vertices[verts_in_face[1]].co-ob.data.vertices[verts_in_face[0]].co ,ob.data.vertices[verts_in_face[2]].co-ob.data.vertices[verts_in_face[0]].co,adequate_polygon_and_distance[0].normal, v.co-ob.data.vertices[verts_in_face[0]].co)
#print(a,b,c)
v.co=a*(ob.data.vertices[verts_in_face[1]].co-ob.data.vertices[verts_in_face[0]].co)+b*(ob.data.vertices[verts_in_face[2]].co-ob.data.vertices[verts_in_face[0]].co)+multiplier*c*adequate_polygon_and_distance[0].normal+ob.data.vertices[verts_in_face[0]].co
print("polygon")
projection_was_made=True
if not projection_was_made:#vertex v wasn't above a face in inner group
#print("edges")
if try_to_move_relative_to_edge(v,ob,ig_edges,multiplier,ig_polygons):
print("edge")
projection_was_made=True
if not projection_was_made:#vertex v wasn't above an edge in inner group
print("vertex")
point=v
vertex_and_distance=()
for inner_vertex in ig_vertices:
if (len(vertex_and_distance)==0 or vertex_and_distance[1]>(inner_vertex.co-point.co).length):
#print("sdfsdf")
vertex_and_distance=(inner_vertex,(inner_vertex.co-point.co).length)
point.co=vertex_and_distance[0].co+multiplier*(point.co-vertex_and_distance[0].co)
#bpy.context.scene.objects.active = bpy.context.scene.objects.active
elseclause of aforstatement. It won't solve the problem, but will make your code simpler. – dr. Sybren Oct 15 '17 at 19:37