5
$\begingroup$

I'm working with Cloth Simulations brought in from another program (Marvelous Designer) via .mdd, applied with the Mesh Cache modifier. No changes can be made to this mesh in Blender without breaking the .mdd. I want to add further details to the cloth in blender, such as thickness, seams, grooves, and other surface detailing that Marvelous Designer doesn't have good tools for. To accomplish this, I want to have my detailed mesh be controlled by the simpler cloth simmed mesh. This isn't much different than if I was doing the cloth sims in Blender, as that is generally done on simple meshes that then control more detailed ones.

My question is, what are my options in Blender for having the simmed mesh control the higher detailed one? From tutorials I've seen from other software (Max and Maya), this is a very common workflow, and they handle it very easily. Does Blender?

I am aware of the following options:

Mesh Deform Modifier: Take the simmed mesh, solidify it to envelope the high detail mesh, and use a Mesh Deform modifier. This works for very simple meshes, but even at high precision, does not handle complex ones well. It also fails badly when dealing with layers. I can limit it's effect to a vertex group on the target object, but I can't have only a certain vertex group of the parent object exert control. So any place where I would have layers touching each other will get destroyed, as even if I weight paint the solidify very precisely, I can't fully avoid the cage overlapping certain areas.

Bones constrained to the simmed mesh: Constrain bones to the simmed mesh and have them control the high detail mesh. This is very inefficient and time consuming, even if you have custom scripts to help set it up. It can also lose detail in important areas, and can't really handle things like folds and creases unless you have as many bones as you have vertices. And constraints can often give very strange results. This method has worked so far for things like belts and straps, but that's about it.

Are there any other options? From what I can tell, a combination of the two I've listed is the best choice. But it seems horribly inefficient and obnoxious. This seems like it should be a very common workflow, and there should be better tools for it. Am I missing something?

$\endgroup$
3
$\begingroup$

From Blender 2.79 onward the new modifier Surface deform makes this answer obsolete.


This code rigs simulated mesh so there is bone for every edge of mesh, such rig can control high-res version of cloth:

enter image description here

  • The code adds panel to 3dView > Toolbar > Rigify Mesh.
  • The mesh has to have rotation and scale applied.
  • The vertex count of mesh is recommended below 2000 (timed on i7 4GHz).
    • 300 verts ~ 6s
    • 560 verts ~ 46s
    • 1k verts ~ 8.2min
    • 2k verts ~ over night
  • After profiling the code and going through forums the bottleneck is setting constraint properties. Specifically constraint['name'].target and constraint['name'].subtarget. The issue is that blender validates the constraints on every change to all other bones. This may be drastically speeduped with new dependency graph (hopefully). (Same as with duplicating large datasets of objects - before bmesh it took hours, now can be done in seconds.)
  • If the constaints will be no issue the rig can be improved so the bones rotate along edges nicer (this requires more bones and constraints).
  • The progress is now also printed in the console:

    enter image description here

  • Known bugs were fixed.

The code:

import bpy
import time
import sys
from mathutils import Matrix, Vector

def new_bone(armature_ob, bone_name, head_loc, tail_vec, parent=None, deform=True, layer=0):
    '''Works only in edit mode'''
    new_bone = armature_ob.data.edit_bones.new(bone_name)
    new_bone.head = head_loc
    new_bone.tail = Matrix.Translation((0,0,0)) * Vector(tail_vec) + new_bone.head
    new_bone.parent = parent
    new_bone.use_deform = deform
    new_bone.layers = [i==layer for i in range(32)]
    armature_ob.data.layers[layer] = True
    return new_bone

def add_constraint(const_type, const_entity, target_obj, sub_target_name):
    '''Works only in pose mode'''
    constraint = const_entity.constraints.new(const_type)
    constraint.target = target_obj
    constraint.subtarget = sub_target_name
    return constraint

class RigifyMeshPanel(bpy.types.Panel):
    bl_label = "Rigify Mesh"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Rigify Mesh"

    def draw(self, context):
        layout = self.layout
        layout.operator("object.rigify_mesh", icon='OUTLINER_OB_ARMATURE')

class RigifyMesh(bpy.types.Operator):
    bl_idname = "object.rigify_mesh"
    bl_label = "Rigify Mesh"
    bl_description = "Generates a Rig with bones for every edge of active mesh"

    @classmethod
    def poll(self, context):
        try:
            active = context.active_object
            return active and active.type == 'MESH'
        except (AttributeError, KeyError, TypeError):
            return False

    def execute(self, context):
        mesh_ob = context.active_object
        progress = ""
        start = time.clock()
        # Force object mode:
        if bpy.context.active_object.mode != 'OBJECT':
            bpy.ops.object.mode_set(mode='OBJECT')
        # Generate vert groups for each vert:
        sys.stdout.write("------------------- RIGIFYING MESH -----------------\n")
        sys.stdout.write("Generating Vertex Groups (step 1/4): ")
        sys.stdout.flush()
        for ivert in [v.index for v in mesh_ob.data.vertices if str(v.index) not in mesh_ob.vertex_groups]:
            progress = "Vert %i of %i" % (ivert, len(mesh_ob.data.vertices)-1)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            mesh_ob.vertex_groups.new(name=str(ivert))
            mesh_ob.vertex_groups[str(ivert)].add([ivert], 1.0, 'REPLACE')
        # Update mesh data
        mesh_ob.update_tag({'DATA'})
        sys.stdout.write("DONE IN "+str(round(time.clock()-start, 3))+"s"+" "*(len(progress)-9)+"\n")
        sys.stdout.flush()
        start = time.clock()
        # Get and Reset Armature
        sys.stdout.write("Cleaning up Armature     (step 2/4): ")
        sys.stdout.flush()
        if bpy.data.objects.get(mesh_ob.name+'_Rig') is not None:
            rig_ob = bpy.data.objects[mesh_ob.name+'_Rig']
            context.scene.objects.active = rig_ob
            # Clean constraints:
            bpy.ops.object.mode_set(mode='POSE')
            for id, pbone in enumerate(rig_ob.pose.bones):
                for constraint in pbone.constraints:
                    progress = "cleaning %i of %i" % (id, len(mesh_ob.data.vertices)+3*len(mesh_ob.data.edges)-1)
                    sys.stdout.write(progress+chr(8)*len(progress))
                    sys.stdout.flush()
                    pbone.constraints.remove(constraint)
        else:
            bpy.ops.object.armature_add(location=mesh_ob.location)
            rig_ob = context.active_object
            rig_ob.name = mesh_ob.name + '_Rig'
        # Clean bones:
        bpy.ops.object.mode_set(mode='EDIT')
        for bone in rig_ob.data.edit_bones:
            rig_ob.data.edit_bones.remove(bone)
        sys.stdout.write("DONE IN "+str(round(time.clock()-start, 3))+"s"+" "*(len(progress)-9)+"\n")
        sys.stdout.flush()
        start = time.clock()
        # Add bones:
        sys.stdout.write("Generating Bones         (step 3/4): ")
        sys.stdout.flush()
        mesh_ob.data.calc_normals()
        for id, vert in enumerate(mesh_ob.data.vertices):
            progress = "Bone %i of %i" % (id, len(mesh_ob.data.vertices)+len(mesh_ob.data.edges)-2)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            new_bone(rig_ob, "vert"+str(id), vert.co, vert.normal/4, None, False, 1)
        for id, edge in enumerate(mesh_ob.data.edges):
            progress = "Bone %i of %i" % (id+len(mesh_ob.data.vertices), len(mesh_ob.data.vertices)+len(mesh_ob.data.edges)-2)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            v0 = mesh_ob.data.vertices[edge.vertices[0]]
            v1 = mesh_ob.data.vertices[edge.vertices[1]]
            new_bone(rig_ob, "edge"+str(id), v0.co, v1.co-v0.co, rig_ob.data.edit_bones[edge.vertices[0]])
        sys.stdout.write("DONE IN "+str(round(time.clock()-start, 3))+"s"+" "*(len(progress)-9)+"\n")
        sys.stdout.flush()
        start = time.clock()
        # Add bone constraints:
        bpy.ops.object.mode_set(mode='POSE')
        pbones = rig_ob.pose.bones
        sys.stdout.write("Generating Constraints   (step 4/4): ")
        sys.stdout.flush()
        # Setup vertex bones constraints:
        for id, vert in enumerate(mesh_ob.data.vertices):
            progress = "Constraint %i of %i" % (id, len(mesh_ob.data.vertices)+3*len(mesh_ob.data.edges)-1)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            child_of = add_constraint('CHILD_OF', pbones["vert"+str(id)], mesh_ob, str(id))
            context_py = context.copy()
            context_py["constraint"] = child_of
            rig_ob.data.bones.active = pbones["vert"+str(id)].bone
            bpy.ops.constraint.childof_set_inverse(context_py, constraint=child_of.name, owner='BONE')
        # Setup edge bones constraints:
        for id, edge in enumerate(mesh_ob.data.edges):
            bone0 = "vert" + str(edge.vertices[0])
            bone1 = "vert" + str(edge.vertices[1])
            progress = "Constraint %i of %i" % (3*id+len(mesh_ob.data.vertices), len(mesh_ob.data.vertices)+3*len(mesh_ob.data.edges)-1)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            add_constraint('DAMPED_TRACK', pbones["edge"+str(id)], rig_ob, bone1)
            progress = "Constraint %i of %i" % (3*id+1+len(mesh_ob.data.vertices), len(mesh_ob.data.vertices)+3*len(mesh_ob.data.edges)-1)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            const = add_constraint('COPY_ROTATION', pbones["edge"+str(id)], rig_ob, bone0)
            const.use_z = False
            const.target_space = 'POSE'
            const.owner_space = 'POSE'
            progress = "Constraint %i of %i" % (3*id+2+len(mesh_ob.data.vertices), len(mesh_ob.data.vertices)+3*len(mesh_ob.data.edges)-1)
            sys.stdout.write(progress+chr(8)*len(progress))
            sys.stdout.flush()
            add_constraint('STRETCH_TO', pbones["edge"+str(id)], rig_ob, bone1)
        # Hide vert bone layer
        rig_ob.data.layers[1] = False
        sys.stdout.write("DONE IN "+str(round(time.clock()-start, 3))+"s"+" "*(len(progress)-9)+"\n")
        sys.stdout.write("----------------------------------------------------\n")
        sys.stdout.flush()
        return{'FINISHED'}

bpy.utils.register_module(__name__)
$\endgroup$
  • $\begingroup$ Thank you! This looks like what I need. I am trying to use it, but the bones seem to get offset all over the place. Setting Inverse on their Child Of seems to fix this, but it needs to be done for all of them. Also, I tried running it on some of my actual cloth objects, and it froze up Blender for ~10 minutes before I closed it. Is that because of poly count? I need to do this on fairly high res meshes. I suppose I can adapt it to work in edit mode limited to a selection and do it piece by piece to avoid this. Lastly, how are you weighting the object it controls? Just automatic weights? $\endgroup$ – Drudge May 8 '15 at 3:02
  • $\begingroup$ Upon further examination, I see that the script IS setting the inverse. But as soon as I enter edit mode on the object, enter and leave edit mode on the rig, or do anything in pose mode, the bones freak out and go all over the place. I can still fix it by setting the inverse manually. $\endgroup$ – Drudge May 8 '15 at 4:33
  • $\begingroup$ @Drudge I didn't test it on an .mdd file, will check. I did this on blender cloth and ran the code at frame 0 when the mesh is same in object and edit mode. The dragon is weighted with envelopes (it has some empty space not filling the volume of cloth nicely - heat weighting failed to find solution for some bones.). I have some ideas how to improve this more and turn it into an addon, I will find time to do it probably tomorrow. $\endgroup$ – Jaroslav Jerryno Novotny May 8 '15 at 6:44
  • $\begingroup$ I was testing it on just a subdivided square, no .mdd yet. I was just trying to pick up a point and move it around in edit mode. I wasn't able to figure out why set inverse is not getting set properly (or maybe it is and then gets unset?) $\endgroup$ – Drudge May 8 '15 at 6:59
  • 1
    $\begingroup$ @Drudge Yeah you are right - this happens on a flat subdivided plane. Weird. When you displace the same plane a bit an run it it works. I'll see what I can do with this in the next version of script and whether its not blender bug. Also you are right that the long running times are because of polycount. I will add some progress output to print to the console. $\endgroup$ – Jaroslav Jerryno Novotny May 8 '15 at 7:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.