4
$\begingroup$

So, right now I have about a dozen different meshes that are all modified versions of a default head mesh. One for example is the same head but fatter, one with more pronounce eyebrows, one is smiling etc.

I am slightly new to blender, and I did not make these models. What I'm trying to do is find a way to merge these multiple files into one file as different shape keys. I can find information about how to make shape keys by editing a mesh but can't for the life of me find a way to blend two meshes into one and use one as the "target" of a blend key. If this is making sense.

The ultimate goal I have in mind is that I would like to have a single file with multiple sliders that I can ultimately export into Unity. I've learned how to export shape keys but I can't figure out how to get from where I am now with multiple meshes to where I want to be with multiple sliders. Thanks in advance for any help you can give a somewhat new user.

$\endgroup$
1

3 Answers 3

2
$\begingroup$

You can use this script to accomplish an obj sequence import to shapekeys. Paste the script into the text editor in blender, and click the Run Script button.

This will add a new command in the file menu called File>Import>OBJ as Shapekey. Select your target object, and then run the new command.

enter image description here

bl_info = {
    'name': 'Load Obj Sequence as Shape Keys',
    'author': 'cmomoney',
    'version': (0, 2),
    'blender': (2, 6, 7),
    'category': 'Import-Export',
    'location': 'File > Import/Export',
    'wiki_url': ''}

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy, os
from bpy.props import *

class LoadObjAsShapekey(bpy.types.Operator):
    bl_idname = 'load.obj_as_shapekey'
    bl_label = 'Import OBJ as Shape Keys'
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = "Import Obj sequence as shape key(s)"

    filepath = StringProperty(name="File path", description="File filepath of Obj", maxlen=4096, default="")
    filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
    filter_glob = StringProperty(default="*.obj", options={'HIDDEN'})
    files = CollectionProperty(name='File path', type=bpy.types.OperatorFileListElement)
    filename_ext = '.obj'

    @classmethod
    def poll(cls, context):
        return context.active_object is not None and context.active_object.type == 'MESH'

    def execute(self, context):
        #get file names, sort, and set target mesh
        spath = os.path.split(self.filepath)
        files = [file.name for file in self.files]
        files.sort()
        target = bpy.context.scene.objects.active
        #add all ojs in sequence as shape  keys
        for f in files:
            fp = os.path.join(spath[0], f)
            self.load_obj(fp)
        #now delete objs
        sknames = [sk.name for sk in target.data.shape_keys.key_blocks]
        bpy.ops.object.select_all(action='DESELECT')
        for obj in sknames:
            if obj != 'Basis':
                target.data.shape_keys.key_blocks[obj].interpolation = 'KEY_LINEAR'
                bpy.context.scene.objects.active = bpy.data.objects[obj]
                bpy.data.objects[obj].select = True
                bpy.ops.object.delete()
            bpy.ops.object.select_all(action='DESELECT')
        #reselect target mesh and make active
        bpy.context.scene.objects.active = target
        target.select = True
        return{'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def load_obj(self, fp):
        bpy.ops.import_scene.obj(filepath=fp,split_mode='OFF')
        bpy.ops.object.join_shapes()
        return

def menu_func_import(self, context):
    self.layout.operator(LoadObjAsShapekey.bl_idname, text="Obj As Shapekey(.obj)")

def register():
    bpy.utils.register_class(LoadObjAsShapekey)
    bpy.types.INFO_MT_file_import.append(menu_func_import)

def unregister():
    bpy.utils.unregister_class(LoadObjAsShapekey)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)

if __name__ == "__main__":
    register()

Please note this is not my script, I'm just posting it here for convenience.

$\endgroup$
10
  • $\begingroup$ I'm sorry but I'm just not understanding this still. I click the "load script" button but nothing seems to happen. I'm not getting any feedback about errors, it's just nothing happens. No shape keys created, no files merged, nothing. I've tried with everything loaded in the scene, and without that. If I need to modify the code with filenames or locations please tell me where. $\endgroup$ Commented Jul 20, 2016 at 20:53
  • $\begingroup$ You have to select the object and the click the "Run Script" button, which then should open the file window to select the OBJ files. Is that what you are doing? $\endgroup$ Commented Jul 21, 2016 at 1:49
  • $\begingroup$ No window pops up. I select the object and click run script but nothing happens. No menu pops up, no error happens, there is simply no response of any kind. Do I need another plugin turned on that isn't by default maybe? $\endgroup$ Commented Jul 22, 2016 at 2:23
  • $\begingroup$ Sorry, that's my fault. Running the script just adds a new "Import > Obj as Shapekey" command to the file menu. The proper instructions are run the script, select the object, then run that command from the file menu. I'll update my answer to be more clear. $\endgroup$ Commented Jul 22, 2016 at 2:34
  • 1
    $\begingroup$ Ok, I think I've figured it out. You'll have to excuse me as this was not a script I wrote, but one I found online. The line that does the file import does not show the import options in the UI, therefore you can't choose Keep Vertex Order as you do with single OBJ imports. I've updated the import line to force the Keep Vertex Order setting. It's the line: bpy.ops.import_scene.obj(filepath=fp,split_mode='OFF') $\endgroup$ Commented Jul 28, 2016 at 1:28
1
$\begingroup$

Updated script to work on 2.8 guys:

bl_info = {
    'name': 'Load Obj Sequence as Shape Keys',
    'author': 'cmomoney',
    'version': (0, 2),
    'blender': (2, 6, 7),
    'category': 'Import-Export',
    'location': 'File > Import/Export',
    'wiki_url': ''}

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy, os
from bpy.props import *

class LoadObjAsShapekey(bpy.types.Operator):
    bl_idname = 'load.obj_as_shapekey'
    bl_label = 'Import OBJ as Shape Keys'
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = "Import Obj sequence as shape key(s)"

    filepath = StringProperty(name="File path", description="File filepath of Obj", maxlen=4096, default="")
    filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
    filter_glob = StringProperty(default="*.obj", options={'HIDDEN'})
    files = CollectionProperty(name='File path', type=bpy.types.OperatorFileListElement)
    filename_ext = '.obj'

    @classmethod
    def poll(cls, context):
        return context.active_object is not None and context.active_object.type == 'MESH'

    def execute(self, context):
        #get file names, sort, and set target mesh
        spath = os.path.split(self.filepath)
        files = [file.name for file in self.files]
        files.sort()
        target = bpy.context.view_layer.objects.active
        #add all ojs in sequence as shape  keys
        for f in files:
            fp = os.path.join(spath[0], f)
            self.load_obj(fp)
        #now delete objs
        sknames = [sk.name for sk in target.data.shape_keys.key_blocks]
        bpy.ops.object.select_all(action='DESELECT')
        for obj in sknames:
            if obj != 'Basis':
                target.data.shape_keys.key_blocks[obj].interpolation = 'KEY_LINEAR'
                bpy.context.view_layer.objects.active = bpy.data.objects[obj]
                bpy.data.objects[obj].select_set(state=True)
                bpy.ops.object.delete()
            bpy.ops.object.select_all(action='DESELECT')
        #reselect target mesh and make active
        bpy.context.view_layer.objects.active = target
        target.select_set(state=True)
        return{'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def load_obj(self, fp):
        bpy.ops.import_scene.obj(filepath=fp,split_mode='OFF')
        bpy.ops.object.join_shapes()
        return

def menu_func_import(self, context):
    self.layout.operator(LoadObjAsShapekey.bl_idname, text="Obj As Shapekey(.obj)")

def register():
    bpy.utils.register_class(LoadObjAsShapekey)
    bpy.types.TOPBAR_MT_file_import.append(menu_func_import)

def unregister():
    bpy.utils.unregister_class(LoadObjAsShapekey)
    bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)

if __name__ == "__main__":
    register()
$\endgroup$
1
  • $\begingroup$ savram thank you for the script. I'm successfully running the script but the import option is disabled and I can't use it. $\endgroup$
    – Sesto
    Commented Nov 1, 2020 at 21:45
1
$\begingroup$

Updated, now works on Blender 3.4:

bl_info = {
    'name': 'Load Obj Sequence as Shape Keys',
    'author': 'cmomoney',
    'version': (0, 2),
    'blender': (3, 4, 0),
    'category': 'Import-Export',
    'location': 'File > Import/Export',
    'wiki_url': ''}

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy, os
from bpy.props import *
from bpy_extras.io_utils import ImportHelper, ExportHelper

class LoadObjAsShapekey(bpy.types.Operator, ImportHelper):
    bl_idname = 'load.obj_as_shapekey'
    bl_label = 'Import OBJ as Shape Keys'
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = "Import Obj sequence as shape key(s)"

    filepath: StringProperty(name="File path", description="File filepath of Obj", maxlen=4096, default="")
    filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
    filter_glob = StringProperty(default="*.obj", options={'HIDDEN'})
    files: CollectionProperty(name='File path', type=bpy.types.OperatorFileListElement)
    filename_ext = '.obj'

    @classmethod
    def poll(cls, context):
        return context.active_object is not None and context.active_object.type == 'MESH'

    def execute(self, context):
        #get file names, sort, and set target mesh
        spath = os.path.split(self.filepath)
        print('spath:', spath)
        files = [file.name for file in self.files]
        files.sort()
        target = bpy.context.view_layer.objects.active
        #add all ojs in sequence as shape  keys
        for f in files:
            fp = os.path.join(spath[0], f)
            self.load_obj(fp)
        #now delete objs
        sknames = [sk.name for sk in target.data.shape_keys.key_blocks]
        bpy.ops.object.select_all(action='DESELECT')
        for obj in sknames:
            if obj != 'Basis':
                target.data.shape_keys.key_blocks[obj].interpolation = 'KEY_LINEAR'
                bpy.context.view_layer.objects.active = bpy.data.objects[obj]
                bpy.data.objects[obj].select_set(state=True)
                bpy.ops.object.delete()
            bpy.ops.object.select_all(action='DESELECT')
        #reselect target mesh and make active
        bpy.context.view_layer.objects.active = target
        target.select_set(state=True)
        return{'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def load_obj(self, fp):
        bpy.ops.import_scene.obj(filepath=fp,split_mode='OFF')
        bpy.ops.object.join_shapes()
        return

def menu_func_import(self, context):
    self.layout.operator(LoadObjAsShapekey.bl_idname, text="Obj As Shapekey(.obj)")

def register():
    bpy.utils.register_class(LoadObjAsShapekey)
    bpy.types.TOPBAR_MT_file_import.append(menu_func_import)

def unregister():
    bpy.utils.unregister_class(LoadObjAsShapekey)
    bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)

if __name__ == "__main__":
    register()
```
$\endgroup$
1
  • $\begingroup$ Thank you for the update, much appreciated! It might be worth changing to the new obj importer as it's a lot faster - bpy.ops.wm.obj_import() $\endgroup$
    – dan283
    Commented Jan 11, 2023 at 7:39

You must log in to answer this question.

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