I’m using a script to generate erlenmeyer flasks with different dimensions and I’ve run into a problem:
When you create new vertices (especially when you make loop cuts and bevels) the new vertices will be indexed in the object data inconsistently. So let’s say I have a circle with 20 verts. Those verts will have the indices 0-19, obviously. If I extrude that upwards the new vertices will have the indices 20-39.
But the more you do this, the more geometry you add, the more likely it becomes that the indexing becomes inconsistent and random between different executions of the script. Watch the difference between this image
And this one
The code is completely identical. Those are just two separate executions. I’ve had this problem in the past and decided that this just means that blender’s bpy is either
A) Bad
B) Too counterintuitive
Is there a more reliable way to select parts of the mesh? Is there a way to prevent the random indexing?
Here’s my code:
import bpy
erlen_specs = '''V R r h
25 42 22 75
50 51 22 90
100 64 22 105
200 79 34 131
250 85 34 145
300 87 34 156
500 105 34 180
1000 131 42 220
2000 166 50 280
3000 187 52 310
5000 220 51 365 '''
erlen_specs = [i.split() for i in erlen_specs.split('\n')]
erlen_specs = [dict(zip(erlen_specs[0],[int(k) for k in erlen_specs[i]])) for i in range(1,len(erlen_specs))]
print(erlen_specs)
def get_override():
'''Obtains the current overwrite for a scene...or something.'''
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
override = {'area': area, 'region': region, 'edit_object':bpy.context.edit_object}
return override
def selection(i,j,sele):
'''Select verts based on index.'''
bpy.ops.object.mode_set(mode = 'OBJECT')
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.mode_set(mode = 'OBJECT')
if sele == 'verts':
for v in range(i,j):
obj.data.vertices[v].select = True
elif sele == 'faces':
for v in range(i,j):
obj.data.polygons[v].select = True
elif sele == 'edges':
for v in range(i,j):
obj.data.edges[v].select = True
bpy.ops.object.mode_set(mode = 'EDIT')
def sele_list(lst,sele):
'''Select verts in lst.'''
bpy.ops.object.mode_set(mode = 'OBJECT')
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.mode_set(mode = 'OBJECT')
if sele == 'verts':
for v in lst:
obj.data.vertices[v].select = True
elif sele == 'faces':
for v in lst:
obj.data.polygons[v].select = True
elif sele == 'edges':
for v in lst:
obj.data.edges[v].select = True
bpy.ops.object.mode_set(mode = 'EDIT')
def make_erlen(R,r,h,V):
# add circle bottom, set to R
bpy.ops.mesh.primitive_circle_add(vertices=20,radius=R, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
# fill circle
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.edge_face_add()
# extrude, don't move
bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"use_normal_flip":False, "use_dissolve_ortho_edges":False, "mirror":False}, TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_axis_ortho":'X', "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "view2d_edge_pan":False, "release_confirm":False, "use_accurate":False, "use_automerge_and_split":False})
# move the new vertices along the z axis by h
bpy.ops.transform.translate(value=(0, 0, h), orient_axis_ortho='X', orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(False, False, True), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)
# scale new vertices down by r/R
bpy.ops.transform.resize(value=(r/R, r/R, r/R), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)
# make new vertices to extrude
bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"use_normal_flip":False, "use_dissolve_ortho_edges":False, "mirror":False}, TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_axis_ortho":'X', "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "view2d_edge_pan":False, "release_confirm":False, "use_accurate":False, "use_automerge_and_split":False})
# pull up by fac
fac = 0.7*(h/2)**(1/2)
bpy.ops.transform.translate(value=(0, 0, fac), orient_axis_ortho='X', orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(False, False, True), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)
# delete top face
bpy.ops.mesh.delete(type='FACE')
# apply scale
bpy.ops.object.editmode_toggle()
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
bpy.ops.object.editmode_toggle()
# select bottom ring
obj = bpy.context.active_object
a,b = 0,20
selection(a,b,'verts')
# bevel bottom
bpy.ops.mesh.bevel(offset=0.08, offset_pct=0, segments=2, affect='EDGES')
# select top ring
obj = bpy.context.active_object
a,b = 0,20
selection(a,b,'verts')
# bevel middle
bpy.ops.mesh.bevel(offset=0.04, offset_pct=0, segments=2, affect='EDGES')
# select bottom ring
obj = bpy.context.active_object
inds = [21,23,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,77]
sele_list(inds,'verts')
# inset bottom face
bpy.ops.mesh.inset(thickness=0.1, depth=0)
bpy.ops.mesh.inset(thickness=0.1, depth=0)
bpy.ops.mesh.poke()
# go object mode
bpy.ops.object.editmode_toggle()
bpy.ops.object.shade_smooth()
bpy.ops.object.modifier_add(type='SOLIDIFY')
bpy.context.object.modifiers["Solidify"].thickness = 0.01
# apply solidify
bpy.ops.object.modifier_apply(modifier="Solidify")
# add edge loop
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.loopcut_slide(get_override(),MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "object_index":0, "edge_index":236, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":-0.75, "single_side":False, "use_even":False, "flipped":False, "use_clamp":True, "mirror":True, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":True, "release_confirm":False, "use_accurate":False})
bpy.ops.mesh.loopcut_slide(get_override(),MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "object_index":0, "edge_index":246, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":0, "single_side":False, "use_even":False, "flipped":False, "use_clamp":True, "mirror":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":True, "release_confirm":False, "use_accurate":False})
# scale middle one
bpy.ops.transform.resize(value=(1.2, 1.2, 1), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(True, True, False), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)
return
# loopcut that will sharpen the edge
bpy.ops.mesh.loopcut_slide(get_override(),MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "object_index":0, "edge_index":541, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":-0.970203, "single_side":False, "use_even":False, "flipped":False, "use_clamp":True, "mirror":True, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":True, "release_confirm":False, "use_accurate":False})
# add subdiv
bpy.ops.object.editmode_toggle()
bpy.ops.object.subdivision_set(level=1, relative=False)
bpy.context.object.name = str(V) +' mL'
for i in range(len(erlen_specs))[:1]:
R = erlen_specs[i]['R']/100
r = erlen_specs[i]['r']/100
h = erlen_specs[i]['h']/100
V = erlen_specs[i]['V']
make_erlen(R,r,h,V)