diff --git a/MMD.md b/MMD.md index 9c4fee8..0e5e376 100644 --- a/MMD.md +++ b/MMD.md @@ -1,5 +1,8 @@ ## MMD to High Fidelity Guide +### Suggested Watching + +https://www.youtube.com/watch?v=tJX8VUPZLKQ ## Requirements diff --git a/README.md b/README.md index f29d363..445c1f9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Plugin ("Project Hermes") is a plugin for Blender to allow for easier content cr ---- + +# Dependencies Included with source + +- py-ipfs-api is from https://github.com/ipfs/py-ipfs-api under MIT license. This is used to export avatars directly to be hosted online of ipfs, a distributed web. + + # Installation Guide ## Simple @@ -70,3 +76,4 @@ You can then set materials to the objects via the material panel, modify the mes If Entity is not Child of another entity, no Join is done. Only Children are merged with their Parents Note that Boolean operations work differently, and some may not keep the UV Unwrapping correctly in some situations. Use at your own risk + diff --git a/hifi_tools/files/fst/operator.py b/hifi_tools/files/fst/operator.py index 70d135e..6d500ce 100644 --- a/hifi_tools/files/fst/operator.py +++ b/hifi_tools/files/fst/operator.py @@ -34,10 +34,8 @@ EnumProperty ) import hifi_tools.files.fst.writer as FSTWriter - from hifi_tools.utils.bones import find_armatures - class HifiBoneOperator(bpy.types.Operator): bl_idname = "hifi_warn.bone_count" bl_label = "" @@ -162,7 +160,7 @@ class FSTWriterOperator(bpy.types.Operator, ExportHelper): selected_only = BoolProperty( default=False, name="Selected Only", description="Selected Only") - anim_url = StringProperty(default="", name="Animation JSON Url", + anim_graph_url = StringProperty(default="", name="Animation JSON Url", description="Avatar Animation JSON url") script = StringProperty(default="", name="Avatar Script Path", @@ -171,26 +169,37 @@ class FSTWriterOperator(bpy.types.Operator, ExportHelper): flow = BoolProperty(default=True, name="Add Flow Script", description="Adds flow script template as an additional Avatar script") - embed = BoolProperty(default=False, name="Embed Textures", description="Embed Textures to Exported Model") bake = BoolProperty(default=False, name="Oven Bake (Experimental)", description="Use the HiFi Oven Tool to bake") + + ipfs = BoolProperty(default=False, name="IPFS", + description="Upload files to the \n InterPlanetary File System Blockchain") + + ipfs_server = StringProperty(default="", name="IPFS Server Url", + description="") + def draw(self, context): layout = self.layout layout.prop(self, "selected_only") - layout.prop(self, "anim_url") + layout.prop(self, "anim_graph_url") layout.prop(self, "script") #layout.prop(self, "flow") layout.prop(self, "embed") oven_tool = context.user_preferences.addons[hifi_tools.__name__].preferences.oventool - if(oven_tool is not None and "oven" in oven_tool): + if (oven_tool is not None and "oven" in oven_tool): layout.prop(self, "bake") + + #layout.prop(self, "ipfs") + #if (self.ipfs): + #layout.prop(self, "ipfs_server") + def execute(self, context): if not self.filepath: diff --git a/hifi_tools/files/fst/writer.py b/hifi_tools/files/fst/writer.py index d23e1db..bbcd85f 100644 --- a/hifi_tools/files/fst/writer.py +++ b/hifi_tools/files/fst/writer.py @@ -51,10 +51,24 @@ prefix_texdir = "texdir = $\n" prefix_filename = "filename = $\n" +prefix_blendshape = "bs = $\n" + +prefix_joint = "joint = ¤ = $\n" +prefix_free_joint = "freeJoint = $\n" + prefix_script = "script = $\n" prefix_anim_graph_url = "animGraphUrl = $\n" +def default_blend_shape(selected): + print("Blend Shakes") + + for obj in selected: + if obj.type == "MESH": + print("Searching Blend Shapes") + # TODO: Make a map of common blendshape names + # if something does not already exist in model + def fst_export(context, selected): # file = open @@ -94,23 +108,28 @@ def fst_export(context, selected): f.write(prefix_texdir.replace('$', scene_id + '.fbm/')) f.write(prefix_filename.replace('$', avatar_file)) - f.write(prefix_script.replace('$', context.script)) - f.write(prefix_anim_graph_url.replace('$', context.anim_graph_url)) - + if len(context.script) > 0: + f.write(prefix_script.replace('$', context.script)) + if context.flow: - print("Add Script") + print("Add Flow Script") + + + if len(context.anim_graph_url) > 0: + f.write(prefix_anim_graph_url.replace('$', context.anim_graph_url)) + # Writing these in separate loops because they need to done in order. for bone in armature.data.bones: if bone.name in joint_maps: print("Writing joint map", prefix_joint_maps[bone.name] + " = " + bone.name) - f.write("joint = " + prefix_joint_maps[bone.name] + " = " + bone.name + "\n") + f.write(prefix_joint.replace('¤',prefix_joint_maps[bone.name]).replace('$', bone.name)) for bone in armature.data.bones: if bone.name in prefix_free_joints: print("Writing joint index", "freeJoint = " + bone.name + "\n") - f.write("freeJoint = " + bone.name + "\n") + f.write(prefix_free_joint.replace('$', bone.name)) retarget_armature({"apply": True}, selected) diff --git a/hifi_tools/files/hifi_json/writer.py b/hifi_tools/files/hifi_json/writer.py index e143fb5..5c53d60 100644 --- a/hifi_tools/files/hifi_json/writer.py +++ b/hifi_tools/files/hifi_json/writer.py @@ -129,6 +129,32 @@ def apply_all_modifiers(modifiers): if modifier.type != 'ARMATURE': bpy.ops.object.modifier_apply(apply_as='DATA', modifier=modifier.name) +def set_relative_to_parent(blender_object, json_data): + if blender_object.parent: + parent = blender_object.parent + + parent_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, parent.name) + + parent_orientation = quat_swap_nzy(relative_rotation(blender_object)) + parent_position = swap_nzy(relative_position(blender_object)) + + json_data["position"] = { + 'x': parent_position.x, + 'y': parent_position.y, + 'z': parent_position.z + } + + json_data["rotation"] = { + 'x': parent_orientation.x, + 'y': parent_orientation.y, + 'z': parent_orientation.z, + 'w': parent_orientation.w + } + + json_data["parentID"] = str(parent_uuid) + + return json_data + def parse_object(blender_object, path, options): # Store existing rotation mode, just in case. @@ -143,7 +169,6 @@ def parse_object(blender_object, path, options): uuid_gen = uuid.uuid5(uuid.NAMESPACE_DNS, blender_object.name) scene_id = str(uuid_gen) - reference_name = blender_object.data.name bo_type = blender_object.type stored_rotation_mode = str(blender_object.rotation_mode) @@ -155,6 +180,10 @@ def parse_object(blender_object, path, options): original_object = None blender_object.select = True uid = "" + reference_name = blender_object.data.name + + # TODO: If Child of armature, skip logic + # Here comes the fun part: Apply all modifiers prior to using them in the instance if len(blender_object.modifiers) > 0: # Lets do a LOW-LEVEL duplicate, too much automation in duplicate @@ -187,7 +216,6 @@ def parse_object(blender_object, path, options): # TODO: Option to also export via gltf instead of fbx # TODO: Add Option to not embedtextures / copy paths - file_path = path + reference_name + uid + ".fbx" atp_enabled = options.atp @@ -239,35 +267,11 @@ def parse_object(blender_object, path, options): 'userData': '{"blender_export":"' + scene_id +'"}, "grabbable_key":["grabbable":false]}' } - - if blender_object.parent: - parent = blender_object.parent - - parent_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, parent.name) - - parent_orientation = quat_swap_nzy(relative_rotation(blender_object)) - parent_position = swap_nzy(relative_position(blender_object)) - - json_data["position"] = { - 'x': parent_position.x, - 'y': parent_position.y, - 'z': parent_position.z - } - - json_data["rotation"] = { - 'x': parent_orientation.x, - 'y': parent_orientation.y, - 'z': parent_orientation.z, - 'w': parent_orientation.w - } - - json_data["parentID"] = str(parent_uuid) + json_data = set_relative_to_parent(blender_object, json_data) if original_object: - print("removing duplicate") bpy.ops.object.delete() blender_object = original_object - print("new set", blender_object) blender_object.select = True elif bo_type == 'LAMP': @@ -315,8 +319,40 @@ def parse_object(blender_object, path, options): # TODO: Spot Lights require rotation by 90 degrees to get pointing in the right direction elif bo_type == 'ARMATURE': # Same as Mesh actually. - print(name, 'is armature') - + # Get all children export as a single file. + print(name, 'is armature. Not Supported as of the moment') + + elif bo_type == 'EMPTY': + print(name, 'Adding an Empty') + + json_data = { + 'id': scene_id, + 'visible': False, + 'collisionless': True, + 'ignoreForCollisions': True, + 'position': { + 'x': position.x, + 'y': position.y, + 'z': position.z + }, + 'dimensions':{ + 'x': 1, + 'y': 1, + 'z': 1, + }, + 'name': 'EMPTY-' + name, + "color": { + "blue": 128, + "green": 0, + "red": 255 + }, + "shape": "Cube", + "type": "Box", + 'userData': '{"blender_export":"' + scene_id +'", "grabbableKey":{"grabbable":false,"ignoreIK":false}}', + } + + json_data = set_relative_to_parent(blender_object, json_data) + else: print('Skipping unsupported feature', name, bo_type) diff --git a/hifi_tools/files/js/flow.js b/hifi_tools/files/js/flow.js index 4b39fa5..9396e01 100644 --- a/hifi_tools/files/js/flow.js +++ b/hifi_tools/files/js/flow.js @@ -72,33 +72,7 @@ Script.include(Script.resolvePath("VectorMath.js")); // CUSTOM DATA STARTS HERE - // |||***PYTHONFILL***||| // - - /* - CUSTOM_FLOW_DATA = { - "hair": { - "active": true, - "stiffness": 0.0, - "radius": 0.04, - "gravity": -0.035, - "damping": 0.8, - "inertia": 0.8, - "delta": 0.35 - } - }; - - CUSTOM_COLLISION_DATA = { - "Spine2": { - "type": "sphere", - "radius": 0.14, - "offset": { - "x": 0, - "y": 0.2, - "z": 0 - } - } - }; - */ + //__PYTHONFILL__CUSTOM_SETS__// // CUSTOM DATA ENDS HERE diff --git a/hifi_tools/files/js/writer.py b/hifi_tools/files/js/writer.py index d0a46a4..dd4ab91 100644 --- a/hifi_tools/files/js/writer.py +++ b/hifi_tools/files/js/writer.py @@ -1,29 +1,6 @@ js_flow = "flow.js" js_flow_library = "VectorMath.js" -js_custom_writer = "// |||***PYTHONFILL***||| //" -#CUSTOM_FLOW_DATA = { -# "hair": { -# "active": true, -# "stiffness": 0.0, -# "radius": 0.04, -# "gravity": -0.035, -# "damping": 0.8, -# "inertia": 0.8, -# "delta": 0.35 -# } -#} - -#CUSTOM_COLLISION_DATA = { -# "Spine2": { -# "type": "sphere", -# "radius": 0.14, -# "offset": { -# "x": 0, -# "y": 0.2, -# "z": 0 -# } -q# } -#} +js_custom_writer = "//__PYTHONFILL__CUSTOM_SETS__//" class FlowData: @@ -45,7 +22,6 @@ class CollisionData: radius = 0.14 offset = Vec3Data() - def js_writer(flow_data, collision_data): print("Open JS Writer")