[mGear5.3.1] Guide Template Manager のEPIC_mannequin_y_upとEPIC_mannequin_z_upの違いの検証

EPIC_mannequin_y_up 今回のモデルにあってる。

EPIC_mannequin_z_up たしかにUEマネキンがそういう向きのことあったよね

試しに

UE5.7.4のマネキンを出力してみた。

SKM_Quinn_Simple

でEPIC_mannequin_y_upのほうをあてると 合う

SKM_Manny_Simple

同じくEPIC_mannequin_y_upが合う

うーんZのほうを向いたマネキンはUE4マネキンなのか?

UE4マネキンをContentsExample(機能別サンプル)からもってきて。。

export

SK_Mannequin

EPIC_mannequin_y_upが UE4マネキンにびったびたに合った!!

UE4マネキン基準ということか!!!!!!クインとマニーはまだ受け入れていないのかmGearさんw

mGear > Shifter > Build From Selectionでビルド

ビルド結果

かわいいUE4マネキン

愛しき屈伸SK_Mannequin

[mGear5.3.1] Auto Fit Biped でMatch Guidesの内部のサイズをマッチさせる処理がSmart AdjustがRunでエラー吐いてるからMatch Guidesは使えないから自分で自動化した。

# Traceback (most recent call last):
#   File "D:\Sandbox\mgear\modules\scripts\mgear\shifter\afg_tools_ui.py", line 555, in runSmartAdjust
#     spine_height_only=True)
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 265, in wrap
#     raise e
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 262, in wrap
#     return func(*args, **kwargs)
#   File "D:\Sandbox\mgear\modules\scripts\mgear\shifter\afg_tools.py", line 700, in smartAdjustEmbedOutput
#     replace=SIDE_MIRROR_INFO[favor_side])
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 265, in wrap
#     raise e
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 262, in wrap
#     return func(*args, **kwargs)
#   File "D:\Sandbox\mgear\modules\scripts\mgear\shifter\afg_tools.py", line 572, in mirrorEmbedNodesSide
#     mirrorEmbedNodes(node, search=search, replace=replace)
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 265, in wrap
#     raise e
#   File "D:\Sandbox\mgear\modules\scripts\mgear\core\utils.py", line 262, in wrap
#     return func(*args, **kwargs)
#   File "D:\Sandbox\mgear\modules\scripts\mgear\shifter\afg_tools.py", line 546, in mirrorEmbedNodes
#     target_mat.rotateTo([0, 0, 0])
# AttributeError: 'TransformationMatrix' object has no attribute 'rotateTo'

今回の Match Guides は内部で Smart Adjust 相当の調整処理に依存していて、そこが落ちるなら実質 Match Guides も使えない と見ていい。トレースバックでも runSmartAdjustsmartAdjustEmbedOutputmirrorEmbedNodesSidemirrorEmbedNodes の流れで、TransformationMatrix.rotateTo が無くて止まっている。mGearのリリースログでも Auto Fit Guide は比較的新しい改善対象で、skip orientation や reference mesh などが後から拡張されてきた機能だと分かるので、版差や環境差の影響を受けやすい領域と見て自然。

なので、今やるべきことは「Match Guides を直そうと頑張る」ではなく、切り分けを完成させること。
結論だけ言うと、こう整理すると強い。

1. AFGの自動フィットは試した。
2. ただし Smart Adjust 系の内部処理で TransformationMatrix.rotateTo エラーが出て止まる。
3. 原因は入力データではなく、mGear側が期待している変換行列APIと実行環境のAPI不整合の可能性が高い。
mGearの公開変換ユーティリティのドキュメントでは、変換行列周りは setMatrixRotation などの関数で扱われていて、少なくとも公開ドキュメント上 TransformationMatrix.rotateTo は確認しにくい。だから、あなたの理解どおり「Smart Adjust 側が詰まっているので Match Guides も成立しない」で問題ない。

mGearのAuto Fit Guideを実機で確認したところ、Guide生成とEmbedまでは触れましたが、Match Guides の内部で Smart Adjust 系の処理が TransformationMatrix.rotateTo で失敗し、自動サイズ合わせまでは通りませんでした。なので、機能の存在だけでなく、どの処理単位で止まるかまで切り分けて確認しました。

Auto Fitは便利ですが、版差やAPI差異の影響を受けやすい処理だと感じました。現場では完全自動前提ではなく、テンプレGuideと手動補正を併用する運用の方が堅いと考えています。

とか、手動なんてめんどくさいので

昔、こんな記事jを書いた mGearのガイドはジョイントから作らないとめんどくさい。

今回は自分のジョイント用だけど書き直したバージョン

# -*- coding: utf-8 -*-
import maya.cmds as cmds



# これは 3点から pole vector 用の位置を出す やつ。
import maya.cmds as cmds
import math

def vec_sub(a, b):
    return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]

def vec_add(a, b):
    return [a[0]+b[0], a[1]+b[1], a[2]+b[2]]

def vec_mul(a, s):
    return [a[0]*s, a[1]*s, a[2]*s]

def vec_len(a):
    return math.sqrt(a[0]**2 + a[1]**2 + a[2]**2)

def vec_norm(a):
    l = vec_len(a)
    if l < 1e-8:
        return [0.0, 0.0, 0.0]
    return [a[0]/l, a[1]/l, a[2]/l]

def vec_dot(a, b):
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]

def vec_cross(a, b):
    return [
        a[1]*b[2] - a[2]*b[1],
        a[2]*b[0] - a[0]*b[2],
        a[0]*b[1] - a[1]*b[0]
    ]

def get_pole_vector_position(start, mid, end, distance_scale=1.0):
    """
    start, mid, end: world position [x,y,z]
    """
    start_to_end = vec_sub(end, start)
    start_to_mid = vec_sub(mid, start)

    line_dir = vec_norm(start_to_end)
    proj_len = vec_dot(start_to_mid, line_dir)
    proj = vec_add(start, vec_mul(line_dir, proj_len))

    arrow = vec_sub(mid, proj)
    arrow_len = vec_len(arrow)

    if arrow_len < 1e-6:
        # ほぼ一直線なら適当な補助方向
        fallback = vec_cross(line_dir, [0, 1, 0])
        if vec_len(fallback) < 1e-6:
            fallback = vec_cross(line_dir, [1, 0, 0])
        arrow = vec_norm(fallback)
        chain_len = vec_len(vec_sub(mid, start)) + vec_len(vec_sub(end, mid))
        return vec_add(mid, vec_mul(arrow, chain_len * 0.5 * distance_scale))

    arrow_dir = vec_norm(arrow)
    chain_len = vec_len(vec_sub(mid, start)) + vec_len(vec_sub(end, mid))

    return vec_add(mid, vec_mul(arrow_dir, chain_len * 0.5 * distance_scale))
    

#upv を置く処理
def move_upv_guide(guide_name, start_joint, mid_joint, end_joint, distance_scale=1.0):
    guide = first_existing([guide_name])
    s = find_unique_by_short_name(start_joint)
    m = find_unique_by_short_name(mid_joint)
    e = find_unique_by_short_name(end_joint)

    if not guide:
        print("[SKIP] upv guide missing:", guide_name)
        return
    if not (s and m and e):
        print("[SKIP] joints missing for upv:", guide_name, start_joint, mid_joint, end_joint)
        return

    pos = get_pole_vector_position(ws_pos(s), ws_pos(m), ws_pos(e), distance_scale)
    set_ws_pos(guide, pos)
    print("[OK] moved upv:", guide_name)
    
# 安全な aim のやり方
# Mayaで transform を向けたいだけならこういう感じ。
def aim_node_to_target(node, target, aim=(1,0,0), up=(0,1,0), world_up=(0,1,0)):
    tmp = cmds.aimConstraint(
        target,
        node,
        aimVector=aim,
        upVector=up,
        worldUpType="vector",
        worldUpVector=world_up
    )
    cmds.delete(tmp)

# まずは位置合わせ追加版
def align_finger_chain(guide_prefix, joint_names):
    """
    例:
    guide_prefix = "index_L0"
    joint_names = ["index_metacarpal_l", "index_01_l", "index_02_l", "index_03_l"]
    """
    root_g = first_existing([guide_prefix + "_root"])
    loc0_g = first_existing([guide_prefix + "_0_loc"])
    loc1_g = first_existing([guide_prefix + "_1_loc"])
    loc2_g = first_existing([guide_prefix + "_2_loc"])

    src0 = find_unique_by_short_name(joint_names[0])
    src1 = find_unique_by_short_name(joint_names[1])
    src2 = find_unique_by_short_name(joint_names[2])
    src3 = find_unique_by_short_name(joint_names[3])

    pairs = [
        (root_g, src0, guide_prefix + "_root"),
        (loc0_g, src1, guide_prefix + "_0_loc"),
        (loc1_g, src2, guide_prefix + "_1_loc"),
        (loc2_g, src3, guide_prefix + "_2_loc"),
    ]

    for g, j, label in pairs:
        if not g:
            print("[SKIP] guide missing:", label)
            continue
        if not j:
            print("[SKIP] joint missing for:", label)
            continue
        set_ws_pos(g, ws_pos(j))
        print("[OK] moved", label)
        
# 指はこれが大事。
# *_blade を root → 先端方向 に向ける。 
def aim_blade_from_chain(blade_name, root_joint, next_joint, world_up=(0, 1, 0)):
    blade = first_existing([blade_name])
    j0 = find_unique_by_short_name(root_joint)
    j1 = find_unique_by_short_name(next_joint)

    if not blade:
        print("[SKIP] blade missing:", blade_name)
        return
    if not (j0 and j1):
        print("[SKIP] joints missing for blade:", blade_name, root_joint, next_joint)
        return

    # bladeをroot位置へ
    set_ws_pos(blade, ws_pos(j0))

    # 一時ロケータを先端位置に置いてaim
    temp = cmds.spaceLocator(name="tmpAim_loc#")[0]
    set_ws_pos(temp, ws_pos(j1))

    try:
        con = cmds.aimConstraint(
            temp,
            blade,
            aimVector=(1, 0, 0),
            upVector=(0, 1, 0),
            worldUpType="vector",
            worldUpVector=world_up
        )
        cmds.delete(con)
    except Exception as e:
        print("[ERROR] aim failed:", blade_name, e)

    cmds.delete(temp)
    print("[OK] aimed blade:", blade_name)
    
# =========================================================
# Utility
# =========================================================

def _short_name(node):
    return node.split("|")[-1]

def find_unique_by_short_name(short_name):
    """Scene内から short name 一致ノードを1つだけ返す"""
    matches = cmds.ls("*|" + short_name, long=True) or []
    root_matches = cmds.ls(short_name, long=True) or []
    all_matches = list(set(matches + root_matches))

    if not all_matches:
        return None
    if len(all_matches) > 1:
        print("[WARN] multiple nodes found for short name: {} -> {}".format(short_name, all_matches))
        return all_matches[0]
    return all_matches[0]

def first_existing(candidates):
    """候補名の中で最初に存在するノードを返す"""
    for c in candidates:
        node = find_unique_by_short_name(c)
        if node:
            return node
    return None

def ws_pos(node):
    return cmds.xform(node, q=True, ws=True, t=True)

def set_ws_pos(node, pos):
    cmds.xform(node, ws=True, t=pos)

def avg_pos(nodes):
    pts = [ws_pos(n) for n in nodes]
    count = float(len(pts))
    return [
        sum(p[0] for p in pts) / count,
        sum(p[1] for p in pts) / count,
        sum(p[2] for p in pts) / count,
    ]

def lerp_pos(a, b, t=0.5):
    pa = ws_pos(a)
    pb = ws_pos(b)
    return [
        pa[0] + (pb[0] - pa[0]) * t,
        pa[1] + (pb[1] - pa[1]) * t,
        pa[2] + (pb[2] - pa[2]) * t,
    ]

def list_guide_nodes():
    """guide配下のノード確認用"""
    guide = find_unique_by_short_name("guide")
    if not guide:
        print("[ERROR] guide root not found")
        return
    nodes = cmds.listRelatives(guide, ad=True, f=True) or []
    nodes = sorted(nodes, key=lambda x: _short_name(x))
    print("=" * 60)
    print("Guide nodes:")
    for n in nodes:
        print(_short_name(n))
    print("=" * 60)

# =========================================================
# Guide candidate names
# EPIC_mannequin_y_up を想定しつつ、少し候補を広めに持つ
# 見つからない場合は list_guide_nodes() で確認して差し替えてください
# =========================================================

GUIDE_CANDIDATES = {
    # Center
    "pelvis": ["body_C0_root", "spine_C0_root"],

    "spine1": [
        "spine_C0_spineBase"
    ],
    "spine2": [
        "spine_C0_spineTop"
    ],
    "chest": [
        "spine_C0_chest"
    ],

    "neck": [
        "neck_C0_neck", "neck_C0_root"
    ],
    "head": [
        "neck_C0_head"
    ],

    # Left arm
    "clav_l": ["clavicle_L0_root"],
    "elbow_l": ["arm_L0_elbow"],
    "wrist_l": ["arm_L0_wrist"],

    # Right arm
    "clav_r": ["clavicle_R0_root"],
    "elbow_r": ["arm_R0_elbow"],
    "wrist_r": ["arm_R0_wrist"],

    # Left leg
    "thigh_l": ["leg_L0_root"],
    "knee_l": ["leg_L0_knee"],
    "ankle_l": ["leg_L0_ankle"],

    # Right leg
    "thigh_r": ["leg_R0_root"],
    "knee_r": ["leg_R0_knee"],
    "ankle_r": ["leg_R0_ankle"],
}

# =========================================================
# Source joints
# 今回あなたのジョイント名に合わせている
# =========================================================

JOINTS = {
    "root": "root",
    "pelvis": "pelvis",

    "spine_01": "spine_01",
    "spine_02": "spine_02",
    "spine_03": "spine_03",
    "spine_04": "spine_04",
    "spine_05": "spine_05",

    "neck_01": "neck_01",
    "neck_02": "neck_02",

    "clavicle_l": "clavicle_l",
    "upperarm_l": "upperarm_l",
    "lowerarm_l": "lowerarm_l",
    "hand_l": "hand_l",

    "clavicle_r": "clavicle_r",
    "upperarm_r": "upperarm_r",
    "lowerarm_r": "lowerarm_r",
    "hand_r": "hand_r",

    "thigh_l": "thigh_l",
    "calf_l": "calf_l",
    "foot_l": "foot_l",

    "thigh_r": "thigh_r",
    "calf_r": "calf_r",
    "foot_r": "foot_r",
}

# =========================================================
# Core
# =========================================================

def resolve_guides():
    resolved = {}
    for key, candidates in GUIDE_CANDIDATES.items():
        node = first_existing(candidates)
        if not node:
            print("[WARN] guide not found for {} candidates={}".format(key, candidates))
        resolved[key] = node
    return resolved

def resolve_joints():
    resolved = {}
    for key, short_name in JOINTS.items():
        node = find_unique_by_short_name(short_name)
        if not node:
            print("[WARN] joint not found: {}".format(short_name))
        resolved[key] = node
    return resolved

def safe_move(guide_node, pos, label):
    if not guide_node:
        print("[SKIP] guide missing: {}".format(label))
        return
    try:
        set_ws_pos(guide_node, pos)
        print("[OK] moved {}".format(label))
    except Exception as e:
        print("[ERROR] failed move {} : {}".format(label, e))

def align_mgear_guides_to_epic():
    guides = resolve_guides()
    joints = resolve_joints()

    print("=" * 60)
    print("Start align")
    print("=" * 60)

    # -----------------------------------------------------
    # Center
    # -----------------------------------------------------
    if joints["pelvis"]:
        safe_move(guides["pelvis"], ws_pos(joints["pelvis"]), "pelvis")

    # spine guide locators が複数ある場合に順番に置く
    # spine
    if joints["spine_01"] and joints["spine_03"]:
        safe_move(guides["spine1"], avg_pos([joints["spine_01"], joints["spine_03"]]), "spine1")
    
    if joints["spine_03"] and joints["spine_05"]:
        safe_move(guides["spine2"], avg_pos([joints["spine_03"], joints["spine_05"]]), "spine2")
    
    if joints["spine_05"]:
        safe_move(guides["chest"], ws_pos(joints["spine_05"]), "chest")

    # neck / head
    if joints["neck_01"]:
        safe_move(guides["neck"], ws_pos(joints["neck_01"]), "neck")
    
    if joints["neck_02"]:
        safe_move(guides["head"], ws_pos(joints["neck_02"]), "head")

    # -----------------------------------------------------
    # Left arm
    # -----------------------------------------------------
    if joints["clavicle_l"]:
        safe_move(guides["clav_l"], ws_pos(joints["clavicle_l"]), "clav_l")

    if joints["lowerarm_l"]:
        safe_move(guides["elbow_l"], ws_pos(joints["lowerarm_l"]), "elbow_l")

    if joints["hand_l"]:
        safe_move(guides["wrist_l"], ws_pos(joints["hand_l"]), "wrist_l")

    # -----------------------------------------------------
    # Right arm
    # -----------------------------------------------------
    if joints["clavicle_r"]:
        safe_move(guides["clav_r"], ws_pos(joints["clavicle_r"]), "clav_r")

    if joints["lowerarm_r"]:
        safe_move(guides["elbow_r"], ws_pos(joints["lowerarm_r"]), "elbow_r")

    if joints["hand_r"]:
        safe_move(guides["wrist_r"], ws_pos(joints["hand_r"]), "wrist_r")

    # -----------------------------------------------------
    # Left leg
    # -----------------------------------------------------
    if joints["thigh_l"]:
        safe_move(guides["thigh_l"], ws_pos(joints["thigh_l"]), "thigh_l")

    if joints["calf_l"]:
        safe_move(guides["knee_l"], ws_pos(joints["calf_l"]), "knee_l")

    if joints["foot_l"]:
        safe_move(guides["ankle_l"], ws_pos(joints["foot_l"]), "ankle_l")

    # -----------------------------------------------------
    # Right leg
    # -----------------------------------------------------
    if joints["thigh_r"]:
        safe_move(guides["thigh_r"], ws_pos(joints["thigh_r"]), "thigh_r")

    if joints["calf_r"]:
        safe_move(guides["knee_r"], ws_pos(joints["calf_r"]), "knee_r")

    if joints["foot_r"]:
        safe_move(guides["ankle_r"], ws_pos(joints["foot_r"]), "ankle_r")

    print("=" * 60)
    print("Done")
    print("=" * 60)
    
    
    
    # align_mgear_guides_to_epic() の最後あたりに追加。
    
    # Arm upv
    move_upv_guide("arm_L0_upv", "upperarm_l", "lowerarm_l", "hand_l", 1.0)
    move_upv_guide("arm_R0_upv", "upperarm_r", "lowerarm_r", "hand_r", 1.0)

    # Leg upv
    move_upv_guide("leg_L0_upv", "thigh_l", "calf_l", "foot_l", 1.0)
    move_upv_guide("leg_R0_upv", "thigh_r", "calf_r", "foot_r", 1.0)
    
    # 指を足す
    # -----------------------------
    # Left fingers
    # -----------------------------
    align_finger_chain("thumb_L0",  ["thumb_01_l", "thumb_02_l", "thumb_03_l", "thumb_03_l"])
    align_finger_chain("index_L0",  ["index_metacarpal_l", "index_01_l", "index_02_l", "index_03_l"])
    align_finger_chain("middle_L0", ["middle_metacarpal_l", "middle_01_l", "middle_02_l", "middle_03_l"])
    align_finger_chain("ring_L0",   ["ring_metacarpal_l", "ring_01_l", "ring_02_l", "ring_03_l"])
    align_finger_chain("pinky_L0",  ["pinky_metacarpal_l", "pinky_01_l", "pinky_02_l", "pinky_03_l"])

    aim_blade_from_chain("thumb_L0_blade",  "thumb_01_l", "thumb_02_l")
    aim_blade_from_chain("index_L0_blade",  "index_metacarpal_l", "index_01_l")
    aim_blade_from_chain("middle_L0_blade", "middle_metacarpal_l", "middle_01_l")
    aim_blade_from_chain("ring_L0_blade",   "ring_metacarpal_l", "ring_01_l")
    aim_blade_from_chain("pinky_L0_blade",  "pinky_metacarpal_l", "pinky_01_l")

    # -----------------------------
    # Right fingers
    # -----------------------------
    align_finger_chain("thumb_R0",  ["thumb_01_r", "thumb_02_r", "thumb_03_r", "thumb_03_r"])
    align_finger_chain("index_R0",  ["index_metacarpal_r", "index_01_r", "index_02_r", "index_03_r"])
    align_finger_chain("middle_R0", ["middle_metacarpal_r", "middle_01_r", "middle_02_r", "middle_03_r"])
    align_finger_chain("ring_R0",   ["ring_metacarpal_r", "ring_01_r", "ring_02_r", "ring_03_r"])
    align_finger_chain("pinky_R0",  ["pinky_metacarpal_r", "pinky_01_r", "pinky_02_r", "pinky_03_r"])

    aim_blade_from_chain("thumb_R0_blade",  "thumb_01_r", "thumb_02_r")
    aim_blade_from_chain("index_R0_blade",  "index_metacarpal_r", "index_01_r")
    aim_blade_from_chain("middle_R0_blade", "middle_metacarpal_r", "middle_01_r")
    aim_blade_from_chain("ring_R0_blade",   "ring_metacarpal_r", "ring_01_r")
    aim_blade_from_chain("pinky_R0_blade",  "pinky_metacarpal_r", "pinky_01_r")
    
# 実行
align_mgear_guides_to_epic()

これがこうなる。ぴったりジョイントにガイドがあう状態。

ちょっと整理したバージョン

# -*- coding: utf-8 -*-
# =========================================================
# mGear Guide Auto Align Script (Comment-rich version)
# ---------------------------------------------------------
# 目的:
#   既存ジョイント(UE系など)から mGear Guide を
#   「位置+PoleVector+指のblade方向」まで自動配置する
#
# 設計思想:
#   ・まず位置を100%合わせる
#   ・IKの安定に重要な PoleVector を自動配置
#   ・指は回転ではなく blade(方向ガイド)で補正
#   ・回転は“必要な箇所だけ”後処理(やりすぎない)
#
# 注意:
#   ・GuideのTransformは直接Freezeしない(mGear構造破壊)
#   ・Mesh / Joint 側は Scale=1 を前提
# =========================================================

import maya.cmds as cmds
import math

# =========================================================
# 基本ユーティリティ
# =========================================================

def _short_name(node):
    """フルパスから短い名前だけ取る"""
    return node.split("|")[-1]


def find_unique_by_short_name(short_name):
    """
    シーン内から短い名前一致ノードを取得
    ※複数ある場合は最初の1つを使う(警告出す)
    """
    matches = cmds.ls("*|" + short_name, long=True) or []
    root_matches = cmds.ls(short_name, long=True) or []
    all_matches = list(set(matches + root_matches))

    if not all_matches:
        return None

    if len(all_matches) > 1:
        print("[WARN] multiple nodes found:", short_name)
        return all_matches[0]

    return all_matches[0]


def first_existing(candidates):
    """
    候補リストの中から存在するノードを返す
    ※Guide名の版差対策
    """
    for c in candidates:
        node = find_unique_by_short_name(c)
        if node:
            return node
    return None


def ws_pos(node):
    """ワールド座標取得"""
    return cmds.xform(node, q=True, ws=True, t=True)


def set_ws_pos(node, pos):
    """ワールド座標で移動"""
    cmds.xform(node, ws=True, t=pos)


def avg_pos(nodes):
    """複数ノードの平均位置(spineなどで使用)"""
    pts = [ws_pos(n) for n in nodes]
    c = float(len(pts))
    return [
        sum(p[0] for p in pts)/c,
        sum(p[1] for p in pts)/c,
        sum(p[2] for p in pts)/c
    ]


# =========================================================
# ベクトル計算(PoleVector用)
# =========================================================

def vec_sub(a, b): return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]
def vec_add(a, b): return [a[0]+b[0], a[1]+b[1], a[2]+b[2]]
def vec_mul(a, s): return [a[0]*s, a[1]*s, a[2]*s]

def vec_len(a):
    return math.sqrt(a[0]**2 + a[1]**2 + a[2]**2)

def vec_norm(a):
    l = vec_len(a)
    if l < 1e-8:
        return [0,0,0]
    return [a[0]/l, a[1]/l, a[2]/l]

def vec_dot(a,b):
    return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]

def vec_cross(a,b):
    return [
        a[1]*b[2]-a[2]*b[1],
        a[2]*b[0]-a[0]*b[2],
        a[0]*b[1]-a[1]*b[0]
    ]


# =========================================================
# PoleVector(IK安定の核心)
# =========================================================

def get_pole_vector_position(start, mid, end):
    """
    3点からIK平面を作り、適切なPoleVector位置を算出
    """
    line = vec_norm(vec_sub(end, start))
    proj_len = vec_dot(vec_sub(mid, start), line)
    proj = vec_add(start, vec_mul(line, proj_len))

    arrow = vec_sub(mid, proj)
    if vec_len(arrow) < 1e-6:
        arrow = vec_cross(line, [0,1,0])

    arrow = vec_norm(arrow)
    length = vec_len(vec_sub(mid,start)) + vec_len(vec_sub(end,mid))

    return vec_add(mid, vec_mul(arrow, length*0.5))


def move_upv(guide, j0, j1, j2):
    """PoleVector Guide配置"""
    g = first_existing([guide])
    a = find_unique_by_short_name(j0)
    b = find_unique_by_short_name(j1)
    c = find_unique_by_short_name(j2)

    if not (g and a and b and c):
        print("[SKIP upv]", guide)
        return

    pos = get_pole_vector_position(ws_pos(a), ws_pos(b), ws_pos(c))
    set_ws_pos(g, pos)
    print("[OK upv]", guide)


# =========================================================
# 指チェーン
# =========================================================

def align_finger(prefix, joints):
    """
    指は単純に位置を順に合わせる
    """
    guides = [
        first_existing([prefix+"_root"]),
        first_existing([prefix+"_0_loc"]),
        first_existing([prefix+"_1_loc"]),
        first_existing([prefix+"_2_loc"])
    ]

    src = [find_unique_by_short_name(j) for j in joints]

    for g, j in zip(guides, src):
        if g and j:
            set_ws_pos(g, ws_pos(j))
            print("[OK finger]", g)


def aim_blade(blade, j0, j1, up=(0,1,0)):
    """
    指の向きはbladeで制御
    → root→先端方向に向ける
    """
    b = first_existing([blade])
    a = find_unique_by_short_name(j0)
    c = find_unique_by_short_name(j1)

    if not (b and a and c):
        print("[SKIP blade]", blade)
        return

    set_ws_pos(b, ws_pos(a))

    tmp = cmds.spaceLocator()[0]
    set_ws_pos(tmp, ws_pos(c))

    con = cmds.aimConstraint(tmp, b,
        aimVector=(1,0,0),
        upVector=(0,1,0),
        worldUpType="vector",
        worldUpVector=up
    )
    cmds.delete(con)
    cmds.delete(tmp)

    print("[OK blade]", blade)


# =========================================================
# メイン処理
# =========================================================

def run():
    print("="*50)
    print("Start mGear Guide Align")
    print("="*50)

    # -------------------------
    # 中心
    # -------------------------
    set_ws_pos(first_existing(["body_C0_root"]), ws_pos(find_unique_by_short_name("pelvis")))

    set_ws_pos(first_existing(["spine_C0_spineBase"]),
               avg_pos([find_unique_by_short_name("spine_01"),
                        find_unique_by_short_name("spine_03")]))

    set_ws_pos(first_existing(["spine_C0_spineTop"]),
               avg_pos([find_unique_by_short_name("spine_03"),
                        find_unique_by_short_name("spine_05")]))

    set_ws_pos(first_existing(["spine_C0_chest"]),
               ws_pos(find_unique_by_short_name("spine_05")))

    set_ws_pos(first_existing(["neck_C0_neck"]),
               ws_pos(find_unique_by_short_name("neck_01")))

    set_ws_pos(first_existing(["neck_C0_head"]),
               ws_pos(find_unique_by_short_name("neck_02")))

    # -------------------------
    # 腕
    # -------------------------
    set_ws_pos(first_existing(["clavicle_L0_root"]), ws_pos(find_unique_by_short_name("clavicle_l")))
    set_ws_pos(first_existing(["arm_L0_elbow"]), ws_pos(find_unique_by_short_name("lowerarm_l")))
    set_ws_pos(first_existing(["arm_L0_wrist"]), ws_pos(find_unique_by_short_name("hand_l")))

    set_ws_pos(first_existing(["clavicle_R0_root"]), ws_pos(find_unique_by_short_name("clavicle_r")))
    set_ws_pos(first_existing(["arm_R0_elbow"]), ws_pos(find_unique_by_short_name("lowerarm_r")))
    set_ws_pos(first_existing(["arm_R0_wrist"]), ws_pos(find_unique_by_short_name("hand_r")))

    # -------------------------
    # 脚
    # -------------------------
    set_ws_pos(first_existing(["leg_L0_root"]), ws_pos(find_unique_by_short_name("thigh_l")))
    set_ws_pos(first_existing(["leg_L0_knee"]), ws_pos(find_unique_by_short_name("calf_l")))
    set_ws_pos(first_existing(["leg_L0_ankle"]), ws_pos(find_unique_by_short_name("foot_l")))

    set_ws_pos(first_existing(["leg_R0_root"]), ws_pos(find_unique_by_short_name("thigh_r")))
    set_ws_pos(first_existing(["leg_R0_knee"]), ws_pos(find_unique_by_short_name("calf_r")))
    set_ws_pos(first_existing(["leg_R0_ankle"]), ws_pos(find_unique_by_short_name("foot_r")))

    # -------------------------
    # PoleVector
    # -------------------------
    move_upv("arm_L0_upv","upperarm_l","lowerarm_l","hand_l")
    move_upv("arm_R0_upv","upperarm_r","lowerarm_r","hand_r")
    move_upv("leg_L0_upv","thigh_l","calf_l","foot_l")
    move_upv("leg_R0_upv","thigh_r","calf_r","foot_r")

    # -------------------------
    # 指
    # -------------------------
    align_finger("index_L0",["index_metacarpal_l","index_01_l","index_02_l","index_03_l"])
    align_finger("index_R0",["index_metacarpal_r","index_01_r","index_02_r","index_03_r"])

    # blade
    aim_blade("index_L0_blade","index_metacarpal_l","index_01_l")
    aim_blade("index_R0_blade","index_metacarpal_r","index_01_r")

    print("="*50)
    print("Done")
    print("="*50)


# 実行
run()

ビルドした結果

mGearのAuto Fit Guideを検証した際に、内部処理に依存する部分で不安定になるケースがあったため、既存ジョイントからGuideを配置するスクリプトを自作しました。

まず位置については、各Guideを対応するジョイントのワールド座標に直接配置し、脊椎のような複数構造は中間点を使って補間しています。

その上で、IKの安定性を確保するために、腕と脚は3点から平面を計算し、Pole Vectorを自動配置する処理を入れています。これにより、ビルド後のIKの破綻を抑えています。

回転については一括で処理せず、特に指に関してはbladeを用いて方向のみ補正し、過剰な自動化による崩れを防いでいます。

このように、位置・IK安定・方向補正を段階的に分離することで、完全自動ではなく、実運用に耐える半自動フローとして設計しています。

mGearを使ってみた。021 GuidからRigContrallerのビルド とスキニング(Maya2020 +mGear3.7.0版)

どうにかこうにか手動でGuidを昔のジョイントに合わせたものがこれ

https://www.mediafire.com/file/c7jlzs28s5bpvn5/MiChoPa094_imported_mGearBipeGuide_And_Genesis8FemaleJoint.zip/file

指が間違ってた。

でこの動画の続きによると

型の始まりのGuidのCUBEをクリックして Dupl.Sym.を押すと左右が対象になるみたいだったけど
つかってない手でやった。

つぎにGuidのトップノードを選択して新シーンとして保存してる

Guidトップ選択して Build From Selection

走りだす。

リグが出力されたようだ このあと動画のようにコントロールリグのサイズを変更しなくてスキンバインドのテストしたほうがいいと思う。

リグデフォーマグループを選択する

上やじるしを押すとすべての読み取るのに必要なジョイントが選択されます。

つまりはこれがしたいみたい。

設定は
ジョイント階層
測地線ボクセル
クラシックリニア
インタラクティブ
距離
チェックあり

チェック
チェック
チェック
チェックなし

ではリグを動かしてみましょう!

動かしにくいので最後にリグコントローラーのサイズを変更しよう。

ここでジョイントの表示サイズがでかすぎたのか変更してる。

表示サイズを大きくすると、ジョイントやボーンが選択しやすくなります。 表示サイズを小さくすると、フレクサなど他のオブジェクトが選択しやすくなります。 ディスプレイ > アニメーション > ジョイント サイズ(Display > Animation > Joint Size)を選択します。

一番下の8角形が大きすぎたので

Control+右クリックで「CV」を選択して小さくスケールしてる。

スケールできないからスケールツールをダブルクリックで起動しなおすことで初期化してスケールしてる。

ここでは逆に大きくした。

手の幅ぐらいのサイズにした

コントローラーハンドルここではBOXが頭にめり込んでるので直している。

した

体も同じくボディに埋まっているコントロールハンドルをCVモードで拡大していく

こんな感じ

mGearを使ってみた。020 (Maya2020 +mGear3.7.0版 既存ジョイントからGuideを再構築するmGearフロー検証

ちょっとさらに仕切り直し

Maya2016 +mGear2.6.1系 から

Maya2020 +mGear3.7.0_beta_01に乗り換えてみた。

1、モデルを用意した

2,mGear/Shifter/Guide Managerを起動

3,mGear/Shifter/Guid Template Samples/Biped Template を実行
黄色いguidが作成された。

4、guideのサイズをモデルに合わせる
  合わせやすいようにシェーディング/ワイヤーフレームシェードにした

ここではもとにあったジョイントに全くびったり合わせるために

ジョイントを読み込みました。

位置合わせツールがあったら楽なのにと思いました。

いろいろやったが下のスクリプトなど作ったがJointの数とターゲットの数も違うし使えなかった仕事としてならたぶん無理やりできるんだろうなと思う。

import maya.cmds as cmds
selectedList=cmds.ls( selection=True )
print("selectedList="+str(selectedList))
jointOriginal=selectedList[0]
guidTarget=selectedList[1]

#world座標を取る いろいろ考えたけど親基準にローカル座標そろえるのやめた。なぜなら親もローカル座標そろえないと話は始まらないから。
#cmds.pointPosition( 'particle1.pt[1]', world=True )
#def getParentWorldPos()
j_parentlist=cmds.listRelatives(jointOriginal, parent=True )
g_parentlist=cmds.listRelatives(jointOriginal, parent=True )
#print("parentlist="+str(parentlist))
jointOriginalParent=j_parentlist[0]
guidTargetParent=g_parentlist[0]
print("jointOriginalParent="+str(jointOriginalParent))
print("guidTargetParent="+str(guidTargetParent))
jointOriginalParentWorldXYZ=cmds.xform(jointOriginalParent,worldSpace=True, q=True, translation=True )
guidTargetParentWorldXYZ=cmds.xform(guidTargetParent,worldSpace=True, q=True, translation=True )
jointOriginalParentWorldXYZrot=cmds.xform(jointOriginalParent,worldSpace=True, q=True, rotation=True )
guidTargetParentWorldXYZrot=cmds.xform(guidTargetParent,worldSpace=True, q=True, rotation=True )
print("jointOriginalParentWorldXYZ="+str(jointOriginalParentWorldXYZ))
print("guidTargetParentWorldXYZ="+str(guidTargetParentWorldXYZ))
w_xDiff=jointOriginalParentWorldXYZ[0]-guidTargetParentWorldXYZ[0]
w_yDiff=jointOriginalParentWorldXYZ[1]-guidTargetParentWorldXYZ[1]
w_zDiff=jointOriginalParentWorldXYZ[2]-guidTargetParentWorldXYZ[2]

w_xDiffR=jointOriginalParentWorldXYZrot[0]-guidTargetParentWorldXYZrot[0]
w_yDiffR=jointOriginalParentWorldXYZrot[1]-guidTargetParentWorldXYZrot[1]
w_zDiffR=jointOriginalParentWorldXYZrot[2]-guidTargetParentWorldXYZrot[2]
worldDiff=[w_xDiff,w_yDiff,w_zDiff]
worldDiffR=[w_xDiffR,w_yDiffR,w_zDiffR]

print("worldDiff="+str(worldDiff))
print("worldDiffR="+str(worldDiffR))
""""""
tx=cmds.getAttr(jointOriginal+".tx")
ty=cmds.getAttr(jointOriginal+".ty")
tz=cmds.getAttr(jointOriginal+".tz")
print(" tx= "+str(tx)+" ty= "+str(ty)+" tz= "+str(tz))
"""
cmds.setAttr(guidTarget+".tx",tx)
cmds.setAttr(guidTarget+".ty",ty)
cmds.setAttr(guidTarget+".tz",tz)
"""
guid_Scale=9.869
goTX=guid_Scale/tx+w_xDiff
goTY=guid_Scale/ty+w_yDiff
goTZ=guid_Scale/tz+w_zDiff
print(" goTX= "+str(goTX)+" goTY= "+str(goTY)+" goTZ= "+str(goTZ))

""""""
#ZをXに
""""""
cmds.setAttr(guidTarget+".tx",goTZ)
cmds.setAttr(guidTarget+".ty",goTY)
cmds.setAttr(guidTarget+".tz",goTX)

"""
cmds.setAttr(guidTarget+".rx",goTZ)
cmds.setAttr(guidTarget+".ry",goTX)
cmds.setAttr(guidTarget+".rz",goTY)
"""

地道にジョイントにguideを合わせるしかない。(今のところ)

単位が違くなってるのはさっきguide側にスケールかけたからだった。

これはjoint rHand

これはguide のarm_R0_wrist

もう少しで、いけそうなのになあ

mGearを使ってみた。004

用意したモデルに

Shifter/Guide Template Samples /Biped Templateをやってみたらエラー

エラー: AttributeError: file C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\core\utils.py line 223: nt.NurbsCurve(u’global_C0_axisShape’) has no attribute or method named ‘lineWidth’ #

バージョン的にだめな予感がしてきたので過去のmGearを探すことにした。

https://github.com/mgear-dev/mgear/releases/tag/v2.6.1

mGearを使ってみた。004

こんどは

mGear/Shifter/Guid Mannager起動してみたら起動したエラー

File “C:/Users/whaison/Documents/maya/2016/ja_JP/scripts/mGear/mgear_3.7.0_beta_01/release/scripts/mgear/shifter_classic_components\ui_slider_01\settingsUI.py”, line 11, in
from PySide2 import QtCore, QtGui, QtWidgets
ImportError: No module named PySide2

Maya2016にはPyside2はありませんよねそりゃ

以下の3つのファイルのソースを変更する

C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\shifter_classic_components\ui_slider_01\settingsUI.py

C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\shifter_classic_components\ui_container_01\settingsUI.py

C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\shifter_classic_components\sdk_control_01\settingsUI.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'C:\Users\Justi\OneDrive\Documents\maya\mGear\scripts\mgear\shifter_classic_components\sdk_control_01\settingsUI.ui'
#
# Created: Sat Aug 24 10:20:58 2019
#      by: pyside2-uic  running on PySide2 2.0.0~alpha0
#
# WARNING! All changes made in this file will be lost!

#from PySide2 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui

エラーなく動いた。

今回の変更はただのUIのロード先の変更なのでたーぶーん全体的には動く予感

mGearを使ってみた。003

エラー: AttributeError: file C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\core\pyqt.py line 160: ‘module’ object has no attribute ‘workspaceControl’

についてだが

この160行目をコメントしたら動いた。

C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\core\pyqt.py

"""pyQt/pySide widgets and helper functions for mGear"""

#############################################
# GLOBAL
#############################################
import os
import traceback
import maya.OpenMayaUI as omui
import pymel.core as pm

from mgear.vendor.Qt import QtWidgets
from mgear.vendor.Qt import QtCompat
from mgear.vendor.Qt import QtGui
from mgear.vendor.Qt import QtSvg

UI_EXT = "ui"

_LOGICAL_DPI_KEY = "_LOGICAL_DPI"

#################
# Old qt importer
#################


def _qt_import(binding, shi=False, cui=False):
    QtGui = None
    QtCore = None
    QtWidgets = None
    wrapInstance = None

    if binding == "PySide2":
        from PySide2 import QtGui, QtCore, QtWidgets
        import shiboken2 as shiboken
        from shiboken2 import wrapInstance
        from pyside2uic import compileUi

    elif binding == "PySide":
        from PySide import QtGui, QtCore
        import PySide.QtGui as QtWidgets
        import shiboken
        from shiboken import wrapInstance
        from pysideuic import compileUi

    elif binding == "PyQt4":
        from PyQt4 import QtGui
        from PyQt4 import QtCore
        import PyQt4.QtGui as QtWidgets
        from sip import wrapinstance as wrapInstance
        from PyQt4.uic import compileUi
        print("Warning: 'shiboken' is not supported in 'PyQt4' Qt binding")
        shiboken = None

    else:
        raise Exception("Unsupported python Qt binding '%s'" % binding)

    rv = [QtGui, QtCore, QtWidgets, wrapInstance]
    if shi:
        rv.append(shiboken)
    if cui:
        rv.append(compileUi)
    return rv


def qt_import(shi=False, cui=False):
    """
    import pyside/pyQt

    Returns:
        multi: QtGui, QtCore, QtWidgets, wrapInstance

    """
    lookup = ["PySide2", "PySide", "PyQt4"]

    preferredBinding = os.environ.get("MGEAR_PYTHON_QT_BINDING", None)
    if preferredBinding is not None and preferredBinding in lookup:
        lookup.remove(preferredBinding)
        lookup.insert(0, preferredBinding)

    for binding in lookup:
        try:
            return _qt_import(binding, shi, cui)
        except Exception:
            pass

    raise _qt_import("ThisBindingSurelyDoesNotExist", False, False)


compileUi = qt_import(shi=True, cui=True)[-1]

#############################################
# helper Maya pyQt functions
#############################################


def ui2py(filePath=None, *args):
    """Convert qtDesigner .ui files to .py"""

    if not filePath:
        startDir = pm.workspace(q=True, rootDirectory=True)
        filePath = pm.fileDialog2(dialogStyle=2,
                                  fileMode=1,
                                  startingDirectory=startDir,
                                  fileFilter='PyQt Designer (*%s)' % UI_EXT,
                                  okc="Compile to .py")
        if not filePath:
            return False
        filePath = filePath[0]
    if not filePath:
        return False

    if not filePath.endswith(UI_EXT):
        filePath += UI_EXT
    compiledFilePath = filePath[:-2] + "py"
    pyfile = open(compiledFilePath, 'w')
    compileUi(filePath, pyfile, False, 4, False)
    pyfile.close()

    info = "PyQt Designer file compiled to .py in: "
    pm.displayInfo(info + compiledFilePath)


def maya_main_window():
    """Get Maya's main window

    Returns:
        QMainWindow: main window.

    """

    main_window_ptr = omui.MQtUtil.mainWindow()
    return QtCompat.wrapInstance(long(main_window_ptr), QtWidgets.QWidget)


def showDialog(dialog, dInst=True, dockable=False, *args):
    """
    Show the defined dialog window

    Attributes:
        dialog (QDialog): The window to show.

    """
    if dInst:
        try:
            for c in maya_main_window().children():
                if isinstance(c, dialog):
                    c.deleteLater()
        except Exception:
            pass

    # Create minimal dialog object

    # if versions.current() >= 20180000:
    #     windw = dialog(maya_main_window())
    # else:
    windw = dialog()

    # ensure clean workspace name
    if hasattr(windw, "toolName") and dockable:
        control = windw.toolName + "WorkspaceControl"
        #if pm.workspaceControl(control, q=True, exists=True):
        #    pm.workspaceControl(control, e=True, close=True)
        #    pm.deleteUI(control, control=True)
    desktop = QtWidgets.QApplication.desktop()
    screen = desktop.screen()
    screen_center = screen.rect().center()
    windw_center = windw.rect().center()
    windw.move(screen_center - windw_center)

    # Delete the UI if errors occur to avoid causing winEvent
    # and event errors (in Maya 2014)
    try:
        if dockable:
            windw.show(dockable=True)
        else:
            windw.show()
        return windw
    except Exception:
        windw.deleteLater()
        traceback.print_exc()


def deleteInstances(dialog, checkinstance):
    """Delete any instance of a given dialog

    Delete any instance of a given dialog and if the dialog is
    instance of checkinstance.

    Attributes:
        dialog (QDialog): The dialog to delete.
        checkinstance (QDialog): The instance to check the type of dialog.

    """
    mayaMainWindow = maya_main_window()
    for obj in mayaMainWindow.children():
        if isinstance(obj, checkinstance):
            if obj.widget().objectName() == dialog.toolName:
                print 'Deleting instance {0}'.format(obj)
                mayaMainWindow.removeDockWidget(obj)
                obj.setParent(None)
                obj.deleteLater()


def fakeTranslate(*args):
    """Fake Translation

    fake QApplication.translate. This function helps to bypass the
    incompativility for the Unicode utf8  deprecated in pyside2

    """
    return args[1]


def position_window(window):
    """ set the position for the windonw

    Function borrowed from Cesar Saez QuickLauncher
    Args:
        window (QtWidget): the window to position
    """
    pos = QtGui.QCursor.pos()
    window.move(pos.x(), pos.y())


def get_main_window(widget=None):
    """Get the active window

    Function borrowed from Cesar Saez QuickLauncher
    Args:
        widget (QtWidget, optional): window

    Returns:
        QtWidget: parent of the window
    """
    widget = widget or QtWidgets.QApplication.activeWindow()
    if widget is None:
        return
    parent = widget.parent()
    if parent is None:
        return widget
    return get_main_window(parent)


def get_instance(parent, gui_class):
    """Get instace of a window from a given parent

    Function borrowed from Cesar Saez QuickLauncher
    Args:
        parent (QtWidget): parent
        gui_class (QtWidget): instance class to check

    """
    for children in parent.children():
        if isinstance(children, gui_class):
            return children
    return None


def get_icon_path(icon_name=None):
    """ Gets the directory path to the icon
    """

    file_dir = os.path.dirname(__file__)

    if "\\" in file_dir:
        file_dir = file_dir.replace("\\", "/")
    if icon_name:
        return "{0}/icons/{1}".format(file_dir, icon_name)
    else:
        return "{}/icons".format(file_dir)


def get_icon(icon, size=24):
    """get svg icon from icon resources folder as a pixel map
    """
    img = get_icon_path("{}.svg".format(icon))
    svg_renderer = QtSvg.QSvgRenderer(img)
    image = QtGui.QImage(size, size, QtGui.QImage.Format_ARGB32)
    # Set the ARGB to 0 to prevent rendering artifacts
    image.fill(0x00000000)
    svg_renderer.render(QtGui.QPainter(image))
    pixmap = QtGui.QPixmap.fromImage(image)

    return pixmap


# dpi scale test -------------------------------------------------------------
def get_logicaldpi():
    """attempting to "cache" the query to the maya main window for speed

    Returns:
        int: dpi of the monitor
    """
    if _LOGICAL_DPI_KEY not in os.environ.keys():
        try:
            logical_dpi = maya_main_window().logicalDpiX()
        except Exception:
            logical_dpi = 96
        finally:
            os.environ[_LOGICAL_DPI_KEY] = str(logical_dpi)
    return int(os.environ.get(_LOGICAL_DPI_KEY)) or 96


def dpi_scale(value, default=96, min_=1, max_=2):
    """Scale the provided value by the scale that maya is using
    which is derived from the 'average' dpi of 96 from windows, linux, osx.

    Args:
        value (int, float): value to scale
        default (int, optional): assumed default from various platforms
        min_ (int, optional): if you do not want the value under 96 dpi
        max_ (int, optional): if you do not want a value higher than 200% scale

    Returns:
        # int, float: scaled value
    """
    return value * max(min_, min(get_logicaldpi() / float(default), max_))

mGear/Shifter/Guid Mannager起動してみたら起動した。

mGearを使ってみた。002

mGear/Shifter/Guid Mannager起動してみたら起動できなかった。

エラー: AttributeError: file C:\Users\whaison\Documents\maya\2016\ja_JP\scripts\mGear\mgear_3.7.0_beta_01\release\scripts\mgear\core\pyqt.py line 160: ‘module’ object has no attribute ‘workspaceControl’

zipフォルダにはいってるdrag_n_drop_install.pyってなんやろね

たぶんインストールが的確じゃなかった。

drag_n_drop_install.pyの

__file__がみつかりません

エラー: line 1: NameError: file line 59: global name ‘file‘ is not defined

こうすりゃ動くはず

"""
Drag and drop this file into your viewport to run the mGear installer.

1. Default Install:
     Will install in the users documents folder where Maya is installed, a
     restart will not be required.

2. Custom Install:
    Will install in the user specified directory, this method will write the
    correct module path in the Maya.env file, but a restart will be required
    in this case.

"""
import os
import sys
import shutil

try:
    import pymel.core as pm
    from pymel import mayautils
    from maya.app.startup import basic
    is_maya = True

except ImportError():
    is_maya = False

# -- maya requires this in order to successfully execute


def onMayaDroppedPythonFile(*args, **kwargs):
    """
    This function is only supported since Maya 2017 Update 3
    """
    pass


def files_exist(file_list):
    """Get all file paths that exist.

    Args:
        file_list (list): List of paths to iterate through

    Returns:
        list: List of all paths found
    """

    file_found = []
    for item in file_list:
        if os.path.exists(item):
            found = item
            file_found.append(found)

    return file_found


def _dropped_install():

    # -- current folder where the installer resides
    #current_folder = os.path.dirname(__file__)
    current_folder = os.path.dirname("C:/Users/whaison/Documents/maya/2016/ja_JP/scripts/mGear/mgear_3.7.0_beta_01/drag_n_drop_install.py")
    # -- folder of all conents of mgear resides
    mgear_folder = os.path.normpath(os.path.join(current_folder, "release"))
    custom_path = False

    # -- default modules folder to install to
    install_path = os.path.normpath(os.path.join(
        os.environ['MAYA_APP_DIR'], "modules"))

    # -- mesage for the main installer dialog window
    message = (
        "mGear will be installed here:\n"
        "{}\n"
        "\n"
        "Make sure to SAVE your scene before continuing.\n"
        "\n"
        "NOTE: Installing to a custom directory will require a restart.\n"
        "\n"
        "Would you like to continue?".format(install_path)
    )

    # -- installer dialog window
    input = pm.confirmDialog(title="Installation Path",
                             message=message,
                             icon="question",
                             button=["OK", "Cancel", "No, Custom Path"],
                             cancelButton="Cancel",
                             dismissString="Cancel")

    if input == "Cancel":
        pm.displayError("mGear installation has been cancelled.")
        return
    elif input == "No, Custom Path":
        install_path = None

    # -- custom path will be set here
    if install_path is None:
        install_path = pm.fileDialog2(
            fileMode=3,
            okCaption="Install here",
            caption="Please choose a folder to install mGear...")[0]
        custom_path = True

    # -- if install path is still None, exit
    if not install_path:
        return

    # -- flush the undo que in case there is anything that might disrupt
    # the install
    pm.flushUndo()

    # -- create a fresh scene incase there are any solvers still
    # loaded in memory
    pm.newFile(force=True)

    # -- mgear install path
    mgear_install_path = os.path.join(install_path, "mgear")

    # -- make sure the the mgear folder does not exist
    if os.path.exists(mgear_install_path):
        shutil.rmtree(mgear_install_path)

    # -- copy to a folder because tge copytree gives issues with
    # existing folders
    shutil.copytree(mgear_folder, mgear_install_path)

    # -- look in install directory for files of same name
    search_names = ["platforms", "mGear.mod", "scripts"]

    # -- construct path names
    full_path = [os.path.join(install_path, x) for x in search_names]

    # -- files of the same name
    found = files_exist(full_path)
    if found:
        # -- message if same files are found
        message = ("mGear files already exist in the install location.\n"
                   "\n"
                   "Would you like to overrite them?")
        # -- same files dialog window
        input = pm.confirmDialog(title="Delete existing files",
                                 message=message,
                                 icon="warning",
                                 button=["OK", "Cancel"],
                                 cancelButton="Cancel",
                                 dismissString="Cancel")

        if input == "Cancel":
            # -- delete the temp folder
            shutil.rmtree(mgear_install_path)
            pm.displayError("mGear installation has been cancelled.")
            return

    # -- iterate over folders and remove them
    for item in os.listdir(mgear_install_path):
        if os.path.exists(os.path.join(install_path, item)):
            # -- unload plugins incase there is a faulty install
            try:
                pm.unloadPlugin("mgear_solvers.mll", force=True)
                pm.unloadPlugin("weightDriver.mll", force=True)
            except:
                pass
            # -- delete file and folders
            if os.path.isfile(os.path.join(install_path, item)):
                os.remove(os.path.join(install_path, item))
            elif os.path.isdir(os.path.join(install_path, item)):
                shutil.rmtree(os.path.join(install_path, item))
        # -- move the folder to the install path
        shutil.move(os.path.join(install_path, "mgear", item), install_path)

    # -- delete the temp folder
    shutil.rmtree(mgear_install_path)

    # -- now let's get mgear up and running
    # -- add to the system path
    if not os.path.join(install_path, "scripts") in sys.path:
        sys.path.append(os.path.join(install_path, "scripts"))

    if custom_path:
        # -- we look for the Maya.env file and write the path to it
        env_file = os.path.normpath(os.path.join(
            os.environ["MAYA_APP_DIR"], pm.about(version=True), "Maya.env"))
        if os.path.exists(env_file):
            f = open(env_file, "w")
            f.write("MAYA_MODULE_PATH={0}".format(install_path))
            f.close()

        # -- custom install dialog message
        message = ("A re-start of Maya will be required after this setup.")

        # -- custom install dialog window
        input = pm.confirmDialog(title="Custom Install Path Restart",
                                 message=message,
                                 icon="information",
                                 button=["OK"])

    # -- if not a custom install we can load in the module
    else:
        # -- allows for not having to restart maya
        pm.loadModule(scan=True)
        pm.loadModule(allModules=True)

        # -- reload user setup files
        basic.executeUserSetup()

        # -- force load the plugins just incase it does not happen
        try:
            pm.loadPlugin("mgear_solvers.mll")
            pm.loadPlugin("weightDriver.mll")
        except:
            pass
        # -- installation message
        message = ("Installation Complete!")
        # -- installation dialog window
        input = pm.confirmDialog(title="Installation",
                                 message=message,
                                 icon="information",
                                 button=["OK"])

    pm.displayInfo("Installation Complete")

if is_maya:
    _dropped_install()

で動いた OK押した

インストール完了と出た。