├── .gitignore ├── CHANGELOG.md ├── README.md ├── blender_distribution ├── MH_Community_for_blender_279.zip └── MH_Community_for_blender_280.zip ├── blender_source └── MH_Community │ ├── __init__.py │ ├── animation_trimming.py │ ├── data │ ├── nodes │ │ ├── defaultMaterial.json │ │ ├── skinMaterial.json │ │ ├── skinMaterialSSS.json │ │ └── skinOverridePresets │ │ │ ├── asian.json │ │ │ ├── default.json │ │ │ ├── pale.json │ │ │ └── tan.json │ └── textures │ │ └── sss.png │ ├── devtools │ ├── __init__.py │ ├── devtools_ui.py │ └── printvgroups.py │ ├── extra_groups.py │ ├── mh_sync │ ├── JsonCall.py │ ├── __init__.py │ ├── bone_ui.py │ ├── directory_ops.py │ ├── expression_transfer.py │ ├── fetch_server_data.py │ ├── import_body_binary.py │ ├── import_proxy_binary.py │ ├── import_weighting.py │ ├── importer_ui.py │ ├── material.py │ ├── meshutils.py │ ├── presets.py │ ├── shapes_from_pose.py │ ├── sync_mesh.py │ ├── sync_ops.py │ └── sync_pose.py │ ├── mocap │ ├── __init__.py │ ├── animation_buffer.py │ ├── capture_armature.py │ ├── empties.py │ ├── keyframe_reduction.py │ ├── kinect2 │ │ ├── KinectToJSON_x64.dll │ │ ├── KinectToJSON_x86.dll │ │ ├── __init__.py │ │ └── kinect2_sensor.py │ ├── mocap_ui.py │ └── sensor_runtime.py │ ├── operators │ ├── __init__.py │ ├── actionkeyframereducer.py │ ├── actiontrimleft.py │ ├── actiontrimright.py │ ├── addsimplematerial.py │ ├── amputateface.py │ ├── amputatefingers.py │ ├── bodyimport.py │ ├── expressiontrans.py │ ├── loadpreset.py │ ├── meshsync.py │ ├── mocapassignment.py │ ├── mocaprefresh.py │ ├── poseright.py │ ├── posesync.py │ ├── removefingerrig.py │ ├── removeikrig.py │ ├── savepreset.py │ ├── separateeyes.py │ ├── snaponfingerrig.py │ ├── snaponikrig.py │ ├── startmocaprecording.py │ ├── stopmocaprecording.py │ └── toSensorRig.py │ ├── rig │ ├── __init__.py │ ├── bonesurgery.py │ ├── cmuriginfo.py │ ├── defaultriginfo.py │ ├── fingerrig.py │ ├── gameriginfo.py │ ├── ikrig.py │ ├── kinect2riginfo.py │ └── riginfo.py │ ├── separate_eyes.py │ └── util.py ├── doc-assist ├── IK_fingers.png ├── MH_server_socket.png ├── blender_anger_no_trans.png ├── blender_anger_trans.png ├── bones_tab.png ├── eye_before.png ├── eyes_after.png ├── kinect_tab.png ├── mesh_tab.png ├── mh_anger.png ├── newPoseLib.jpg ├── poseLib.jpg ├── rig_after.png └── rig_before.png └── rebuild_dist.bash /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .project 4 | .idea 5 | .settings 6 | *.swp 7 | 8 | /deploy_media.xml 9 | /package.json 10 | /scripts/ 11 | /nbproject/ 12 | /node_modules/ 13 | 14 | venv 15 | 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | These are the changes since the release of MakeHuman 1.2.0. 4 | 5 | ## 1.1.0 6 | 7 | * The number of subdivisons on import is now configurable 8 | * There is a new setting for applying extra tweaks for slotted body materials (only relevant when also having checked "Enhanced skin material", "Extra vertex groups" and "Slots for extra groups") 9 | 10 | ## 1.0.0 11 | 12 | This is the same as "0.7.0" which was bundled with MakeHuman 1.2.0. We had planned to put the version stamp before the release but forgot. 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MH Community Plug-in for Blender (MPFB1) # 2 | 3 | *MPFB1 HAS BEEN DEPRECATED IN FAVOR OF [MPFB2](https://github.com/makehumancommunity/mpfb2), SEE [DIFFERENCES BETWEEB MPFB1 AND MPFB2](https://static.makehumancommunity.org/mpfb/faq/differences_between_mpfb1_and_mpfb2.html)* 4 | 5 | The contents of this repo are still available, but mainly for historical reasons. You will need a very old blender version in order 6 | to run the stuff here, probably something like blender 2.8. 7 | 8 | 9 | 10 | This is a blender plug-in which brings features related to MakeHuman. It provides for post import operations specific to MakeHuman meshes, and armatures. Sync operations require that MakeHuman be running, with server connections being accepted. 11 | 12 | ## How to install ## 13 | 14 | ### MakeHuman ### 15 | 16 | Just use the community version of MakeHuman. It not only has the plug-ins need here, but also has the one needed to export in MHX2 format. Location not currently known. For adding plugins to an existing installation, [see](http://www.makehumancommunity.org/content/plugins.html). 17 | 18 | ### Blender ### 19 | 20 | * Download the file blender_distribution/MH_Community.zip (or find it locally if you downloaded the zip above) 21 | * DO NOT UNZIP THIS FILE 22 | * In blender go to file -> user preferences -> addons 23 | * Click "install from file" 24 | * Navigate to the MH_Community.zip file 25 | * Enable as you would any other add-on, then save preferences to make it permanent. 26 | 27 | ### Kinect v2 sensor (Windows 8 or higher) ### 28 | 29 | Get the runtime driver from microsoft & follow the directions from [here](https://www.microsoft.com/en-us/download/details.aspx?id=44559). 30 | 31 | ## Tool layout ## 32 | The operators are grouped by type: 33 | 34 | ||| 35 | 36 | ## Mesh based Operations ## 37 | 38 | 39 | ### Sync mesh ### 40 | The user interface 41 | 42 | ### Separate Eyes & Center origins around mass ### 43 | |Eye Mesh Before| Eye Meshes After| 44 | | --- | --- 45 | || 46 | 47 | The single Eye mesh as it comes out of MakeHuman is only really adjustable while using the Default (163 bones) or Default No Toes (137 bones) armatures. This is way too many Bones for some downstream systems. Android & iOS for example can barely manage around 23 bones using WebGL 1.0. 48 | 49 | There is another way to move them by just separating each eye, and assigning the same rotation. Most gaming frameworks, including 'Blender Game', also have a feature called billboard mode. When switched on, the framework ensures that a mesh is always facing the camera. This allows for the effect of making the eyes follow the camera with very little work. 50 | 51 | It was observed that eyes also needed to be setback a minor amount to keep from violating the skin when rotation was large (15% eye depth for billboard mode & 10% for manual setting). This is not really noticable, however piercing the skin most definitely is. 52 | 53 | If managing the rotation outside of billboard mode, staying between -0.12 and 0.12 radians seem to be the practical vertical limits. Left & right limits vary by eye. 54 | 55 | |Eye|Rotate Left| Rotate Right| 56 | | --- | --- | --- | 57 | |Left|-0.50|0.35| 58 | |Right|-0.35|0.50| 59 | 60 | ## Bone Operations ## 61 | 62 | ### Sync pose ### 63 | In addition to MakeHuman running & accepting socket connections, the current model must have the Default, or Default No Toes skeleton selected. The skeleton running in Blender must match. If possible, you should use the same MH model that was used to do the export. The current pose and expression are transferred to Blender. If feet were exported on the ground, check the `Feet on Ground` check just below. This will clear the location on the `root` bone. Many of the poses have location changes to root, so either way some pose adjustment will be required. Expression transfers will all work fine, though. 64 | 65 | Many expressions, e.g. Anger01, have location translation on locked bones. To allow this translation to occur ensure the `No Location Translation` checkbox remains unchecked. Allowing translation will result in poses which more closely resemble those in MakeHuman. There is one side effect, though. Clearing locations for these bones, Alt-G, will not work. Syncing again when MakeHuman's expression is set to `None` is one way to restore Alt-G. 66 | 67 | |Make Human| With Trans| Without Trans | 68 | | --- | --- | --- | 69 | ||| 70 | # Expression Transfer # 71 | |New Library|Active Library 72 | | --- | --- 73 | || 74 | 75 | This feature lets you move expressions to Blender, both stock and your custom expressions, without them being put on the export file (actually custom expressions are never on the export). Having the results being stored in a Pose Library also allows you to set to any of them whenever you wish, without using the MHX2 user interface, or requiring specific MHX2 overrides during import. This is specifically tested without overriding an MHX2 import at all. Collada format will also work assuming the skeleton imports correctly. 76 | 77 | Behind the scenes `Sync pose` is being called in a loop, so the same setup described above also applies here. Also, an active Pose Library must be present, in addition to the skeleton being selected, to allow the `To Poselib` button to be enabled. 78 | 79 | One quirk to creating a Pose Library, if you need to, is the `Pose` element in the Outliner must be selected for the `New` button to be enabled. 80 | 81 | You can also specify an expression tag to filter by just like in MakeHuman. This has been not case sensitive. 82 | ## Convert to IK Rig ## 83 | |Before|After 84 | | --- | --- 85 | || 86 | This operator converts the Game Rig post export to a IK capable one. This is done by: 87 | - Adding IK bones on elbows, wrists, knees, and ankles. 88 | - Adding IK constraints on bones to allow these IK bones to be used to pose. 89 | - Unlocking the pose location of the pelvis & clavicle bones. 90 | 91 | This operation will work for models imported using MHX2 or Collada, from a bone naming stand point, but issues resulting from importing bones using Collada still remain. 92 | 93 | ## Add Finger IK Bones ## 94 | 95 | |Finger Rig (shown with GZM_Knuckle custom shape on Default Rig)| 96 | | --- 97 | | 98 | 99 | Finger control bones are added with this operation. This works for both the Default Rig, and Game Rig. Finger bones under control are hidden to avoid confusion, except for the thumb. If you ever need to see them again, then go to edit mode where all bones are shown. Select the bone to unhide, then return to pose mode. You can now unhide it. 100 | 101 | If there is a mesh named GZM_Knuckle in the scene, it will be assigned as the custom shape. 102 | 103 | ## Remove Finger Bones ## 104 | This operation removes all bones below the wrists. Any bone weights which were held by the deleted bones are transferred to the wrists, based on vertex groups. Not a major task, but saves the tedious task of weight painting manually. 105 | 106 | -------------------------------------------------------------------------------- /blender_distribution/MH_Community_for_blender_279.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/blender_distribution/MH_Community_for_blender_279.zip -------------------------------------------------------------------------------- /blender_distribution/MH_Community_for_blender_280.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/blender_distribution/MH_Community_for_blender_280.zip -------------------------------------------------------------------------------- /blender_source/MH_Community/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | BLENDER_REGION = "UI" 7 | 8 | if bpy.app.version < (2, 80, 0): 9 | BLENDER_REGION = "TOOLS" 10 | 11 | bl_info = { 12 | "name": "MH Community Plug-in", 13 | "author": "Joel Palmius", 14 | "version": (1, 1, 0), 15 | "blender": (2, 80, 0), 16 | "location": "View3D > Properties > MH", 17 | "description": "MakeHuman interactive operations", 18 | "wiki_url": "https://github.com/makehumancommunity/makehuman-plugin-for-blender", 19 | "category": "MakeHuman"} 20 | 21 | print("Loading MH community plug-in v %d.%d.%d" % bl_info["version"]) 22 | from . import mh_sync # directory 23 | from . import mocap # directory 24 | from . import separate_eyes 25 | from .rig import RigInfo, BoneSurgery, IkRig, FingerRig 26 | from . import animation_trimming 27 | 28 | from bpy.props import BoolProperty, StringProperty, EnumProperty, IntProperty, CollectionProperty, FloatProperty 29 | from .mh_sync.importer_ui import addImporterUIToTab, registerImporterConstantsAndSettings, addImporterSettingsToTab 30 | from .mh_sync.bone_ui import addBoneUIToTab, registerBoneConstantsAndSettings 31 | from .mocap.mocap_ui import addMocapUIToTab, registerMocapConstantsAndSettings, unregisterMocap 32 | from .devtools import addDevtoolsToTab, registerDevtoolsConstantsAndSettings, DEVTOOLS_CLASSES 33 | 34 | #=============================================================================== 35 | class MHC_PT_Community_Panel(bpy.types.Panel): 36 | bl_label = "MakeHuman v %d.%d.%d" % bl_info["version"] 37 | bl_space_type = "VIEW_3D" 38 | bl_region_type = BLENDER_REGION 39 | bl_category = "MakeHuman" 40 | 41 | def draw(self, context): 42 | layout = self.layout 43 | scn = context.scene 44 | 45 | layout.prop(scn, 'mhTabs', expand=True) 46 | 47 | if scn.mhTabs == MESH_TAB: 48 | # Broken out to mh_sync/importer_ui 49 | addImporterUIToTab(layout, scn) 50 | 51 | layout.separator() 52 | 53 | generalSyncBox = layout.box() 54 | generalSyncBox.label(text="Various") 55 | generalSyncBox.operator("mh_community.sync_mh_mesh", text="Sync with MH") 56 | generalSyncBox.operator("mh_community.separate_eyes") 57 | 58 | addBoneUIToTab(layout, scn) 59 | addDevtoolsToTab(layout, scn) 60 | 61 | elif scn.mhTabs == SETTINGS_TAB: 62 | addImporterSettingsToTab(layout, scn) 63 | 64 | else: 65 | addMocapUIToTab(layout, scn) 66 | #=============================================================================== 67 | MESH_TAB = 'A' 68 | MOCAP_TAB = 'B' 69 | SETTINGS_TAB = 'C' 70 | 71 | bpy.types.Armature.exportedUnits = bpy.props.StringProperty( 72 | name='Exported Units', 73 | description='either METERS, DECIMETERS, or CENTIMETERS. determined in RigInfo.determineExportedUnits(). Stored in armature do only do once.', 74 | default = '' 75 | ) 76 | 77 | classes = [ 78 | MHC_PT_Community_Panel 79 | ] 80 | 81 | from .operators import * 82 | classes.extend(OPERATOR_CLASSES) 83 | 84 | classes.extend(DEVTOOLS_CLASSES) 85 | 86 | def register(): 87 | from bpy.utils import register_class 88 | for cls in classes: 89 | register_class(cls) 90 | 91 | bpy.types.Scene.mhTabs = bpy.props.EnumProperty( 92 | name='meshOrBoneOrMocap', 93 | items = ( 94 | (MESH_TAB , "Mesh" , "Operators related to Make Human meshes and rigs"), 95 | (MOCAP_TAB, "Mocap", "Motion Capture using supported sensors"), 96 | (SETTINGS_TAB, "Settings", "Settings for MH operations"), 97 | ), 98 | default = MESH_TAB 99 | ) 100 | 101 | registerImporterConstantsAndSettings() 102 | registerBoneConstantsAndSettings() 103 | registerMocapConstantsAndSettings() 104 | 105 | 106 | def unregister(): 107 | from bpy.utils import unregister_class 108 | for cls in reversed(classes): 109 | unregister_class(cls) 110 | 111 | del bpy.types.Scene.MhHandleHelper 112 | del bpy.types.Scene.MhScaleMode 113 | 114 | unregisterMocap() 115 | 116 | if __name__ == "__main__": 117 | unregister() 118 | register() 119 | 120 | print("MH community plug-in load complete") 121 | -------------------------------------------------------------------------------- /blender_source/MH_Community/animation_trimming.py: -------------------------------------------------------------------------------- 1 | from mathutils import Quaternion, Vector, Euler 2 | import bpy 3 | #=============================================================================== 4 | class AnimationTrimming(): 5 | 6 | def __init__(self, armature): 7 | self.armature = armature 8 | self.action = armature.animation_data.action 9 | self.first = int(self.action.frame_range[0]) 10 | self.last = int(self.action.frame_range[1]) 11 | 12 | # get the actual frames with data 13 | self.frames = dict() # use dictionary, so frames common amoung bones only listed once 14 | for fcurve in self.action.fcurves: 15 | for key in fcurve.keyframe_points: 16 | frame = key.co.x 17 | self.frames[frame] = True # actual value has no meaning 18 | 19 | self.frames = sorted(self.frames) 20 | 21 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 22 | # operation entry points 23 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 24 | def deleteAndShift(self): 25 | current_mode = bpy.context.object.mode 26 | bpy.ops.object.mode_set(mode='POSE') 27 | 28 | # drop from the front up to, but not including, the current frame 29 | firstGoodFrame = bpy.context.scene.frame_current 30 | self.dropRange(self.first, firstGoodFrame) 31 | 32 | # shift remaining keys on a bone, property basis, to the left 33 | for bone in self.armature.pose.bones: 34 | rotProperty = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler' 35 | self.shiftLeft(bone, rotProperty, firstGoodFrame) 36 | if self.hasLocationKeys(bone): 37 | self.shiftLeft(bone, 'location', firstGoodFrame) 38 | 39 | # dope sheet not reflecting deletions immediately, so do "something" 40 | bpy.context.scene.frame_set(self.first) 41 | bpy.ops.object.mode_set(mode=current_mode) 42 | 43 | def dropToRight(self): 44 | current_mode = bpy.context.object.mode 45 | bpy.ops.object.mode_set(mode='POSE') 46 | 47 | # drop all frames for the bones after the current frame 48 | lastGoodFrame = bpy.context.scene.frame_current 49 | self.dropRange(lastGoodFrame + 1, self.last + 1) 50 | 51 | # dope sheet not reflecting deletions immediately, so do "something" 52 | bpy.context.scene.frame_set(bpy.context.scene.frame_current) 53 | bpy.ops.object.mode_set(mode=current_mode) 54 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 55 | # below only called internally 56 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 57 | def dropRange(self, first, lastNotIncluded): 58 | for bone in self.armature.pose.bones: 59 | for frameNum in range(first, lastNotIncluded): 60 | # does not error when location property missing, so just do all possiblities 61 | rotProperty = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler' 62 | bone.keyframe_delete(rotProperty, -1, frameNum) 63 | bone.keyframe_delete('location' , -1, frameNum) 64 | 65 | def shiftLeft(self, bone, property, firstGoodFrame): 66 | newFrameNum = firstGoodFrame 67 | 68 | # for each kept frame shift it, then delete the old one 69 | for oldFrameNum in self.frames: 70 | if oldFrameNum < firstGoodFrame: continue 71 | 72 | # find the value of property of the bone at a given frame & set the bone to it 73 | values = self.findKeyValues(bone, property, oldFrameNum) 74 | if len(values) == 0: continue 75 | 76 | if property == 'rotation_quaternion': 77 | bone.rotation_quaternion = Quaternion(values) 78 | elif property == 'rotation_euler': 79 | bone.rotation_euler = Euler(values) 80 | elif property == 'location': 81 | bone.location = Vector(values) 82 | 83 | # add a new keyframe 84 | bone.keyframe_insert(property, frame = oldFrameNum - newFrameNum, group = bone.name) 85 | 86 | # delete the old key frame 87 | bone.keyframe_delete(property, -1, oldFrameNum) 88 | 89 | # a given key only contains a single float, so return all found for a given property / frame number 90 | def findKeyValues(self, bone, property, frameNum): 91 | ret = [] 92 | dataPath = 'pose.bones["' + bone.name + '"].' + property 93 | for c in self.action.fcurves: 94 | if c.data_path == dataPath: 95 | ret.append(c.evaluate(frameNum)) 96 | 97 | return ret 98 | 99 | def hasLocationKeys(self, bone): 100 | dataPath = 'pose.bones["' + bone.name + '"].location' 101 | 102 | for c in self.action.fcurves: 103 | if c.data_path == dataPath: 104 | return True 105 | 106 | return False 107 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/defaultMaterial.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | 4 | }, 5 | "nodes" : { 6 | "output": { 7 | "type": "ShaderNodeOutputMaterial", 8 | "label": "Material output", 9 | "location": [350, 100], 10 | "group": "", 11 | "values": {}, 12 | "create": true 13 | }, 14 | "principled": { 15 | "type": "ShaderNodeBsdfPrincipled", 16 | "label": "Principled", 17 | "location": [0, 100], 18 | "group": "", 19 | "values": { 20 | "Base Color": [0.8, 0.8, 0.8, 1.0] 21 | }, 22 | "create": true 23 | }, 24 | "diffuseTexture": { 25 | "type": "ShaderNodeTexImage", 26 | "label": "diffuseTexture", 27 | "location": [-400, 100], 28 | "group": "", 29 | "values": {}, 30 | "imageData": { 31 | "path": "", 32 | "colorspace_settings_name": "sRGB" 33 | }, 34 | "create": false 35 | }, 36 | "normalMapTexture": { 37 | "type": "ShaderNodeTexImage", 38 | "label": "normal map texture", 39 | "location": [-600, -500], 40 | "group": "", 41 | "values": {}, 42 | "imageData": { 43 | "path": "", 44 | "colorspace_settings_name": "Non-Color" 45 | }, 46 | "create": false 47 | }, 48 | "bumpMapTexture": { 49 | "type": "ShaderNodeTexImage", 50 | "label": "normal map texture", 51 | "location": [-600, -200], 52 | "group": "", 53 | "values": {}, 54 | "imageData": { 55 | "path": "", 56 | "colorspace_settings_name": "Non-Color" 57 | }, 58 | "create": false 59 | }, 60 | "bumpOrNormal": { 61 | "type": "ShaderNodeNormalMap", 62 | "label": "normal map", 63 | "location": [-300, -300], 64 | "group": "", 65 | "values": { 66 | "Strength": 1.0 67 | }, 68 | "create": false 69 | }, 70 | "bumpAndNormal": { 71 | "type": "ShaderNodeNormalMap", 72 | "label": "normal map", 73 | "location": [-300, -300], 74 | "group": "", 75 | "values": { 76 | "Strength": 1.0 77 | }, 78 | "create": false 79 | }, 80 | "bumpMap": { 81 | "type": "ShaderNodeBump", 82 | "label": "bump map", 83 | "location": [-100, -300], 84 | "group": "", 85 | "values": { 86 | "Strength": 0.5 87 | }, 88 | "create": false 89 | } 90 | }, 91 | "connections" : [ 92 | { 93 | "outputNode": "principled", 94 | "outputSocket": "BSDF", 95 | "inputNode": "output", 96 | "inputSocket": "Surface" 97 | }, 98 | { 99 | "outputNode": "diffuseTexture", 100 | "outputSocket": "Color", 101 | "inputNode": "principled", 102 | "inputSocket": "Base Color" 103 | }, 104 | { 105 | "outputNode": "diffuseTexture", 106 | "outputSocket": "Alpha", 107 | "inputNode": "principled", 108 | "inputSocket": "Alpha" 109 | }, 110 | { 111 | "outputNode": "normalMapTexture", 112 | "outputSocket": "Color", 113 | "inputNode": "bumpOrNormal", 114 | "inputSocket": "Color" 115 | }, 116 | { 117 | "outputNode": "normalMapTexture", 118 | "outputSocket": "Color", 119 | "inputNode": "bumpAndNormal", 120 | "inputSocket": "Color" 121 | }, 122 | { 123 | "outputNode": "bumpOrNormal", 124 | "outputSocket": "Normal", 125 | "inputNode": "principled", 126 | "inputSocket": "Normal" 127 | }, 128 | { 129 | "outputNode": "bumpMapTexture", 130 | "outputSocket": "Color", 131 | "inputNode": "bumpMap", 132 | "inputSocket": "Height" 133 | }, 134 | { 135 | "outputNode": "bumpAndNormal", 136 | "outputSocket": "Normal", 137 | "inputNode": "bumpMap", 138 | "inputSocket": "Normal" 139 | }, 140 | { 141 | "outputNode": "bumpMap", 142 | "outputSocket": "Normal", 143 | "inputNode": "principled", 144 | "inputSocket": "Normal" 145 | } 146 | ] 147 | } 148 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/skinMaterial.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "skinGroup": { 4 | "label": "Skin", 5 | "inputs": { 6 | "colorMixIn": { 7 | "type": "NodeSocketColor", 8 | "value": [1.0, 0.2, 0.2, 1.0] 9 | }, 10 | "colorMixInStrength": { 11 | "type": "NodeSocketFloat", 12 | "value": 0.05 13 | }, 14 | "Roughness": { 15 | "type": "NodeSocketFloat", 16 | "value": 0.5 17 | }, 18 | "Brightness": { 19 | "type": "NodeSocketFloat", 20 | "value": 0.0 21 | }, 22 | "Contrast": { 23 | "type": "NodeSocketFloat", 24 | "value": 0.0 25 | }, 26 | "Clearcoat": { 27 | "type": "NodeSocketFloat", 28 | "value": 0.1 29 | }, 30 | "Clearcoat Roughness": { 31 | "type": "NodeSocketFloat", 32 | "value": 0.3 33 | }, 34 | "Pore scale": { 35 | "type": "NodeSocketFloat", 36 | "value": 2500.0 37 | }, 38 | "Pore detail": { 39 | "type": "NodeSocketFloat", 40 | "value": 2.0 41 | }, 42 | "Pore distortion": { 43 | "type": "NodeSocketFloat", 44 | "value": 1.0 45 | }, 46 | "Pore strength": { 47 | "type": "NodeSocketFloat", 48 | "value": 0.2 49 | } 50 | }, 51 | "outputs": { 52 | "shaderOutput": { 53 | "type": "NodeSocketShader" 54 | } 55 | }, 56 | "location": [0, 100], 57 | "create": true 58 | } 59 | }, 60 | "nodes" : { 61 | "skinGroupInputs": { 62 | "type": "NodeGroupInput", 63 | "label": "Skin input", 64 | "location": [-1200, -100], 65 | "group": "skinGroup", 66 | "values": {}, 67 | "create": true 68 | }, 69 | "skinGroupOutputs": { 70 | "type": "NodeGroupOutput", 71 | "label": "Skin output", 72 | "location": [600, 100], 73 | "group": "skinGroup", 74 | "values": {}, 75 | "create": true 76 | }, 77 | "output": { 78 | "type": "ShaderNodeOutputMaterial", 79 | "label": "Material output", 80 | "location": [350, 100], 81 | "group": "", 82 | "values": {}, 83 | "create": true 84 | }, 85 | "principled": { 86 | "type": "ShaderNodeBsdfPrincipled", 87 | "label": "Principled", 88 | "location": [0, 100], 89 | "group": "skinGroup", 90 | "values": { 91 | "Base Color": [0.8, 0.8, 0.8, 1.0] 92 | }, 93 | "create": true 94 | }, 95 | "diffuseTexture": { 96 | "type": "ShaderNodeTexImage", 97 | "label": "diffuseTexture", 98 | "location": [-800, 300], 99 | "group": "skinGroup", 100 | "values": {}, 101 | "imageData": { 102 | "path": "", 103 | "colorspace_settings_name": "sRGB" 104 | }, 105 | "create": false 106 | }, 107 | "normalMapTexture": { 108 | "type": "ShaderNodeTexImage", 109 | "label": "normal map texture", 110 | "location": [-800, -500], 111 | "group": "skinGroup", 112 | "values": {}, 113 | "imageData": { 114 | "path": "", 115 | "colorspace_settings_name": "Non-Color" 116 | }, 117 | "create": false 118 | }, 119 | "bumpMapTexture": { 120 | "type": "ShaderNodeTexImage", 121 | "label": "normal map texture", 122 | "location": [-600, -200], 123 | "group": "skinGroup", 124 | "values": {}, 125 | "imageData": { 126 | "path": "", 127 | "colorspace_settings_name": "Non-Color" 128 | }, 129 | "create": false 130 | }, 131 | "bumpOrNormal": { 132 | "type": "ShaderNodeNormalMap", 133 | "label": "normal map", 134 | "location": [-500, -500], 135 | "group": "skinGroup", 136 | "values": { 137 | "Strength": 1.0 138 | }, 139 | "create": false 140 | }, 141 | "bumpAndNormal": { 142 | "type": "ShaderNodeNormalMap", 143 | "label": "normal map", 144 | "location": [-300, -300], 145 | "group": "skinGroup", 146 | "values": { 147 | "Strength": 1.0 148 | }, 149 | "create": false 150 | }, 151 | "bumpMap": { 152 | "type": "ShaderNodeBump", 153 | "label": "bump map", 154 | "location": [-100, -300], 155 | "group": "skinGroup", 156 | "values": {}, 157 | "create": false 158 | }, 159 | "colorMix": { 160 | "type": "ShaderNodeMixRGB", 161 | "label": "Color mix", 162 | "location": [-500, 200], 163 | "group": "skinGroup", 164 | "values": {}, 165 | "create": true 166 | }, 167 | "brightContrast": { 168 | "type": "ShaderNodeBrightContrast", 169 | "label": "Brightness and Contrast", 170 | "location": [-300, 100], 171 | "group": "skinGroup", 172 | "values": {}, 173 | "create": true 174 | }, 175 | "noise": { 176 | "type": "ShaderNodeTexNoise", 177 | "label": "noise", 178 | "location": [-800, -800], 179 | "group": "skinGroup", 180 | "values": { 181 | "Scale": 2000.0, 182 | "Detail": 2.0, 183 | "Distortion": 1.0 184 | }, 185 | "create": true 186 | }, 187 | "colorramp": { 188 | "type": "ShaderNodeValToRGB", 189 | "label": "ramp", 190 | "location": [-500, -800], 191 | "group": "skinGroup", 192 | "values": {}, 193 | "stops": [0.2, 0.5], 194 | "create": true 195 | }, 196 | "noiseBump": { 197 | "type": "ShaderNodeBump", 198 | "label": "noise bump", 199 | "location": [-200, -500], 200 | "group": "skinGroup", 201 | "values": { 202 | "Strength": 0.2 203 | }, 204 | "create": true 205 | }, 206 | "texco": { 207 | "type": "ShaderNodeTexCoord", 208 | "label": "Texture coords", 209 | "location": [-1200, 300], 210 | "group": "skinGroup", 211 | "values": {}, 212 | "create": true 213 | } 214 | }, 215 | "connections" : [ 216 | { 217 | "outputNode": "principled", 218 | "outputSocket": "BSDF", 219 | "inputNode": "skinGroupOutputs", 220 | "inputSocket": "shaderOutput" 221 | }, 222 | { 223 | "outputNode": "skinGroup", 224 | "outputSocket": "shaderOutput", 225 | "inputNode": "output", 226 | "inputSocket": "Surface" 227 | }, 228 | { 229 | "outputNode": "diffuseTexture", 230 | "outputSocket": "Color", 231 | "inputNode": "colorMix", 232 | "inputSocket": "Color1" 233 | }, 234 | { 235 | "outputNode": "skinGroupInputs", 236 | "outputSocket": "colorMixIn", 237 | "inputNode": "colorMix", 238 | "inputSocket": "Color2" 239 | }, 240 | { 241 | "outputNode": "skinGroupInputs", 242 | "outputSocket": "colorMixInStrength", 243 | "inputNode": "colorMix", 244 | "inputSocket": "Fac" 245 | }, 246 | { 247 | "outputNode": "colorMix", 248 | "outputSocket": "Color", 249 | "inputNode": "brightContrast", 250 | "inputSocket": "Color" 251 | }, 252 | { 253 | "outputNode": "brightContrast", 254 | "outputSocket": "Color", 255 | "inputNode": "principled", 256 | "inputSocket": "Base Color" 257 | }, 258 | { 259 | "outputNode": "skinGroupInputs", 260 | "outputSocket": "Brightness", 261 | "inputNode": "brightContrast", 262 | "inputSocket": "Bright" 263 | }, 264 | { 265 | "outputNode": "skinGroupInputs", 266 | "outputSocket": "Contrast", 267 | "inputNode": "brightContrast", 268 | "inputSocket": "Contrast" 269 | }, 270 | { 271 | "outputNode": "skinGroupInputs", 272 | "outputSocket": "Roughness", 273 | "inputNode": "principled", 274 | "inputSocket": "Roughness" 275 | }, 276 | { 277 | "outputNode": "skinGroupInputs", 278 | "outputSocket": "Clearcoat Roughness", 279 | "inputNode": "principled", 280 | "inputSocket": "Clearcoat Roughness" 281 | }, 282 | { 283 | "outputNode": "skinGroupInputs", 284 | "outputSocket": "Clearcoat", 285 | "inputNode": "principled", 286 | "inputSocket": "Clearcoat" 287 | }, 288 | { 289 | "outputNode": "diffuseTexture", 290 | "outputSocket": "Alpha", 291 | "inputNode": "principled", 292 | "inputSocket": "Alpha" 293 | }, 294 | { 295 | "outputNode": "normalMapTexture", 296 | "outputSocket": "Color", 297 | "inputNode": "bumpOrNormal", 298 | "inputSocket": "Color" 299 | }, 300 | { 301 | "outputNode": "bumpOrNormal", 302 | "outputSocket": "Normal", 303 | "inputNode": "noiseBump", 304 | "inputSocket": "Normal" 305 | }, 306 | { 307 | "outputNode": "noise", 308 | "outputSocket": "Fac", 309 | "inputNode": "colorramp", 310 | "inputSocket": "Fac" 311 | }, 312 | { 313 | "outputNode": "colorramp", 314 | "outputSocket": "Color", 315 | "inputNode": "noiseBump", 316 | "inputSocket": "Height" 317 | }, 318 | { 319 | "outputNode": "noiseBump", 320 | "outputSocket": "Normal", 321 | "inputNode": "principled", 322 | "inputSocket": "Normal" 323 | }, 324 | { 325 | "outputNode": "texco", 326 | "outputSocket": "UV", 327 | "inputNode": "noise", 328 | "inputSocket": "Vector" 329 | }, 330 | { 331 | "outputNode": "skinGroupInputs", 332 | "outputSocket": "Pore scale", 333 | "inputNode": "noise", 334 | "inputSocket": "Scale" 335 | }, 336 | { 337 | "outputNode": "skinGroupInputs", 338 | "outputSocket": "Pore detail", 339 | "inputNode": "noise", 340 | "inputSocket": "Detail" 341 | }, 342 | { 343 | "outputNode": "skinGroupInputs", 344 | "outputSocket": "Pore distortion", 345 | "inputNode": "noise", 346 | "inputSocket": "Distortion" 347 | }, 348 | { 349 | "outputNode": "skinGroupInputs", 350 | "outputSocket": "Pore strength", 351 | "inputNode": "noiseBump", 352 | "inputSocket": "Strength" 353 | }, 354 | { 355 | "outputNode": "texco", 356 | "outputSocket": "UV", 357 | "inputNode": "diffuseTexture", 358 | "inputSocket": "Vector" 359 | }, 360 | { 361 | "outputNode": "texco", 362 | "outputSocket": "UV", 363 | "inputNode": "normalMapTexture", 364 | "inputSocket": "Vector" 365 | } 366 | ] 367 | } 368 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/skinOverridePresets/asian.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "colorMixIn": [0.8, 0.3, 0.0, 1.0], 4 | "colorMixInStrength": 0.15, 5 | "Brightness": -0.05, 6 | "Roughness": 0.45 7 | }, 8 | "ears": { 9 | "colorMixIn": [0.8, 0.3, 0.0, 1.0], 10 | "colorMixInStrength": 0.15, 11 | "Brightness": -0.05, 12 | "Roughness": 0.45 13 | }, 14 | "nipple": { 15 | "colorMixIn": [0.8, 0.3, 0.0, 1.0], 16 | "colorMixInStrength": 0.15, 17 | "Brightness": -0.05, 18 | "Roughness": 0.45, 19 | "Pore scale": 1500, 20 | "Pore distortion": 0.5 21 | }, 22 | "lips": { 23 | "colorMixIn": [0.8, 0.3, 0.0, 1.0], 24 | "colorMixInStrength": 0.15, 25 | "Brightness": -0.05, 26 | "Roughness": 0.3, 27 | "Pore scale": 500, 28 | "Pore strength": 0.15, 29 | "Pore detail": 1.0 30 | }, 31 | "fingernails": { 32 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 33 | "colorMixInStrength": 0.2, 34 | "Roughness": 0.2, 35 | "Pore strength": 0.05 36 | }, 37 | "toenails": { 38 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 39 | "colorMixInStrength": 0.2, 40 | "Roughness": 0.2, 41 | "Pore strength": 0.05 42 | }, 43 | "genitals": { 44 | "colorMixIn": [0.8, 0.3, 0.0, 1.0], 45 | "colorMixInStrength": 0.15, 46 | "Brightness": -0.05, 47 | "Roughness": 0.2, 48 | "Pore scale": 1000, 49 | "Pore strength": 0.1 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/skinOverridePresets/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "Roughness": 0.45, 4 | "colorMixInStrength": 0.0 5 | }, 6 | "ears": { 7 | "Roughness": 0.45, 8 | "colorMixInStrength": 0.0 9 | }, 10 | "nipple": { 11 | "Roughness": 0.45, 12 | "Pore scale": 1500, 13 | "Pore distortion": 0.5, 14 | "colorMixInStrength": 0.0 15 | }, 16 | "lips": { 17 | "Roughness": 0.3, 18 | "Pore scale": 500, 19 | "Pore strength": 0.15, 20 | "Pore detail": 1.0, 21 | "colorMixInStrength": 0.0 22 | }, 23 | "fingernails": { 24 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 25 | "colorMixInStrength": 0.2, 26 | "Roughness": 0.2, 27 | "Pore strength": 0.05 28 | }, 29 | "toenails": { 30 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 31 | "colorMixInStrength": 0.2, 32 | "Roughness": 0.2, 33 | "Pore strength": 0.05 34 | }, 35 | "genitals": { 36 | "Roughness": 0.2, 37 | "Pore scale": 1000, 38 | "Pore strength": 0.1, 39 | "colorMixInStrength": 0.0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/skinOverridePresets/pale.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "colorMixIn": [1.0, 0.3, 0.3, 1.0], 4 | "colorMixInStrength": 0.1, 5 | "Brightness": 0.05, 6 | "Roughness": 0.45 7 | }, 8 | "ears": { 9 | "colorMixIn": [1.0, 0.3, 0.3, 1.0], 10 | "colorMixInStrength": 0.1, 11 | "Brightness": 0.05, 12 | "Roughness": 0.45 13 | }, 14 | "nipple": { 15 | "colorMixIn": [1.0, 0.3, 0.3, 1.0], 16 | "colorMixInStrength": 0.1, 17 | "Brightness": 0.05, 18 | "Roughness": 0.45, 19 | "Pore scale": 1500, 20 | "Pore distortion": 0.5 21 | }, 22 | "lips": { 23 | "colorMixIn": [1.0, 0.3, 0.3, 1.0], 24 | "colorMixInStrength": 0.1, 25 | "Brightness": 0.05, 26 | "Roughness": 0.3, 27 | "Pore scale": 500, 28 | "Pore strength": 0.15, 29 | "Pore detail": 1.0 30 | }, 31 | "fingernails": { 32 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 33 | "colorMixInStrength": 0.2, 34 | "Roughness": 0.2, 35 | "Pore strength": 0.05 36 | }, 37 | "toenails": { 38 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 39 | "colorMixInStrength": 0.2, 40 | "Roughness": 0.2, 41 | "Pore strength": 0.05 42 | }, 43 | "genitals": { 44 | "colorMixIn": [1.0, 0.3, 0.3, 1.0], 45 | "colorMixInStrength": 0.1, 46 | "Brightness": 0.05, 47 | "Roughness": 0.2, 48 | "Pore scale": 1000, 49 | "Pore strength": 0.1 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/nodes/skinOverridePresets/tan.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "colorMixIn": [0.138, 0.03, 0.0, 1.0], 4 | "colorMixInStrength": 0.1, 5 | "Brightness": -0.05, 6 | "Roughness": 0.45 7 | }, 8 | "ears": { 9 | "colorMixIn": [0.138, 0.03, 0.0, 1.0], 10 | "colorMixInStrength": 0.1, 11 | "Brightness": -0.05, 12 | "Roughness": 0.45 13 | }, 14 | "nipple": { 15 | "colorMixIn": [0.138, 0.03, 0.0, 1.0], 16 | "colorMixInStrength": 0.1, 17 | "Brightness": -0.05, 18 | "Roughness": 0.45, 19 | "Pore scale": 1500, 20 | "Pore distortion": 0.5 21 | }, 22 | "lips": { 23 | "colorMixIn": [0.138, 0.03, 0.0, 1.0], 24 | "colorMixInStrength": 0.1, 25 | "Brightness": -0.05, 26 | "Roughness": 0.3, 27 | "Pore scale": 500, 28 | "Pore strength": 0.15, 29 | "Pore detail": 1.0 30 | }, 31 | "fingernails": { 32 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 33 | "colorMixInStrength": 0.2, 34 | "Roughness": 0.2, 35 | "Pore strength": 0.05 36 | }, 37 | "toenails": { 38 | "colorMixIn": [1.0, 1.0, 1.0, 1.0], 39 | "colorMixInStrength": 0.2, 40 | "Roughness": 0.2, 41 | "Pore strength": 0.05 42 | }, 43 | "genitals": { 44 | "colorMixIn": [0.138, 0.03, 0.0, 1.0], 45 | "colorMixInStrength": 0.1, 46 | "Brightness": -0.05, 47 | "Roughness": 0.2, 48 | "Pore scale": 1000, 49 | "Pore strength": 0.1 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /blender_source/MH_Community/data/textures/sss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/blender_source/MH_Community/data/textures/sss.png -------------------------------------------------------------------------------- /blender_source/MH_Community/devtools/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .devtools_ui import addDevtoolsToTab, registerDevtoolsConstantsAndSettings 5 | from .printvgroups import MHC_OT_PrintVGroupsOperator 6 | 7 | DEVTOOLS_CLASSES = [ 8 | MHC_OT_PrintVGroupsOperator 9 | ] 10 | __all__ = ["addDevtoolsToTab", "registerDevtoolsConstantsAndSettings","MHC_OT_PrintVGroupsOperator"] 11 | 12 | -------------------------------------------------------------------------------- /blender_source/MH_Community/devtools/devtools_ui.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import BoolProperty, StringProperty, EnumProperty, IntProperty, CollectionProperty, FloatProperty 3 | 4 | def registerDevtoolsConstantsAndSettings(): 5 | pass 6 | 7 | def addDevtoolsToTab(layout, scn): 8 | 9 | devtoolsBox = layout.box() 10 | devtoolsBox.label(text="Developer tools", icon="MESH_DATA") 11 | devtoolsBox.operator("mh_community.print_vertex_groups", text="Dump vgroups") 12 | 13 | -------------------------------------------------------------------------------- /blender_source/MH_Community/devtools/printvgroups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | import pprint 6 | 7 | class MHC_OT_PrintVGroupsOperator(bpy.types.Operator): 8 | """Import a human from MH""" 9 | bl_idname = "mh_community.print_vertex_groups" 10 | bl_label = "Dump vertex groups of active object to /tmp/vgroups.py" 11 | bl_options = {'REGISTER'} 12 | 13 | @classmethod 14 | def poll(cls, context): 15 | obj = context.object 16 | return obj and obj.type == 'MESH' and hasattr(obj, "MhObjectType") 17 | 18 | def execute(self, context): 19 | obj = context.object 20 | objuuid = "basemesh" 21 | 22 | if obj.MhObjectType != "Basemesh": 23 | objuuid = obj.MhProxyUUID 24 | 25 | vgroupsRoot = dict() 26 | vgroupsRoot[objuuid] = dict() 27 | vgroups = vgroupsRoot[objuuid] 28 | 29 | vgIdxToName = dict() 30 | 31 | if obj: 32 | with open("/tmp/vgroups.py","w") as f: 33 | f.write("#!/usr/bin/python\n") 34 | f.write("# -*- coding: utf-8 -*-\n\n") 35 | f.write("vgroupInfo = dict()\n") 36 | vn = "vgroupInfo[\"" + objuuid + "\"]" 37 | f.write(vn + " = dict()\n") 38 | 39 | for vg in obj.vertex_groups: 40 | vgroups[vg.name] = [] 41 | vgIdxToName[vg.index] = vg.name 42 | 43 | for vert in obj.data.vertices: 44 | for groupe in vert.groups: 45 | group = vgIdxToName[groupe.group] 46 | vgroups[group].append(vert.index) 47 | for vgname in vgroups.keys(): 48 | f.write(vn + "[\"" + vgname + "\"] = ") 49 | pprint.pprint(vgroups[vgname],f,width=50000,compact=True) 50 | 51 | else: 52 | self.report({'ERROR'}, "No object") 53 | 54 | self.report({'INFO'}, "Wrote " + objuuid + " to /tmp/vgroups.py") 55 | 56 | return {'FINISHED'} 57 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/JsonCall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import re 4 | import json 5 | import socket 6 | from ..util import showMessageBox 7 | 8 | # Why is the encoding routine even necessary? Why not simply use the 9 | # json.dumps function already available in the imported json library? 10 | # 11 | # Largely, this is because we want to format the floats with eight 12 | # decimals no matter where in the hierarchy they appear, including 13 | # converting strings containing numbers into real numbers. 14 | # 15 | # Also, this way we can avoid accidentally including internal 16 | # makehuman data types (the encoding routine will croak on anything 17 | # that isn't scalar, array or dict) 18 | 19 | DEBUG_JSON = False 20 | 21 | CONNECTION_REFUSED_MESSAGE = '\nCannot connect to MakeHuman App!\n\n' \ 22 | 'Check settings in MPFB and MakeHuman.\n' \ 23 | 'Check security settings of the OS.' 24 | 25 | class JsonCall(): 26 | 27 | 28 | def __init__(self,jsonData = None): 29 | self.params = {} 30 | self.data = None 31 | self.function = "generic" 32 | self.error = "" 33 | self.debug = DEBUG_JSON 34 | 35 | if jsonData: 36 | self.initializeFromJson(jsonData) 37 | 38 | 39 | def initializeFromJson(self,jsonData): 40 | 41 | jsonData = jsonData.replace('\\', '\\\\') # allow windows paths in data 42 | 43 | if DEBUG_JSON: 44 | print("JSON raw string:\n") 45 | print(jsonData) 46 | print("") 47 | 48 | j = json.loads(jsonData) 49 | if not j: 50 | return 51 | self.function = j["function"] 52 | self.error = j["error"] 53 | if j["params"]: 54 | for key in j["params"]: 55 | self.params[key] = j["params"][key] 56 | if j["data"]: 57 | self.data = j["data"] 58 | 59 | 60 | def setData(self,data = ""): 61 | self.data = data 62 | 63 | 64 | def getData(self): 65 | return self.data 66 | 67 | 68 | def setParam(self,name,value): 69 | self.params[name] = value 70 | 71 | 72 | def getParam(self,name): 73 | if not name in self.params: 74 | return None 75 | return self.params[name] 76 | 77 | 78 | def setFunction(self,func): 79 | self.function = func 80 | 81 | 82 | def getFunction(self): 83 | return self.function 84 | 85 | 86 | def setError(self,error): 87 | self.error = error 88 | 89 | 90 | def getError(self): 91 | return self.error 92 | 93 | 94 | def _guessValueType(self,val): 95 | 96 | if val == None: 97 | return "none" 98 | 99 | if self._isDict(val): 100 | return "dict" 101 | 102 | if self._isArray(val): 103 | return "array" 104 | 105 | if self._isNumeric(val): 106 | return "numeric" 107 | 108 | return "string" 109 | 110 | 111 | def _isArray(self,val): 112 | return (hasattr(val, '__len__') and (not isinstance(val, str))) 113 | 114 | 115 | def _isDict(self,val): 116 | return type(val) is dict 117 | 118 | 119 | def _isNumeric(self,val): 120 | if val == None: 121 | return False 122 | if isinstance(val,int): 123 | return True 124 | if isinstance(val,float): 125 | return True 126 | num_format = re.compile("^[\-]?[0-9][0-9]*\.?[0-9]+$") 127 | isnumber = re.match(num_format,str(val)) 128 | return isnumber 129 | 130 | 131 | def _numberAsString(self,val): 132 | if isinstance(val,float): 133 | return "{0:.8f}".format(val) 134 | else: 135 | return str(val) 136 | 137 | 138 | def _dictAsString(self,val): 139 | ret = "{ " 140 | 141 | first = True 142 | 143 | for key in val.keys(): 144 | if first: 145 | first = False 146 | else: 147 | ret = ret + ", " 148 | ret = ret + self.pythonValueToJsonValue(val[key],key) 149 | 150 | return ret + " }" 151 | 152 | 153 | def _arrayAsString(self,array): 154 | ret = "[ " 155 | n = len(array) 156 | for i in range(n): 157 | val = array[i] 158 | ret = ret + self.pythonValueToJsonValue(val) 159 | if i + 1 < n: 160 | ret += "," 161 | return ret + " ]" 162 | 163 | 164 | def pythonValueToJsonValue(self,val,keyName = None): 165 | 166 | out = "" 167 | 168 | if keyName: 169 | out = "\"" + keyName + "\": " 170 | 171 | vType = self._guessValueType(val) 172 | 173 | if val == None: 174 | return out + "null" 175 | 176 | if vType == "dict": 177 | return out + self._dictAsString(val) 178 | 179 | if vType == "array": 180 | return out + self._arrayAsString(val) 181 | 182 | if vType == "numeric": 183 | return out + self._numberAsString(val) 184 | 185 | return out + "\"" + str(val) + "\"" 186 | 187 | 188 | def serialize(self): 189 | ret = "{\n" 190 | ret = ret + " \"function\": \"" + self.function + "\",\n" 191 | ret = ret + " \"error\": \"" + self.error + "\",\n" 192 | ret = ret + " \"params\": {\n" 193 | 194 | first = True 195 | 196 | for key in self.params.keys(): 197 | if not first: 198 | ret = ret + ",\n" 199 | else: 200 | first = False 201 | ret = ret + " " + self.pythonValueToJsonValue(self.params[key],key) 202 | 203 | ret = ret + "\n },\n" 204 | 205 | ret = ret + " " + self.pythonValueToJsonValue(self.data,"data") + "\n}\n" 206 | 207 | if DEBUG_JSON: 208 | print("END RESULT JSON:\n") 209 | print(ret.replace('\\', '\\\\')) 210 | return ret.replace('\\', '\\\\') # allow windows paths in data 211 | 212 | 213 | def send(self, host = "127.0.0.1", port = 12345, expectBinaryResponse = False): 214 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 215 | try: 216 | client.connect((host, port)) 217 | except ConnectionRefusedError: 218 | showMessageBox(message=CONNECTION_REFUSED_MESSAGE, title='CONNECTION REFUSED', icon='ERROR') 219 | return None 220 | client.send(bytes(self.serialize(), 'utf-8')) 221 | 222 | data = None 223 | 224 | if not expectBinaryResponse: 225 | data = "" 226 | while True: 227 | buf = client.recv(1024) 228 | if len(buf) > 0: 229 | data += buf.strip().decode('utf-8') 230 | else: 231 | break 232 | if data: 233 | data = JsonCall(data) 234 | else: 235 | if DEBUG_JSON: 236 | print("Getting binary response") 237 | data = bytearray() 238 | while True: 239 | buf = client.recv(1024) 240 | #print("Got " + str(len(buf)) + " bytes.") 241 | if len(buf) > 0: 242 | data += bytearray(buf) 243 | else: 244 | break 245 | if DEBUG_JSON: 246 | print("Total received length: " + str(len(data))) 247 | 248 | return data 249 | 250 | 251 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Author: Joel Palmius 5 | 6 | if "bpy" in locals(): 7 | print("Reloading sync plug-in") 8 | import imp 9 | imp.reload(sync_ops) 10 | imp.reload(sync_mesh) 11 | imp.reload(sync_pose) 12 | imp.reload(import_body_binary) 13 | imp.reload(directory_ops) 14 | imp.reload(expression_transfer) 15 | imp.reload(shapes_from_pose) 16 | else: 17 | print("Loading sync plug-in") 18 | from . import sync_ops 19 | from . import sync_mesh 20 | from . import sync_pose 21 | from . import import_body_binary 22 | from . import directory_ops 23 | from . import expression_transfer 24 | from . import shapes_from_pose 25 | 26 | import bpy 27 | print("sync plug-in loaded") -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/bone_ui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from bpy.props import BoolProperty, StringProperty, EnumProperty 6 | 7 | destinations = [] 8 | destinations.append( ("SHAPEKEYS", "Shape Keys", "Shapes keys on each mesh. Check for pointless in Information area.", 1) ) 9 | destinations.append( ("POSELIBRARY", "Pose Library", "Write the expression to a Pose library", 2) ) 10 | 11 | def registerBoneConstantsAndSettings(): 12 | # Properties for bone operations 13 | 14 | bpy.types.Scene.mhExprDestination = EnumProperty(items=destinations, name="Destination", description="Whether the resulting expressions are written as \nshape keys or into the current pose library.", default="SHAPEKEYS") 15 | bpy.types.Scene.MhNoLocation = BoolProperty(name="No Location Translation", description="Some Expressions have bone translation on locked bones.\nChecking this causes it to be cleared. When false,\nALT-G will NOT clear these.", default=True) 16 | bpy.types.Scene.MhExprFilterTag = StringProperty(name="Tag", description="This is the tag to search for when getting expressions.\nBlank gets all expressions.", default="") 17 | 18 | 19 | def addBoneUIToTab(layout, scn): 20 | layout.label(text="Bone Operations:", icon="ARMATURE_DATA") 21 | 22 | armSyncBox = layout.box() 23 | armSyncBox.label(text="Skeleton Sync:") 24 | armSyncBox.prop(scn, "MhNoLocation") 25 | armSyncBox.operator("mh_community.sync_pose", text="Sync with MH") 26 | armSyncBox.label(text="Expression Transfer:") 27 | armSyncBox.prop(scn, "mhExprDestination") 28 | armSyncBox.prop(scn, "MhExprFilterTag") 29 | armSyncBox.operator("mh_community.expressions_trans") 30 | 31 | layout.separator() 32 | ampBox = layout.box() 33 | ampBox.label(text="Amputations:") 34 | ampBox.operator("mh_community.amputate_fingers") 35 | ampBox.operator("mh_community.amputate_face") 36 | 37 | layout.separator() 38 | ikBox = layout.box() 39 | ikBox.label(text="IK Rig:") 40 | body = ikBox.row() 41 | body.operator("mh_community.add_ik_rig") 42 | body.operator("mh_community.remove_ik_rig") 43 | 44 | ikBox.label(text="Finger IK Rig:") 45 | finger = ikBox.row() 46 | finger.operator("mh_community.add_finger_rig") 47 | finger.operator("mh_community.remove_finger_rig") -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/directory_ops.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | bl_info = { 5 | "name": "Get MH directories" 6 | } 7 | 8 | from .sync_ops import SyncOperator 9 | 10 | import bpy 11 | 12 | class GetUserDir(SyncOperator): 13 | def __init__(self, readyFunction): 14 | super().__init__('getUserDir') 15 | self.readyFunction = readyFunction 16 | self.executeJsonCall() 17 | 18 | def callback(self,json_obj): 19 | self.readyFunction(json_obj.data) 20 | 21 | class GetSysDir(SyncOperator): 22 | def __init__(self, readyFunction): 23 | super().__init__('getSysDir') 24 | self.readyFunction = readyFunction 25 | self.executeJsonCall() 26 | 27 | def callback(self,json_obj): 28 | self.readyFunction(json_obj.data) -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/expression_transfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | bl_info = { 5 | "name": "Transfer MakeHuman expressions", 6 | "category": "Armature", 7 | } 8 | 9 | from .directory_ops import GetUserDir, GetSysDir 10 | from .sync_pose import SyncPose 11 | from .shapes_from_pose import shapesFromPose 12 | 13 | import bpy 14 | from json import load 15 | import os 16 | import time 17 | 18 | class ExpressionTransfer(): 19 | def __init__(self, operator, skeleton, toShapeKeys, exprFilter): 20 | self.operator = operator 21 | self.skeleton = skeleton 22 | self.toShapeKeys = toShapeKeys 23 | self.exprFilter = exprFilter 24 | 25 | if not self.toShapeKeys: 26 | self.frameNum = len(self.skeleton.pose_library.pose_markers) 27 | GetUserDir(self.UserDirReady) 28 | 29 | def UserDirReady(self, dir): 30 | self.processDirectory(dir) 31 | GetSysDir(self.SysDirReady) 32 | 33 | def SysDirReady(self, dir): 34 | self.processDirectory(dir) 35 | 36 | # get the full path file names of expressions, so that they may be passed 37 | # to later calls to sync pose 38 | def processDirectory(self, baseDir): 39 | expDirectory = os.path.normpath(os.path.join(baseDir, "data/expressions")) 40 | print("dir:" + expDirectory) 41 | sp = SyncPose() 42 | hadWarnings = False 43 | 44 | # go through all in dir to get filename to pass & name to assign to pose 45 | didSomething = False 46 | for fileName in os.listdir(expDirectory): 47 | start = time.time() 48 | filepath = os.path.join(expDirectory, fileName) 49 | # make sure this is not a .thumb file 50 | if ".mhpose" not in filepath: 51 | continue 52 | 53 | with open(filepath, "rU") as file: 54 | expr_data = load(file) 55 | name = expr_data["name"] 56 | 57 | if self.exprFilter is not "": 58 | tagFound = False 59 | for key in expr_data["tags"]: 60 | if key.lower() == self.exprFilter: 61 | tagFound = True 62 | break 63 | 64 | if not tagFound: 65 | continue 66 | 67 | sp.process(filepath, True) 68 | if self.toShapeKeys: 69 | hadWarnings |= shapesFromPose(self.operator, self.skeleton, name) 70 | else: 71 | self.frameNum += 1 72 | bpy.ops.poselib.pose_add(frame=self.frameNum, name=name) 73 | 74 | complete = time.time() 75 | 76 | totalTime = '%4f' % (complete - start) 77 | mhTime = '%4f' % (sp.startCallBack - start) 78 | callbackTime = '%4f' % (sp.callBackComplete - sp.startCallBack) 79 | saveTime = '%4f' % (complete - sp.callBackComplete) 80 | print('total time: ' + totalTime + ', makehuman: ' + mhTime + ', callback: ' + callbackTime + ', save: ' + saveTime) 81 | 82 | didSomething = True 83 | 84 | # write out one last warning, so it shows at bottom 85 | if hadWarnings: self.operator.report({'WARNING'}, 'Some meshes had to be excluded, since their current modifiers change the number of vertices') 86 | 87 | # change back to rest pose; since did something know we are in pose mode 88 | if didSomething: 89 | sp.restoreOriginal() 90 | bpy.ops.pose.select_all(action='SELECT') 91 | bpy.ops.pose.transforms_clear() 92 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/fetch_server_data.py: -------------------------------------------------------------------------------- 1 | from .sync_ops import SyncOperator 2 | 3 | class FetchServerData(SyncOperator): 4 | def __init__(self, functionName, readyFunction, expectBinary=False, params=None): 5 | super().__init__(functionName) 6 | self.readyFunction = readyFunction 7 | self.binary = expectBinary 8 | self.executeJsonCall(expectBinaryResponse=expectBinary, params=params) 9 | 10 | def callback(self, json_obj): 11 | if self.binary: 12 | self.readyFunction(json_obj) 13 | else: 14 | self.readyFunction(json_obj.data) 15 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/import_weighting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | bl_info = { 5 | "name": "Import weighting from MakeHuman", 6 | "category": "Mesh", 7 | } 8 | 9 | import bpy 10 | import bmesh 11 | import pprint 12 | import struct 13 | import itertools 14 | 15 | from mathutils import Matrix, Vector 16 | from .material import * 17 | from .fetch_server_data import FetchServerData 18 | from .import_proxy_binary import ImportProxyBinary 19 | from ..util import profile 20 | 21 | pp = pprint.PrettyPrinter(indent=4) 22 | 23 | class ImportWeighting(): 24 | 25 | def __init__(self, objectToWorkWith, skeletonObject=None, onFinished=None): 26 | 27 | self.myObject = objectToWorkWith 28 | self.skeletonObj = skeletonObject 29 | self.onFinished = onFinished 30 | self.processedVertices = 0 31 | self.debug = False 32 | 33 | if self.debug: profile("start weighting") 34 | 35 | self.isBaseMesh = (self.myObject.MhObjectType == "Basemesh") 36 | 37 | if self.debug: 38 | print("Import weighting for: " + objectToWorkWith.name) 39 | print("isBaseMesh: " + str(self.isBaseMesh)) 40 | 41 | if self.isBaseMesh: 42 | FetchServerData('getBodyWeightInfo', self.gotWeightInfo) 43 | else: 44 | self.uuid = self.myObject.MhProxyUUID 45 | if self.debug: 46 | print("Mesh uuid: " + self.uuid) 47 | FetchServerData('getProxyWeightInfo', self.gotWeightInfo, params={ "uuid": self.uuid }) 48 | 49 | 50 | def gotWeightInfo(self, data): 51 | if self.debug: profile("gotWeightInfo") 52 | 53 | #pp.pprint(data) 54 | assert(not data is None) 55 | self.sumVerts = data["sumVerts"] 56 | self.sumVertListBytes = data["sumVertListBytes"] 57 | self.sumWeightsBytes = data["sumWeightsBytes"] 58 | self.weights = data["weights"] 59 | if self.isBaseMesh: 60 | FetchServerData('getBodyWeightsVertList', self.gotVertListData, expectBinary=True) 61 | else: 62 | FetchServerData('getProxyWeightsVertList', self.gotVertListData, expectBinary=True, params={ "uuid": self.uuid }) 63 | 64 | def gotVertListData(self, data): 65 | 66 | if self.debug: profile("gotVertListData") 67 | 68 | if self.debug: 69 | print("vert list: " + str(len(data)) + " bytes") 70 | self.vertListBytes = bytearray(data) 71 | if self.isBaseMesh: 72 | FetchServerData('getBodyWeights', self.gotWeightsData, expectBinary=True) 73 | else: 74 | FetchServerData('getProxyWeights', self.gotWeightsData, expectBinary=True, params={ "uuid": self.uuid }) 75 | 76 | def gotWeightsData(self, data): 77 | 78 | if self.debug: profile("gotWeightsData") 79 | 80 | if self.debug: 81 | print("weight data: " + str(len(data)) + " bytes") 82 | self.weightBytes = bytearray(data) 83 | for info in self.weights: 84 | self.handleWeight(info) 85 | 86 | if self.debug: profile("weightsHandled") 87 | self.finalize() 88 | 89 | def handleWeight(self, info): 90 | 91 | beforeTime = int(round(time.time() * 1000)) 92 | 93 | boneName = info["bone"] 94 | numVerts = info["numVertices"] 95 | 96 | if self.debug: 97 | print("Handling weights for bone " + boneName + " (" + str(numVerts) + " vertices)") 98 | 99 | vertGroup = self.myObject.vertex_groups.new(name=boneName) 100 | 101 | bytesStart = self.processedVertices * 4 # both vert list and weights come as four bytes per vertex 102 | bytesEnd = self.processedVertices * 4 + numVerts * 4 103 | self.processedVertices = self.processedVertices + numVerts 104 | 105 | currentListBytes = self.vertListBytes[bytesStart:bytesEnd] 106 | currentWeightBytes = self.weightBytes[bytesStart:bytesEnd] 107 | 108 | i = 0 109 | while i < numVerts: 110 | oneWeightBytes = currentWeightBytes[i*4:i*4+4] 111 | oneVertBytes = currentListBytes[i*4:i*4+4] 112 | weight = struct.unpack("f", bytes(oneWeightBytes))[0] 113 | vertNum = struct.unpack("I", bytes(oneVertBytes))[0] 114 | vertGroup.add([vertNum], weight, 'ADD') 115 | i = i + 1 116 | 117 | afterTime = int(round(time.time() * 1000)) 118 | 119 | totalTime = afterTime - beforeTime 120 | 121 | if totalTime > 5: 122 | print("Weighting bone " + boneName + " for " + self.myObject.name + " took " + str(totalTime) + " milliseconds") 123 | 124 | 125 | def finalize(self): 126 | if not self.onFinished is None: 127 | self.onFinished() 128 | 129 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/meshutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | 5 | def convertBufferToShapedNumpyArray(data, typeCode, shape, scaleFactor = None): 6 | numpyRawMesh = np.frombuffer(data, np.dtype(typeCode)) 7 | if scaleFactor is None: 8 | numpyScaledMesh = numpyRawMesh 9 | else: 10 | numpyScaledMesh = np.multiply(numpyRawMesh, scaleFactor) 11 | numpyMesh = numpyScaledMesh.reshape(shape) 12 | return numpyMesh 13 | 14 | def addNumpyArrayAsVerts(bm, numpyMesh, vertCache = None, vertPosCache = None): 15 | iMax = len(numpyMesh) 16 | i = 0 17 | while i < iMax: 18 | # Coordinate order from MH is XZY 19 | x = numpyMesh[i][0] 20 | z = numpyMesh[i][1] 21 | y = numpyMesh[i][2] 22 | 23 | vert = bm.verts.new((x, -y, z)) 24 | vert.index = i 25 | 26 | if not vertCache is None: 27 | vertCache.append(vert) 28 | 29 | if not vertPosCache is None: 30 | vertPosCache[i][0] = x 31 | vertPosCache[i][1] = y 32 | vertPosCache[i][2] = z 33 | 34 | i = i + 1 35 | 36 | def addNumpyArrayAsFaces(bm, numpyMesh, vertCache, faceCache=None, smooth = True): 37 | iMax = len(numpyMesh) 38 | 39 | i = 0 40 | while i < iMax: 41 | 42 | verts = [None, None, None, None] 43 | vertIdxs = numpyMesh[i] 44 | 45 | stride = 0 46 | while stride < 4: 47 | vertIdx = numpyMesh[i][stride] 48 | vert = vertCache[vertIdx] 49 | verts[stride] = vert 50 | stride = stride + 1 51 | 52 | face = bm.faces.new(verts) 53 | face.index = i 54 | face.smooth = smooth 55 | if not faceCache is None: 56 | faceCache.append(face) 57 | i = i + 1 58 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/presets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy, os, json 5 | from bpy.props import BoolProperty, StringProperty, EnumProperty, IntProperty, CollectionProperty, FloatProperty 6 | 7 | _defaultSettings = dict() 8 | _defaultSettings["MhHandleHelper"] = "MASK" 9 | _defaultSettings["MhScaleMode"] = "METER" 10 | _defaultSettings["MhDetailedHelpers"] = False 11 | _defaultSettings["MhAddSimpleMaterials"] = False 12 | _defaultSettings["MhImportWhat"] = "EVERYTHING" 13 | _defaultSettings["MhPrefixProxy"] = True 14 | _defaultSettings["MhMaskBase"] = True 15 | _defaultSettings["MhAddSubdiv"] = True 16 | _defaultSettings["MhSubdivLevels"] = 1 17 | _defaultSettings["MhHandleMaterials"] = "REUSE" 18 | _defaultSettings["MhMaterialObjectName"] = True 19 | _defaultSettings["MhPrefixMaterial"] = True 20 | _defaultSettings["MhFixRoughness"] = True 21 | _defaultSettings["MhHiddenFaces"] = "MASK" 22 | _defaultSettings["MhImportRig"] = True 23 | _defaultSettings["MhRigBody"] = True 24 | _defaultSettings["MhRigClothes"] = True 25 | _defaultSettings["MhRigIsParent"] = True 26 | _defaultSettings["MhAdjustPosition"] = True 27 | _defaultSettings["MhAddCollection"] = True 28 | _defaultSettings["MhSubCollection"] = False 29 | _defaultSettings["MhEnhancedSkin"] = True 30 | _defaultSettings["MhEnhancedSSS"] = False 31 | _defaultSettings["MhUseMakeSkin"] = True 32 | _defaultSettings["MhOnlyBlendMat"] = False 33 | _defaultSettings["MhExtraGroups"] = False 34 | _defaultSettings["MhExtraSlots"] = False 35 | _defaultSettings["MhTweakSlots"] = "NONE" 36 | _defaultSettings["MhHost"] = '127.0.0.1' 37 | _defaultSettings["MhPort"] = 12345 38 | 39 | def _setSettingInUi(settingName, value): 40 | scn = bpy.context.scene 41 | if hasattr(scn, settingName): 42 | print(str(settingName) + " = " + str(value)) 43 | setattr(scn, settingName, value) 44 | 45 | def _readSettingFromUi(settingName): 46 | scn = bpy.context.scene 47 | if hasattr(scn, settingName): 48 | return getattr(scn, settingName) 49 | return "" 50 | 51 | def getSettingsFromUI(scene): 52 | settings = getCleanDefaultSettings() 53 | for key in settings.keys(): 54 | if hasattr(scene,key): 55 | settings[key] = getattr(scene,key) 56 | return settings 57 | 58 | def getCleanDefaultSettings(): 59 | return _defaultSettings.copy() 60 | 61 | def _getCleanMcMtSettings(): 62 | settings = _defaultSettings.copy() 63 | settings["MhScaleMode"] = "DECIMETER" 64 | settings["MhDetailedHelpers"] = True 65 | settings["MhAddSimpleMaterials"] = True 66 | settings["MhAdjustPosition"] = False 67 | settings["MhEnhancedSkin"] = False 68 | settings["MhEnhancedSSS"] = False 69 | return settings 70 | 71 | def getCleanMakeTargetSettings(): 72 | mt = _getCleanMcMtSettings() 73 | mt["MhMaskBase"] = False 74 | mt["MhAddSubdiv"] = False 75 | mt["MhSubdivLevels"] = 0 76 | mt["MhHiddenFaces"] = "NOTHING" 77 | mt["MhImportRig"] = False 78 | mt["MhRigBody"] = False 79 | mt["MhRigClothes"] = False 80 | mt["MhRigIsParent"] = False 81 | mt["MhImportWhat"] = "BODY" 82 | return mt 83 | 84 | def getCleanMakeClothesSettings(): 85 | mc = _getCleanMcMtSettings() 86 | return mc 87 | 88 | def _loadOrCreateSettings(settings, filename): 89 | path = os.path.join(bpy.utils.resource_path('USER'),filename) 90 | print(path) 91 | if os.path.exists(path): 92 | with open(path,'r') as f: 93 | loaded = json.load(f) 94 | #print(loaded) 95 | for key in loaded.keys(): 96 | settings[key] = loaded[key] 97 | else: 98 | with open(path, 'w') as f: 99 | json.dump(settings, f) 100 | return settings 101 | 102 | def loadOrCreateDefaultSettings(): 103 | settings = getCleanDefaultSettings() 104 | settings = _loadOrCreateSettings(settings,"makehuman.default.settings.json") 105 | return settings 106 | 107 | def loadOrCreateMakeTargetSettings(): 108 | settings = getCleanMakeTargetSettings() 109 | settings = _loadOrCreateSettings(settings,"makehuman.maketarget.settings.json") 110 | return settings 111 | 112 | def loadOrCreateMakeClothesSettings(): 113 | settings = getCleanMakeClothesSettings() 114 | settings = _loadOrCreateSettings(settings,"makehuman.makeclothes.settings.json") 115 | return settings 116 | 117 | def saveUISettings(scene, filename): 118 | settings = getSettingsFromUI(scene) 119 | path = os.path.join(bpy.utils.resource_path('USER'), filename) 120 | with open(path, 'w') as f: 121 | json.dump(settings, f) 122 | 123 | def saveDefaultSettings(scene): 124 | saveUISettings(scene, "makehuman.default.settings.json") 125 | 126 | def saveMakeTargetSettings(scene): 127 | saveUISettings(scene, "makehuman.maketarget.settings.json") 128 | 129 | def saveMakeClothesSettings(scene): 130 | saveUISettings(scene, "makehuman.makeclothes.settings.json") 131 | 132 | def applySettings(settings): 133 | for key in settings.keys(): 134 | _setSettingInUi(key,settings[key]) 135 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/shapes_from_pose.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy 3 | 4 | MIN_PCT_CHANGED = 5 5 | MIN_CHANGED = 50 6 | #=============================================================================== 7 | def shapesFromPose(operator, skeleton, shapeName): 8 | nWarnings = 0 9 | scene = bpy.context.scene 10 | meshes = getMeshesForRig(scene, skeleton) 11 | allBones = getAllBones(skeleton) 12 | operator.report({'INFO'}, shapeName + ' stats:') 13 | 14 | for mesh in meshes: 15 | tVerts = len(mesh.data.vertices) 16 | 17 | # delete if key already exists 18 | deleteShape(mesh, shapeName) 19 | 20 | # get temporary version with modifiers applied 21 | depsgraph = bpy.context.evaluated_depsgraph_get() 22 | objectWithModifiers = mesh.evaluated_get(depsgraph) 23 | tmp = objectWithModifiers.to_mesh() 24 | 25 | # need to make sure the number of vertices, like no 'hide faces modifier' 26 | if tVerts != len(tmp.vertices): 27 | operator.report({'WARNING'}, ' ' + mesh.name + ': Had to be skipped, since current modifiers change the number of vertices') 28 | nWarnings += 1 29 | continue 30 | 31 | # add an empty key (create a basis when none) 32 | key = mesh.shape_key_add(name = shapeName, from_mix = False) 33 | key.value = 0 # keep un-applied 34 | 35 | # get basis, so can write only verts different than 36 | basis = mesh.data.shape_keys.key_blocks['Basis'] 37 | 38 | # assign the key the vert values of the current pose, when different than Basis 39 | nDiff = 0 40 | for v in tmp.vertices: 41 | # first pass; exclude verts not influenced by the Bones selected 42 | if not isVertexInfluenced(mesh.vertex_groups, v, allBones) : continue 43 | 44 | value = v.co 45 | baseval = basis.data[v.index].co 46 | if not similar_vertex(value, baseval): 47 | key.data[v.index].co = value 48 | nDiff += 1 49 | 50 | if nDiff > 0: 51 | if 100 * nDiff / tVerts > MIN_PCT_CHANGED or nDiff >= MIN_CHANGED: 52 | operator.report({'INFO'}, ' ' + mesh.name + ': ' + str(nDiff) + ' of ' + str(tVerts)) 53 | else: 54 | operator.report({'WARNING'}, ' ' + mesh.name + ': was skipped since the # of vertices changed was less then ' + str(MIN_PCT_CHANGED) + '%, and also less then the minimum # of ' + str(MIN_CHANGED) + ' (was ' + str(nDiff) + ' )') 55 | mesh.shape_key_remove(key) 56 | else: 57 | # when no verts different, delete key for this mesh 58 | mesh.shape_key_remove(key) 59 | 60 | # remove temp mesh 61 | mesh.to_mesh_clear() 62 | 63 | return nWarnings 64 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 65 | # determine all the meshes which are controlled by skeleton 66 | def getMeshesForRig(scene, skeleton): 67 | meshes = [] 68 | for object in [object for object in scene.objects]: 69 | if object.type == 'MESH' and len(object.vertex_groups) > 0 and skeleton == object.find_armature(): 70 | meshes.append(object) 71 | # ensure that there is a Basis key 72 | if not object.data.shape_keys: 73 | object.shape_key_add(name = 'Basis') 74 | 75 | return meshes 76 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 77 | # This also returns hidden bones, critical for finger shape keys 78 | def getAllBones(skeleton): 79 | vGroupNames = [] 80 | for bone in skeleton.data.bones: 81 | vGroupNames.append(bone.name) 82 | 83 | return vGroupNames 84 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 85 | def deleteShape(mesh, shapeName): 86 | if not mesh.data.shape_keys: 87 | return 88 | 89 | for key_block in mesh.data.shape_keys.key_blocks: 90 | if key_block.name == shapeName: 91 | mesh.shape_key_remove(key_block) 92 | return 93 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 94 | def isVertexInfluenced(mesh_vertex_groups, vertex, allBones): 95 | for group in vertex.groups: 96 | for bone in allBones: 97 | if mesh_vertex_groups[group.group].name == bone: 98 | return True 99 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 100 | def similar_vertex(vertA, vertB, tolerance = 0.00015): 101 | if vertA is None or vertB is None: return False 102 | if (abs(vertA.x - vertB.x) > tolerance or 103 | abs(vertA.y - vertB.y) > tolerance or 104 | abs(vertA.z - vertB.z) > tolerance ): 105 | return False 106 | return True -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/sync_mesh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | bl_info = { 5 | "name": "Synchronize MakeHuman mesh", 6 | "category": "Mesh", 7 | } 8 | 9 | from .sync_ops import SyncOperator 10 | 11 | import bpy 12 | import pprint 13 | 14 | pp = pprint.PrettyPrinter(indent=4) 15 | 16 | class SyncMesh(SyncOperator): 17 | def __init__(self): 18 | super().__init__('getCoord') 19 | self.executeJsonCall() 20 | 21 | def callback(self,json_obj): 22 | 23 | print("Update mesh") 24 | 25 | obj = bpy.context.active_object 26 | print(type(json_obj)) 27 | data = json_obj.data 28 | l = len(data) 29 | print("Length of vertex array in incoming data: " + str(l)) 30 | l2 = len(obj.data.vertices) 31 | print("Length of vertex array in selected object: " + str(l2)) 32 | 33 | i = 0 34 | 35 | while i < l and i < l2: 36 | obj.data.vertices[i].co[0] = data[i][0] 37 | obj.data.vertices[i].co[1] = -data[i][2] 38 | obj.data.vertices[i].co[2] = data[i][1] 39 | i = i + 1 40 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/sync_ops.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | import json 6 | 7 | from .JsonCall import JsonCall 8 | 9 | class SyncOperator: 10 | def __init__(self, operator): 11 | self.call = JsonCall() 12 | self.call.setFunction(operator) 13 | 14 | def executeJsonCall(self, expectBinaryResponse=False, params=None): 15 | if not params is None: 16 | self.call.params = params 17 | json_obj = self.call.send(host=bpy.context.scene.MhHost, port=bpy.context.scene.MhPort, expectBinaryResponse=expectBinaryResponse) 18 | if json_obj: 19 | self.callback(json_obj) 20 | 21 | def callback(self,json_obj): 22 | raise Exception('needs to be overridden by subclass') 23 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mh_sync/sync_pose.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | bl_info = { 5 | "name": "Synchronize MakeHuman armature pose", 6 | "category": "Armature", 7 | } 8 | 9 | from .sync_ops import SyncOperator 10 | from ..rig import RigInfo 11 | 12 | import bpy 13 | from mathutils import Matrix 14 | import time 15 | 16 | FIRST_BONE_NOT_FOR_EXPRESSIONS = 'head' 17 | HEAD_PARENTS = 'head-neck03-neck02-neck01-spine01-spine02-spine03-spine04-spine05-root' 18 | 19 | class SyncPose(SyncOperator): 20 | def __init__(self): 21 | super().__init__('getPose') 22 | 23 | self.skeleton = bpy.context.active_object 24 | self.rigInfo = RigInfo.determineRig(self.skeleton) 25 | self.unitMultplier = self.rigInfo.unitMultplierToExported() / 10 # makehuman is internally in decimeters 26 | 27 | self.bones = self.skeleton.pose.bones 28 | self.haveDots = self.bonesHaveDots() 29 | self.rootBone = self.getRootBone() 30 | 31 | # when allowing translation it is a must that previous rest pose be saved & applied, but always doing 32 | self.restPoses = {} 33 | bpy.ops.object.mode_set(mode='POSE') 34 | bpy.ops.pose.select_all(action='SELECT') 35 | bpy.ops.pose.transforms_clear() 36 | for bone in self.bones: 37 | self.restPoses[bone.name] = bone.matrix 38 | 39 | # self.bonesInOrder = self.getChildBones(self.rootBone) 40 | 41 | def process(self, poseFilename = None, isExpression = False): 42 | if poseFilename is not None: 43 | self.call.setParam("poseFilename", poseFilename) 44 | 45 | self.isExpression = isExpression # used in callback 46 | self.executeJsonCall() 47 | 48 | def callback(self,json_obj): 49 | self.startCallBack = time.time() 50 | 51 | bpy.ops.object.mode_set(mode='POSE') 52 | bpy.ops.pose.select_all(action='SELECT') 53 | bpy.ops.pose.transforms_clear() 54 | 55 | #apply as passed back 56 | for bone in self.bones: 57 | # Could have been exported with a Pose not currently matching MH session. 58 | # For expressions, only set bones with an ancestor of head to avoid extra stuff in pose library. 59 | # Still might work if both are facing forward. 60 | if not self.isExpression or self.hasAncestor(bone, FIRST_BONE_NOT_FOR_EXPRESSIONS) or bone.name in HEAD_PARENTS: 61 | self.apply(bone, json_obj, bpy.context.scene.MhNoLocation) 62 | 63 | if self.isExpression: 64 | self.selectRootToHead(); 65 | bpy.ops.pose.transforms_clear() 66 | self.selectFaceBones() 67 | 68 | self.callBackComplete = time.time() 69 | 70 | def apply(self, bone, json_obj, MhNoLocation): 71 | # the dots in collada exported bone names are replaced with '_', check for data with that changed back 72 | name = bone.name.replace("_", ".") if not self.haveDots else bone.name 73 | 74 | if name in json_obj.data: 75 | matrix = Matrix(json_obj.data[name]) 76 | matrix.translation.x *= self.unitMultplier 77 | matrix.translation.y *= self.unitMultplier 78 | matrix.translation.z *= self.unitMultplier 79 | 80 | # An alternative to just assigning the matrix, which almost works, just here show what was tried 81 | # local = self.skeleton.convert_space(pose_bone = bone, matrix = matrix, from_space = 'WORLD', to_space = 'LOCAL_WITH_PARENT') 82 | # loc, rot, scale = local.decompose() 83 | 84 | # bone.location = loc 85 | # bone.rotation_quaternion = rot 86 | # bone.scale = scale 87 | bone.matrix = matrix 88 | 89 | if MhNoLocation: 90 | bone.location[0] = 0 91 | bone.location[1] = 0 92 | bone.location[2] = 0 93 | 94 | if bpy.app.version < (2, 80, 0): 95 | bpy.context.scene.update() 96 | else: 97 | bpy.context.view_layer.update() 98 | 99 | else: 100 | print(name + ' bone not found coming from MH') 101 | 102 | def bonesHaveDots(self): 103 | for bone in self.bones: 104 | if "." in bone.name: 105 | return True 106 | 107 | return False 108 | 109 | def getRootBone(self): 110 | for bone in self.bones: 111 | if bone.parent is None: 112 | return bone 113 | 114 | # cannot really happen, but 115 | return None 116 | 117 | def restoreOriginal(self): 118 | for bone in self.bones: 119 | bone.matrix = self.restPoses[bone.name] 120 | 121 | if bpy.app.version < (2, 80, 0): 122 | bpy.context.scene.update() 123 | else: 124 | bpy.context.view_layer.update() 125 | 126 | def getChildBones(self, pBone): 127 | ret = [] 128 | # recursively call against all direct children 129 | for bone in self.bones: 130 | if bone.parent is not None and bone.parent.name == pBone.name: 131 | kids = self.getChildBones(bone) 132 | for kid in kids: 133 | ret.append(kid) 134 | ret.append(pBone) 135 | return ret 136 | 137 | def selectRootToHead(self, clearFirst = True): 138 | if clearFirst: 139 | bpy.ops.pose.select_all(action='DESELECT') 140 | 141 | # cannot use pose bones for selections 142 | for bone in self.skeleton.data.bones: 143 | if not self.hasAncestor(bone, FIRST_BONE_NOT_FOR_EXPRESSIONS): 144 | bone.select = True 145 | 146 | def selectFaceBones(self, clearFirst = True): 147 | if clearFirst: 148 | bpy.ops.pose.select_all(action='DESELECT') 149 | 150 | # cannot use pose bones for selections 151 | for bone in self.skeleton.data.bones: 152 | if self.hasAncestor(bone, FIRST_BONE_NOT_FOR_EXPRESSIONS): 153 | bone.select = True 154 | 155 | def hasAncestor(self, poseBone, ancestorName): 156 | while poseBone.parent is not None: 157 | if poseBone.parent.name == ancestorName: 158 | return True 159 | else: 160 | poseBone = poseBone.parent 161 | 162 | return False 163 | 164 | def getRestTranslation(self, bone): 165 | # need to change to edit mode to work with editbones 166 | bpy.ops.object.mode_set(mode='EDIT') 167 | for editBone in self.skeleton.data.edit_bones: 168 | if editBone.name == bone.name: 169 | ret = editBone.matrix.to_translation() 170 | bpy.ops.object.mode_set(mode='POSE') 171 | return ret 172 | 173 | def selectRootToHeadHold(self): 174 | bpy.ops.pose.select_all(action='DESELECT') 175 | headBone = self.skeleton.data.bones[FIRST_BONE_NOT_FOR_EXPRESSIONS] 176 | headBone.select = True 177 | 178 | bpy.ops.pose.select_hierarchy(direction='PARENT') 179 | 180 | def selectFaceBonesHold(self): 181 | bpy.ops.pose.select_all(action='DESELECT') 182 | headBone = self.skeleton.data.bones[FIRST_BONE_NOT_FOR_EXPRESSIONS] 183 | headBone.select = True 184 | 185 | bpy.ops.pose.select_hierarchy(direction='CHILD') 186 | 187 | headBone.select = False 188 | 189 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/__init__.py: -------------------------------------------------------------------------------- 1 | if "bpy" in locals(): 2 | import imp 3 | imp.reload(kinect2) # directory 4 | 5 | imp.reload(animation_buffer) 6 | imp.reload(capture_armature) 7 | imp.reload(empties) 8 | imp.reload(keyframe_reduction) 9 | imp.reload(mocap_ui) 10 | else: 11 | from . import kinect2 # directory 12 | 13 | from . import animation_buffer 14 | from . import capture_armature 15 | from . import empties 16 | from . import keyframe_reduction 17 | from . import mocap_ui 18 | 19 | import bpy -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/animation_buffer.py: -------------------------------------------------------------------------------- 1 | from .capture_armature import * 2 | from ..rig.riginfo import * 3 | 4 | import bpy 5 | #=============================================================================== 6 | class AnimationBuffer: 7 | #=========================================================================== 8 | # Called during sensor capture 9 | #=========================================================================== 10 | def __init__(self, name, firstBody): 11 | self.name = name 12 | self.firstBody = firstBody 13 | self.frameNums = [] 14 | self.joints = [] 15 | self.hands = [] 16 | self.clipPlanes = [] 17 | 18 | self.frame = -1 19 | self.capture = None 20 | 21 | def loadSensorFrame(self, frame, joints, hands, clipPlane): 22 | self.frameNums.append(frame) 23 | self.joints.append(joints) 24 | self.hands.append(hands) 25 | self.clipPlanes.append(clipPlane) 26 | 27 | # shaves a little off of peaks as well, but may make keyframe reduction easier as well 28 | def removeTwitching(self, jointDict): 29 | nTwitches = 0 30 | lastIdx = len(self.frameNums) - 1 # since looking one in future, can only iterate to one less than total 31 | for jointName, parentName in jointDict.items(): 32 | for i in range(1, lastIdx): 33 | prev = self.joints[i - 1][jointName]['location'] 34 | curr = self.joints[i ][jointName]['location'] 35 | next = self.joints[i + 1][jointName]['location'] 36 | 37 | if self.twitched(prev['x'], curr['x'], next['x']): 38 | self.joints[i][jointName]['location']['x'] = prev['x'] 39 | nTwitches += 1 40 | 41 | if self.twitched(prev['y'], curr['y'], next['y']): 42 | self.joints[i][jointName]['location']['y'] = prev['y'] 43 | nTwitches += 1 44 | 45 | if self.twitched(prev['z'], curr['z'], next['z']): 46 | self.joints[i][jointName]['location']['z'] = prev['z'] 47 | nTwitches += 1 48 | 49 | print('number of twitches for ' + self.name + ': ' + str(nTwitches) + ', over ' + str(len(self.frameNums)) + ' frames') 50 | 51 | def twitched(self, prev, curr, next): 52 | if prev < curr and curr > next: return True 53 | if prev > curr and curr < next: return True 54 | return False 55 | 56 | #=========================================================================== 57 | # Called post capture 58 | #=========================================================================== 59 | def assign(self, rigInfo, baseActionName, sensorMappingToBones, sensorJointDict): 60 | current_mode = bpy.context.object.mode 61 | 62 | armature = rigInfo.armature 63 | # when a previous action is assigned, unlink it from https://developer.blender.org/T51011 64 | # or problems like here appear https://blender.stackexchange.com/questions/148456/add-multiple-actions-to-an-armature 65 | if armature.animation_data and armature.animation_data.action: 66 | # save last action, so it does not get removed without user doing something 67 | armature.animation_data.action.use_fake_user = True 68 | 69 | priorAreaType = bpy.context.area.type 70 | bpy.context.area.type = 'DOPESHEET_EDITOR' 71 | bpy.context.space_data.mode = 'ACTION' 72 | bpy.ops.action.unlink() 73 | bpy.context.area.type = priorAreaType 74 | 75 | self.reset() 76 | self.capture = CaptureArmature(rigInfo, sensorMappingToBones, sensorJointDict, self.firstBody) 77 | 78 | for idx, frameNum in enumerate(self.frameNums): 79 | self.capture.assignAndRetargetFrame(self.joints[idx]) 80 | self.insertFrame(rigInfo, sensorMappingToBones, frameNum) 81 | 82 | self.reset() 83 | bpy.context.scene.frame_set(0) # puts at first frame, not last 84 | armature.animation_data.action.name = armature.name + '-' + baseActionName 85 | 86 | bpy.ops.object.mode_set(mode=current_mode) 87 | #=========================================================================== 88 | def reset(self): 89 | if self.capture is not None: 90 | self.capture.cleanUp() 91 | self.capture = None 92 | 93 | self.frame = -1 94 | #=========================================================================== 95 | def insertFrame(self, rigInfo, sensorMappingToBones, frameNum): 96 | armature = rigInfo.armature 97 | for jointName, boneName in sensorMappingToBones.items(): 98 | if boneName is None or boneName not in armature.pose.bones: continue 99 | if bpy.context.scene.MhExcludeFingers and rigInfo.isFinger(boneName): continue 100 | 101 | bone = armature.pose.bones[boneName] 102 | localMat = armature.convert_space(pose_bone = bone, matrix = bone.matrix, from_space = 'POSE', to_space = 'LOCAL') 103 | rot = localMat.to_quaternion() 104 | 105 | bone.rotation_quaternion = rot 106 | bone.keyframe_insert('rotation_quaternion', frame = frameNum, group = boneName) 107 | 108 | # add translation key for root bone 109 | bone = armature.pose.bones[rigInfo.root] 110 | localMat = armature.convert_space(pose_bone = bone, matrix = bone.matrix, from_space = 'POSE', to_space = 'LOCAL') 111 | bone.location = localMat.to_translation() 112 | bone.keyframe_insert('location', frame = frameNum, group = boneName) 113 | 114 | #=========================================================================== 115 | def oneRight(self, rigInfo, sensorMappingToBones, sensorJointDict): 116 | if self.capture is None: 117 | self.capture = CaptureArmature(rigInfo, sensorMappingToBones, sensorJointDict, self.firstBody) 118 | 119 | self.frame = self.frame + 1 if self.frame + 1 < len(self.joints) else 0 120 | 121 | print('--------------------------------------------') 122 | self.capture.assignAndRetargetFrame(self.joints[self.frame]) 123 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/capture_armature.py: -------------------------------------------------------------------------------- 1 | from .empties import * 2 | from ..rig import BoneSurgery, RigInfo 3 | 4 | from math import radians 5 | import bpy 6 | 7 | TMP_SKEL_NAME = 'captureArmature' 8 | #=============================================================================== 9 | class CaptureArmature: 10 | def __init__(self, rigInfo, sensorMappingToBones, sensorJointDict, firstBody): 11 | self.rigInfo = rigInfo 12 | self.retargetTo = rigInfo.armature 13 | self.sensorMappingToBones = sensorMappingToBones 14 | 15 | # need to get these while target skeleton is still the active object 16 | unitMult = self.rigInfo.unitMultplierToExported() 17 | bBoneSz = 0.06 * unitMult 18 | 19 | # target must be at rest before copying 20 | bpy.ops.object.mode_set(mode='POSE') 21 | self.setActiveObject(self.retargetTo) 22 | bpy.ops.pose.select_all(action = 'SELECT') 23 | bpy.ops.pose.transforms_clear() 24 | 25 | bpy.ops.object.mode_set(mode='OBJECT') 26 | self.retargetTo.select_set(True) # THIS is what is required to duplicate, not active 27 | bpy.ops.object.duplicate(linked=False) 28 | 29 | self.captureSkel = bpy.context.active_object 30 | self.captureSkel.name = TMP_SKEL_NAME 31 | BoneSurgery.connectSkeleton(self.captureSkel, False) # actually, False indicates to disconnect everything 32 | 33 | # - - - - - - - - - - - - - - - - - - - - - - 34 | # for better debug, change to b-bone 35 | bpy.ops.object.mode_set(mode='POSE') 36 | bpy.ops.pose.select_all(action = 'SELECT') 37 | if bpy.app.version < (2, 80, 0): 38 | self.captureSkel.data.draw_type = 'BBONE' 39 | else: 40 | self.captureSkel.data.display_type = 'BBONE' 41 | 42 | bpy.ops.transform.transform(mode='BONE_SIZE', value=(bBoneSz, bBoneSz, bBoneSz, 0)) 43 | 44 | self.constraintsAdded = False 45 | self.limitsAdded = False 46 | 47 | self.empties = Empties(RigInfo.determineRig(self.captureSkel), sensorMappingToBones, sensorJointDict, firstBody) 48 | 49 | #=========================================================================== 50 | def addConstraints(self): 51 | # assign copyRotation from captured to retargeted 52 | bpy.ops.object.mode_set(mode='POSE') 53 | for jointName, boneName in self.sensorMappingToBones.items(): 54 | if boneName is None or boneName not in self.retargetTo.pose.bones: continue 55 | 56 | bone = self.retargetTo.pose.bones[boneName] 57 | constraint = bone.constraints.new('COPY_ROTATION') 58 | constraint.target = self.captureSkel 59 | constraint.subtarget = bone.name 60 | 61 | # Any parts of an arm MUST be WORLD to WORLD, since TPose may not be how was imported. 62 | # Where as all vertical bones need to be LOCAL to LOCAL to avoid having to position bones. 63 | # The sensor also can very by temperature / clothes, so it impossible to come up with one. 64 | space = 'WORLD' if self.rigInfo.isArmBone(bone.name) else 'LOCAL' 65 | constraint.owner_space = space 66 | constraint.target_space = space 67 | 68 | constraint.name = 'RETARGET_ROT' 69 | 70 | # also add copy location for root bone, not in sensorMappingToBones 71 | bone = self.retargetTo.pose.bones[self.rigInfo.root] 72 | constraint = bone.constraints.new('COPY_LOCATION') 73 | constraint.target = self.captureSkel 74 | constraint.subtarget = bone.name 75 | constraint.name = 'RETARGET_LOC' 76 | 77 | self.constraintsAdded = True 78 | 79 | #=========================================================================== 80 | def addLimits(self): 81 | bpy.ops.object.mode_set(mode='POSE') 82 | self.addRotationLimit(self.rigInfo.head , -20, 40, -40, 40, -20, 20) 83 | self.addRotationLimit(self.rigInfo.neckBase , -20, 25, -40, 40, -20, 20) 84 | # self.addRotationLimit(self.rigInfo.upperSpine , -20, 30, -40, 40, -20, 20) 85 | # self.addRotationLimit(self.rigInfo.pelvis , -20, 70, -50, 50, -35, 35) 86 | 87 | # self.addRotationLimit(self.rigInfo.clavicle(True ), -20, 20, -20, 20, -10, 10) 88 | # self.addRotationLimit(self.rigInfo.clavicle(False), -20, 20, -20, 20, -10, 10) 89 | 90 | self.addRotationLimit(self.rigInfo.hand(True ) , -20, 20, -60, 50, -50, 50) 91 | self.addRotationLimit(self.rigInfo.hand(False) , -20, 20, -60, 50, -50, 50) 92 | 93 | self.addRotationLimit(self.rigInfo.thumb(True ) , -30, 45, -40, 40, -20, 20) 94 | self.addRotationLimit(self.rigInfo.thumb(False) , -30, 45, -40, 40, -20, 20) 95 | 96 | self.addRotationLimit(self.rigInfo.handTip(True ) , -20, 10, -20, 20, -10, 10) 97 | self.addRotationLimit(self.rigInfo.handTip(False) , -20, 10, -20, 20, -10, 10) 98 | 99 | # self.addRotationLimit(self.rigInfo.thigh(True ) , -90, 50, -30, 30, -30, 10) 100 | # self.addRotationLimit(self.rigInfo.thigh(False) , -90, 50, -30, 30, -10, 30) 101 | 102 | # self.addRotationLimit(self.rigInfo.calf(True ) , -1, 40, -20, 20, -10, 10) 103 | # self.addRotationLimit(self.rigInfo.calf(False) , -1, 40, -20, 20, -10, 10) 104 | 105 | self.addRotationLimit(self.rigInfo.calf(True ) , -.1, 120, 0, 0, 0, 0) 106 | self.addRotationLimit(self.rigInfo.calf(False) , -.1, 120, 0, 0, 0, 0) 107 | 108 | self.addRotationLimit(self.rigInfo.foot(True ) , -5, 10, -1, 1, -5, 5) 109 | self.addRotationLimit(self.rigInfo.foot(False) , -5, 10, -1, 1, -5, 5) 110 | 111 | self.limitsAdded = True 112 | 113 | def addRotationLimit(self, boneName, xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0): 114 | if boneName is None or boneName is not boneName in self.retargetTo.pose.bones: return 115 | 116 | constraint = self.retargetTo.pose.bones[boneName].constraints.new('LIMIT_ROTATION') 117 | constraint.use_transform_limit = True 118 | constraint.owner_space = 'LOCAL' 119 | constraint.name = 'RETARGET_ROT_LIMIT' 120 | 121 | if xMin != 0 or xMax != 0: 122 | constraint.use_limit_x = True 123 | if xMin != 0: 124 | constraint.min_x = radians(xMin) 125 | if xMax != 0: 126 | constraint.max_x = radians(xMax) 127 | 128 | if yMin != 0 or yMax != 0: 129 | constraint.use_limit_y = True 130 | if yMin != 0: 131 | constraint.min_y = radians(yMin) 132 | if yMax != 0: 133 | constraint.max_y = radians(yMax) 134 | 135 | if zMin != 0 or zMax != 0: 136 | constraint.use_limit_z = True 137 | if zMin != 0: 138 | constraint.min_z = radians(zMin) 139 | if zMax != 0: 140 | constraint.max_z = radians(zMax) 141 | #=========================================================================== 142 | # called every frame, after all the empties have been set, which the capture armature contrstrainted by 143 | def assignAndRetargetFrame(self, jointData): 144 | self.empties.assign(jointData) 145 | 146 | if not self.constraintsAdded: 147 | self.update() 148 | 149 | # set the first frame as the rest pose of the captured skeleton 150 | self.setActiveObject(self.captureSkel) 151 | bpy.ops.pose.armature_apply() 152 | 153 | # add the contraints from the captured skeleton to the target skeleton 154 | self.addConstraints() 155 | self.addLimits() 156 | 157 | # get changes applied for the next step 158 | self.update() 159 | #=========================================================================== 160 | # nuke duplicate armature 161 | def cleanUp(self): 162 | # could have been refreshed in a new scene, double check that there is stuff to clean 163 | if TMP_SKEL_NAME not in bpy.data.objects: return 164 | 165 | self.empties.nukeConstraints() 166 | self.empties.nuke() 167 | 168 | bpy.ops.object.mode_set(mode='OBJECT') 169 | objs = bpy.data.objects 170 | objs.remove(objs[TMP_SKEL_NAME], do_unlink = True) 171 | 172 | self.setActiveObject(self.retargetTo) 173 | bpy.ops.object.mode_set(mode='POSE') 174 | for bone in self.retargetTo.pose.bones: 175 | for c in bone.constraints: 176 | if 'RETARGET' in c.name: 177 | bone.constraints.remove(c) 178 | 179 | # return target skeleton to rest pose 180 | bpy.ops.pose.select_all(action = 'SELECT') 181 | bpy.ops.pose.transforms_clear() 182 | 183 | #=========================================================================== 184 | # abstracted for differences between Blender 2.79 & 2.80, nuke eventually 185 | def setActiveObject(self, object): 186 | if bpy.app.version < (2, 80, 0): 187 | bpy.context.scene.objects.active = object 188 | else: 189 | bpy.context.view_layer.objects.active = object 190 | 191 | # abstracted for differences between Blender 2.79 & 2.80, nuke eventually 192 | def update(self): 193 | if bpy.app.version < (2, 80, 0): 194 | bpy.context.scene.update() 195 | else: 196 | bpy.context.view_layer.update() 197 | -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/empties.py: -------------------------------------------------------------------------------- 1 | from .capture_armature import * 2 | from ..rig.riginfo import * 3 | 4 | from mathutils import Vector 5 | import bpy 6 | #=============================================================================== 7 | ARMATURE_BASE = 'ARMATURE_BASE' 8 | class Empties: 9 | def __init__(self, capturedRigInfo, sensorMappingToBones, sensorJointDict, firstBody): 10 | self.capturedRigInfo = capturedRigInfo 11 | self.capturedArmature = capturedRigInfo.armature 12 | self.sensorMappingToBones = sensorMappingToBones 13 | self.sensorJointDict = sensorJointDict 14 | self.firstBody = firstBody 15 | for jointName, parentName in self.sensorJointDict.items(): 16 | if parentName is None: 17 | self.sensorRoot = jointName 18 | break 19 | 20 | self.constraintsApplied = False 21 | 22 | # grab stuff from rig info for scaling & placement 23 | self.pelvisInWorldSpace = capturedRigInfo.pelvisInWorldSpace() 24 | self.rootInWorldSpace = capturedRigInfo.rootInWorldSpace() 25 | 26 | # Add all the empties in OBJECT Mode, one for each bone 27 | # use the Bone list, not bones though, since user could have removed fingers 28 | bpy.ops.object.mode_set(mode='OBJECT') 29 | self.empties = {} 30 | for jointName, parentName in self.sensorJointDict.items(): 31 | self.addEmpty(jointName) 32 | 33 | # add one for the base of the root bone 34 | self.addEmpty(ARMATURE_BASE) 35 | 36 | def addEmpty(self, jointName): 37 | o = bpy.data.objects.new(jointName, None) 38 | 39 | if bpy.app.version < (2, 80, 0): 40 | o.empty_draw_size = 0.1 41 | o.empty_draw_type = 'ARROWS' 42 | o.name = jointName 43 | # o.show_name = True 44 | bpy.context.scene.objects.link(o) # needed to make visible, not really required outside of dev 45 | else: 46 | o.empty_display_size = 0.1 47 | o.empty_display_type = 'PLAIN_AXES' 48 | # o.show_name = True 49 | bpy.context.scene.collection.objects.link(o) # needed to make visible, not really required outside of dev 50 | 51 | self.empties[jointName] = o 52 | 53 | def nuke(self): 54 | objs = bpy.data.objects 55 | for jointName, parentName in self.sensorJointDict.items(): 56 | objs.remove(objs[jointName], do_unlink = True) 57 | 58 | objs.remove(objs[ARMATURE_BASE], do_unlink = True) 59 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 60 | # Location Assignment Methods 61 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 62 | def assign(self, jointData): 63 | if not hasattr(self, 'mult'): self.calibrate(jointData) 64 | 65 | # assign empties based on location 66 | for sensorName in jointData: 67 | if jointData[sensorName]['state'] != 'Not Tracked': 68 | loc = jointData[sensorName]['location'] 69 | self.assignEmpty(sensorName, loc) 70 | 71 | if not self.constraintsApplied: 72 | self.addConstraints() 73 | 74 | def assignEmpty(self, sensorName, loc): 75 | empty = self.empties[sensorName] 76 | 77 | empty.location = Vector((loc['x'], loc['z'], loc['y'])) 78 | 79 | if sensorName == self.sensorRoot: 80 | changeInRootLoc = (empty.location - self.sensorRootBasis) * self.mult 81 | # add-in current location, so recordings can be done when armature raised or moved when recording 82 | # This is only placing the empty, which will then converted back from world to local. 83 | self.empties[ARMATURE_BASE].location = changeInRootLoc + self.rootInWorldSpace 84 | 85 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 86 | # Calibration Methods 87 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 88 | def calibrate(self, jointData): 89 | sensorRootLoc = jointData[self.sensorRoot]['location'] 90 | # since using sensor data, switch y with z 91 | # for non-first bodies origin from sensor is first's; want to keep that 92 | if self.firstBody: 93 | self.sensorRootBasis = Vector((sensorRootLoc['x'], sensorRootLoc['z'], sensorRootLoc['y'])) 94 | else: 95 | self.sensorRootBasis = Vector(( 0, 0, sensorRootLoc['y'])) 96 | 97 | self.mult = self.pelvisInWorldSpace.z / sensorRootLoc['y'] 98 | print('sensor to armature multiplier: ' + str(self.mult) + ', armature pelvis height: ' +str(self.pelvisInWorldSpace.z) + ' over sensors: ' + str(sensorRootLoc['y'])) 99 | 100 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 101 | # Constraints add & removal Methods 102 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 103 | def addConstraints(self): 104 | bpy.ops.object.mode_set(mode='POSE') 105 | bpy.ops.pose.transforms_clear() 106 | 107 | for jointName, parentName in self.sensorJointDict.items(): 108 | if parentName is None: continue 109 | 110 | # bone whose tail is at this joint 111 | boneName = self.sensorMappingToBones[jointName] 112 | if boneName is None or boneName not in self.capturedArmature.pose.bones: continue 113 | 114 | bone = self.capturedArmature.pose.bones[boneName] 115 | 116 | # add a COPY_LOCATION of the empty which is the parent 117 | locConstraint = bone.constraints.new('COPY_LOCATION') 118 | locConstraint.target = self.empties[parentName] 119 | locConstraint.name = 'MOCAP_LOC' 120 | 121 | # add a STRETCH_TO 122 | stretchConstraint = bone.constraints.new('STRETCH_TO') 123 | stretchConstraint.target = self.empties[jointName] 124 | stretchConstraint.name = 'MOCAP_STRETCH' 125 | 126 | # also add copy location for root bone 127 | bone = self.capturedArmature.pose.bones[self.capturedRigInfo.root] 128 | constraint = bone.constraints.new('COPY_LOCATION') 129 | constraint.target = self.empties[ARMATURE_BASE] 130 | constraint.name = 'MOCAP_LOC' 131 | 132 | self.constraintsApplied = True 133 | 134 | # remove any constraints on bones with the name starting with 'MOCAP'. 135 | # no need to check for hands, no constraint will found in this case 136 | def nukeConstraints(self): 137 | bpy.ops.object.mode_set(mode='POSE') 138 | for bone in self.capturedArmature.pose.bones: 139 | for c in bone.constraints: 140 | if c.name == 'MOCAP_LOC' or c.name == 'MOCAP_STRETCH': 141 | bone.constraints.remove(c) 142 | 143 | self.constraintsApplied = False -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/keyframe_reduction.py: -------------------------------------------------------------------------------- 1 | from ..rig.riginfo import * 2 | 3 | import bpy 4 | #=============================================================================== 5 | class KeyFrameReduction(): 6 | 7 | def __init__(self, rigInfo, minRetracementPct): 8 | print('----------------------\nminRetracement%:' + str(minRetracementPct)) 9 | self.armature = rigInfo.armature 10 | self.action = self.armature.animation_data.action 11 | 12 | self.minRetracementRatio = minRetracementPct / 100 13 | 14 | self.keyBones = [] 15 | self.keyBones.append(rigInfo.upperArm( True)) 16 | self.keyBones.append(rigInfo.upperArm(False)) 17 | self.keyBones.append(rigInfo.lowerArm( True)) 18 | self.keyBones.append(rigInfo.lowerArm(False)) 19 | self.keyBones.append(rigInfo.calf ( True)) 20 | self.keyBones.append(rigInfo.calf (False)) 21 | 22 | self.rootBone = rigInfo.root 23 | 24 | self.nSwitches = [] 25 | self.frames = dict() # use dictionary, so frames common amoung bones only listed once 26 | for fcurve in self.action.fcurves: 27 | for key in fcurve.keyframe_points: 28 | frame = key.co.x 29 | self.frames[frame] = True # actual value has no meaning 30 | 31 | self.frames = sorted(self.frames) 32 | self.nFrames = len(self.frames) 33 | 34 | self.nSwitches = [] 35 | for idx in range(self.nFrames): 36 | self.nSwitches.append(0) 37 | 38 | bpy.ops.object.mode_set(mode='POSE') 39 | for boneName in self.keyBones: 40 | values = self.getRotationValuesFor(self.armature.pose.bones[boneName]) 41 | print(self.setReversals(values) + " for " + boneName) 42 | 43 | self.nukeNonKeyFrames() 44 | 45 | def getRotationValuesFor(self, bone): 46 | rotations = [] 47 | for idx in range(self.nFrames): 48 | bpy.context.scene.frame_set(self.frames[idx]) 49 | rotations.append(bone.rotation_quaternion.to_euler("XYZ") if bone.rotation_mode == 'QUATERNION' else bone.rotation_euler) 50 | return rotations 51 | 52 | def nukeNonKeyFrames(self): 53 | # always keep the first frame & last frame 54 | for idx in range(1, self.nFrames - 1): 55 | if self.nSwitches[idx] > 0: continue 56 | 57 | for bone in self.armature.pose.bones: 58 | property = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler' 59 | bone.keyframe_delete(property, -1, self.frames[idx]) 60 | if bone.name == self.rootBone: 61 | bone.keyframe_delete('location', -1, self.frames[idx]) 62 | 63 | def setReversals(self, values): 64 | firstValue = values[0] 65 | secondValue = values[1] 66 | 67 | xUp = secondValue.x - firstValue.x > 0 68 | xWaterMark = secondValue.x 69 | yUp = secondValue.y - firstValue.y > 0 70 | yWaterMark = secondValue.y 71 | zUp = secondValue.z - firstValue.z > 0 72 | zWaterMark = secondValue.z 73 | 74 | thisBones = "" 75 | 76 | for idx in range(2, self.nFrames): 77 | count = 0 78 | value = values[idx] 79 | if xUp: 80 | if xWaterMark < value.x: 81 | xWaterMark = value.x 82 | else: 83 | retracement = xWaterMark - value.x 84 | amountMove = xWaterMark - firstValue.x 85 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 86 | count += 1 87 | xUp = False 88 | else: 89 | if xWaterMark > value.x: 90 | xWaterMark = value.x 91 | else: 92 | retracement = value.x - xWaterMark 93 | amountMove = firstValue.x - xWaterMark 94 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 95 | count += 1 96 | xUp = True 97 | 98 | 99 | if yUp: 100 | if yWaterMark < value.y: 101 | yWaterMark = value.y 102 | else: 103 | retracement = yWaterMark - value.y 104 | amountMove = yWaterMark - firstValue.y 105 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 106 | count += 1 107 | yUp = False 108 | else: 109 | if yWaterMark > value.y: 110 | yWaterMark = value.y 111 | else: 112 | retracement = value.y - yWaterMark 113 | amountMove = firstValue.y - yWaterMark 114 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 115 | count += 1 116 | yUp = True 117 | 118 | 119 | if zUp: 120 | if zWaterMark < value.z: 121 | zWaterMark = value.z 122 | else: 123 | retracement = zWaterMark - value.z 124 | amountMove = zWaterMark - firstValue.z 125 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 126 | count += 1 127 | zUp = False 128 | else: 129 | if zWaterMark > value.z: 130 | zWaterMark = value.z 131 | else: 132 | retracement = value.z - zWaterMark 133 | amountMove = firstValue.z - zWaterMark 134 | if amountMove > 0 and self.minRetracementRatio < retracement / amountMove: 135 | count += 1 136 | zUp = True 137 | 138 | if count > 1: 139 | self.nSwitches[idx] += 1 140 | thisBones += " " + str(self.frames[idx]) 141 | 142 | return thisBones -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/kinect2/KinectToJSON_x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/blender_source/MH_Community/mocap/kinect2/KinectToJSON_x64.dll -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/kinect2/KinectToJSON_x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/blender_source/MH_Community/mocap/kinect2/KinectToJSON_x86.dll -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/kinect2/__init__.py: -------------------------------------------------------------------------------- 1 | if "bpy" in locals(): 2 | print("Reloading kinect Sensor plug-in") 3 | import imp 4 | imp.reload(kinect2_sensor) 5 | else: 6 | print("Loading kinect Sensor plug-in") 7 | from . import kinect2_sensor 8 | 9 | import bpy 10 | print("kinect Sensor plug-in loaded") -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/kinect2/kinect2_sensor.py: -------------------------------------------------------------------------------- 1 | from ..sensor_runtime import * 2 | 3 | from struct import calcsize # needed to figure out whether on 32 or 64 bit Blender 4 | from ctypes import cdll, c_char, c_char_p, c_void_p, CFUNCTYPE 5 | from os import path 6 | from sys import platform 7 | #=============================================================================== 8 | #Dump of file KinectToJSON_x64.dll 9 | # 10 | #File Type: DLL 11 | # 12 | # Section contains the following exports for KinectToJSON_x64.dll 13 | # 14 | # 00000000 characteristics 15 | # FFFFFFFF time date stamp 16 | # 0.00 version 17 | # 1 ordinal base 18 | # 4 number of functions 19 | # 4 number of names 20 | # 21 | # ordinal hint RVA name 22 | # 23 | # 1 0 000115D7 ?beginBodyTracking@@YAJP6AXPEAD@Z@Z = @ILT+1490(?beginBodyTracking@@YAJP6AXPEAD@Z@Z) 24 | # 2 1 000114BA ?closeSensor@@YAXXZ = @ILT+1205(?closeSensor@@YAXXZ) 25 | # 3 2 00011398 ?endBodyTracking@@YAXXZ = @ILT+915(?endBodyTracking@@YAXXZ) 26 | # 4 3 0001109B ?openSensor@@YAJDDD@Z = @ILT+150(?openSensor@@YAJDDD@Z) 27 | 28 | # ordinal seems to be alphabetical; calling by ordinal here 29 | BEGIN_BODY_TRACKING = 1 30 | CLOSE_SENSOR = 2 31 | END_BODY_TRACKING = 3 32 | OPEN_SENSOR = 4 33 | 34 | #=============================================================================== 35 | # all static class. 36 | class Kinect2Sensor(): 37 | DLL = None 38 | LOAD_EXCEPTION = None 39 | callback_func = None # cannot be a local var, or segfault 40 | 41 | JOINTS = { 42 | # keys are kinect joints names coming from the sensor 43 | # values are the "parent" joints 44 | 'SpineBase' : None, 45 | 'SpineMid' : 'SpineBase', 46 | 'SpineShoulder': 'SpineMid', 47 | 48 | 'Neck' : 'SpineShoulder', 49 | 'Head' : 'Neck', 50 | 51 | 'ShoulderLeft' : 'SpineShoulder', 52 | 'ElbowLeft' : 'ShoulderLeft', 53 | 'WristLeft' : 'ElbowLeft', 54 | 'HandLeft' : 'WristLeft', 55 | 'HandTipLeft' : 'HandLeft', 56 | 'ThumbLeft' : 'WristRight', # thumb works better with wrist parent than hand 57 | 58 | 'ShoulderRight': 'SpineShoulder', 59 | 'ElbowRight' : 'ShoulderRight', 60 | 'WristRight' : 'ElbowRight', 61 | 'HandRight' : 'WristRight', 62 | 'HandTipRight' : 'HandRight', 63 | 'ThumbRight' : 'WristRight', # thumb works better with wrist parent than hand 64 | 65 | 'HipLeft' : 'SpineBase', 66 | 'KneeLeft' : 'HipLeft', 67 | 'AnkleLeft' : 'KneeLeft', 68 | 'FootLeft' : 'AnkleLeft', 69 | 70 | 'HipRight' : 'SpineBase', 71 | 'KneeRight' : 'HipRight', 72 | 'AnkleRight' : 'KneeRight', 73 | 'FootRight' : 'AnkleRight', 74 | } 75 | 76 | @staticmethod 77 | def getSensorInfo(): 78 | ret = SensorInfo() 79 | ret.setJointDict(Kinect2Sensor.JOINTS) 80 | ret.setPelvisName('SpineBase') 81 | ret.setAnkleNames('AnkleLeft', 'AnkleRight') 82 | ret.setKneeNames ('KneeLeft' , 'KneeRight') 83 | ret.setWristNames('WristLeft', 'WristRight') 84 | ret.setElbowNames('ElbowLeft', 'ElbowRight') 85 | 86 | return ret 87 | 88 | @staticmethod 89 | def loadLibrary(): 90 | # This is only performed once. Subsequent calls just echo first test, so can be used in UI polling 91 | if Kinect2Sensor.LOAD_EXCEPTION is not None: 92 | return Kinect2Sensor.LOAD_EXCEPTION 93 | 94 | if Kinect2Sensor.DLL is not None: 95 | return None 96 | 97 | # Sensor only works on windows. Keep Linux, others from erroring 98 | if platform != "win32" and platform != "win64": 99 | LOAD_EXCEPTION = 'Kinect2 only works on a Windows operating system.' 100 | return LOAD_EXCEPTION 101 | 102 | # actually try to load the dll, inside try block, in case they failed do to install Kinect runtime re-distributable 103 | try: 104 | is64Bit = calcsize("P") * 8 == 64 # using sys.platform is NOT reliable 105 | fileName = 'KinectToJSON_' + ('x64' if is64Bit else 'x86') + '.dll' 106 | moduleDirectory = path.dirname(__file__) 107 | filepath = path.join(moduleDirectory, fileName) 108 | Kinect2Sensor.DLL = cdll.LoadLibrary(filepath) 109 | print('DLL: ' + fileName + ', loaded from: ' + moduleDirectory) 110 | 111 | except: 112 | LOAD_EXCEPTION = 'DLL: ' + fileName + ', on path: ' + moduleDirectory +', failed to load.\nIs Kinect re-distributable installed?' 113 | return LOAD_EXCEPTION 114 | 115 | # the only way to be sure is to open the sensor & check the result 116 | Kinect2Sensor.DLL[OPEN_SENSOR].argtypes = (c_char, c_char, c_char) 117 | hresult = Kinect2Sensor.DLL[OPEN_SENSOR](c_char('\1'.encode()), c_char('F'.encode()), c_char('W'.encode())) 118 | testWorked = Kinect2Sensor.SUCCEEDED(hresult) 119 | 120 | #when successful, have the side effect of sensor being open. Reverse that 121 | if testWorked: 122 | Kinect2Sensor.DLL[CLOSE_SENSOR]() 123 | return None 124 | else: 125 | LOAD_EXCEPTION = 'Kinect open sensor failed. Is it plugged in & connected?' 126 | return LOAD_EXCEPTION 127 | 128 | @staticmethod 129 | def capture(): 130 | problemMsg = Kinect2Sensor.loadLibrary() 131 | if problemMsg is not None: 132 | return problemMsg 133 | 134 | # prep args & define to call 135 | tPoseStart = c_char('\1'.encode()) # always say true 136 | ForM = c_char('F'.encode()) # forward, not mirror 137 | worldSpace = c_char('W'.encode()) # world space, not camera 138 | Kinect2Sensor.DLL[OPEN_SENSOR].argtypes = (c_char, c_char, c_char) 139 | 140 | hresult = Kinect2Sensor.DLL[OPEN_SENSOR](tPoseStart, ForM, worldSpace) 141 | if not Kinect2Sensor.SUCCEEDED(hresult): 142 | return 'Sensor did not open. Is it still plugged in / connected?' 143 | 144 | # call to start tracking 145 | callback_type = CFUNCTYPE(c_void_p, c_char_p) 146 | Kinect2Sensor.callback_func = callback_type(Kinect2Sensor.bodyReaderCallback) 147 | 148 | Kinect2Sensor.DLL[BEGIN_BODY_TRACKING].argtypes = [c_void_p, c_char_p] 149 | hresult = Kinect2Sensor.DLL[BEGIN_BODY_TRACKING](Kinect2Sensor.callback_func) 150 | if not Kinect2Sensor.SUCCEEDED(hresult): 151 | return 'Error beginning capture.' 152 | 153 | return None 154 | 155 | @staticmethod 156 | def bodyReaderCallback(data): 157 | Sensor.process(data) 158 | 159 | @staticmethod 160 | def close(): 161 | # closeSensor automatically closes body reader in DLL 162 | millisFromFloor = Kinect2Sensor.DLL[CLOSE_SENSOR]() 163 | return millisFromFloor 164 | 165 | # windows dll call return evaluater 166 | @staticmethod 167 | def SUCCEEDED(hresult): 168 | return hresult == 0 -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/mocap_ui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from bpy.props import BoolProperty, EnumProperty, StringProperty, IntProperty, CollectionProperty, FloatProperty 6 | 7 | sensorTypeItems = [] 8 | sensorTypeItems.append( ('KINECT2', 'Kinect2', 'A Kinect2 either from an XBox 1, or from an XBox 360 with the USB adapter kit', 1) ) 9 | 10 | # extra classes to support animation lists 11 | class MHC_UL_AnimationItems(bpy.types.UIList): 12 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 13 | layout.prop(item, "name", text="", emboss=False, translate=False, icon='ARMATURE_DATA') 14 | 15 | class AnimationProps(bpy.types.PropertyGroup): 16 | id: IntProperty() 17 | name: StringProperty() 18 | 19 | #=============================================================================== 20 | def registerMocapConstantsAndSettings(): 21 | bpy.utils.register_class(MHC_UL_AnimationItems) 22 | bpy.utils.register_class(AnimationProps) 23 | 24 | # Properties for mocap operations 25 | bpy.types.Scene.MhSensorType = EnumProperty(items = sensorTypeItems, name = 'Type', description = 'The type of sensor you have connected to your computer', default = 'KINECT2' ) 26 | 27 | bpy.types.Scene.MhSensorCameraHeight = StringProperty(name="Height", description="How high the sensor THINKS it is above floor. Make sure\nthis is close to reality. If not, adjust angle, &\ntry again. Power cycle sensor when moving recommended.") 28 | 29 | bpy.types.Scene.MhSensorAnimations = CollectionProperty(type=AnimationProps) 30 | bpy.types.Scene.MhSensorAnimation_index = IntProperty(default=0) 31 | bpy.types.Scene.MhSensorBaseActionName = StringProperty(name="Action", description="This is the base name of the action to create. To handle multiple bodies, this will be prefixed by armature.", default="untitled") 32 | bpy.types.Scene.MhExcludeFingers = BoolProperty(name="Exclude Fingers", default = False, description="When true, actions will not have key frames for finger & thumb bones") 33 | 34 | bpy.types.Scene.MhReversalMinRetracement = FloatProperty(name='Min % Retracement', default=45, description="The percent of the move to be reversed to qualify as a reversal.") 35 | 36 | def unregisterMocap(): 37 | bpy.utils.unregister_class(AnimationProps) 38 | bpy.utils.unregister_class(MHC_UL_AnimationItems) 39 | bts = bpy.types.Scene 40 | del bts.MhSensorType 41 | del bts.MhSensorCameraHeight 42 | del bts.MhSensorAnimations 43 | del bts.MhSensorAnimation_index 44 | del bts.MhSensorBaseActionName 45 | del bts.MhExcludeFingers 46 | del bts.MhReversalMinRetracement 47 | 48 | MOCAP_DEBUG_OPS = False 49 | 50 | def addMocapUIToTab(layout, scn): 51 | layout.label(text="Sensor Integration:", icon="CAMERA_DATA") 52 | sensorDevice = layout.box() 53 | sensorDevice.label(text="Sensor Device:") 54 | sensorDevice.prop(scn, "MhSensorType") 55 | sensorDevice.operator("mh_community.to_sensor_rig") 56 | 57 | BoxCapture = layout.box() 58 | BoxCapture.label(text="Motion Capture:") 59 | recordBtns = BoxCapture.row() 60 | recordBtns.operator("mh_community.start_mocap", icon="RENDER_ANIMATION") 61 | recordBtns.operator("mh_community.stop_mocap", icon="CANCEL") 62 | results = BoxCapture.row() 63 | results.prop(scn, "MhSensorCameraHeight") 64 | results.enabled = False 65 | 66 | boxAssignment = layout.box() 67 | boxAssignment.label(text="Action Assignment:") 68 | boxAssignment.operator("mh_community.refresh_mocap") 69 | boxAssignment.template_list("MHC_UL_AnimationItems", "", scn, "MhSensorAnimations", scn, "MhSensorAnimation_index") 70 | boxAssignment.prop(scn, "MhSensorBaseActionName") 71 | boxAssignment.prop(scn, "MhExcludeFingers") 72 | boxAssignment.operator("mh_community.assign_mocap") 73 | 74 | actionTrimming = layout.box() 75 | actionTrimming.label(text="Action Trimming:") 76 | cuts = actionTrimming.row() 77 | cuts.operator("mh_community.trim_left") 78 | cuts.operator("mh_community.trim_right") 79 | 80 | actionSmoothing = layout.box() 81 | actionSmoothing.label(text="Key Frame Reduction Smoothing:") 82 | actionSmoothing.prop(scn, "MhReversalMinRetracement") 83 | actionSmoothing.operator("mh_community.keyframe_animation") 84 | 85 | if MOCAP_DEBUG_OPS: 86 | diagnostics = layout.box() 87 | diagnostics.label(text="Diagnostics:") 88 | diagnostics.operator("mh_community.pose_right") -------------------------------------------------------------------------------- /blender_source/MH_Community/mocap/sensor_runtime.py: -------------------------------------------------------------------------------- 1 | from .animation_buffer import * 2 | from ..rig.riginfo import * 3 | 4 | from json import loads 5 | 6 | import bpy 7 | 8 | METERS_TO_INCHES = 39.3701 9 | #=============================================================================== 10 | # all static class. Allows data to be retrieved across .blends 11 | class Sensor(): 12 | # state members for poll() 13 | recording = False 14 | 15 | # static so can be referenced in a static method callback 16 | # temporary holding places until recording complete 17 | sensorType = None 18 | jointDict = None 19 | frame_buffer = None 20 | trackedBodies = None 21 | dumped = False 22 | 23 | # permanent parsed animations by body 24 | animationBuffers = None 25 | 26 | @staticmethod 27 | def beginRecording(sensorType = 'KINECT2'): 28 | Sensor.dumped = False 29 | # initialize temporary stores for returned data 30 | Sensor.frame_buffer = [] 31 | Sensor.trackedBodies = [] 32 | 33 | if sensorType == 'KINECT2': 34 | from .kinect2.kinect2_sensor import Kinect2Sensor 35 | Sensor.sensorInfo = Kinect2Sensor.getSensorInfo() 36 | problemMsg = Kinect2Sensor.capture() 37 | 38 | elif sensorType == 'KINECT_AZURE': 39 | pass 40 | 41 | if problemMsg is not None: 42 | return problemMsg 43 | else: 44 | Sensor.recording = True 45 | Sensor.sensorType = sensorType 46 | return None 47 | 48 | @staticmethod 49 | def process(data): 50 | # dump the first frame to console 51 | if not Sensor.dumped: 52 | print(data.decode('ascii')) 53 | Sensor.dumped = True 54 | 55 | try: 56 | json_obj = loads(data.decode('ascii')) # parse the data into a collection, after converting from binary 57 | 58 | except: 59 | print ('problem in JSON:\n' + data.decode('ascii') + '\n') 60 | return 61 | 62 | for bod in json_obj['bodies']: 63 | thisId = bod['id'] 64 | alreadyFound = False 65 | for id in Sensor.trackedBodies: 66 | if thisId == id: 67 | alreadyFound = True 68 | break 69 | 70 | if not alreadyFound: 71 | Sensor.trackedBodies.append(thisId) 72 | 73 | Sensor.frame_buffer.append(json_obj) 74 | 75 | @staticmethod 76 | def stopRecording(): 77 | if Sensor.sensorType == 'KINECT2': 78 | from .kinect2.kinect2_sensor import Kinect2Sensor 79 | millisFromFloor = Kinect2Sensor.close() 80 | 81 | elif Sensor.sensorType == 'KINECT_AZURE': 82 | return 83 | 84 | inches = METERS_TO_INCHES * millisFromFloor / 1000 85 | bpy.context.scene.MhSensorCameraHeight = ('%.1f' % inches) + ' inches, ' + ('%.3f' % (millisFromFloor / 1000)) + ' meters' 86 | Sensor.recording = False 87 | 88 | maxBodies = len(Sensor.trackedBodies) 89 | nFrames = len(Sensor.frame_buffer) 90 | if maxBodies == 0 or nFrames == 0: 91 | return 'No bodies / frames were captured. No actions created.' 92 | 93 | # not using nFrames, if using multi-bodies, with missing frames due to testing mult-bodies using single person 94 | lastFrame = Sensor.frame_buffer[nFrames - 1]['frame'] 95 | print ('Max bodies: ' + str(maxBodies) + ', n frames: ' + str(nFrames) + ' (0 - ' + str(lastFrame) + ')') 96 | 97 | # empty all previous animations 98 | Sensor.animationBuffers = [] 99 | 100 | for idx, id in enumerate(Sensor.trackedBodies): 101 | # create a animation buffer & add it to the static array 102 | animation = AnimationBuffer('Body ' + str(idx), idx == 0) 103 | Sensor.animationBuffers.append(animation) 104 | 105 | # pull all the data for this body 106 | n = 0 107 | for data in Sensor.frame_buffer: 108 | # check that this body is in this frame, then add frame data 109 | for bod in data['bodies']: 110 | if id == bod['id']: 111 | # hands might not be in data, when not Kinect2 112 | hands = bod['hands'] if 'hands' in bod else [] 113 | animation.loadSensorFrame(data['frame'], bod['joints'], hands, data['floorClipPlane']) 114 | # print('frame ' + str(n) + ', clip plane ' + str(data['floorClipPlane']['w']) + ', root location: ' + str(bod['joints']['SpineBase']['location']['y'])) 115 | break 116 | 117 | animation.removeTwitching(Sensor.sensorInfo.jointDict) 118 | 119 | # update list of recordings 120 | Sensor.displayRecordings() 121 | 122 | # assign scene frame rate, beginning & end frame 123 | bpy.context.scene.render.fps = 30 124 | bpy.context.scene.frame_start = 0 125 | bpy.context.scene.frame_end = lastFrame 126 | 127 | # clear temporary holding spots 128 | Sensor.frame_buffer = None 129 | Sensor.trackedBodies = None 130 | 131 | return None 132 | 133 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 134 | # As Sensor is static class & also holds all data from last session, 135 | # these methods below are used to update body list, assign action, & animate 136 | # one frame at a time, using capture skeleton (diagnostic) 137 | @staticmethod 138 | def displayRecordings(): 139 | # empty all previous list items 140 | collectionProperty = bpy.context.scene.MhSensorAnimations 141 | for i in range(len(collectionProperty) - 1, -1, -1): 142 | collectionProperty.remove(i) 143 | 144 | if Sensor.animationBuffers is None: return 145 | 146 | for i in range(len(Sensor.animationBuffers)): 147 | # add an item to the collection, so shows up in the list 148 | item = collectionProperty.add() 149 | item.id = i 150 | item.name = Sensor.animationBuffers[i].name 151 | 152 | @staticmethod 153 | def assign(rigInfo, idx, baseActionName): 154 | Sensor.animationBuffers[idx].assign(rigInfo, baseActionName, rigInfo.getSensorMapping(Sensor.sensorType), Sensor.sensorInfo.jointDict) 155 | 156 | @staticmethod 157 | def assignIk(rigInfo, idx, baseActionName): 158 | Sensor.animationBuffers[idx].assignIk(rigInfo, baseActionName, Sensor.sensorInfo) 159 | 160 | @staticmethod 161 | def oneRight(rigInfo, idx): 162 | Sensor.animationBuffers[idx].oneRight(rigInfo, rigInfo.getSensorMapping(Sensor.sensorType), Sensor.sensorInfo.jointDict) 163 | 164 | #=============================================================================== 165 | class SensorInfo(): 166 | 167 | def setJointDict(self, value): 168 | self.jointDict = value 169 | 170 | def setPelvisName(self, value): 171 | self.pelvisName = value 172 | 173 | def setAnkleNames(self, left, right): 174 | self.leftAnkleName = left 175 | self.rightAnkleName = right 176 | 177 | def setKneeNames(self, left, right): 178 | self.leftKneeName = left 179 | self.rightKneeName = right 180 | 181 | def setWristNames(self, left, right): 182 | self.leftWristName = left 183 | self.rightWristName = right 184 | 185 | def setElbowNames(self, left, right): 186 | self.leftElbowName = left 187 | self.rightElbowName = right 188 | -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from .actionkeyframereducer import MHC_OT_ActionKeyframeReducerOperator 6 | from .actiontrimleft import MHC_OT_ActionTrimLeftOperator 7 | from .actiontrimright import MHC_OT_ActionTrimRightOperator 8 | from .amputateface import MHC_OT_AmputateFaceOperator 9 | from .amputatefingers import MHC_OT_AmputateFingersOperator 10 | from .bodyimport import MHC_OT_BodyImportOperator 11 | from .expressiontrans import MHC_OT_ExpressionTransOperator 12 | from .mocapassignment import MHC_OT_MocapAssignmentOperator 13 | from .mocaprefresh import MHC_OT_MocapRefreshOperator 14 | from .meshsync import MHC_OT_MeshSyncOperator 15 | from .poseright import MHC_OT_PoseRightOperator 16 | from .posesync import MHC_OT_PoseSyncOperator 17 | from .removefingerrig import MHC_OT_RemoveFingerRigOperator 18 | from .removeikrig import MHC_OT_RemoveIkRigOperator 19 | from .separateeyes import MHC_OT_SeparateEyesOperator 20 | from .snaponfingerrig import MHC_OT_SnapOnFingerRigOperator 21 | from .snaponikrig import MHC_OT_SnapOnIkRigOperator 22 | from .startmocaprecording import MHC_OT_StartMocapRecordingOperator 23 | from .stopmocaprecording import MHC_OT_StopMocapRecordingOperator 24 | from .toSensorRig import MHC_OT_ToSensorRigOperator 25 | from .loadpreset import MHC_OT_LoadPresetOperator 26 | from .savepreset import MHC_OT_SavePresetOperator 27 | from .addsimplematerial import MHC_OT_AddSimpleMaterials 28 | 29 | OPERATOR_CLASSES = ( 30 | MHC_OT_ActionKeyframeReducerOperator, 31 | MHC_OT_ActionTrimLeftOperator, 32 | MHC_OT_ActionTrimRightOperator, 33 | MHC_OT_AmputateFaceOperator, 34 | MHC_OT_AmputateFingersOperator, 35 | MHC_OT_BodyImportOperator, 36 | MHC_OT_ExpressionTransOperator, 37 | MHC_OT_MocapAssignmentOperator, 38 | MHC_OT_MocapRefreshOperator, 39 | MHC_OT_MeshSyncOperator, 40 | MHC_OT_PoseRightOperator, 41 | MHC_OT_PoseSyncOperator, 42 | MHC_OT_RemoveFingerRigOperator, 43 | MHC_OT_RemoveIkRigOperator, 44 | MHC_OT_SeparateEyesOperator, 45 | MHC_OT_SnapOnFingerRigOperator, 46 | MHC_OT_SnapOnIkRigOperator, 47 | MHC_OT_StartMocapRecordingOperator, 48 | MHC_OT_StopMocapRecordingOperator, 49 | MHC_OT_ToSensorRigOperator, 50 | MHC_OT_LoadPresetOperator, 51 | MHC_OT_SavePresetOperator, 52 | MHC_OT_AddSimpleMaterials 53 | ) 54 | 55 | __all__ = [ 56 | 'MHC_OT_ActionKeyframeReducerOperator', 57 | 'MHC_OT_ActionTrimLeftOperator', 58 | 'MHC_OT_ActionTrimRightOperator', 59 | 'MHC_OT_AmputateFaceOperator', 60 | 'MHC_OT_AmputateFingersOperator', 61 | 'MHC_OT_BodyImportOperator', 62 | 'MHC_OT_ExpressionTransOperator', 63 | 'MHC_OT_MocapAssignmentOperator', 64 | 'MHC_OT_MocapRefreshOperator', 65 | 'MHC_OT_MeshSyncOperator', 66 | 'MHC_OT_PoseRightOperator', 67 | 'MHC_OT_PoseSyncOperator', 68 | 'MHC_OT_RemoveFingerRigOperator', 69 | 'MHC_OT_RemoveIkRigOperator', 70 | 'MHC_OT_SeparateEyesOperator', 71 | 'MHC_OT_SnapOnFingerRigOperator', 72 | 'MHC_OT_SnapOnIkRigOperator', 73 | 'MHC_OT_StartMocapRecordingOperator', 74 | 'MHC_OT_StopMocapRecordingOperator', 75 | 'MHC_OT_ToSensorRigOperator', 76 | 'MHC_OT_LoadPresetOperator', 77 | 'MHC_OT_SavePresetOperator', 78 | 'OPERATOR_CLASSES' 79 | ] 80 | -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/actionkeyframereducer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | class MHC_OT_ActionKeyframeReducerOperator(bpy.types.Operator): 8 | """Both smooth and reduce the number of frames by detecting key frames.\n\nDo not do multiple times. Undo, change args, & do again.""" 9 | bl_idname = 'mh_community.keyframe_animation' 10 | bl_label = 'Reduce' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | from ..mocap.keyframe_reduction import KeyFrameReduction 15 | 16 | armature = context.object 17 | problemMsg = None 18 | rigInfo = RigInfo.determineRig(armature) 19 | if rigInfo is None: 20 | problemMsg = 'Unknown rigs are not supported.' 21 | elif not rigInfo.isMocapCapable(): 22 | problemMsg = 'Rig is not capable of motion capture.' 23 | elif rigInfo.hasIKRigs(): 24 | problemMsg = 'Cannot be done while rig has an IK snap-on.' 25 | elif armature.animation_data is None: 26 | problemMsg = 'No current action on rig to keyframe' 27 | 28 | if problemMsg is not None: 29 | self.report({'ERROR'}, problemMsg) 30 | else: 31 | KeyFrameReduction(rigInfo, context.scene.MhReversalMinRetracement) 32 | 33 | return {'FINISHED'} 34 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 | @classmethod 36 | def poll(cls, context): 37 | ob = context.object 38 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/actiontrimleft.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_ActionTrimLeftOperator(bpy.types.Operator): 7 | """Remove all keyframes, of the current action, before the current frame, shifting remaining to the left.\n\nCan be done with any armature based action against any rig.""" 8 | bl_idname = 'mh_community.trim_left' 9 | bl_label = 'Left' 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def execute(self, context): 13 | from ..animation_trimming import AnimationTrimming 14 | 15 | armature = context.object 16 | problemMsg = None 17 | if armature.animation_data is None: 18 | problemMsg = 'No current action on rig to trim' 19 | 20 | if problemMsg is not None: 21 | self.report({'ERROR'}, problemMsg) 22 | else: 23 | trimmer = AnimationTrimming(armature) 24 | trimmer.deleteAndShift() 25 | return {'FINISHED'} 26 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 27 | @classmethod 28 | def poll(cls, context): 29 | ob = context.object 30 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/actiontrimright.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_ActionTrimRightOperator(bpy.types.Operator): 7 | """Remove all keyframes, of the current action, after the current frame.\n\nCan be done with any armature based action against any rig.""" 8 | bl_idname = 'mh_community.trim_right' 9 | bl_label = 'Right' 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def execute(self, context): 13 | from ..animation_trimming import AnimationTrimming 14 | 15 | armature = context.object 16 | problemMsg = None 17 | if armature.animation_data is None: 18 | problemMsg = 'No current action on rig to trim' 19 | 20 | if problemMsg is not None: 21 | self.report({'ERROR'}, problemMsg) 22 | else: 23 | trimmer = AnimationTrimming(armature) 24 | trimmer.dropToRight() 25 | return {'FINISHED'} 26 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 27 | @classmethod 28 | def poll(cls, context): 29 | ob = context.object 30 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/addsimplematerial.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | DEBUG_MODE = True 7 | 8 | HELPER_GROUPS = {'Body': ['body'], 9 | 'Tongue': ['helper-tongue'], 10 | 'Joints': ['JointCubes'], 11 | 'Eyes': ['helper-l-eye', 'helper-r-eye'], 12 | 'Eyelashes': ['helper-l-eyelashes-1', 'helper-l-eyelashes-2', 13 | 'helper-r-eyelashes-1', 'helper-r-eyelashes-2'], 14 | 'Teeth': ['helper-upper-teeth','helper-lower-teeth'], 15 | 'Genitals': ['helper-genital'], 16 | 'Tights': ['helper-tights'], 17 | 'Skirt': ['helper-skirt'], 18 | 'Hair': ['helper-hair'], 19 | 'Ground': ['joint-ground'] 20 | } 21 | 22 | 23 | COLORS = {'Body': (1.0, 1.0, 1.0, 1.0), 24 | 'Tongue': (0.5, 0.0, 0.5, 1.0), 25 | 'Joints': (0.0, 1.0, 0.0, 1.0), 26 | 'Eyes': (0.0, 1.0, 1.0, 1.0), 27 | 'Eyelashes': (1.0, 0.0, 1.0, 1.0), 28 | 'Teeth': (0.0, 0.5, 0.5, 1.0), 29 | 'Genitals': (0.5, 0.0, 1.0, 1.0), 30 | 'Tights': (1.0, 0.0, 0.0, 1.0), 31 | 'Skirt': (0.0, 0.0, 1.0, 1.0), 32 | 'Hair': (1.0, 1.0, 0.0, 1.0), 33 | 'Ground': (1.0, 0.5, 0.5, 1.0)} 34 | 35 | 36 | class MHC_OT_AddSimpleMaterials(bpy.types.Operator): 37 | 38 | bl_idname = 'mh_community.add_simple_materials' 39 | bl_label = 'Add simple material to Helpers' 40 | bl_options = {'REGISTER', 'UNDO'} 41 | 42 | @classmethod 43 | def poll(cls, context): 44 | obj = context.object 45 | # Check if the MhHuman property is True and if the mesh has detailed vertex groups, 46 | # assuming this is True when the joint-ground vertex group exists. 47 | return getattr(obj, 'MhHuman', False) and obj.vertex_groups.find('joint-ground') >= 0 48 | 49 | def execute(self, context): 50 | obj = context.object 51 | 52 | if DEBUG_MODE: 53 | print('\n\n+++ Adding simple materials to helper vertex groups +++\n') 54 | 55 | clearMaterialSlots(obj) 56 | 57 | bpy.ops.object.mode_set(mode='EDIT') 58 | 59 | for name, groups in HELPER_GROUPS.items(): 60 | addMaterial(obj, name, COLORS.get(name, (0.0, 0.0, 0.0, 1.0))) 61 | bpy.ops.mesh.select_all(action='DESELECT') 62 | for group in groups: 63 | vgIdx = obj.vertex_groups.find(group) 64 | if vgIdx >= 0: 65 | obj.vertex_groups.active_index = vgIdx 66 | bpy.ops.object.vertex_group_select() 67 | else: 68 | if DEBUG_MODE: 69 | print(f'Missing vertex group: {group}') 70 | mslotIdx = obj.material_slots.find(name) 71 | if mslotIdx >= 0: 72 | obj.active_material_index = mslotIdx 73 | bpy.ops.object.material_slot_assign() 74 | 75 | bpy.ops.mesh.select_all(action='DESELECT') 76 | bpy.ops.object.mode_set(mode='OBJECT') 77 | 78 | if DEBUG_MODE: 79 | print('\n+++ {FINISHED} +++\n\n') 80 | 81 | return {'FINISHED'} 82 | 83 | 84 | def clearMaterialSlots(obj): 85 | for _ in obj.material_slots.keys(): 86 | bpy.ops.object.material_slot_remove() 87 | 88 | def createMaterial(name: str, color=(0.0, 0.0, 0.0, 1.0)): 89 | if not name in bpy.data.materials: 90 | material = bpy.data.materials.new(name) 91 | material.diffuse_color = color 92 | else: 93 | material = bpy.data.materials.get(name) 94 | if DEBUG_MODE: 95 | print(f'Material already exists {name}') 96 | return material 97 | 98 | def addMaterial(obj, name: str, color=(0.0, 0.0, 0.0, 1.0)): 99 | slotCount = len(obj.material_slots.keys()) 100 | material = createMaterial(name, color) 101 | bpy.ops.object.material_slot_add() 102 | obj.material_slots[slotCount].material = material 103 | -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/amputateface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, BoneSurgery 6 | 7 | class MHC_OT_AmputateFaceOperator(bpy.types.Operator): 8 | """Remove face bones, and assign their weights to head bone""" 9 | bl_idname = "mh_community.amputate_face" 10 | bl_label = "Face" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | self.report({'ERROR'}, 'Rig cannot be identified') 19 | return {'FINISHED'} 20 | 21 | # find all meshes which use this armature 22 | meshes = rigInfo.getMeshesForRig(context.scene) 23 | 24 | # could still have face bones on Kinect2 rig, which has different name, so check by rig 25 | boneName = 'head' if rigInfo.name == 'Default Rig' else 'Head' 26 | BoneSurgery.amputate(armature, meshes, boneName) 27 | 28 | self.report({'INFO'}, 'Amputated fingers to ' + rigInfo.name) 29 | return {'FINISHED'} 30 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 31 | @classmethod 32 | def poll(cls, context): 33 | ob = context.object 34 | if ob is None or ob.type != 'ARMATURE': return False 35 | 36 | # can now assume ob is an armature 37 | rigInfo = RigInfo.determineRig(ob) 38 | return rigInfo is not None and rigInfo.isExpressionCapable() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/amputatefingers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, BoneSurgery 6 | 7 | class MHC_OT_AmputateFingersOperator(bpy.types.Operator): 8 | """Remove finger bones, and assign their weights to hand bone""" 9 | bl_idname = "mh_community.amputate_fingers" 10 | bl_label = "Fingers" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | self.report({'ERROR'}, 'Rig cannot be identified') 19 | return {'FINISHED'} 20 | 21 | # find all meshes which use this armature 22 | meshes = rigInfo.getMeshesForRig(context.scene) 23 | 24 | BoneSurgery.amputate(armature, meshes, rigInfo.hand(True )) 25 | BoneSurgery.amputate(armature, meshes, rigInfo.hand(False)) 26 | 27 | self.report({'INFO'}, 'Amputated fingers to ' + rigInfo.name) 28 | return {'FINISHED'} 29 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 30 | @classmethod 31 | def poll(cls, context): 32 | ob = context.object 33 | if ob is None or ob.type != 'ARMATURE': return False 34 | 35 | # can now assume ob is an armature 36 | rigInfo = RigInfo.determineRig(ob) 37 | return rigInfo is not None and rigInfo.hasFingers() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/bodyimport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_BodyImportOperator(bpy.types.Operator): 7 | """Import a human from MH""" 8 | bl_idname = "mh_community.import_body" 9 | bl_label = "Import body from MH" 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def execute(self, context): 13 | from ..mh_sync.import_body_binary import ImportBodyBinary 14 | ImportBodyBinary() 15 | return {'FINISHED'} -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/expressiontrans.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | class MHC_OT_ExpressionTransOperator(bpy.types.Operator): 8 | """Transfer MakeHuman expressions to a pose library or shapekeys. Requirements:\n\nMust be the Default armature.\nMust have a current Pose library when not going to shapekeys.""" 9 | bl_idname = "mh_community.expressions_trans" 10 | bl_label = "Transfer" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | from ..mh_sync.expression_transfer import ExpressionTransfer 15 | 16 | armature = context.object 17 | 18 | toShapeKeys = context.scene.mhExprDestination == 'SHAPEKEYS' 19 | exprFilter = context.scene.MhExprFilterTag.lower() 20 | ExpressionTransfer(self, armature, toShapeKeys, exprFilter) 21 | return {'FINISHED'} 22 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 23 | @classmethod 24 | def poll(cls, context): 25 | ob = context.object 26 | if ob is None or ob.type != 'ARMATURE': return False 27 | if context.scene.mhExprDestination == 'POSELIBRARY' and not ob.pose_library: return False 28 | 29 | # can now assume ob is an armature with an active pose library 30 | rigInfo = RigInfo.determineRig(ob) 31 | return rigInfo is not None and rigInfo.isExpressionCapable() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/loadpreset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..mh_sync.presets import * 6 | 7 | class MHC_OT_LoadPresetOperator(bpy.types.Operator): 8 | """Load an importer UI preset""" 9 | bl_idname = "mh_community.load_preset" 10 | bl_label = "Load preset" 11 | bl_options = {'REGISTER'} 12 | 13 | def execute(self, context): 14 | what = context.scene.MhGeneralPreset 15 | settings = None 16 | 17 | if what == "DEFAULT": 18 | settings = loadOrCreateDefaultSettings() 19 | if what == "MAKETARGET": 20 | settings = loadOrCreateMakeTargetSettings() 21 | if what == "MAKECLOTHES": 22 | settings = loadOrCreateMakeClothesSettings() 23 | 24 | if settings is None: 25 | self.report({'ERROR'}, "Could not find settings") 26 | return {'FINISHED'} 27 | 28 | applySettings(settings) 29 | 30 | self.report({'INFO'}, "Presets " + what + " loaded") 31 | return {'FINISHED'} 32 | -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/meshsync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_MeshSyncOperator(bpy.types.Operator): 7 | """Synchronize the shape of a human with MH""" 8 | bl_idname = "mh_community.sync_mh_mesh" 9 | bl_label = "Synchronize MH Mesh" 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def execute(self, context): 13 | from ..mh_sync.sync_mesh import SyncMesh 14 | SyncMesh() 15 | return {'FINISHED'} 16 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 | @classmethod 18 | def poll(cls, context): 19 | ob = context.object 20 | return ob and ob.type == 'MESH' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/mocapassignment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | class MHC_OT_MocapAssignmentOperator(bpy.types.Operator): 8 | """Assign an animation to an action of the selected skeleton.""" 9 | bl_idname = 'mh_community.assign_mocap' 10 | bl_label = 'Assign' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | from ..mocap.sensor_runtime import Sensor 15 | 16 | armature = context.object 17 | problemMsg = None 18 | rigInfo = RigInfo.determineRig(armature) 19 | if rigInfo is None: 20 | problemMsg = 'Unknown rigs are not supported.' 21 | elif not rigInfo.isMocapCapable(): 22 | problemMsg = 'Rig is not capable of motion capture.' 23 | elif rigInfo.hasIKRigs(): 24 | problemMsg = 'Cannot be done while rig has an IK snap-on.' 25 | elif len(context.scene.MhSensorAnimations) == 0: 26 | problemMsg = 'No current capture being buffered.' 27 | elif rigInfo.name == 'Default Rig' and not rigInfo.hasRestTpose(): 28 | problemMsg = 'The default rig can only be assigned when it has a rest T-Pose.' 29 | 30 | if problemMsg is not None: 31 | self.report({'ERROR'}, problemMsg) 32 | else: 33 | baseActionName = context.scene.MhSensorBaseActionName 34 | Sensor.assign(rigInfo, context.scene.MhSensorAnimation_index, baseActionName) 35 | return {'FINISHED'} 36 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 37 | @classmethod 38 | def poll(cls, context): 39 | ob = context.object 40 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/mocaprefresh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_MocapRefreshOperator(bpy.types.Operator): 7 | """Re-populate any captures recorded with a previously loaded blend.\nGood for populating multi-character animation across .blend files.""" 8 | bl_idname = 'mh_community.refresh_mocap' 9 | bl_label = 'Refresh List' 10 | bl_options = {'REGISTER'} 11 | 12 | def execute(self, context): 13 | from ..mocap.sensor_runtime import Sensor 14 | 15 | Sensor.displayRecordings() 16 | return {'FINISHED'} -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/poseright.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | 8 | class MHC_OT_PoseRightOperator(bpy.types.Operator): 9 | """This is a diagnostic operator, which poses both the capture & final armatures one frame at a time.""" 10 | bl_idname = 'mh_community.pose_right' 11 | bl_label = 'Next Frame' 12 | bl_options = {'REGISTER', 'UNDO'} 13 | 14 | def execute(self, context): 15 | from ..mocap.sensor_runtime import Sensor 16 | 17 | armature = context.object 18 | problemMsg = None 19 | rigInfo = RigInfo.determineRig(armature) 20 | if rigInfo is None: 21 | problemMsg = 'Unknown rigs are not supported.' 22 | elif not rigInfo.isMocapCapable(): 23 | problemMsg = 'Rig is not capable of motion capture.' 24 | elif len(context.scene.MhSensorAnimations) == 0: 25 | problemMsg = 'No current capture being buffered.' 26 | 27 | if problemMsg is not None: 28 | self.report({'ERROR'}, problemMsg) 29 | else: 30 | Sensor.oneRight(rigInfo, context.scene.MhSensorAnimation_index) 31 | return {'FINISHED'} 32 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 33 | @classmethod 34 | def poll(cls, context): 35 | ob = context.object 36 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/posesync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | class MHC_OT_PoseSyncOperator(bpy.types.Operator): 8 | """Synchronize the pose of the skeleton of a human with MH. Requirements:\n\nMust be the Default armature.\nMust be exported in decimeters to allow location translation.""" 9 | bl_idname = "mh_community.sync_pose" 10 | bl_label = "Synchronize MH Pose" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | from ..mh_sync.sync_pose import SyncPose 15 | 16 | sp = SyncPose() 17 | sp.process() 18 | return {'FINISHED'} 19 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 20 | @classmethod 21 | def poll(cls, context): 22 | ob = context.object 23 | if ob is None or ob.type != 'ARMATURE': return False 24 | 25 | # can now assume ob is an armature 26 | rigInfo = RigInfo.determineRig(ob) 27 | return rigInfo is not None and rigInfo.isPoseCapable() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/removefingerrig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, FingerRig 6 | 7 | class MHC_OT_RemoveFingerRigOperator(bpy.types.Operator): 8 | """Remove the finger IK rig previously added.""" 9 | bl_idname = 'mh_community.remove_finger_rig' 10 | bl_label = '-' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | report({'ERROR'}, 'Rig cannot be identified') 19 | return {'FINISHED'} 20 | 21 | FingerRig(rigInfo).remove() 22 | 23 | self.report({'INFO'}, 'Removed finger IK Rig') 24 | return {'FINISHED'} 25 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 26 | @classmethod 27 | def poll(cls, context): 28 | ob = context.object 29 | if ob is None or ob.type != 'ARMATURE': return False 30 | 31 | # can now assume ob is an armature 32 | rigInfo = RigInfo.determineRig(ob) 33 | return rigInfo is not None and rigInfo.hasFingerIK() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/removeikrig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, IkRig 6 | 7 | class MHC_OT_RemoveIkRigOperator(bpy.types.Operator): 8 | """Remove the IK rig previously added.""" 9 | bl_idname = 'mh_community.remove_ik_rig' 10 | bl_label = '-' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | problemMsg = None 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | problemMsg = 'Unknown rigs are not supported.' 19 | 20 | if problemMsg is not None: 21 | self.report({'ERROR'}, problemMsg) 22 | else: 23 | IkRig(rigInfo).remove() 24 | self.report({'INFO'}, 'Removed IK Rig') 25 | return {'FINISHED'} 26 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 27 | @classmethod 28 | def poll(cls, context): 29 | ob = context.object 30 | if ob is None or ob.type != 'ARMATURE': return False 31 | 32 | # can now assume ob is an armature 33 | rigInfo = RigInfo.determineRig(ob) 34 | 35 | # just need to check IK is there to be removed 36 | return rigInfo is not None and rigInfo.hasIK() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/savepreset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..mh_sync.presets import * 6 | 7 | class MHC_OT_SavePresetOperator(bpy.types.Operator): 8 | """Overwrite the selected preset with the current settings""" 9 | bl_idname = "mh_community.save_preset" 10 | bl_label = "Save preset" 11 | bl_options = {'REGISTER'} 12 | 13 | def execute(self, context): 14 | what = context.scene.MhGeneralPreset 15 | 16 | if what == "DEFAULT": 17 | saveDefaultSettings(context.scene) 18 | self.report({'INFO'}, "Presets " + what + " saved") 19 | return {'FINISHED'} 20 | if what == "MAKETARGET": 21 | saveMakeTargetSettings(context.scene) 22 | self.report({'INFO'}, "Presets " + what + " saved") 23 | return {'FINISHED'} 24 | if what == "MAKECLOTHES": 25 | saveMakeClothesSettings(context.scene) 26 | self.report({'INFO'}, "Presets " + what + " saved") 27 | return {'FINISHED'} 28 | 29 | self.report({'ERROR'}, "Could not find settings") 30 | return {'FINISHED'} 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/separateeyes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | HIPOLY_VERTS = 1064 7 | LOWPOLY_VERTS = 96 8 | class MHC_OT_SeparateEyesOperator(bpy.types.Operator): 9 | """Separate The Eye mesh into left & right meshes, and move origin to center of mass of each.""" 10 | bl_idname = 'mh_community.separate_eyes' 11 | bl_label = 'Separate Eyes' 12 | bl_options = {'REGISTER', 'UNDO'} 13 | 14 | def execute(self, context): 15 | from ..separate_eyes import SeparateEyes 16 | 17 | SeparateEyes(context.object) 18 | return {'FINISHED'} 19 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 20 | @classmethod 21 | def poll(cls, context): 22 | ob = context.object 23 | 24 | # must be a mesh 25 | if not ob or ob.type != 'MESH': 26 | return False 27 | 28 | # vertex count must match 29 | nVerts = len(ob.data.vertices) 30 | return nVerts == HIPOLY_VERTS or nVerts == LOWPOLY_VERTS -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/snaponfingerrig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, FingerRig 6 | 7 | class MHC_OT_SnapOnFingerRigOperator(bpy.types.Operator): 8 | """Snap on finger control bones.\nNote an IK rig is always added with .ik in bones names, regardless of imported with MHX or Collada.""" 9 | bl_idname = 'mh_community.add_finger_rig' 10 | bl_label = '+' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | report({'ERROR'}, 'Rig cannot be identified') 19 | return {'FINISHED'} 20 | 21 | FingerRig(rigInfo).add() 22 | 23 | self.report({'INFO'}, 'Added finger IK Rig to ' + rigInfo.name) 24 | return {'FINISHED'} 25 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 26 | @classmethod 27 | def poll(cls, context): 28 | ob = context.object 29 | if ob is None or ob.type != 'ARMATURE': return False 30 | 31 | # can now assume ob is an armature 32 | rigInfo = RigInfo.determineRig(ob) 33 | if rigInfo is None or not rigInfo.fingerIKCapable(): return False 34 | 35 | # just need to check not already added 36 | return not rigInfo.hasFingerIK() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/snaponikrig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo, IkRig 6 | 7 | class MHC_OT_SnapOnIkRigOperator(bpy.types.Operator): 8 | """Add bones which convert this to an IK Rig\n\nOnly Game or Kinect2 rigs.""" 9 | bl_idname = 'mh_community.add_ik_rig' 10 | bl_label = '+' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | problemMsg = None 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | problemMsg = 'Unknown rigs are not supported.' 19 | elif not rigInfo.IKCapable(): 20 | problemMsg = 'Rig is not capable of having an IK rig.' 21 | 22 | if problemMsg is not None: 23 | self.report({'ERROR'}, problemMsg) 24 | else: 25 | IkRig(rigInfo).add() 26 | self.report({'INFO'}, 'Added IK Rig to ' + rigInfo.name) 27 | return {'FINISHED'} 28 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 29 | @classmethod 30 | def poll(cls, context): 31 | ob = context.object 32 | if ob is None or ob.type != 'ARMATURE': return False 33 | 34 | # can now assume ob is an armature 35 | rigInfo = RigInfo.determineRig(ob) 36 | 37 | # just need to check not already added 38 | return rigInfo is not None and not rigInfo.hasIK() -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/startmocaprecording.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_StartMocapRecordingOperator(bpy.types.Operator): 7 | """Begin a motion capture session.""" 8 | bl_idname = 'mh_community.start_mocap' 9 | bl_label = 'Record' 10 | bl_options = {'REGISTER'} 11 | 12 | def execute(self, context): 13 | from ..mocap.sensor_runtime import Sensor 14 | 15 | device = context.scene.MhSensorType 16 | problemMsg = Sensor.beginRecording(device) 17 | if problemMsg is not None: 18 | self.report({'ERROR'}, problemMsg) 19 | 20 | return {'FINISHED'} 21 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 22 | @classmethod 23 | def poll(cls, context): 24 | from ..mocap.sensor_runtime import Sensor 25 | return not Sensor.recording -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/stopmocaprecording.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class MHC_OT_StopMocapRecordingOperator(bpy.types.Operator): 7 | """Complete a motion capture session.""" 8 | bl_idname = 'mh_community.stop_mocap' 9 | bl_label = 'Stop' 10 | bl_options = {'REGISTER'} 11 | 12 | def execute(self, context): 13 | from ..mocap.sensor_runtime import Sensor 14 | 15 | problemMsg = Sensor.stopRecording() 16 | if problemMsg is not None: 17 | self.report({'ERROR'}, problemMsg) 18 | 19 | return {'FINISHED'} 20 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 21 | @classmethod 22 | def poll(cls, context): 23 | from ..mocap.sensor_runtime import Sensor 24 | return Sensor.recording -------------------------------------------------------------------------------- /blender_source/MH_Community/operators/toSensorRig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from ..rig import RigInfo 6 | 7 | class MHC_OT_ToSensorRigOperator(bpy.types.Operator): 8 | """Transform a default Rig, with or without toes, to one suited for use with the selected device.""" 9 | bl_idname = 'mh_community.to_sensor_rig' 10 | bl_label = 'Custom Rig Conversion' 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | armature = context.object 15 | problemMsg = None 16 | rigInfo = RigInfo.determineRig(armature) 17 | if rigInfo is None: 18 | problemMsg = 'Unknown rigs are not supported.' 19 | elif rigInfo.name != 'Default Rig': 20 | problemMsg = 'Only the default rig can be converted.' 21 | elif rigInfo.hasIKRigs(): 22 | problemMsg = 'Cannot be done while rig has an IK snap-on.' 23 | elif not rigInfo.fingerIKCapable(): 24 | problemMsg = 'Cannot be done after fingers have been amputated' 25 | 26 | if problemMsg is not None: 27 | self.report({'ERROR'}, problemMsg) 28 | else: 29 | sensorType = context.scene.MhSensorType 30 | if sensorType == 'KINECT2': 31 | from ..rig.kinect2riginfo import Kinect2RigInfo 32 | Kinect2RigInfo.convertFromDefault(rigInfo) 33 | 34 | elif device == 'KINECT_AZURE': 35 | pass 36 | 37 | self.report({'INFO'}, 'Converted to a sensor specific rig') 38 | return {'FINISHED'} 39 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 40 | @classmethod 41 | def poll(cls, context): 42 | ob = context.object 43 | return ob is not None and ob.type == 'ARMATURE' -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .riginfo import RigInfo 5 | from .cmuriginfo import CMURigInfo 6 | from .defaultriginfo import DefaultRigInfo 7 | from .gameriginfo import GameRigInfo 8 | from .kinect2riginfo import Kinect2RigInfo 9 | from .bonesurgery import BoneSurgery 10 | from .ikrig import IkRig 11 | from .fingerrig import FingerRig 12 | 13 | __all__ = [ 14 | RigInfo, 15 | CMURigInfo, 16 | DefaultRigInfo, 17 | GameRigInfo, 18 | Kinect2RigInfo, 19 | BoneSurgery, 20 | IkRig, 21 | FingerRig 22 | ] 23 | -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/bonesurgery.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class BoneSurgery: 4 | @staticmethod 5 | def amputate(armature, meshes, sheerBoneName): 6 | bpy.ops.object.mode_set(mode='EDIT') 7 | eBones = armature.data.edit_bones 8 | sheerBone = eBones[sheerBoneName] 9 | 10 | # while still in edit mode select all child bones 11 | bpy.ops.armature.select_all(action='DESELECT') 12 | # could have already been done, or no toes exported 13 | if not BoneSurgery.selectChildBones(sheerBone, False) : return 14 | 15 | # get the names of all the donor bones / vertex groups, before bones deleted 16 | vGroupNames = [] 17 | for editBone in armature.data.edit_bones: 18 | if editBone.select: 19 | vGroupNames.append(editBone.name) 20 | 21 | # while still in edit mode delete all selected bones, mesh vertex groups unchanged so can do afterward 22 | bpy.ops.armature.delete() 23 | 24 | # transfer the weights of the deleted bone onto the sheer point 25 | BoneSurgery.transferVertexGroups(meshes, vGroupNames, sheerBoneName) 26 | 27 | # a new vertex group may have needed to be added, changing active object; set back to armature 28 | # bpy.context.scene.objects.active = armature 29 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 30 | @staticmethod 31 | def deleteBone(armature, meshes, boneName, weightToBoneName, transferTail = False): 32 | bpy.ops.object.mode_set(mode='EDIT') 33 | eBones = armature.data.edit_bones 34 | 35 | # could have already been done 36 | if boneName in eBones: 37 | bone = eBones[boneName] 38 | else: return 39 | 40 | if weightToBoneName is not None: 41 | weightToBone = eBones[weightToBoneName] 42 | 43 | if transferTail: 44 | weightToBone.tail = bone.tail 45 | 46 | # select bone to go & nuke 47 | bpy.ops.armature.select_all(action='DESELECT') 48 | bone.select = True 49 | bpy.ops.armature.delete() 50 | 51 | # transfer the weights of the deleted bone onto the sheer point 52 | if weightToBoneName is not None: 53 | BoneSurgery.transferVertexGroups(meshes, [boneName], weightToBoneName) 54 | 55 | # a new vertex group may have needed to be added, changing active object; set back to armature 56 | # bpy.context.scene.objects.active = armature 57 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 58 | @staticmethod 59 | def transferVertexGroups(meshes, vGroupNames, weightToBoneName): 60 | # VertexGroup.add(), called in transferVertexGroup(), will not work in edit mode 61 | bpy.ops.object.mode_set(mode='OBJECT') 62 | 63 | # assign the verts of the vertex groups of each mesh to the vertex group of the sheer bone 64 | for mesh in meshes: 65 | # find idx of sheer bone vertex group, when mesh participating 66 | weightToBoneVGroupIdx = BoneSurgery.isParticipating(mesh, vGroupNames, weightToBoneName) 67 | if weightToBoneVGroupIdx == -1: continue 68 | 69 | vgroups = mesh.vertex_groups 70 | 71 | for groupName in vGroupNames: 72 | donorGroupIdx = vgroups.find(groupName) 73 | if donorGroupIdx != -1: 74 | BoneSurgery.transferVertexGroup(mesh, donorGroupIdx, weightToBoneVGroupIdx) 75 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 76 | @staticmethod 77 | def isParticipating(mesh, vGroupNames, weightToBoneName): 78 | # if the weightToBone vertex group is already in the mesh, let it go thru as participating 79 | weightToBoneVGroupIdx = mesh.vertex_groups.find(weightToBoneName) 80 | if weightToBoneVGroupIdx != -1: 81 | return weightToBoneVGroupIdx 82 | 83 | # still need to check if there are vertex donor groups, then add group to donate to 84 | vgroups = mesh.vertex_groups 85 | particpating = False 86 | 87 | for groupName in vGroupNames: 88 | donorGroupIdx = vgroups.find(groupName) 89 | if donorGroupIdx != -1: 90 | particpating = True 91 | 92 | # when there is no current vertex group to the weight to bone, need to add an additional vertex group 93 | if particpating: 94 | print("need to add " + weightToBoneName + " group to " + mesh.name) 95 | mesh.vertex_groups.new(name = weightToBoneName) 96 | 97 | # need to re-find the new group 98 | weightToBoneVGroupIdx = mesh.vertex_groups.find(weightToBoneName) 99 | return weightToBoneVGroupIdx 100 | 101 | else: return -1 102 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 103 | @staticmethod 104 | def transferVertexGroup(mesh, donorGroupIdx, weightToBoneVGroupIdx): 105 | weightToBoneVGroup = mesh.vertex_groups[weightToBoneVGroupIdx] 106 | 107 | for vIndex, vertex in enumerate(mesh.data.vertices): 108 | for group in vertex.groups: 109 | if group.group == donorGroupIdx: 110 | weight = group.weight 111 | weightToBoneVGroup.add([vIndex], weight, 'ADD') 112 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 113 | # RECURSIVE 114 | @staticmethod 115 | def selectChildBones(bone, thisBoneToo): 116 | ret = thisBoneToo 117 | 118 | # recursively call this method for each child 119 | for child in bone.children: 120 | BoneSurgery.selectChildBones(child, True) 121 | ret = True 122 | 123 | # select this bone too, if asked 124 | if thisBoneToo: 125 | bone.select = True 126 | 127 | return ret 128 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 129 | @staticmethod 130 | def connectSkeleton(armature, connect = True, exceptions = []): 131 | bpy.ops.object.mode_set(mode='EDIT') 132 | eBones = armature.data.edit_bones 133 | 134 | for bone in eBones: 135 | if bone.parent is not None and bone.name not in exceptions: 136 | bone.use_connect = connect -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/cmuriginfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from .riginfo import RigInfo 6 | 7 | class CMURigInfo (RigInfo): 8 | def __init__(self, armature): 9 | super().__init__(armature, 'CMU Rig', 'LeftUpLeg') 10 | 11 | self.pelvis = 'Hips' 12 | self.root = 'Hips' 13 | self.head = 'Head' 14 | self.neckBase = 'Neck' 15 | self.upperSpine = 'Spine1' 16 | self.kneeIKChainLength = 1 17 | self.footIKChainLength = 2 18 | self.handIKChainLength = 2 19 | self.elbowIKChainLength = 1 20 | 21 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 22 | # for IK rigging & mocap support 23 | def IKCapable(self): return True 24 | def clavicle(self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'Shoulder' 25 | def upperArm(self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'Arm' 26 | def lowerArm(self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'ForeArm' 27 | def hand (self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'Hand' # also used for amputation 28 | def handTip (self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'HandFinger1' # for mocap only, not IK 29 | def thumb (self, isLeft, forMocap = False): return 'LThumb' if isLeft else 'RThumb' # for mocap only, not IK 30 | # - - - 31 | def hip (self, isLeft, forMocap = False): return 'LHipJoint' if isLeft else 'RHipJoint' # for mocap only, not IK 32 | def thigh (self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'UpLeg' 33 | def calf (self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'Leg' # also used by super.hasFeetOnGround() 34 | def foot (self, isLeft, forMocap = False): return ('Left' if isLeft else 'Right') + 'Foot' # also used for super.determineExportedUnits() 35 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 36 | # for Finger rigging support 37 | def fingerIKCapable(self): return False 38 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 39 | # for mocap support 40 | def isMocapCapable(self): return True -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/defaultriginfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from .riginfo import RigInfo 6 | 7 | class DefaultRigInfo (RigInfo): 8 | def __init__(self, armature): 9 | super().__init__(armature, 'Default Rig', 'shoulder01.L') 10 | 11 | self.pelvis = 'spine05' 12 | self.root = 'root' 13 | self.head = 'head' 14 | self.neckBase = 'neck01' 15 | self.upperSpine = 'spine01' 16 | self.kneeIKChainLength = 2 17 | self.footIKChainLength = 4 18 | self.handIKChainLength = 4 19 | self.elbowIKChainLength = 2 20 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 21 | # cannot be static, since dot is only at instance level 22 | def boneFor(self, baseName, isLeft): 23 | return baseName + self.dot + ('L' if isLeft else 'R') 24 | 25 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 26 | # specific to DefaultRigInfo 27 | def hasRestTpose(self): 28 | current_mode = bpy.context.object.mode 29 | bpy.ops.object.mode_set(mode='EDIT') 30 | eBones = self.armature.data.edit_bones 31 | 32 | # test that both sides have wrist Z greater than the head of spine1 head, & are equal 33 | minZ = eBones['spine01'].head.z 34 | left = round(eBones[self.hand(True )].tail.z, 4) 35 | right = round(eBones[self.hand(False)].tail.z, 4) 36 | 37 | bpy.ops.object.mode_set(mode=current_mode) 38 | return left > minZ and left == right 39 | 40 | def clavicle(self, isLeft, forMocap = False): 41 | base = 'shoulder01.' if forMocap else 'clavicle.' 42 | return base + ('L' if isLeft else 'R') 43 | def upperArm(self, isLeft, forMocap = False): 44 | base = 'upperarm01.' if forMocap else 'upperarm02.' 45 | return base + ('L' if isLeft else 'R') 46 | def lowerArm(self, isLeft, forMocap = False): 47 | base = 'lowerarm01.' if forMocap else 'lowerarm02.' 48 | return base + ('L' if isLeft else 'R') 49 | def hip(self, isLeft, forMocap = False): return self.boneFor('pelvis', isLeft) # for mocap only 50 | def thigh(self, isLeft, forMocap = False): 51 | base = 'upperleg01.' if forMocap else 'upperleg02.' 52 | return base + ('L' if isLeft else 'R') 53 | 54 | def _defaultLockInfo(self): 55 | out = {} 56 | out["lockX"] = True 57 | out["lockY"] = False 58 | out["lockZ"] = True 59 | out["limitXMin"] = None 60 | out["limitXMax"] = None 61 | out["limitYMin"] = -20 62 | out["limitYMax"] = 20 63 | out["limitZMin"] = None 64 | out["limitZMax"] = None 65 | return out 66 | 67 | def additionalLocks(self): 68 | 69 | bones = {} 70 | bones["lowerarm02"] = self._defaultLockInfo() 71 | bones["upperarm02"] = self._defaultLockInfo() 72 | bones["lowerleg02"] = self._defaultLockInfo() 73 | bones["upperleg02"] = self._defaultLockInfo() 74 | 75 | out = {} 76 | for key in bones.keys(): 77 | out[key + ".L"] = bones[key] 78 | out[key + ".R"] = bones[key] 79 | 80 | return out 81 | 82 | 83 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 84 | # for IK & mocap rigging support 85 | def IKCapable(self): return True 86 | def hand(self, isLeft, forMocap = False): return self.boneFor('wrist', isLeft) # also used for amputation 87 | def handTip (self, isLeft, forMocap = False): return None # for mocap only, not IK 88 | def thumb (self, isLeft, forMocap = False): return None # for mocap only, not IK 89 | def calf(self, isLeft, forMocap = False): 90 | base = 'lowerleg01.' if forMocap else 'lowerleg02.' 91 | return base + ('L' if isLeft else 'R') 92 | def foot(self, isLeft, forMocap = False): return self.boneFor('foot', isLeft) # used for super.determineExportedUnits() 93 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 94 | # for Finger rigging support 95 | def fingerIKCapable(self): return self.pinkyFingerParent(False) in self.armature.data.bones 96 | def thumbParent(self, isLeft): return self.boneFor('finger1-1', isLeft) 97 | def thumbBones (self, isLeft): 98 | ret = [] 99 | ret.append(self.boneFor('finger1-2', isLeft)) 100 | ret.append(self.boneFor('finger1-3', isLeft)) 101 | return ret 102 | 103 | def indexFingerParent(self, isLeft): return self.boneFor('metacarpal1', isLeft) 104 | def indexFingerBones (self, isLeft): 105 | ret = [] 106 | ret.append(self.boneFor('finger2-1', isLeft)) 107 | ret.append(self.boneFor('finger2-2', isLeft)) 108 | ret.append(self.boneFor('finger2-3', isLeft)) 109 | return ret 110 | 111 | def middleFingerParent(self, isLeft): return self.boneFor('metacarpal2', isLeft) 112 | def middleFingerBones(self , isLeft): 113 | ret = [] 114 | ret.append(self.boneFor('finger3-1', isLeft)) 115 | ret.append(self.boneFor('finger3-2', isLeft)) 116 | ret.append(self.boneFor('finger3-3', isLeft)) 117 | return ret 118 | 119 | def ringFingerParent(self, isLeft): return self.boneFor('metacarpal3', isLeft) 120 | def ringFingerBones(self , isLeft): 121 | ret = [] 122 | ret.append(self.boneFor('finger4-1', isLeft)) 123 | ret.append(self.boneFor('finger4-2', isLeft)) 124 | ret.append(self.boneFor('finger4-3', isLeft)) 125 | return ret 126 | 127 | def pinkyFingerParent(self, isLeft): return self.boneFor('metacarpal4', isLeft) 128 | def pinkyFingerBones(self , isLeft): 129 | ret = [] 130 | ret.append(self.boneFor('finger5-1', isLeft)) 131 | ret.append(self.boneFor('finger5-2', isLeft)) 132 | ret.append(self.boneFor('finger5-3', isLeft)) 133 | return ret 134 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 135 | # for mocap support 136 | def isMocapCapable(self): return True -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/fingerrig.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy 3 | #=============================================================================== 4 | CUSTOM_SHAPE = 'GZM_Knuckle' # name of mesh to use as custom shape if found 5 | #=============================================================================== 6 | class FingerRig(): 7 | def __init__(self, rigInfo): 8 | self.rigInfo = rigInfo 9 | self.armature = self.rigInfo.armature 10 | self.scene = bpy.context.scene 11 | #=============================================================================== 12 | def add(self): 13 | self.buildThumb(True) 14 | self.buildThumb(False) 15 | self.buildFingerPair(2) 16 | self.buildFingerPair(3) 17 | self.buildFingerPair(4) 18 | self.buildFingerPair(5) 19 | 20 | # tell BabylonJS exporter not to export IK bones 21 | if hasattr(self.scene, "ignoreIKBones"): 22 | self.scene.ignoreIKBones = True 23 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 24 | def buildThumb(self, isLeft): 25 | side = 'L' if isLeft else 'R' 26 | 27 | bpy.ops.object.mode_set(mode='EDIT') 28 | eBones = self.armature.data.edit_bones 29 | 30 | parent = eBones[self.rigInfo.thumbParent(isLeft)] 31 | thumbBoneNames = self.rigInfo.thumbBones(isLeft) 32 | 33 | self.build(eBones, 'thumb.ik.' + side, parent, thumbBoneNames, False) 34 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 | def buildFingerPair(self, which): 36 | self.buildFinger(which, True) 37 | self.buildFinger(which, False) 38 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 39 | def buildFinger(self, which, isLeft): 40 | side = 'L' if isLeft else 'R' 41 | 42 | bpy.ops.object.mode_set(mode='EDIT') 43 | eBones = self.armature.data.edit_bones 44 | 45 | if which == 2: 46 | name = 'index.ik.' 47 | parent = eBones[self.rigInfo.indexFingerParent(isLeft)] 48 | fingerBoneNames = self.rigInfo.indexFingerBones(isLeft) 49 | 50 | elif which == 3: 51 | name = 'middle.ik.' 52 | parent = eBones[self.rigInfo.middleFingerParent(isLeft)] 53 | fingerBoneNames = self.rigInfo.middleFingerBones(isLeft) 54 | 55 | elif which == 4: 56 | name = 'ring.ik.' 57 | parent = eBones[self.rigInfo.ringFingerParent(isLeft)] 58 | fingerBoneNames = self.rigInfo.ringFingerBones(isLeft) 59 | 60 | elif which == 5: 61 | name = 'pinky.ik.' 62 | parent = eBones[self.rigInfo.pinkyFingerParent(isLeft)] 63 | fingerBoneNames = self.rigInfo.pinkyFingerBones(isLeft) 64 | 65 | self.build(eBones, name + side, parent, fingerBoneNames, True) 66 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 67 | def build(self, eBones, controlBoneName, parent, fingerBoneNames, hide): 68 | lastIdx = len(fingerBoneNames) - 1 69 | firstFinger = eBones[fingerBoneNames[0 ]] 70 | lastFinger = eBones[fingerBoneNames[lastIdx]] 71 | 72 | controlBone = eBones.new(controlBoneName) 73 | controlBone.head = firstFinger.head.copy() 74 | controlBone.tail = lastFinger .tail.copy() 75 | controlBone.parent = parent 76 | 77 | # make changes that must be done in pose mode 78 | bpy.ops.object.mode_set(mode='POSE') 79 | controlPBone = self.armature.pose.bones[controlBoneName] 80 | controlPBone.lock_location[0] = True 81 | controlPBone.lock_location[1] = True 82 | controlPBone.lock_location[2] = True 83 | 84 | # apply custom_shape to the pose bone version 85 | if CUSTOM_SHAPE in self.scene.objects: 86 | controlPBone.custom_shape = self.scene.objects [CUSTOM_SHAPE] 87 | 88 | for bIndex, boneName in enumerate(fingerBoneNames): 89 | self.addCopyRotation(boneName, controlBoneName, bIndex == 0) 90 | if hide: 91 | self.armature.data.bones[boneName].hide = True 92 | 93 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 94 | def addCopyRotation(self, boneName, subtargetName, use_z): 95 | # apply constraints to the pose bone version 96 | bpy.ops.object.mode_set(mode='POSE') 97 | pBones = self.armature.pose.bones 98 | 99 | pBone = pBones[boneName] 100 | con = pBone.constraints.new('COPY_ROTATION') 101 | con.target = self.armature 102 | con.subtarget = subtargetName 103 | con.use_y = False 104 | con.use_z = use_z 105 | con.use_offset = True 106 | con.target_space = 'LOCAL' 107 | con.owner_space = 'LOCAL' 108 | #=============================================================================== 109 | def remove(self): 110 | self.removeSide(True) 111 | self.removeSide(False) 112 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 113 | def removeSide(self, isLeft): 114 | side = 'L' if isLeft else 'R' 115 | self.demolish('thumb.ik.' + side, self.rigInfo.thumbBones (isLeft)) 116 | self.demolish('index.ik.' + side, self.rigInfo.indexFingerBones (isLeft)) 117 | self.demolish('middle.ik.' + side, self.rigInfo.middleFingerBones(isLeft)) 118 | self.demolish('ring.ik.' + side, self.rigInfo.ringFingerBones (isLeft)) 119 | self.demolish('pinky.ik.' + side, self.rigInfo.pinkyFingerBones (isLeft)) 120 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 121 | # no need of BoneSurgery module, since no weights to give back 122 | def demolish(self, controlBoneName, fingerBoneNames): 123 | bpy.ops.object.mode_set(mode='EDIT') 124 | bpy.ops.armature.select_all(action='DESELECT') 125 | self.armature.data.edit_bones[controlBoneName].select = True 126 | bpy.ops.armature.delete() 127 | 128 | bpy.ops.object.mode_set(mode='POSE') 129 | for bIndex, boneName in enumerate(fingerBoneNames): 130 | self.armature.data.bones[boneName].select = True 131 | self.armature.data.bones[boneName].hide = False 132 | 133 | for bone in bpy.context.selected_pose_bones: 134 | # Create a list of all the copy location constraints on this bone 135 | copyRotConstraints = [ c for c in bone.constraints if c.type == 'COPY_ROTATION' ] 136 | 137 | # Iterate over all the bone's copy location constraints and delete them all 138 | for c in copyRotConstraints: 139 | bone.constraints.remove( c ) # Remove constraint -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/gameriginfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | from . import RigInfo 6 | 7 | class GameRigInfo (RigInfo): 8 | def __init__(self, armature): 9 | super().__init__(armature, 'Game Rig', 'ball_r') 10 | 11 | self.pelvis = 'pelvis' 12 | self.root = 'Root' 13 | self.head = 'head' 14 | self.neckBase = 'neck_01' 15 | self.upperSpine = 'spine_03' 16 | self.kneeIKChainLength = 1 17 | self.footIKChainLength = 2 18 | self.handIKChainLength = 2 19 | self.elbowIKChainLength = 1 20 | 21 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 22 | # for IK rigging & mocap support 23 | def IKCapable(self): return True 24 | def clavicle(self, isLeft, forMocap = False): return 'clavicle_' + ('l' if isLeft else 'r') 25 | def upperArm(self, isLeft, forMocap = False): return 'upperarm_' + ('l' if isLeft else 'r') 26 | def lowerArm(self, isLeft, forMocap = False): return 'lowerarm_' + ('l' if isLeft else 'r') 27 | def hand (self, isLeft, forMocap = False): return 'hand_' + ('l' if isLeft else 'r') # also used for amputation 28 | def handTip (self, isLeft, forMocap = False): return None # for mocap only, not IK 29 | def thumb (self, isLeft, forMocap = False): return None # for mocap only, not IK 30 | # - - - 31 | def hip (self, isLeft, forMocap = False): return None # for mocap only, not IK 32 | def thigh (self, isLeft, forMocap = False): return 'thigh_' + ('l' if isLeft else 'r') 33 | def calf (self, isLeft, forMocap = False): return 'calf_' + ('l' if isLeft else 'r') # also used by super.hasFeetOnGround() 34 | def foot (self, isLeft, forMocap = False): return 'foot_' + ('l' if isLeft else 'r') # also used for super.determineExportedUnits() 35 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 36 | # for Finger rigging support 37 | def fingerIKCapable(self): return self.pinkyFingerParent(False) in self.armature.data.bones 38 | def thumbParent(self, isLeft): return 'thumb_01_' + ('l' if isLeft else 'r') 39 | def thumbBones (self, isLeft): 40 | ret = [] 41 | ret.append('thumb_02_' + ('l' if isLeft else 'r')) 42 | ret.append('thumb_03_' + ('l' if isLeft else 'r')) 43 | return ret 44 | 45 | def indexFingerParent(self, isLeft): return 'hand_' + ('l' if isLeft else 'r') 46 | def indexFingerBones (self, isLeft): 47 | ret = [] 48 | ret.append('index_01_' + ('l' if isLeft else 'r')) 49 | ret.append('index_02_' + ('l' if isLeft else 'r')) 50 | ret.append('index_03_' + ('l' if isLeft else 'r')) 51 | return ret 52 | 53 | def middleFingerParent(self, isLeft): return 'hand_' + ('l' if isLeft else 'r') 54 | def middleFingerBones(self , isLeft): 55 | ret = [] 56 | ret.append('middle_01_' + ('l' if isLeft else 'r')) 57 | ret.append('middle_02_' + ('l' if isLeft else 'r')) 58 | ret.append('middle_03_' + ('l' if isLeft else 'r')) 59 | return ret 60 | 61 | def ringFingerParent(self, isLeft): return 'hand_' + ('l' if isLeft else 'r') 62 | def ringFingerBones(self , isLeft): 63 | ret = [] 64 | ret.append('ring_01_' + ('l' if isLeft else 'r')) 65 | ret.append('ring_02_' + ('l' if isLeft else 'r')) 66 | ret.append('ring_03_' + ('l' if isLeft else 'r')) 67 | return ret 68 | 69 | def pinkyFingerParent(self, isLeft): return 'hand_' + ('l' if isLeft else 'r') 70 | def pinkyFingerBones(self , isLeft): 71 | ret = [] 72 | ret.append('pinky_01_' + ('l' if isLeft else 'r')) 73 | ret.append('pinky_02_' + ('l' if isLeft else 'r')) 74 | ret.append('pinky_03_' + ('l' if isLeft else 'r')) 75 | return ret 76 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 77 | # for mocap support 78 | def isMocapCapable(self): return True -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/ikrig.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy 3 | from . import DefaultRigInfo 4 | from ..util import bl28 5 | 6 | 7 | # =============================================================================== 8 | class IkRig(): 9 | def __init__(self, rigInfo): 10 | self.rigInfo = rigInfo 11 | self.armature = self.rigInfo.armature 12 | 13 | # =============================================================================== 14 | def add(self): 15 | bpy.ops.object.mode_set(mode='POSE') 16 | bpy.ops.pose.select_all(action='SELECT') 17 | bpy.ops.pose.transforms_clear() 18 | 19 | if bl28(): 20 | self.armature.data.display_type = 'BBONE' 21 | else: 22 | self.armature.data.draw_type = 'BBONE' 23 | 24 | # make all regular bone slightly smaller, so IK's fit around 25 | unitMult = 0.1 * self.rigInfo.unitMultplierToExported() 26 | val = 0.6 * unitMult 27 | bpy.ops.transform.transform(mode='BONE_SIZE', value=(val, val, val, 0)) 28 | bpy.ops.pose.select_all(action='DESELECT') 29 | 30 | self.changeLocks(False) 31 | 32 | self.addElbowAndHandIK(True) 33 | self.addElbowAndHandIK(False) 34 | 35 | self.addKneeAndFootIK(True) 36 | self.addKneeAndFootIK(False) 37 | bpy.ops.transform.transform(mode='BONE_SIZE', value=(unitMult, unitMult, unitMult, 0)) 38 | 39 | if isinstance(self.rigInfo, DefaultRigInfo): 40 | pBones = self.armature.pose.bones 41 | locks = self.rigInfo.additionalLocks() 42 | 43 | for key in locks: 44 | lock = locks[key] 45 | bone = pBones[key] 46 | bone.lock_ik_x = lock["lockX"] 47 | bone.lock_ik_y = lock["lockY"] 48 | bone.lock_ik_z = lock["lockZ"] 49 | 50 | RADIAN = 3.14 / 180.0 51 | 52 | if not lock["limitXMin"] is None or not lock["limitXMax"] is None: 53 | bone.use_ik_limit_x = True 54 | if not lock["limitXMin"] is None: 55 | bone.ik_min_x = lock["limitXMin"] * RADIAN 56 | if not lock["limitXMax"] is None: 57 | bone.ik_max_x = lock["limitXMax"] * RADIAN 58 | if not lock["limitYMin"] is None or not lock["limitYMax"] is None: 59 | bone.use_ik_limit_y = True 60 | if not lock["limitYMin"] is None: 61 | bone.ik_min_y = lock["limitYMin"] * RADIAN 62 | if not lock["limitYMax"] is None: 63 | bone.ik_max_y = lock["limitYMax"] * RADIAN 64 | if not lock["limitZMin"] is None or not lock["limitZMax"] is None: 65 | bone.use_ik_limit_z = True 66 | if not lock["limitZMin"] is None: 67 | bone.ik_min_z = lock["limitZMin"] * RADIAN 68 | if not lock["limitZMax"] is None: 69 | bone.ik_max_z = lock["limitZMax"] * RADIAN 70 | 71 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 72 | def addElbowAndHandIK(self, isLeft): 73 | side = 'L' if isLeft else 'R' 74 | 75 | # add bones while in edit mode 76 | bpy.ops.object.mode_set(mode='EDIT') 77 | eBones = self.armature.data.edit_bones 78 | 79 | upperArmName = self.rigInfo.upperArm(isLeft) 80 | upperArm = eBones[upperArmName] 81 | 82 | lowerArmName = self.rigInfo.lowerArm(isLeft) 83 | 84 | handName = self.rigInfo.hand(isLeft) 85 | hand = eBones[handName] 86 | hand.hide = True 87 | # - - - - - - - - 88 | elbowHead = upperArm.tail.copy() 89 | elbowHead.y = abs(elbowHead.y) * -4 # always forward, negative 90 | elbowTail = elbowHead.copy() 91 | elbowTail.y = elbowTail.y * 2 92 | 93 | elbowIKName = 'elbow.ik.' + side 94 | elbowIK = eBones.new(elbowIKName) 95 | elbowIK.head = elbowHead 96 | elbowIK.tail = elbowTail 97 | #elbowIK.parent = eBones[self.rigInfo.root] 98 | elbowIK.use_deform = False 99 | elbowIK.select = True 100 | # - - - - - - - - 101 | handIKName = 'hand.ik.' + side 102 | handIK = eBones.new(handIKName) 103 | handIK.head = hand.head.copy() 104 | handIK.tail = hand.tail.copy() 105 | handIK.roll = hand.roll 106 | #handIK.parent = eBones[self.rigInfo.root] 107 | handIK.use_deform = False 108 | handIK.select = True 109 | # - - - - - - - - 110 | self.addIK_Constraint(upperArmName, elbowIKName, self.rigInfo.elbowIKChainLength) 111 | self.addIK_Constraint(lowerArmName, handIKName, self.rigInfo.handIKChainLength) 112 | self.addCopyRotation(handName, handIKName) 113 | 114 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 115 | def addKneeAndFootIK(self, isLeft): 116 | side = 'L' if isLeft else 'R' 117 | 118 | # add bones while in edit mode 119 | bpy.ops.object.mode_set(mode='EDIT') 120 | eBones = self.armature.data.edit_bones 121 | 122 | thighName = self.rigInfo.thigh(isLeft) 123 | thigh = eBones[thighName] 124 | 125 | calfName = self.rigInfo.calf(isLeft) 126 | 127 | footName = self.rigInfo.foot(isLeft) 128 | foot = eBones[footName] 129 | foot.hide = True 130 | 131 | kneeHead = thigh.tail.copy() 132 | kneeHead.y = abs(kneeHead.y) * -10 # always forward, negative 133 | kneeTail = kneeHead.copy() 134 | kneeTail.y = kneeHead.y * 1.5 135 | 136 | kneeIKName = 'knee.ik.' + side 137 | kneeIK = eBones.new(kneeIKName) 138 | kneeIK.head = kneeHead 139 | kneeIK.tail = kneeTail 140 | #kneeIK.parent = eBones[self.rigInfo.root] 141 | kneeIK.use_deform = False 142 | kneeIK.select = True 143 | # - - - - - - - - 144 | footIKName = 'foot.ik.' + side 145 | footIK = eBones.new(footIKName) 146 | footIK.head = foot.head.copy() 147 | footIK.tail = foot.tail.copy() 148 | footIK.roll = foot.roll 149 | #footIK.parent = eBones[self.rigInfo.root] 150 | footIK.use_deform = False 151 | footIK.select = True 152 | # - - - - - - - - 153 | self.addIK_Constraint(thighName, kneeIKName, self.rigInfo.kneeIKChainLength) 154 | self.addIK_Constraint(calfName, footIKName, self.rigInfo.footIKChainLength) 155 | self.addCopyRotation(footName, footIKName) 156 | 157 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 158 | def addCopyRotation(self, boneName, subtargetName): 159 | print('adding copy rotation constraint to ' + boneName + ', with sub target ' + subtargetName) 160 | # apply constraints to the pose bone version 161 | bpy.ops.object.mode_set(mode='POSE') 162 | pBones = self.armature.pose.bones 163 | 164 | pBone = pBones[boneName] 165 | con = pBone.constraints.new('COPY_ROTATION') 166 | con.target = self.armature 167 | con.subtarget = subtargetName 168 | con.name = 'IK_SNAPON_ROT' 169 | 170 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 171 | def addIK_Constraint(self, boneName, ikBoneName, chain_count): 172 | print('adding IK constraint to ' + boneName + ', with sub target ' + ikBoneName + ', and chain length ' + str(chain_count)) 173 | # apply constraints to the pose bone version 174 | bpy.ops.object.mode_set(mode='POSE') 175 | pBones = self.armature.pose.bones 176 | 177 | pBone = pBones[boneName] 178 | con = pBone.constraints.new('IK') 179 | con.target = self.armature 180 | con.subtarget = ikBoneName 181 | con.chain_count = chain_count 182 | con.name = 'IK_SNAPON_IK' 183 | 184 | # =============================================================================== 185 | def changeLocks(self, locked): 186 | # take location locks off pelvis & clavicles 187 | pelvis = self.armature.pose.bones[self.rigInfo.pelvis] 188 | pelvis.lock_location[0] = locked 189 | pelvis.lock_location[1] = locked 190 | pelvis.lock_location[2] = locked 191 | 192 | lClavicle = self.armature.pose.bones[self.rigInfo.clavicle(True)] 193 | lClavicle.lock_location[0] = locked 194 | lClavicle.lock_location[1] = locked 195 | lClavicle.lock_location[2] = locked 196 | 197 | rClavicle = self.armature.pose.bones[self.rigInfo.clavicle(False)] 198 | rClavicle.lock_location[0] = locked 199 | rClavicle.lock_location[1] = locked 200 | rClavicle.lock_location[2] = locked 201 | 202 | # =============================================================================== 203 | def remove(self): 204 | self.changeLocks(True) 205 | self.removeSide(True) 206 | self.removeSide(False) 207 | # reverse making all regular bone slightly smaller, so IK's fit around; does not work; seems value cannot be > 1 in code 208 | unitMult = 0.1 * self.rigInfo.unitMultplierToExported() 209 | val = 1 / (0.6 * unitMult) 210 | bpy.ops.transform.transform(mode='BONE_SIZE', value=(val, val, val, 0)) 211 | 212 | self.armature.data.display_type = 'WIRE' 213 | 214 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 215 | def removeSide(self, isLeft): 216 | side = 'L' if isLeft else 'R' 217 | if 'elbow.ik.L' in self.armature.pose.bones: 218 | self.demolish('elbow.ik.' + side, [self.rigInfo.upperArm(isLeft)]) 219 | self.demolish('hand.ik.' + side, [self.rigInfo.lowerArm(isLeft), self.rigInfo.hand(isLeft)]) 220 | self.demolish('knee.ik.' + side, [self.rigInfo.thigh(isLeft)]) 221 | self.demolish('foot.ik.' + side, [self.rigInfo.calf(isLeft), self.rigInfo.foot(isLeft)]) 222 | 223 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 224 | # no need of BoneSurgery module, since no weights to give back 225 | def demolish(self, controlBoneName, boneNames): 226 | bpy.ops.object.mode_set(mode='EDIT') 227 | bpy.ops.armature.select_all(action='DESELECT') 228 | self.armature.data.edit_bones[controlBoneName].select = True 229 | bpy.ops.armature.delete() 230 | 231 | bpy.ops.object.mode_set(mode='POSE') 232 | for boneName in boneNames: 233 | self.armature.data.bones[boneName].hide = False 234 | 235 | for bone in self.armature.pose.bones: 236 | for c in bone.constraints: 237 | if 'IK_SNAPON_' in c.name: 238 | bone.constraints.remove(c) 239 | -------------------------------------------------------------------------------- /blender_source/MH_Community/rig/riginfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import bpy 5 | 6 | class RigInfo: 7 | @staticmethod 8 | def determineRig(armature): 9 | from .gameriginfo import GameRigInfo 10 | from .defaultriginfo import DefaultRigInfo 11 | from .cmuriginfo import CMURigInfo 12 | from .kinect2riginfo import Kinect2RigInfo 13 | 14 | # in the case where a test bone (wt dot) is not truely unique, order of tests might be important 15 | game = GameRigInfo(armature) 16 | if game.matches(): return game 17 | 18 | default = DefaultRigInfo(armature) 19 | if default.matches(): return default 20 | 21 | cmu = CMURigInfo(armature) 22 | if cmu.matches(): return cmu 23 | 24 | kinect2 = Kinect2RigInfo(armature) 25 | if kinect2.matches(): return kinect2 26 | 27 | return None 28 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 29 | # pass not only a unique bone name, but one that has a dot. Collada would change that to a '_' 30 | def __init__(self, armature, name, uniqueBoneName): 31 | self.armature = armature 32 | self.name = name 33 | self.uniqueBoneName = uniqueBoneName 34 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 | def matches(self): 36 | boneWithoutDot = self.uniqueBoneName.replace(".", "_") 37 | for bone in self.armature.data.bones: 38 | # test without dot version first in case skeleton has no dots 39 | if bone.name == boneWithoutDot: 40 | self.dot = '_' 41 | return True 42 | 43 | if bone.name == self.uniqueBoneName: 44 | self.dot = '.' 45 | return True 46 | 47 | return False 48 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 49 | def determineExportedUnits(self): 50 | if len(self.armature.data.exportedUnits) > 0: return self.armature.data.exportedUnits 51 | 52 | current_mode = bpy.context.object.mode 53 | bpy.ops.object.mode_set(mode='EDIT') 54 | eBones = self.armature.data.edit_bones 55 | 56 | headTail = eBones[self.head].tail.z 57 | footTail = eBones[self.foot(False)].tail.z 58 | bpy.ops.object.mode_set(mode=current_mode) 59 | 60 | totalHeight = headTail - footTail # done this way to be feet on ground independent 61 | if totalHeight < 5: ret = 'METERS' # decimeter threshold 62 | elif totalHeight <= 22: ret = 'DECIMETERS' # 21.7 to 23.9 is sort of no man's land of decimeters of tallest & inches of smallest 63 | else: ret = 'CENTIMETERS' 64 | 65 | print ('armature exported units is ' + ret + ', headTail: ' + str(headTail) + ', footTail: ' + str(footTail)) 66 | 67 | self.armature.data.exportedUnits = ret 68 | return ret 69 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 70 | def unitMultplierToExported(self): 71 | units = self.determineExportedUnits() 72 | 73 | if units == 'METERS': return 1 74 | elif units == 'DECIMETERS': return 10 75 | else: return 100 76 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 77 | def hasIKRigs(self): 78 | return self.hasFingerIK() or self.hasIK() 79 | def hasFingerIK(self): 80 | return 'thumb.ik.L' in self.armature.data.bones 81 | def hasIK(self): 82 | return 'elbow.ik.L' in self.armature.data.bones 83 | 84 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 85 | def hasFingers(self): 86 | hand = self.hand(False) 87 | if hand not in self.armature.data.bones: 88 | return False 89 | 90 | for bone in self.armature.data.bones: 91 | if bone.parent is not None and bone.parent.name == hand: 92 | return True 93 | 94 | return False 95 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 96 | def isExpressionCapable(self): 97 | return 'special03' in self.armature.data.bones 98 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 99 | def isPoseCapable(self): 100 | return self.name == 'Default Rig' and not self.hasIKRigs() 101 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 102 | # determine all the meshes which are controlled by skeleton, 103 | def getMeshesForRig(self, scene): 104 | meshes = [] 105 | for object in [object for object in scene.objects]: 106 | if object.type == 'MESH' and len(object.vertex_groups) > 0 and self.armature == object.find_armature(): 107 | meshes.append(object) 108 | 109 | return meshes 110 | 111 | #=========================================================================== 112 | # for mocap support 113 | # the retargeting constraint space for arm bones is 'WORLD', while 'LOCAL' for rest 114 | def isArmBone(self, boneName): 115 | bones = self.clavicle(True) + self.clavicle(False) + \ 116 | self.upperArm(True) + self.upperArm(False) + \ 117 | self.lowerArm(True) + self.lowerArm(False) + \ 118 | self.hand(True) + self.hand(False) 119 | return self.isFinger(boneName) or boneName in bones 120 | 121 | def isFinger(self, boneName): 122 | # for some skeleton, no thumbs or hand tips, so defensively coded 123 | if boneName == self.handTip(True ): return True 124 | if boneName == self.handTip(False): return True 125 | if boneName == self.thumb (True ): return True 126 | if boneName == self.thumb (False): return True 127 | return False 128 | 129 | # for animation scaling when compared with sensor's equivalent 130 | # being in world space also includes any amount the armature may have been raised / lowered to allow mesh to touch ground 131 | def pelvisInWorldSpace(self): 132 | return self.getBoneInWorldSpace(self.armature.pose.bones[self.pelvis]) 133 | 134 | # for animation root placement 135 | def rootInWorldSpace(self): 136 | return self.getBoneInWorldSpace(self.armature.pose.bones[self.root]) 137 | 138 | # for animation root placement 139 | def getBoneInWorldSpace(self, bone): 140 | current_mode = bpy.context.object.mode 141 | bpy.ops.object.mode_set(mode='POSE') 142 | 143 | worldMat = self.armature.convert_space(pose_bone = bone, matrix = bone.matrix, from_space = 'POSE', to_space = 'WORLD') 144 | bpy.ops.object.mode_set(mode=current_mode) 145 | 146 | offset = worldMat.to_translation() 147 | return offset 148 | 149 | def getSensorMapping(self, sensorType = 'KINECT2'): 150 | if sensorType == 'KINECT2': 151 | 152 | return { 153 | # keys are kinect joints names coming from the sensor 154 | # values are bone names whose tail is at the joint 155 | 'SpineBase' : None, 156 | 'SpineMid' : self.pelvis, 157 | 'SpineShoulder': self.upperSpine, 158 | 159 | 'Neck' : self.neckBase, 160 | 'Head' : self.head, 161 | 162 | 'ShoulderLeft' : self.clavicle(True, True), 163 | 'ElbowLeft' : self.upperArm(True, True), 164 | 'WristLeft' : self.lowerArm(True, True), 165 | 'HandLeft' : self.hand(True, True), 166 | 'HandTipLeft' : self.handTip(True, True), 167 | 'ThumbLeft' : self.thumb(True, True), 168 | 169 | 'ShoulderRight': self.clavicle(False, True), 170 | 'ElbowRight' : self.upperArm(False, True), 171 | 'WristRight' : self.lowerArm(False, True), 172 | 'HandRight' : self.hand(False, True), 173 | 'HandTipRight' : self.handTip(False, True), 174 | 'ThumbRight' : self.thumb(False, True), 175 | 176 | 'HipLeft' : self.hip(True, True), 177 | 'KneeLeft' : self.thigh(True, True), 178 | 'AnkleLeft' : self.calf(True, True), 179 | 'FootLeft' : self.foot(True, True), 180 | 181 | 'HipRight' : self.hip(False, True), 182 | 'KneeRight' : self.thigh(False, True), 183 | 'AnkleRight' : self.calf(False, True), 184 | 'FootRight' : self.foot(False, True) 185 | } 186 | # add next sensor, eg., KINECT_AZURE 187 | elif sensorType == 'KINECT_AZURE': 188 | return None 189 | 190 | # this sensor is not supported 191 | else: return None 192 | -------------------------------------------------------------------------------- /blender_source/MH_Community/separate_eyes.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | #=============================================================================== 3 | 4 | class SeparateEyes(): 5 | def __init__(self, combinedMesh): 6 | priorName = combinedMesh.name 7 | nVertsHalf = len(combinedMesh.data.vertices) / 2 8 | 9 | # switch to edit, and de-select all verts Change selection mode 10 | bpy.ops.object.mode_set(mode='EDIT') 11 | bpy.ops.mesh.select_all(action='DESELECT') 12 | bpy.context.tool_settings.mesh_select_mode = (True , False , False) 13 | 14 | #selection using code do not actually work unless in object mode 15 | bpy.ops.object.mode_set(mode='OBJECT') 16 | 17 | # select the first half of the vertices, which is the lest side 18 | for vIndex, vert in enumerate(combinedMesh.data.vertices): 19 | if vIndex == nVertsHalf: break 20 | vert.select = True 21 | 22 | # switch back to edit & separate, which leaves the right side mesh still selected 23 | bpy.ops.object.mode_set(mode='EDIT') 24 | bpy.ops.mesh.separate(type='SELECTED') 25 | 26 | # interface is in a strange state, combinedMesh is Right side, switch to Object mode, rename, & move origin 27 | bpy.ops.object.mode_set(mode='OBJECT') 28 | combinedMesh.name = priorName + '_R' 29 | bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') 30 | 31 | # select left side, make active, rename, move origin, 32 | bpy.ops.object.mode_set(mode='OBJECT') 33 | bpy.ops.object.select_all(action='DESELECT') 34 | left = bpy.data.objects[priorName + '.001'] 35 | left.select_set(True) 36 | bpy.context.view_layer.objects.active = left 37 | left.name = priorName + '_L' 38 | bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') 39 | -------------------------------------------------------------------------------- /blender_source/MH_Community/util.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import time 3 | from addon_utils import check,paths,enable,modules 4 | 5 | _startMillis = None 6 | _lastMillis = None 7 | 8 | ENABLE_PROFILING=True 9 | LEAST_REQUIRED_MAKESKIN_VERSION = (0,9,0) 10 | 11 | def profile(position = "timestamp"): 12 | global ENABLE_PROFILING 13 | if not ENABLE_PROFILING: 14 | return 15 | 16 | global _startMillis 17 | global _lastMillis 18 | 19 | if _startMillis is None: 20 | _startMillis = int(round(time.time() * 1000)) 21 | _lastMillis = _startMillis - 1 22 | 23 | currentMillis = int(round(time.time() * 1000)) 24 | print(position + ": " + str(currentMillis - _startMillis) + " / " + str(currentMillis - _lastMillis)) 25 | _lastMillis = currentMillis 26 | 27 | 28 | def bl28(): 29 | return bpy.app.version >= (2, 80, 0) 30 | 31 | def linkObject(obj, parent=None): 32 | if bl28(): 33 | if parent: 34 | parent.objects.link(obj) 35 | else: 36 | bpy.context.collection.objects.link(obj) 37 | else: 38 | bpy.context.scene.objects.link(obj) 39 | 40 | def activateObject(obj): 41 | if bl28(): 42 | bpy.context.view_layer.objects.active = obj 43 | else: 44 | bpy.context.scene.objects.active = obj 45 | 46 | def selectObject(obj): 47 | if bl28(): 48 | obj.select_set(True) 49 | else: 50 | obj.select = True 51 | 52 | def deselectObject(obj): 53 | if bl28(): 54 | obj.select_set(False) 55 | else: 56 | obj.select = False 57 | 58 | def checkMakeSkinAvailable(): 59 | for path in paths(): 60 | for mod_name, mod_path in bpy.path.module_names(path): 61 | is_enabled, is_loaded = check(mod_name) 62 | if mod_name == "makeskin": 63 | return is_enabled and is_loaded 64 | return False 65 | 66 | def showMessageBox(message='', title='MessageBox', icon='INFO'): 67 | 68 | def draw(self, context): 69 | lines = message.split('\n') 70 | for line in lines: 71 | self.layout.label(text=line) 72 | 73 | print(message) 74 | bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) 75 | -------------------------------------------------------------------------------- /doc-assist/IK_fingers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/IK_fingers.png -------------------------------------------------------------------------------- /doc-assist/MH_server_socket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/MH_server_socket.png -------------------------------------------------------------------------------- /doc-assist/blender_anger_no_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/blender_anger_no_trans.png -------------------------------------------------------------------------------- /doc-assist/blender_anger_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/blender_anger_trans.png -------------------------------------------------------------------------------- /doc-assist/bones_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/bones_tab.png -------------------------------------------------------------------------------- /doc-assist/eye_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/eye_before.png -------------------------------------------------------------------------------- /doc-assist/eyes_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/eyes_after.png -------------------------------------------------------------------------------- /doc-assist/kinect_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/kinect_tab.png -------------------------------------------------------------------------------- /doc-assist/mesh_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/mesh_tab.png -------------------------------------------------------------------------------- /doc-assist/mh_anger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/mh_anger.png -------------------------------------------------------------------------------- /doc-assist/newPoseLib.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/newPoseLib.jpg -------------------------------------------------------------------------------- /doc-assist/poseLib.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/poseLib.jpg -------------------------------------------------------------------------------- /doc-assist/rig_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/rig_after.png -------------------------------------------------------------------------------- /doc-assist/rig_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makehumancommunity/makehuman-plugin-for-blender/a0f83ba4e9a3625de3dd6f90366dbafedb4160d8/doc-assist/rig_before.png -------------------------------------------------------------------------------- /rebuild_dist.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAME=MH_Community 4 | ZNAME="${NAME}_for_blender" 5 | BLVER=280 6 | ZIP="${ZNAME}_${BLVER}.zip" 7 | DIR=`pwd` 8 | 9 | rm blender_distribution/$ZIP 10 | cd blender_source 11 | zip -r ../blender_distribution/$ZIP $NAME --exclude $NAME/\*__pycache__\* --exclude \*.pyc 12 | 13 | cp ../blender_distribution/$ZIP /tmp 14 | cd /tmp 15 | rm -rf MH_Community 16 | unzip $ZIP 17 | sed -i -e 's/"blender": (2, 80/"hejhopp": (2, 79/g' MH_Community/__init__.py 18 | BLVER=279 19 | ZIP="${ZNAME}_${BLVER}.zip" 20 | zip -r $ZIP $NAME 21 | 22 | cd $DIR 23 | cp /tmp/$ZIP blender_distribution 24 | 25 | --------------------------------------------------------------------------------