I made a script. It’s a bit rough but does the job.
# Render frames in specified order.
# frame_order, formatting:
#
# 30-120x3 means render every 3rd frame between frames 30-120
# 20-150 means render every frame in range 20-150
# 43 means render frame 43
#
# "1, 50, 100, 50-100x6, 1-100x3, 1-50" means
# first render frames 1, 50, 100, then every 6th frame between 50-100,
# then every 3rd frame between 1-100, and then all between 1-50.
#
# render_all - True: render all frames between start and end frames
# False: only render frames specified in formatting,
# still limited by start and end frames
frame_order = "1, 50, 100, 50-100x6, 1-100x3, 1-50"
render_all = True
####
import bpy, re, os
# Add full range of frames at the end.
if render_all:
frame_order = frame_order + "," + str(bpy.context.scene.frame_start) + "-" + str(bpy.context.scene.frame_end)
# clean and split to list of instructions
frame_order = frame_order.replace(" ", "")
frame_order = frame_order.split(",")
cmds = list()
# split to values and separators
for i in range(0, len(frame_order)):
cmds.append([x for x in re.split(r'(\d+)',frame_order[i]) if x])
frames = list()
# 3 allowed types: frame range with steps, frame range, frame
# Flood frames list with frames, duplicates will be skipped anyway
for i in range(0, len(cmds)):
if((len(cmds[i]) == 5) and (cmds[i][1] == "-") and (cmds[i][3] == "x")):
frames.extend([x for x in range(int(cmds[i][0]), int(cmds[i][2]), int(cmds[i][4]))])
elif((len(cmds[i]) == 3) and (cmds[i][1] == "-")):
frames.extend([x for x in range(int(cmds[i][0]), int(cmds[i][2])+1)])
elif((len(cmds[i]) == 1) and (cmds[i][0].isnumeric())):
frames.append(int(cmds[i][0]))
else:
print("
Invalid frame order syntax")
break
# Amount of digits in maximum frame. Used for 0 padding
digits = len(str(max(frames)))
outputpath = bpy.context.scene.render.filepath
for i in range(0, len(frames)):
bpy.context.scene.frame_current = frames[i]
out = outputpath + str(frames[i]).zfill(digits)
if(bpy.context.scene.render.use_file_extension):
out = out + bpy.context.scene.render.file_extension
if((os.path.isfile(out) == False) and frames[i] >= bpy.context.scene.frame_start and (frames[i] <= bpy.context.scene.frame_end)):
bpy.context.scene.render.filepath = outputpath + str(frames[i]).zfill(digits)
bpy.ops.render.render(write_still=True)
bpy.context.scene.render.filepath = outputpath