1

I'm looking for a way to achieve non-destructive, generative differential mesh growth with blender.

enter image description here

I know of the differential growth add-on. That doesnt really work for me as it's

  • destructive
  • doesnt really work on closed 3d geometry

what I tried:

1. Animation Nodes Get Mesh from Object with Remesh Modifier, cache Mesh once per frame, move vertices, apply mesh back to the cached object and repeat. Kind of worked - but not really.

2. Geometry Nodes doesnt work at all because there is no way to reapply a modifier or loop a function with iterations

3. Sverchok Gave up pretty quickly to test ideas in Animation Nodes (which I am more familiar with)

My main problem is the subdivision part of most differential growth algorythms (Common rule is "Subdivide if edge is longer than x"). Not sure how I could solve that in Blender. Any Tips, Ideas on that?

bstnhnsl
  • 2,690
  • 12
  • 29

1 Answers1

1

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:

enter image description here

Jakemoyo
  • 4,375
  • 10
  • 20
  • Not at all unfortunately as your solution is destructive. – bstnhnsl May 06 '22 at 08:36
  • 1
    @bstnhnsl a Python solution is not destructive if you keep the original object copy. Otherwise any iterative process is destructive, because each step needs to create a new state (so "destruct" current state by changing it) to be used by the next step. You said yourself geonodes don't work, because you can't reapply the modifier, but applying the modifier is a destructive step. – Markus von Broady May 06 '22 at 09:11
  • Yeah especially as you already said the Remesh modifier wasn't working, I don't see how else you could repeatedly add new geo to the mesh in a wholly non-destructive way other than that. – Jakemoyo May 06 '22 at 09:39
  • If there was a Remesh node in animation nodes would be satisfied 100%. I would have control over WHEN the remesh happens and can go back to my original geometry and change it without making copies or continuously pressing cmd+z. You are totally right, applying a modifier like "go to modifier section and hit apply" is a destructive step, that not what I mean or try to do. – bstnhnsl May 06 '22 at 10:48
  • @bstnhnsl do you consider a physics simulation destructive? You can code a (somewhat elaborate) Python solution that works like physics simulations in that it maintains cache of evaluated states. https://blender.stackexchange.com/questions/238383/distributed-interaction-visualization/238409#238409 – Markus von Broady May 06 '22 at 10:58
  • @bstnhnsl I expanded my answer with something that might more closely align with your goals. – Jakemoyo May 06 '22 at 13:26
  • I‘ll have a look at it when I find time. Thanks! – bstnhnsl May 07 '22 at 10:07
  • I found a somewhat working solution with sverchok and a python script for the actual mesh growth and subdivision. That works a bit better for me than your solution (probably just personal preference). I'll add an answer showcasing my solution – bstnhnsl May 16 '22 at 08:59