Skip to content
This repository has been archived by the owner on Jul 21, 2024. It is now read-only.

MB-Lab: Add a simplified export expression feature #270

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 120 additions & 2 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import logging

import time
import time, ntpath

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use path instead.

import datetime
import json
import os
Expand Down Expand Up @@ -896,6 +896,11 @@ def mbcrea_enum_expressions_items_update(self, context):
bpy.types.Scene.mblab_facs_rig = bpy.props.BoolProperty(
name="Import FACS Rig")

bpy.types.Scene.mblab_copy_to_all_phenotype = bpy.props.BoolProperty(
name="Apply to all Phenotype")
bpy.types.Scene.mblab_override_expressions = bpy.props.BoolProperty(
name="Override Existing Expressions")

#Hair color Drop Down List
bpy.types.Scene.mblab_hair_color = bpy.props.EnumProperty(
items=hair_style_list,
Expand Down Expand Up @@ -2989,6 +2994,9 @@ def draw(self, context):

box_tools = self.layout.box()
box_tools.label(text="TOOLS CATEGORIES", icon="RNA")
box_tools.operator('mbcrea.button_import_shape_keys', icon='EXPORT')
box_tools.prop(scn, "mblab_copy_to_all_phenotype")
box_tools.prop(scn, "mblab_override_expressions")
if gui_active_panel_first != "adaptation_tools":
box_tools.operator('mbcrea.button_adaptation_tools_on', icon=icon_expand)
else:
Expand Down Expand Up @@ -4285,6 +4293,115 @@ def execute(self, context):
print(copie)
return {'FINISHED'}

class ButtonExportShapeKeys(bpy.types.Operator):
bl_label = 'Import Expression Shape Keys'
bl_idname = 'mbcrea.button_import_shape_keys'
bl_description = 'PRE-FINALIZATION Tool: Import character shape keys to character morphs.\n\
Morphs will be available next time you create same character type'
bl_context = 'objectmode'
bl_options = {'REGISTER', 'INTERNAL'}

def execute(self, context):
# iterate through all shape keys on the mesh data and generate
# The delta between given shape key and the basis shape key.
# If no basis shape key operation will fail.
# If any of the shape keys override existing shape keys, operation
# will fail. This operation is meant to amend to current Lab keys
# and not to override them.
mesh = algorithms.get_active_body()
mname = mesh.name
if not mesh:
self.report({'ERROR'}, "No active mesh. Please select an MB-Lab Character")
# do a first pass over existing
basis_found = False
key_blocks = None
try:
key_blocks = bpy.data.objects[mname].data.shape_keys.key_blocks
except:
self.ShowMessageBox("Mesh has no shape keys", "Error", 'ERROR')
return {'FINISHED'}

expression_keys = self.getExpressionKeys()
for key in bpy.data.objects[mname].data.shape_keys.key_blocks:
if key.name == 'Basis':
if basis_found:
self.ShowMessageBox("More than one instance of the Basis Shape Key. Invalid",
"Error", 'ERROR')
return {'FINISHED'}
basis_found = True
continue
if key.name in expression_keys and not mblab_override_expressions:
self.ShowMessageBox("Shape key %s already exists. Aborting operation" % key.name, "Error", 'ERROR')
return {'FINISHED'}
if not basis_found:
self.ShowMessageBox("Basis shape key not found. Invalid setup", "Error", 'ERROR')
return {'FINISHED'}

# keys are good lets calculate each one relative to the basis
basis_vertices = []
new_expressions = {}
for key in bpy.data.objects[mname].data.shape_keys.key_blocks:
if key.name == 'Basis':
basis_vertices = self.getVertexList(key.data)
continue
key_vertices = self.getVertexList(key.data)
indexed_vertices = morphcreator.substract_with_index(basis_vertices, key_vertices)
if len(indexed_vertices) < 1:
continue
# append and write to the expressions file
new_expressions[key.name] = indexed_vertices
self.writeShapeKeyData(new_expressions)
return {'FINISHED'}

def writeData(self, path, new_data):
data = file_ops.load_json_data(path)
data = dict(data, **new_data)
file_ops.save_json_data(path, data)

def writeShapeKeyData(self, new_data):
phenotype_path = mblab_humanoid.morph_engine.get_expressions_file()
id1 = ntpath.basename(phenotype_path).split('_')[0]
id2 = ntpath.basename(phenotype_path).split('_')[1]
if bpy.context.scene.mblab_copy_to_all_phenotype:
# we have to check all files
for path in mblab_humanoid.morph_engine.get_all_expressions_files():
fname = ntpath.basename(path)
cur_id1 = fname.split('_')[0]
cur_id2 = fname.split('_')[1]
# Apply if this is the same phenotype
if cur_id1 == id1 and (cur_id2 == id2 or not 'an' in cur_id2):
self.writeData(path, new_data)
else:
path = mblab_humanoid.morph_engine.get_expressions_file()
self.writeData(path, new_data)

def getExpressionKeys(self):
keys = []
if bpy.context.scene.mblab_copy_to_all_phenotype:
# we have to check all files
for path in mblab_humanoid.morph_engine.get_all_expressions_files():
data = file_ops.load_json_data(path)
keys += list(data.keys())
else:
path = mblab_humanoid.morph_engine.get_expressions_file()
data = file_ops.load_json_data(path)
keys += list(data.keys())
return keys

def getVertexList(self, key_data):
kl_v = key_data.values()
result = []
for l in kl_v:
result.append(l.co)
return result
Comment on lines +4392 to +4396

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify to list comprehension.

return [l.co for l in key_data.values()]


def ShowMessageBox(self, message = "", title = "Error !", icon = 'ERROR'):

def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)


class ButtonAdaptationToolsON(bpy.types.Operator):
bl_label = 'Model edition'
bl_idname = 'mbcrea.button_adaptation_tools_on'
Expand Down Expand Up @@ -4754,6 +4871,7 @@ def execute(self, context):
OBJECT_OT_remove_color_preset,
OBJECT_OT_undo_remove_color,
ButtonForTest,
ButtonExportShapeKeys,
ButtonAdaptationToolsON,
ButtonAdaptationToolsOFF,
ButtonCompatToolsON,
Expand Down Expand Up @@ -4823,4 +4941,4 @@ def unregister():
if __name__ == "__main__":
register()

# <pep8 compliant>
# <pep8 compliant>
2 changes: 1 addition & 1 deletion file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def exists_database(lib_path):
def save_json_data(json_path, char_data):
try:
with open(json_path, "w") as j_file:
json.dump(char_data, j_file)
json.dump(char_data, j_file, indent=2)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove. We should let the indent come from other configuration or make this an option instead of hard-coding.

j_file.close()
except IOError:
if simple_path(json_path) != "":
Expand Down
12 changes: 10 additions & 2 deletions morphengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from . import proxyengine
from . import utils
#End Teto
import time, json
import time, json, glob
import operator

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -174,6 +174,14 @@ def init_final_form(self):
def __repr__(self):
return "MorphEngine {0} with {1} morphings".format(self.obj_name, len(self.morph_data))

def get_expressions_file(self):
return os.path.join(file_ops.get_data_path(), "expressions_morphs",
self.expressions_filename)

def get_all_expressions_files(self):
file_filter = os.path.join(file_ops.get_data_path(), "expressions_morphs", "*.json")
return glob.glob(file_filter)

def get_object(self):
if self.obj_name in bpy.data.objects:
return bpy.data.objects[self.obj_name]
Expand Down Expand Up @@ -419,4 +427,4 @@ def calculate_morph(self, morph_name, val, add_vertices_to_update=True):
self.morph_values[morph_name] = val
else:
logger.debug("Morph data {0} not found".format(morph_name))