1
$\begingroup$

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?

$\endgroup$

1 Answer 1

1
$\begingroup$

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

$\endgroup$
8
  • $\begingroup$ Not at all unfortunately as your solution is destructive. $\endgroup$
    – bstnhnsl
    May 6, 2022 at 8:36
  • 1
    $\begingroup$ @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. $\endgroup$ May 6, 2022 at 9:11
  • $\begingroup$ 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. $\endgroup$
    – Jakemoyo
    May 6, 2022 at 9:39
  • $\begingroup$ 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. $\endgroup$
    – bstnhnsl
    May 6, 2022 at 10:48
  • $\begingroup$ @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. blender.stackexchange.com/questions/238383/… $\endgroup$ May 6, 2022 at 10:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .