Would running something like this every frame of your animation do the trick?
import bpy
import bmesh
# This is just the most expedient way to get in and out of access to the
# mesh data.
# You don't necessarily have to do it in edit mode, it just takes like 3-4
# more lines of code.
obj = bpy.context.active_object
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
too_long = 1
num_subdivs = 1
long_edges = [e for e in bm.edges[:] if e.calc_length() >= too_long]
bmesh.ops.subdivide_edges(bm, edges=long_edges, cuts=num_subdivs)
bmesh.update_edit_mesh(mesh)
Edit
So I wrote a sort of DIY "mesh cache" wrapped around a function that distorted the mesh and "Remeshed" it using the essence of my previous method. Theoretically this should be entirely "non-destructive" because each state of the mesh is written to disk and can be loaded back in at will.
import bpy
import bmesh
from mathutils import noise
import statistics
import json
from pathlib import Path
bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.shade_smooth()
# get the object and convert it to a workable mesh
obj = bpy.context.active_object
mesh = obj.data
bm = bmesh.new()
bm.from_mesh(mesh)
# just values I fiddled with until the noise
# and displace didn't completely destroy the mesh
strength = 4
displace_strength = 3
# stand in function for whatever procedural growth operation
# you have going, just a proof of concept
def displace_mesh(verts, strength=4, displace_strength=3):
# loop through the vertices, displace them
# on their normal vector, then offset that by using
# a noise from mathutils
for vert in verts:
norm_offset = vert.normal / displace_strength
vert.co += norm_offset
pos = vert.co
noise_pos = noise.noise_vector(pos) / strength
sub = pos + noise_pos
vert.co = sub
vert.co += norm_offset
# get all the edges and "Remesh" them according to
# whether the exceed the average length of all edges
# then triangulate the whole mesh
edges = bm.edges[:]
edge_l = [e.calc_length() for e in bm.edges[:]]
edge_mean_l = statistics.mean(edge_l)
long_edges = [e for e in edges if e.calc_length() > edge_mean_l]
bmesh.ops.subdivide_edges(bm, edges=long_edges, cuts=1)
bmesh.ops.triangulate(bm, faces=bm.faces[:])
# get the number of frames
num_frames = bpy.data.scenes["Scene"].frame_end
# loop through the frames
for i in range(num_frames):
# create a python dictionary of the mesh, we can write
# write each iteration out as a .json file and then later
# reread it in using bpy.types.Mesh.from_pydata().
# Basically a DIY "cache".
pydata = {}
faces = []
edges = []
verts = [vert.co.to_tuple() for vert in bm.verts[:]]
for face in bm.faces[:]:
faces.append([v.index for v in face.verts])
for edge in bm.edges[:]:
edges.append([v.index for v in edge.verts])
pydata["vertices"] = verts
pydata["edges"] = edges
pydata["faces"] = faces
file_name = parent_dir / f"mesh_data_frame_{str(i).zfill(3)}.json"
# writing the file to disk
with open(file_name, "w") as json_file:
json.dump(pydata, json_file)
# At this point we have copied the mesh in it's current
# state to disk. Now that it's saved, we can use or distortion function
# to mess with it
displace_mesh(bm.verts[:])
# and repeat
Later, in a separate script we can import each mesh individually. You could do whatever you want with each mesh at this point, render it's frame, then delete it and move on to the next, add more modifiers etc. I only offset them to show them all lined up.
import bpy
import json
import bmesh
from pathlib import Path
# get all .json files from the current directory
file = Path('.')
files = sorted(list(file.glob("*.json")))
# iterate over the files and create a new object and mesh
# from the data in the file, the link it to the scene
for i, j in enumerate(files):
with open(j.name, 'r') as f:
data = json.load(f)
obj_name = f'mesh_{i}'
mesh = bpy.data.meshes.new(name=obj_name)
mesh.from_pydata(**data)
obj = bpy.data.objects.new(obj_name, mesh)
bpy.context.collection.objects.link(obj)
# line em up for display
offset = 0
for obj in bpy.data.objects:
obj.location.x += offset
offset += 10
End Result:
