[UE5.7.4][C++]遂に完成したC++プラグインUE用HLSL.EditorのMaterial作成機能

もうこれでノードスパゲッティから解放される

導入方法

0,以下のリンクをダウンロード (特に理由がなければ最新版使ってください)

furcraeaHLSLEditor_UE5.7.4V7.zip
https://drive.google.com/file/d/1ieBRb5zpW36LGldmREROGcWJ8WZot1Xg/view?usp=sharing

furcraeaHLSLEditor_UE5.7.4V6_2.zip
https://drive.google.com/file/d/1BzMiQt4dXcu8p-dIfkyH3v1SRVSKjiCn/view?usp=sharing

furcraeaHLSLEditor_UE5.7.4V6.zip
https://drive.google.com/file/d/1APYn65u1yo3xkJlDFmqJ2yuCE5zHViqc/view?usp=sharing

furcraeaHLSLEditor_UE5.7.4V5.zip
https://drive.google.com/file/d/1YiUHmR1g6d9WwvAtd1rGADsiNZEXRMNX/view?usp=sharing

furcraeaHLSLEditor_UE5.7.4.zip (V4)
https://drive.google.com/file/d/1gInP7W5hFB3pwlwbrbkekSzC8xOwusap/view?usp=sharing

furcraeaHLSLEditor_UE5.7.3V4.zip
https://drive.google.com/file/d/1XhVZgV6FRiyGOOquqO5nJVFXbmg_8DA-/view?usp=sharing

furcraeaHLSLEditor_UE5.7.3V3.zip
https://drive.google.com/file/d/1Qmz5Y_uT0VGKkrk1wskvw2DRbnND6or4/view?
usp=drive_link

furcraeaHLSLEditor_UE5.7.3V2.zip
https://drive.google.com/file/d/1CSXmSebk7hLM02qM5jKDv2ndNJ4jhUi7/view?usp=sharing

1,Pluginsフォルダにzipを解凍して入れてください。

  パスの例:UEプロジェクトフォルダ\Plugins\furcraeaHLSLEditor\Shaders

2,プラグインで「furcraeaHLSLEditor」を有効化

使用方法

コンテンツブラウザで右クリック>Miscellaneous>Code Material Assetを作成

できた New Code Material AssetをダブルクリックでHLSL編集画面が開く

Saveをクリックすると。。。GeneratedフォルダーにM_NewCodeMaterialAssetが作成されます。>CodeMaterialAssetと同じフォルダーに保存するように変更しました。


まだまだ、1つしかCodeMatUser.ushがないので一つしかマテリアルが成立しないなどの不備があるので更新していく予定です。<-以下の画像のように修正済みです。

furcraeaHLSLEditorをGitHub で公開しました。
https://github.com/nobolu-ootsuka-unrealengine/furcraeaHLSLEditor/commits?author=nobolu-ootsuka-unrealengine

修正履歴



1つしかCodeMatUser.ushがないので一つしかマテリアルが成立しないなどの不備を修正

furcraeaHLSLEditor_UE5.7.3V2

Projects と RenderCore を bBuildEditorをガード外に移動(ランタイムでも必要なモジュールのため)


furcraeaHLSLEditor_UE5.7.3V3

CodeMaterialAsset が吐くMaterialのフォルダを (固定)Generated から (ユーザーが作った)CodeMaterialAssetと同じフォルダに変更した

CodeMaterialCompiler.cpp に2箇所変更しました。

① MakeMaterialPackagePath 関数(生成先パスを決定)

  • 変更前:/Game/Generated/M_xxx 固定
  • 変更後:Asset->GetOutermost()->GetName() でアセット自身のパッケージパスを取得し、FPackageName::GetLongPackagePath()
    でフォルダ部分を抜き出して {同じフォルダ}/M_xxx を返す ② CreateOrLoadMaterial の MakeDirectory 呼び出し
  • 変更前:MakeDirectory(“/Game/Generated”) 固定
  • 変更後:MakeDirectory(FPackageName::GetLongPackagePath(MakeMaterialPackagePath(Asset))) —
    生成先パスからフォルダを算出して作成 これにより、例えば Content/0_furcraeaTokyo/CodeMaterialAsset/NewCodeMaterialAsset に保存された CodeMaterialAsset
    に対しては、生成マテリアルも同じ Content/0_furcraeaTokyo/CodeMaterialAsset/M_NewCodeMaterialAsset に出力されます。

furcraeaHLSLEditor_UE5.7.3V4

アウトライン表示用のVertexNormalを拡張のために

FragmentShader VertexShader(Optional)に分離しました!

安定性のためにincludeを辞めました。

furcraeaHLSLEditor_UE5.7.4V5.zip


ルール: FragmentShaderCode / VertexShaderCode には 関数定義を書かない。ヘルパーが必要なら全てインライン化する。
コンパイラの整理

  • ExtractHelperFunctions + エスケープトリック (将来の一般的ヘルパー対応) は保持
    // @param float3 ColorC = 0.90,0.20,1.20//でBPでパラメーター化

furcraeaHLSLEditor_UE5.7.4V6.zip


・日本語コメントがあるとVertex Shader側のコードカラーリングが効いてない不具合を修正
・こんどは両ペインで改行が入力できないを解決
・ユーザーが .ush のボディ内で Phase * Time のように Time を直接書いても View.GameTime として展開され、HLSL
コンパイルエラー → クラッシュが防がれます。

furcraeaHLSLEditor_UE5.7.4V7.zip

ポストプロセスマテリアルを CodeMaterialAssetで作れるようになりました。
Domain が「Post Process」の時に Blendable Location ドロップダウンが横に表示されます:

CustomDepth のアウトライン系には Before Tonemapping が一般的です。

自動生成パラメーターにテクスチャが指定できるようになりました。
// ===== Fragment Shader =====
// @param Texture2D AlbedoTex
// @param Texture2D HatchTex = /Game/3Dnchu/Textures/T_Stroke-SD11.T_Stroke-SD11
// @param float HatchTiling = 10.0

なぜか裏面だけに色が乗ってる場合

● Vertex Shader Code に何か入っているはずです。

Vertex Shader Code が空でないとプラグインが「シェル法アウトライン用」と判断して:

  • TwoSided = true
  • BlendMode = Masked
  • OpacityMask = TwoSidedSign × -1(裏面のみ表示) を自動設定します。これが原因です。 修正手順:
  1. CodeMaterialAsset エディタで Vertex Shader Code を完全に空 にする
  2. Save をクリックして再生成 Vertex Shader Code が空になると:
  • TwoSided = false
  • BlendMode = Opaque
  • OpacityMask なし → 表面だけに通常表示

[mGear5.3.1] 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安定・方向補正を段階的に分離することで、完全自動ではなく、実運用に耐える半自動フローとして設計しています。

[Houdini] Houdini Technical Artist HDA関連 021

[Houdini] Houdini Technical Artist HDA related 021

■ 最終形(これが実務ベース)

vector Nn = normalize(@N);

// 傾き制御
vector worldUp = {0,1,0};
Nn = normalize(lerp(worldUp, Nn, 0.3));

// 向き合わせ
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);

// ランダムひねり
float angle = rand(@ptnum + 10.0) * M_PI * 2;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));

// サイズ
@pscale = fit01(rand(@ptnum), 0.5, 1.5);

// 浮かせる
@P += Nn * (@pscale * 0.5);

ビューポートの右下の目のアイコンでSmoothShade

  • 地面の傾き ✔
  • 棒が法線に沿って傾いてる ✔
  • 密度も出てる ✔
  • 表示もOK ✔

👉 完全に正しく動いてる

■ (TA的に重要)

👉 これ実は👇

UEのFoliage / HISM配置と同じことやってる

  • Scatter → 配置ポイント
  • N → Surface Normal
  • orient → Transform
  • pscale → Scale variation

生える場所を制限(超重要)

// 法線
vector Nn = normalize(@N);

// --- ① 急斜面は削除 ---
if (Nn.y < 0.7) {
    removepoint(0, @ptnum);
    return; // ←これ重要(下の処理止める)
}

// --- ② 傾き制御 ---
vector worldUp = {0,1,0};
Nn = normalize(lerp(worldUp, Nn, 0.3));

// --- ③ 向き合わせ ---
matrix3 m = dihedral({0,1,0}, Nn);
p@orient = quaternion(m);

// --- ④ ランダムひねり ---
float angle = rand(@ptnum + 10.0) * M_PI * 2;
p@orient = qmultiply(p@orient, quaternion(angle, Nn));

// --- ⑤ サイズ ---
@pscale = fit01(rand(@ptnum), 0.5, 1.5);

// --- ⑥ 浮かせる ---
@P += Nn * (@pscale * 0.5);

  • 傾きバラバラ ✔
  • サイズバラバラ ✔
  • ひねり入ってる ✔
  • 急斜面減ってる ✔

👉 完全に意図通り

[自作PC] FURCRAEA_BUILT 総額102万程度の構成 

[Custom-built PC] FURCRAEA_BUILT – Configuration costing approximately 1.02 million yen.

【パーツ構成URL】
◆CPU➡ https://amzn.to/4dh953L
intel インテル CPU 第13世代 Core i9-13900K
◆マザボ➡ https://amzn.to/4rZYI8c
ASUS ROG MAXIMUS Z790 FORMULAにした
◆メモリ➡ https://amzn.to/4cd8uiw
CORSAIR DDR5-6000MHz デスクトップPC用メモリ VENGEANCE DDR5シリーズ (PC5-48000) Intel XMP メモリキット 32GB ホワイト [16GB×2枚] CMK32GX5M2E6000C36W
◆CPUクーラー➡ https://amzn.to/40X0BHR
CORSAIR iCUE LINK H150i LCD AIO 水冷式 CPU クーラー – 【QX120 RGB ファン/IPS LCDスクリーン付き】 – 360mm ラジエーター Intel LGA 1700、AMD AM5 に対応 – iCUE LINK システムハブ付属 ホワイト CW-9061010-WW
◆M.2 SSD(2TB)➡ https://amzn.to/4sFSrQk
CORSAIR MP600 PRO Low Profileシリーズ 2TBモデル 【LPX PCIe Gen4 x4 NVMe M.2】 SSD メモリ ホワイト CSSD-F2000GBMP600PLPW PS5拡張適用
◆HDD➡ https://amzn.to/4sE41ey
Western Digital ウエスタンデジタル WD Red Plus 内蔵 HDD ハードディスク 10TB CMR 3.5インチ SATA 7200rpm キャッシュ256MB NAS メーカー保証3年 WD101EFBX-EC 【国内正規取扱代理店】
◆電源ユニット➡ https://amzn.to/4s1jDYE
ASUS pc電源ユニット Lambda A++ 1200W ATX ROG-THOR-1200P2-GAMING Aura Sync 国内正規代理店品
◆グラボ➡ https://amzn.to/4118ITB
ASUS GeForce RTX 4080 搭載ビデオカード 16GB OC GDDR6X / ROG-STRIX-RTX4080-O16G-WHITE 日本正規流通品
◆PCケース➡ https://amzn.to/4uY1sWg
長尾製作所 オープンフレーム ver.ATX WHITE
※本記事にはアフィリエイトリンクが含まれます。

この構成なら、かなりちゃんとしてる。
白で統一したハイエンド寄りの見た目重視構成としてまとまってるし、互換性も基本問題なしです。CPUはCore i9-13900Kで24コア32スレッド、最大5.8GHz。マザボのROG MAXIMUS Z790 FORMULAはLGA1700対応で13/14/12世代をサポートしているので、組み合わせ自体は普通に合っています。メモリもDDR5-6000のIntel XMP対応で、Z790環境と噛み合っています。

かなり強いのは、見た目だけじゃなく冷却と電源もちゃんと盛れているところ。
13900Kは公称でProcessor Base Power 125W、Maximum Turbo Power 253Wなので発熱は重めですが、H150i LCDは360mmラジエーター採用でLGA1700対応です。ROG THOR 1200P2は80 PLUS Platinumの1200W電源なので、RTX 4080とi9の組み合わせでも電源容量はかなり余裕があります。

グラボもROG STRIX RTX 4080 WHITEなら、性能も見た目もこの路線にかなり合ってる。
ただしこのクラスは大型カードなので、ケース側の物理条件は確認したいところですが、今回のケースは長尾製作所のオープンフレーム ver.ATXで、そもそも“PCパーツが主役”の検証用・展示向けフレーム寄りなので、サイズ干渉の心配は一般的なケースよりかなり少ないです。

気をつけるなら、いちばん大事なのは13900KまわりのBIOSです。
Intelは13世代/14世代Coreデスクトップ向けの不安定化要因として、Intel推奨を超える電力設定やマイクロコードの問題を案内していて、ASUSも2024年8月以降のBIOSで新しいマイクロコードとIntel Default Settingsを反映したと案内しています。なので、この構成なら組んだ直後にZ790 FORMULAのBIOSを最新へ更新して、Intel Default Settings系で使うのがかなり重要です。すでにダメージを受けた個体は更新で治るわけではない、という注意も出ています。

あと、細かく見ると少しだけクセはあります。
SSDのMP600 PRO Low Profile 2TBはGen4 x4 NVMeで使えるので問題ないですが、Low ProfileはPS5向けっぽい背の低さ重視で、マザボ側にしっかりしたM.2ヒートシンクがあるZ790 FORMULAだと“このSSDでなければならない理由”は薄めです。逆にHDDのWD Red Plus 10TBはCMR・7200rpm・256MBキャッシュのモデルで、保存用としては真っ当です。

結論としては、
**この構成は「かなり豪華で、白統一の見た目も強い、ちゃんと実用になるハイエンド自作」**です。
そのうえで本音を言うと、

  • マザボはかなり高級寄り
  • 電源1200Wは余裕多め
  • オープンフレームだからホコリ・騒音・安全性は普通のケースより不利
  • 13900Kは最新BIOS前提で使いたい

この4点がポイントです。

かなり“見せる自作PC”としては強い構成。
必要なら次に、**この構成の「良い点 / 無駄に高い点 / 今買うなら置き換え候補」**を容赦なく仕分けます。

参考URL


z790 Hero 【総額75万】今一番アツいケース『NZXT H9 』で最強の自作PCを組み立てます。せろりんね
https://youtu.be/qP1vu1NQd7w?si=vFDLyee4NsAAqM75

【自作PC】最新の人気白パーツで最強なゲーミングPC組んでみた!【CORSAIR】せろりんね
https://www.youtube.com/watch?v=Qtii3saYP1s

ASUS ROG MAXIMUS Z790 FORMULA を最新 の bios に アップデートしたよ。

1,BIOSまたはMEファームウェアをアップデートする前に、BitLocker回復キーをバックアップし、オペレーティングシステムでBitLocker暗号化を一時停止してください。

BIOSをアップデートする前にBitLockerを一時停止する

[スタート]メニューをクリックします。
[コントロール パネル]、[システムとセキュリティ]、[BitLockerドライブ暗号化]の順に移動します。

無効なのでよし。

2,ASUS ASUS ROG MAXIMUS Z790 FORMULA 最新のUEFI BIOSは、サポートページからダウンロード
https://rog.asus.com/jp/motherboards/rog-maximus/rog-maximus-z790-formula/helpdesk_bios/

バージョン 2001
14.04 MB 2025/05/19
SHA-256 :5F301C30C43AF188BA0726D9DE5AE83860221197CC57CB92DE23BE20BAC2165D
「1. Intelマイクロコードをバージョン0x12Fにアップデートすることで、Intel第13世代および第14世代システムにおけるVminシフトの不安定性の原因となる可能性のあるシステム状態をさらに改善します。
このBIOSをアップデートすると、対応するIntel MEも同時にバージョン16.1.35.2557にアップデートされます。このBIOSをアップデートした後、後で古いBIOSに戻した場合でも、MEのバージョンは更新されたままになりますのでご注意ください。USB
BIOS Flashbackツールを実行する前に、BIOSRenamerを使用してBIOSファイル(A5471.CAP)の名前を変更してください。」

3,アップデート手順


USBメモリの準備:
USBメモリをFAT32形式でフォーマットする。
ダウンロードした圧縮ファイルを解凍し、中身をUSBメモリのルート直下(フォルダに入れない)にコピーする。
BIOSアップデート (EZ Flash 3):
USBメモリをPCのBIOS USBポートに差し込み、PCを再起動する。
画面が表示されたら [Delete] キーを連打しBIOS (UEFI) 画面に入る。
[F7] キーを押して「Advanced Mode」に入り、

「Tool」メニューの「ASUS EZ Flash 3 Utility」を選択する。

USBメモリ内のBIOSファイルを選択し、

bitlockerのオフにしたかい?の警告が
でるのでYES

EZFLASH
このファイルを読み込みますか?


YES
BIOSを本当にアップデートしますか?YES



アップデート成功!自動的にリセットします
何回も自動で再起動される
1.bios updating.


2.ME Firmware is updating.


3.BIOS is updating Thunderbolt Num firmware.

4.BIOS is updating Thunderbolt Nvm firmware.
Do not shut down or reset the system to prevent system bootup failure.
ImageSize : 0x66000
Written so far : 0x64000 bytes
Image write finished.

いずれも
not shut down or reset the system to prevent system bootup failure.
なので触らない事

自動的に再起動する。絶対に電源を切らない

Before making any adjustments in the BIOS,
please press F5 to load the factory default settings to ensure smooth operation.
If setting up Intel RAID,
configure VD options according to the interface type for better compatibility.
Now, please press F1 to enter BIOS setup.

BIOSで設定を変更する前に、
F5キーを押して工場出荷時の設定を読み込み、スムーズな動作を確保してください。
Intel RAIDを設定する場合は、
互換性を高めるために、インターフェースの種類に応じてVDオプションを設定してください。
次に、F1キーを押してBIOSセットアップに入ります。

F5押したが、反応なしなのでF1押した

無事にBIOS アップデートが成功していた。Intel Default Settingもある。

設定を保存して再起動で

Windowsも起動した。

完了:


ASUS ROG Zephyrus G14 GA401IV バッテリー交換



ROG Zephyrus G14 GA401IVのバッテリー(型番: C41N1908)交換は、裏蓋のネジ11本を外し、内部コネクタを抜いて交換する比較的容易な作業です。バッテリーは約6,000円〜1万円で販売されており、PH1ドライバーや樹脂製のヘラがあれば約20〜30分で完了します。


バッテリー交換の手順
事前準備: パソコンの電源を切り、ACアダプタを抜きます。
裏蓋の取り外し: 底面の11本のネジを外します(ゴム足の下のネジも含む)。

下側(ヒンジと反対側)のネジは完全に外れない構造になっているため、そこからプラスチックのヘラでケースをこじ開けます。


バッテリーの取り外し:
バッテリーを固定しているネジ4本を外します。



バッテリーとマザーボードを繋ぐコネクタの金属ブラケットを右にずらします。


黒いサイドのポッチをドライバーでおしてから白いコネクタを上に引き抜いてバッテリーを切り離します。



新しいバッテリーの取り付け: 逆の手順で装着します。


動作確認・再組立て: 裏蓋を閉じる前に電源が入るか確認すると確実です。

Audio Radial Slider のデフォルトvalueの表示が1にならない件

Audio Radial Slider のデフォルトvalueの表示が1にならない件が3日かけてやっと解決した

階層とメソッド一覧

   hierarchy: AudioVolumeRadialSlider
   hierarchy: AudioRadialSlider
   hierarchy: Widget
   hierarchy: Visual
   hierarchy: Object
   UFunction: SetWidgetLayout
   UFunction: SetValueTextReadOnly
   UFunction: SetUnitsTextReadOnly
   UFunction: SetUnitsText
   UFunction: SetTextLabelBackgroundColor
   UFunction: SetSliderThickness
   UFunction: SetSliderProgressColor
   UFunction: SetSliderBarColor
   UFunction: SetShowUnitsText
   UFunction: SetShowLabelOnlyOnHover
   UFunction: SetOutputRange
   UFunction: SetHandStartEndRatio
   UFunction: SetCenterBackgroundColor
   UFunction: GetSliderValue
   UFunction: GetOutputValue
   UFunction: SetVisibility
   UFunction: SetUserFocus
   UFunction: SetToolTipText
   UFunction: SetToolTip
   UFunction: SetRenderTranslation
   UFunction: SetRenderTransformPivot
   UFunction: SetRenderTransformAngle
   UFunction: SetRenderTransform
   UFunction: SetRenderShear
   UFunction: SetRenderScale
   UFunction: SetRenderOpacity
   UFunction: SetNavigationRuleExplicit
   UFunction: SetNavigationRuleCustomBoundary
   UFunction: SetNavigationRuleCustom
   UFunction: SetNavigationRuleBase
   UFunction: SetNavigationRule
   UFunction: SetKeyboardFocus
   UFunction: SetIsEnabled
   UFunction: SetFocus
   UFunction: SetCursor
   UFunction: SetClipping
   UFunction: SetAllNavigationRules
   UFunction: ResetCursor
   UFunction: RemoveFromParent
   UFunction: OnReply__DelegateSignature
   UFunction: OnPointerEvent__DelegateSignature
   UFunction: K2_RemoveFieldValueChangedDelegate
   UFunction: K2_BroadcastFieldValueChanged
   UFunction: K2_AddFieldValueChangedDelegate
   UFunction: IsVisible
   UFunction: IsRendered
   UFunction: IsInViewport
   UFunction: IsHovered
   UFunction: InvalidateLayoutAndVolatility
   UFunction: HasUserFocusedDescendants
   UFunction: HasUserFocus
   UFunction: HasMouseCaptureByUser
   UFunction: HasMouseCapture
   UFunction: HasKeyboardFocus
   UFunction: HasFocusedDescendants
   UFunction: HasAnyUserFocus
   UFunction: GetWidget__DelegateSignature
   UFunction: GetVisibility
   UFunction: GetTickSpaceGeometry
   UFunction: GetText__DelegateSignature
   UFunction: GetSlateVisibility__DelegateSignature
   UFunction: GetSlateColor__DelegateSignature
   UFunction: GetSlateBrush__DelegateSignature
   UFunction: GetRenderTransformAngle
   UFunction: GetRenderOpacity
   UFunction: GetParent
   UFunction: GetPaintSpaceGeometry
   UFunction: GetOwningPlayer
   UFunction: GetOwningLocalPlayer
   UFunction: GetMouseCursor__DelegateSignature
   UFunction: GetLinearColor__DelegateSignature
   UFunction: GetIsEnabled
   UFunction: GetInt32__DelegateSignature
   UFunction: GetGameInstance
   UFunction: GetFloat__DelegateSignature
   UFunction: GetDesiredSize
   UFunction: GetClipping
   UFunction: GetCheckBoxState__DelegateSignature
   UFunction: GetCachedGeometry
   UFunction: GetBool__DelegateSignature
   UFunction: GetAccessibleText
   UFunction: GetAccessibleSummaryText
   UFunction: GenerateWidgetForString__DelegateSignature
   UFunction: GenerateWidgetForObject__DelegateSignature
   UFunction: ForceVolatile
   UFunction: ForceLayoutPrepass
   UFunction: ExecuteUbergraph
   hierarchy: AudioVolumeRadialSlider
   hierarchy: AudioRadialSlider
   hierarchy: Widget
   hierarchy: Visual
   hierarchy: Object
   UFunction: SetWidgetLayout
   UFunction: SetValueTextReadOnly
   UFunction: SetUnitsTextReadOnly
   UFunction: SetUnitsText
   UFunction: SetTextLabelBackgroundColor
   UFunction: SetSliderThickness
   UFunction: SetSliderProgressColor
   UFunction: SetSliderBarColor
   UFunction: SetShowUnitsText
   UFunction: SetShowLabelOnlyOnHover
   UFunction: SetOutputRange
   UFunction: SetHandStartEndRatio
   UFunction: SetCenterBackgroundColor
   UFunction: GetSliderValue
   UFunction: GetOutputValue
   UFunction: SetVisibility
   UFunction: SetUserFocus
   UFunction: SetToolTipText
   UFunction: SetToolTip
   UFunction: SetRenderTranslation
   UFunction: SetRenderTransformPivot
   UFunction: SetRenderTransformAngle
   UFunction: SetRenderTransform
   UFunction: SetRenderShear
   UFunction: SetRenderScale
   UFunction: SetRenderOpacity
   UFunction: SetNavigationRuleExplicit
   UFunction: SetNavigationRuleCustomBoundary
   UFunction: SetNavigationRuleCustom
   UFunction: SetNavigationRuleBase
   UFunction: SetNavigationRule
   UFunction: SetKeyboardFocus
   UFunction: SetIsEnabled
   UFunction: SetFocus
   UFunction: SetCursor
   UFunction: SetClipping
   UFunction: SetAllNavigationRules
   UFunction: ResetCursor
   UFunction: RemoveFromParent
   UFunction: OnReply__DelegateSignature
   UFunction: OnPointerEvent__DelegateSignature
   UFunction: K2_RemoveFieldValueChangedDelegate
   UFunction: K2_BroadcastFieldValueChanged
   UFunction: K2_AddFieldValueChangedDelegate
   UFunction: IsVisible
   UFunction: IsRendered
   UFunction: IsInViewport
   UFunction: IsHovered
   UFunction: InvalidateLayoutAndVolatility
   UFunction: HasUserFocusedDescendants
   UFunction: HasUserFocus
   UFunction: HasMouseCaptureByUser
   UFunction: HasMouseCapture
   UFunction: HasKeyboardFocus
   UFunction: HasFocusedDescendants
   UFunction: HasAnyUserFocus
   UFunction: GetWidget__DelegateSignature
   UFunction: GetVisibility
   UFunction: GetTickSpaceGeometry
   UFunction: GetText__DelegateSignature
   UFunction: GetSlateVisibility__DelegateSignature
   UFunction: GetSlateColor__DelegateSignature
   UFunction: GetSlateBrush__DelegateSignature
   UFunction: GetRenderTransformAngle
   UFunction: GetRenderOpacity
   UFunction: GetParent
   UFunction: GetPaintSpaceGeometry
   UFunction: GetOwningPlayer
   UFunction: GetOwningLocalPlayer
   UFunction: GetMouseCursor__DelegateSignature
   UFunction: GetLinearColor__DelegateSignature
   UFunction: GetIsEnabled
   UFunction: GetInt32__DelegateSignature
   UFunction: GetGameInstance
   UFunction: GetFloat__DelegateSignature
   UFunction: GetDesiredSize
   UFunction: GetClipping
   UFunction: GetCheckBoxState__DelegateSignature
   UFunction: GetCachedGeometry
   UFunction: GetBool__DelegateSignature
   UFunction: GetAccessibleText
   UFunction: GetAccessibleSummaryText
   UFunction: GenerateWidgetForString__DelegateSignature
   UFunction: GenerateWidgetForObject__DelegateSignature
   UFunction: ForceVolatile
   UFunction: ForceLayoutPrepass
   UFunction: ExecuteUbergraph

● AudioRadialSlider という独自クラスです。ソースを探します:

● わかりました。ではビルド・実行後のOutput Logで Cast OK が出ているか確認してください。それだけ教えてください。

LogTemp: Warning: AudioUI: Cast OK [AudioVolume_Master] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_se] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogViewport: Display: Player bShowMouseCursor Changed, False -> True
PIE: Server logged in
PIE: Play in editor total start time 0.113 seconds.
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_Master] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000
LogTemp: Warning: AudioUI: Cast OK [AudioVolume_se] before=1.000000
LogTemp: Warning: AudioUI: after=1.000000

● before=1.0 — 値はすでに1.0になっています。 でも表示が違う?

これは表示テキスト(ラベル)の問題です。UAudioRadialSlider は Value(0〜1 正規化)を OutputRange
に変換して表示します。OutputRange の設定次第で表示が変わります。

● Searched for 1 pattern, read 1 file (ctrl+o to expand)

● SynchronizeProperties は Value を Slate に送っていません。RebuildWidget を確認します:

● Searched for 1 pattern (ctrl+o to expand)

● 原因判明。SynchronizeProperties が Value を Slate に送っていません。GetCachedWidget() で Slate
ウィジェットを取得して直接 SetValue します:

● Searched for 4 patterns, read 1 file (ctrl+o to expand)

● SAudioRadialSlider::SetSliderValue() を使います。Slate ウィジェットに直接セットします:

● Write(D:\Sandbox\UE573petit25Cl\UE573petit25Cl\Source\UE573petit25Cl\AudioUI.h)

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AudioUI.generated.h"

/**
 *  Base class for Audio settings UI.
 *  Initializes AudioVolumeRadialSlider defaults and routes value changes to AudioManager.
 */
UCLASS(abstract)
class UE573PETIT25CL_API UAudioUI : public UUserWidget
{
	GENERATED_BODY()

protected:

	virtual void NativePreConstruct() override;
	virtual void NativeConstruct() override;

public:

	UFUNCTION()
	void InitSliderDefaults();

private:

	UFUNCTION()
	void OnMasterVolumeChanged(float Value);

	UFUNCTION()
	void OnSEVolumeChanged(float Value);
};

● Write(D:\Sandbox\UE573petit25Cl\UE573petit25Cl\Source\UE573petit25Cl\AudioUI.cpp)

// Copyright Epic Games, Inc. All Rights Reserved.

#include "AudioUI.h"
#include "AudioManager.h"
#include "AudioRadialSlider.h"
#include "SAudioRadialSlider.h"
#include "Blueprint/WidgetTree.h"
#include "TimerManager.h"
#include "Engine/World.h"

void UAudioUI::NativePreConstruct()
{
	Super::NativePreConstruct();
	InitSliderDefaults();
}

void UAudioUI::NativeConstruct()
{
	Super::NativeConstruct();

	// Bind slider delegates
	if (UAudioRadialSlider* Master = Cast<UAudioRadialSlider>(GetWidgetFromName(TEXT("AudioVolume_Master"))))
	{
		Master->OnValueChanged.AddDynamic(this, &UAudioUI::OnMasterVolumeChanged);
	}

	if (UAudioRadialSlider* SE = Cast<UAudioRadialSlider>(GetWidgetFromName(TEXT("AudioVolume_se"))))
	{
		SE->OnValueChanged.AddDynamic(this, &UAudioUI::OnSEVolumeChanged);
	}

	if (UWorld* World = GetWorld())
	{
		FTimerHandle Handle;
		World->GetTimerManager().SetTimerForNextTick(this, &UAudioUI::InitSliderDefaults);
	}
}

void UAudioUI::InitSliderDefaults()
{
	if (!WidgetTree) return;

	WidgetTree->ForEachWidget([](UWidget* Widget)
	{
		if (UAudioRadialSlider* Slider = Cast<UAudioRadialSlider>(Widget))
		{
			Slider->Value = 1.0f;

			TSharedPtr<SWidget> CachedWidget = Slider->GetCachedWidget();
			if (CachedWidget.IsValid())
			{
				StaticCastSharedPtr<SAudioRadialSlider>(CachedWidget)->SetSliderValue(1.0f);
			}
		}
	});
}

void UAudioUI::OnMasterVolumeChanged(float Value)
{
	if (UAudioManager* AM = GetGameInstance()->GetSubsystem<UAudioManager>())
	{
		AM->SetMasterVolume(Value);
	}
}

void UAudioUI::OnSEVolumeChanged(float Value)
{
	if (UAudioManager* AM = GetGameInstance()->GetSubsystem<UAudioManager>())
	{
		AM->SetSEVolume(Value);
	}
}

● ビルドして確認してください。

● よかったです!長い道のりでしたね。

原因のまとめ:

  • AudioVolumeRadialSlider は URadialSlider ではなく AudioWidgets プラグインの UAudioVolumeRadialSlider
  • SynchronizeProperties() が Value を Slate に送らない(プラグインの仕様)
  • Slate ウィジェット (SAudioRadialSlider) に直接 SetSliderValue() を呼ぶ必要があった

● お疲れ様でした!

今回の実装まとめ:

  • AudioVolume_Master → AudioManager::SetMasterVolume()
  • AudioVolume_se → AudioManager::SetSEVolume()
  • 初期値は両方 1.0(SAudioRadialSlider::SetSliderValue で Slate に直接セット)

おまけ

AudioManager.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "AudioManager.generated.h"

class USoundBase;
class UAudioComponent;

/**
 *  Game-wide audio manager — BGM playlist + volume control.
 *
 *  Access from anywhere:
 *    UGameInstance* GI = GetGameInstance();          // or GetWorld()->GetGameInstance()
 *    UAudioManager* AM = GI->GetSubsystem<UAudioManager>();
 *
 *  BGM asset note:
 *    USoundBase assets used as BGM tracks must have looping DISABLED so that
 *    OnAudioFinished fires when the track ends and the playlist advances.
 */
UCLASS()
class UE573PETIT25CL_API UAudioManager : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:

	// -------------------------------------------------------
	// Playlist
	// -------------------------------------------------------

	/** BGM tracks to play in order (or randomly when bRandomPlayback is true) */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BGM")
	TArray<TObjectPtr<USoundBase>> BGMPlaylist;

	/**
	 *  If true, tracks are played in a random order.
	 *  A full cycle (every track plays once) is guaranteed before any track repeats.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BGM")
	bool bRandomPlayback = false;

	// -------------------------------------------------------
	// Volume  (0.0 – 1.0)
	// -------------------------------------------------------

	/** Overall volume multiplier applied to all audio */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float MasterVolume = 1.0f;

	/** Volume multiplier applied to BGM on top of MasterVolume */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float BGMVolume = 1.0f;

	/** Volume multiplier applied to SE on top of MasterVolume */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Volume", meta=(ClampMin=0, ClampMax=1))
	float SEVolume = 1.0f;

public:

	// -------------------------------------------------------
	// BGM API
	// -------------------------------------------------------

	/**
	 *  Starts BGM playlist playback.
	 *  @param TrackIndex  0-based index of the first track to play.
	 *                     Ignored in random mode — a fresh shuffle is always started.
	 */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void PlayBGM(int32 TrackIndex = 0);

	/** Stops BGM playback and destroys the internal audio component */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void StopBGM();

	/** Pauses BGM playback */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void PauseBGM();

	/** Resumes a paused BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void ResumeBGM();

	/** Skips to the next track immediately */
	UFUNCTION(BlueprintCallable, Category="Audio|BGM")
	void SkipTrack();

	// -------------------------------------------------------
	// Volume API
	// -------------------------------------------------------

	/** Sets the master volume and immediately applies it to the playing BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetMasterVolume(float Volume);

	/** Sets the BGM volume and immediately applies it to the playing BGM */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetBGMVolume(float Volume);

	/** Sets the SE volume (applied to subsequent PlaySE calls) */
	UFUNCTION(BlueprintCallable, Category="Audio|Volume")
	void SetSEVolume(float Volume);

	// -------------------------------------------------------
	// SE API
	// -------------------------------------------------------

	/** Plays a non-spatialized sound effect at MasterVolume * SEVolume */
	UFUNCTION(BlueprintCallable, Category="Audio|SE")
	void PlaySE2D(USoundBase* Sound);

	/** Plays a spatialized sound effect at the given world location at MasterVolume * SEVolume */
	UFUNCTION(BlueprintCallable, Category="Audio|SE", meta=(WorldContext="WorldContextObject"))
	void PlaySE3D(USoundBase* Sound, const FVector& Location, UObject* WorldContextObject);

private:

	/** Currently active BGM audio component */
	UPROPERTY()
	TObjectPtr<UAudioComponent> BGMComponent;

	/** Index of the track that is currently playing */
	int32 CurrentTrackIndex = 0;

	/** Remaining track indices for random mode; rebuilt when the list is exhausted */
	TArray<int32> ShuffledIndices;

	/** Plays the track at the given playlist index */
	void PlayTrackAtIndex(int32 Index);

	/** Called when the current BGM track finishes — advances to the next track */
	UFUNCTION()
	void OnBGMFinished();

	/** Returns the index of the next track to play */
	int32 PickNextTrackIndex();

	/** Applies the current MasterVolume * BGMVolume to the active audio component */
	void RefreshBGMVolume();

	/** Fills ShuffledIndices with a fresh Fisher-Yates permutation of all track indices */
	void RebuildShuffledIndices();
};

AudioManager.cpp

// Copyright Epic Games, Inc. All Rights Reserved.

#include "AudioManager.h"
#include "Components/AudioComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundBase.h"

// -------------------------------------------------------
// BGM API
// -------------------------------------------------------

void UAudioManager::PlayBGM(int32 TrackIndex)
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	// Stop any currently playing track before starting the playlist
	StopBGM();

	if (bRandomPlayback)
	{
		// Start a fresh shuffle so every track plays before any repeats
		RebuildShuffledIndices();
		PlayTrackAtIndex(ShuffledIndices.Pop(EAllowShrinking::No));
	}
	else
	{
		PlayTrackAtIndex(FMath::Clamp(TrackIndex, 0, BGMPlaylist.Num() - 1));
	}
}

void UAudioManager::StopBGM()
{
	if (BGMComponent)
	{
		// Remove delegate first so Stop() does not accidentally trigger OnBGMFinished
		BGMComponent->OnAudioFinished.RemoveDynamic(this, &UAudioManager::OnBGMFinished);
		BGMComponent->Stop();
		BGMComponent->DestroyComponent();
		BGMComponent = nullptr;
	}
}

void UAudioManager::PauseBGM()
{
	if (BGMComponent)
	{
		BGMComponent->SetPaused(true);
	}
}

void UAudioManager::ResumeBGM()
{
	if (BGMComponent)
	{
		BGMComponent->SetPaused(false);
	}
}

void UAudioManager::SkipTrack()
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	PlayTrackAtIndex(PickNextTrackIndex());
}

// -------------------------------------------------------
// Volume API
// -------------------------------------------------------

void UAudioManager::SetMasterVolume(float Volume)
{
	MasterVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
	RefreshBGMVolume();
}

void UAudioManager::SetBGMVolume(float Volume)
{
	BGMVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
	RefreshBGMVolume();
}

void UAudioManager::SetSEVolume(float Volume)
{
	SEVolume = FMath::Clamp(Volume, 0.0f, 1.0f);
}

// -------------------------------------------------------
// SE API
// -------------------------------------------------------

void UAudioManager::PlaySE2D(USoundBase* Sound)
{
	if (!Sound)
	{
		return;
	}

	UGameplayStatics::SpawnSound2D(GetGameInstance(), Sound, MasterVolume * SEVolume);
}

void UAudioManager::PlaySE3D(USoundBase* Sound, const FVector& Location, UObject* WorldContextObject)
{
	if (!Sound || !WorldContextObject)
	{
		return;
	}

	UGameplayStatics::SpawnSoundAtLocation(WorldContextObject, Sound, Location,
		FRotator::ZeroRotator, MasterVolume * SEVolume);
}

// -------------------------------------------------------
// Private helpers
// -------------------------------------------------------

void UAudioManager::PlayTrackAtIndex(int32 Index)
{
	if (!BGMPlaylist.IsValidIndex(Index) || !BGMPlaylist[Index])
	{
		return;
	}

	CurrentTrackIndex = Index;

	// Tear down previous component and unsubscribe before spawning a new one
	if (BGMComponent)
	{
		BGMComponent->OnAudioFinished.RemoveDynamic(this, &UAudioManager::OnBGMFinished);
		BGMComponent->Stop();
		BGMComponent->DestroyComponent();
		BGMComponent = nullptr;
	}

	// Spawn a 2D audio component:
	//   bPersistAcrossLevelTransition = true  — BGM survives level loads
	//   bAutoDestroy                  = false — we control the lifetime manually
	BGMComponent = UGameplayStatics::SpawnSound2D(
		GetGameInstance(),
		BGMPlaylist[Index],
		MasterVolume * BGMVolume,
		1.0f,   // PitchMultiplier
		0.0f,   // StartTime
		nullptr,// ConcurrencySettings
		true,   // bPersistAcrossLevelTransition
		false   // bAutoDestroy
	);

	if (BGMComponent)
	{
		BGMComponent->OnAudioFinished.AddDynamic(this, &UAudioManager::OnBGMFinished);
	}
}

void UAudioManager::OnBGMFinished()
{
	if (BGMPlaylist.IsEmpty())
	{
		return;
	}

	PlayTrackAtIndex(PickNextTrackIndex());
}

int32 UAudioManager::PickNextTrackIndex()
{
	if (bRandomPlayback)
	{
		// Rebuild the shuffle pool when every track has been played once
		if (ShuffledIndices.IsEmpty())
		{
			RebuildShuffledIndices();
		}
		return ShuffledIndices.Pop(EAllowShrinking::No);
	}

	// Sequential: wrap around to the beginning after the last track
	return (CurrentTrackIndex + 1) % BGMPlaylist.Num();
}

void UAudioManager::RefreshBGMVolume()
{
	if (BGMComponent)
	{
		BGMComponent->SetVolumeMultiplier(MasterVolume * BGMVolume);
	}
}

void UAudioManager::RebuildShuffledIndices()
{
	const int32 Count = BGMPlaylist.Num();
	ShuffledIndices.Reset(Count);

	for (int32 i = 0; i < Count; ++i)
	{
		ShuffledIndices.Add(i);
	}

	// Fisher-Yates shuffle
	for (int32 i = Count - 1; i > 0; --i)
	{
		const int32 j = FMath::RandRange(0, i);
		ShuffledIndices.Swap(i, j);
	}
}

[maya][skinweight]キャラクターの背丈やスケール、回転等を変更する方法

0.前提として同階層にrootとmeshがスキンバインドされて並んでいる状態
root
mesh

1.rootを複製してとっておく
もとのrootとmeshの名前をroot_oldとmesh_oldにする

2.root_oldのジョイントを使ってメッシュの背丈を変更する。
3.変更されたmesh_oldを複製しmeshとする

4.meshをフリーズする

5.上記で変更されたmeshにあわせてrootの背丈を変更してフリーズする

5.rootとmeshスキンバインドする
6.mesh_oldからmeshへスキンウェイトをコピーする。
7.rootとmesh選択してFBX出力
おしまい。

このキャラクターのディティールはこっち

[UE5][C++] The following modules are missing or built with a different engine version: プラグイン名 Would you like to rebuild them now?

出る時の必殺リセット(確実)

UE を閉じてからプロジェクト直下で削除:

Binaries/

Intermediate/

(あれば)Plugins/furcraeaHLSLEditor/Binaries/

(あれば)Plugins/furcraeaHLSLEditor/Intermediate/

その後

.uproject 右クリック → Generate Visual Studio project files

VS で Development Editor / Win64 で Rebuild

起動