├── .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 |
--------------------------------------------------------------------------------