[maya][hair][nurbs]nurbsLoftでの束感のあるヘアーモデリングの方法

[Maya][Hair][NURBS] Method for modeling hair with a “clumped” look using NURBS Loft

根元からNurbsCircleを置いて行って

ctrl+d複製し拡大縮小しながら次の円も書いていきます。

ctrl+d複製し縮小しながら移動して先端の部分を用意します

nurbsを全部選択し、サーフェス>作成>ロフトで、loftedSurface1ができます。

loftedSurface1を選択して、修正>変換>NURBSをポリゴンに 

でポリゴンに変換できます。

[maya][python]接合したメッシュの法線をいい感じに丸く滑らかにするツール furcraeaSmoothNormalTool.pyとfurcraeaSeamNormalTool.py

できます。まずは 選択したメッシュ、フェース、頂点の法線を滑らかにする Maya Python ツールとして作るのが扱いやすいです。

下のコードは、次の機能を持っています。

  • 選択範囲だけ法線をソフト化
  • メッシュ全体をソフト化
  • 角度を指定
  • 法線ロック解除
  • 法線の平均化
  • ハードエッジ化
  • 法線履歴の削除

furcraeaSmoothNormalTool.py

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


WINDOW_NAME = "furcraeaSmoothNormalTool"


def get_selected_mesh_components():
    """
    現在の選択を取得します。
    オブジェクト選択の場合は、そのメッシュ全体を返します。
    コンポーネント選択の場合は、そのまま返します。
    """
    selection = cmds.ls(selection=True, long=True, flatten=True) or []

    if not selection:
        cmds.warning("メッシュ、フェース、エッジ、頂点を選択してください。")
        return []

    result = []

    for item in selection:
        # コンポーネント選択
        if "." in item:
            result.append(item)
            continue

        # TransformからShapeを取得
        if cmds.nodeType(item) == "transform":
            shapes = cmds.listRelatives(
                item,
                shapes=True,
                noIntermediate=True,
                fullPath=True
            ) or []

            mesh_shapes = [
                shape for shape in shapes
                if cmds.nodeType(shape) == "mesh"
            ]

            if mesh_shapes:
                result.append(item)

        # Shapeが直接選択されている場合
        elif cmds.nodeType(item) == "mesh":
            parents = cmds.listRelatives(
                item,
                parent=True,
                fullPath=True
            ) or []

            if parents:
                result.append(parents[0])

    if not result:
        cmds.warning("選択内にポリゴンメッシュがありません。")

    return result


def unlock_normals():
    """選択したメッシュまたはコンポーネントの法線ロックを解除します。"""
    targets = get_selected_mesh_components()
    if not targets:
        return

    cmds.undoInfo(openChunk=True)

    try:
        for target in targets:
            try:
                cmds.polyNormalPerVertex(
                    target,
                    unFreezeNormal=True
                )
            except RuntimeError:
                pass

        cmds.inViewMessage(
            amg="<hl>法線ロックを解除しました</hl>",
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def soften_normals(angle=None):
    """
    指定角度以下のエッジをソフト化します。

    angle:
        180の場合、基本的にすべてのエッジを滑らかにします。
        30~60程度なら、鋭い角を残しやすくなります。
    """
    targets = get_selected_mesh_components()
    if not targets:
        return

    if angle is None:
        angle = cmds.floatSliderGrp(
            "smoothNormalAngleSlider",
            query=True,
            value=True
        )

    cmds.undoInfo(openChunk=True)

    try:
        for target in targets:
            # ロックされた法線がある場合に備えて解除
            try:
                cmds.polyNormalPerVertex(
                    target,
                    unFreezeNormal=True
                )
            except RuntimeError:
                pass

            cmds.polySoftEdge(
                target,
                angle=angle,
                constructionHistory=True
            )

        cmds.inViewMessage(
            amg="<hl>法線を滑らかにしました:</hl> {}°".format(angle),
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def soften_all():
    """選択したメッシュまたはコンポーネントを完全にソフト化します。"""
    soften_normals(180.0)


def harden_normals():
    """選択した範囲をハードエッジ化します。"""
    targets = get_selected_mesh_components()
    if not targets:
        return

    cmds.undoInfo(openChunk=True)

    try:
        for target in targets:
            try:
                cmds.polyNormalPerVertex(
                    target,
                    unFreezeNormal=True
                )
            except RuntimeError:
                pass

            cmds.polySoftEdge(
                target,
                angle=0,
                constructionHistory=True
            )

        cmds.inViewMessage(
            amg="<hl>ハードエッジ化しました</hl>",
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def average_vertex_normals():
    """
    選択頂点の法線を平均化します。
    頂点以外を選択した場合は頂点へ変換します。
    """
    selection = cmds.ls(selection=True, long=True, flatten=True) or []

    if not selection:
        cmds.warning("平均化するメッシュまたはコンポーネントを選択してください。")
        return

    vertices = cmds.polyListComponentConversion(
        selection,
        toVertex=True
    )

    vertices = cmds.ls(
        vertices,
        flatten=True,
        long=True
    ) or []

    if not vertices:
        cmds.warning("頂点を取得できませんでした。")
        return

    cmds.undoInfo(openChunk=True)

    try:
        try:
            cmds.polyNormalPerVertex(
                vertices,
                unFreezeNormal=True
            )
        except RuntimeError:
            pass

        cmds.polyAverageNormal(
            vertices,
            distance=0.0,
            replaceNormalXYZ=(0, 0, 0)
        )

        cmds.inViewMessage(
            amg="<hl>頂点法線を平均化しました</hl>",
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def conform_normals():
    """
    法線の向きを統一します。
    裏返ったフェースが混在している場合に使用します。
    """
    targets = get_selected_mesh_components()
    if not targets:
        return

    cmds.undoInfo(openChunk=True)

    try:
        for target in targets:
            cmds.polyNormal(
                target,
                normalMode=2,
                userNormalMode=0,
                constructionHistory=True
            )

        cmds.inViewMessage(
            amg="<hl>法線方向を統一しました</hl>",
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def reverse_normals():
    """選択したフェースまたはメッシュの法線を反転します。"""
    targets = get_selected_mesh_components()
    if not targets:
        return

    cmds.undoInfo(openChunk=True)

    try:
        for target in targets:
            cmds.polyNormal(
                target,
                normalMode=0,
                userNormalMode=0,
                constructionHistory=True
            )

        cmds.inViewMessage(
            amg="<hl>法線を反転しました</hl>",
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def delete_normal_history():
    """
    選択メッシュの履歴を削除します。
    法線処理以外の履歴も削除されるため注意してください。
    """
    targets = get_selected_mesh_components()
    if not targets:
        return

    transforms = set()

    for target in targets:
        object_name = target.split(".")[0]

        if cmds.nodeType(object_name) == "mesh":
            parents = cmds.listRelatives(
                object_name,
                parent=True,
                fullPath=True
            ) or []

            if parents:
                transforms.add(parents[0])
        else:
            transforms.add(object_name)

    if transforms:
        cmds.delete(list(transforms), constructionHistory=True)

        cmds.inViewMessage(
            amg="<hl>ヒストリを削除しました</hl>",
            pos="midCenter",
            fade=True
        )


def show_smooth_normal_tool():
    """UIを表示します。"""
    if cmds.window(WINDOW_NAME, exists=True):
        cmds.deleteUI(WINDOW_NAME)

    window = cmds.window(
        WINDOW_NAME,
        title="Smooth Normal Tool",
        sizeable=False,
        widthHeight=(340, 390)
    )

    cmds.columnLayout(
        adjustableColumn=True,
        rowSpacing=8
    )

    cmds.separator(height=8, style="none")

    cmds.text(
        label="メッシュ・フェース・エッジ・頂点に対応",
        align="center"
    )

    cmds.separator(height=8, style="in")

    cmds.floatSliderGrp(
        "smoothNormalAngleSlider",
        label="Soft Angle",
        field=True,
        minValue=0.0,
        maxValue=180.0,
        fieldMinValue=0.0,
        fieldMaxValue=180.0,
        value=60.0,
        columnWidth3=(80, 60, 180)
    )

    cmds.button(
        label="指定角度で滑らかにする",
        height=36,
        command=lambda *_: soften_normals()
    )

    cmds.button(
        label="すべて滑らかにする  180°",
        height=32,
        command=lambda *_: soften_all()
    )

    cmds.button(
        label="ハードエッジにする  0°",
        height=32,
        command=lambda *_: harden_normals()
    )

    cmds.separator(height=10, style="in")

    cmds.button(
        label="頂点法線を平均化",
        height=32,
        command=lambda *_: average_vertex_normals()
    )

    cmds.button(
        label="法線ロックを解除",
        height=32,
        command=lambda *_: unlock_normals()
    )

    cmds.separator(height=10, style="in")

    cmds.rowLayout(
        numberOfColumns=2,
        adjustableColumn=1,
        columnWidth2=(165, 165)
    )

    cmds.button(
        label="法線方向を統一",
        height=30,
        command=lambda *_: conform_normals()
    )

    cmds.button(
        label="法線を反転",
        height=30,
        command=lambda *_: reverse_normals()
    )

    cmds.setParent("..")

    cmds.separator(height=10, style="in")

    cmds.button(
        label="ヒストリを削除",
        height=30,
        command=lambda *_: delete_normal_history()
    )

    cmds.separator(height=8, style="none")

    cmds.showWindow(window)


show_smooth_normal_tool()

メッシュの接合部で法線がめちゃくちゃになってるパターンをどうにか滑らかにしたい

その場合は polySoftEdge だけでは直らない可能性が高いです。

接合部で法線が乱れる原因は主に次の2種類です。

  1. 頂点は接合されているが、法線がロック・分割されている
  2. 別メッシュ同士で、同じ位置にある頂点の法線が一致していない

特に髪・顔・首・胴体など、別メッシュの境界を滑らかにつなぎたい場合は、接合部の近接頂点を探して、両方の法線を平均化する必要があります。

以下は、選択した複数メッシュ間で、同じ位置にある頂点の法線を平均化するPythonツールです。

furcraeaSeamNormalTool.py

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

import maya.cmds as cmds
import maya.api.OpenMaya as om


WINDOW_NAME = "furcraeaSeamNormalTool"


def get_selected_mesh_dag_paths():
    """
    選択されたTransformまたはMesh Shapeから、
    MeshのMDagPathを取得する。
    """
    selection = om.MGlobal.getActiveSelectionList()
    mesh_paths = []

    for index in range(selection.length()):
        try:
            dag_path = selection.getDagPath(index)
        except RuntimeError:
            continue

        if dag_path.node().hasFn(om.MFn.kTransform):
            try:
                dag_path.extendToShape()
            except RuntimeError:
                continue

        if dag_path.node().hasFn(om.MFn.kMesh):
            mesh_paths.append(dag_path)

    return mesh_paths


def unlock_mesh_normals(dag_path):
    """
    メッシュ全体の頂点法線ロックを解除する。
    """
    mesh_name = dag_path.fullPathName()

    try:
        cmds.polyNormalPerVertex(
            mesh_name,
            unFreezeNormal=True
        )
    except RuntimeError:
        pass


def get_vertex_data(dag_path):
    """
    各頂点について以下を取得する。

    - 頂点番号
    - ワールド座標
    - 頂点法線
    """
    mesh_fn = om.MFnMesh(dag_path)

    points = mesh_fn.getPoints(om.MSpace.kWorld)
    normals = mesh_fn.getVertexNormals(
        True,
        om.MSpace.kWorld
    )

    result = []

    for vertex_id, point in enumerate(points):
        normal = normals[vertex_id]

        result.append({
            "dag_path": dag_path,
            "mesh_fn": mesh_fn,
            "vertex_id": vertex_id,
            "position": om.MVector(point.x, point.y, point.z),
            "normal": om.MVector(normal.x, normal.y, normal.z)
        })

    return result


def build_spatial_hash(vertex_data, tolerance):
    """
    頂点座標をグリッド化して、近接頂点を高速に検索する。
    """
    spatial_hash = {}

    for data in vertex_data:
        position = data["position"]

        key = (
            int(round(position.x / tolerance)),
            int(round(position.y / tolerance)),
            int(round(position.z / tolerance))
        )

        spatial_hash.setdefault(key, []).append(data)

    return spatial_hash


def get_neighbor_keys(position, tolerance):
    """
    誤差境界をまたいだ頂点も検索できるように、
    周囲27セルを返す。
    """
    center_x = int(round(position.x / tolerance))
    center_y = int(round(position.y / tolerance))
    center_z = int(round(position.z / tolerance))

    for offset_x in (-1, 0, 1):
        for offset_y in (-1, 0, 1):
            for offset_z in (-1, 0, 1):
                yield (
                    center_x + offset_x,
                    center_y + offset_y,
                    center_z + offset_z
                )


def average_seam_normals(tolerance=0.001, border_only=True):
    """
    選択した複数メッシュ間で、
    同じ位置にある頂点法線を平均化する。

    tolerance:
        同じ位置と判定する距離。

    border_only:
        Trueの場合は境界頂点のみを対象にする。
    """
    dag_paths = get_selected_mesh_dag_paths()

    if len(dag_paths) < 2:
        cmds.warning(
            "接合部を修正する2つ以上のメッシュを選択してください。"
        )
        return

    cmds.undoInfo(openChunk=True)

    try:
        all_vertex_data = []

        for dag_path in dag_paths:
            unlock_mesh_normals(dag_path)

            mesh_vertex_data = get_vertex_data(dag_path)

            if border_only:
                border_ids = get_border_vertex_ids(dag_path)

                mesh_vertex_data = [
                    data
                    for data in mesh_vertex_data
                    if data["vertex_id"] in border_ids
                ]

            all_vertex_data.extend(mesh_vertex_data)

        spatial_hash = build_spatial_hash(
            all_vertex_data,
            tolerance
        )

        processed = set()
        matched_groups = []

        for source_data in all_vertex_data:
            source_key = (
                source_data["dag_path"].fullPathName(),
                source_data["vertex_id"]
            )

            if source_key in processed:
                continue

            matching_vertices = []

            for neighbor_key in get_neighbor_keys(
                source_data["position"],
                tolerance
            ):
                candidates = spatial_hash.get(
                    neighbor_key,
                    []
                )

                for candidate in candidates:
                    candidate_key = (
                        candidate["dag_path"].fullPathName(),
                        candidate["vertex_id"]
                    )

                    if candidate_key in processed:
                        continue

                    # 同一メッシュ内の近接頂点は対象外
                    if (
                        candidate["dag_path"].fullPathName()
                        == source_data["dag_path"].fullPathName()
                    ):
                        continue

                    distance = (
                        candidate["position"]
                        - source_data["position"]
                    ).length()

                    if distance <= tolerance:
                        matching_vertices.append(candidate)

            if not matching_vertices:
                continue

            group = [source_data] + matching_vertices

            # 同一頂点の重複を除去
            unique_group = {}
            for data in group:
                key = (
                    data["dag_path"].fullPathName(),
                    data["vertex_id"]
                )
                unique_group[key] = data

            group = list(unique_group.values())

            if len(group) < 2:
                continue

            matched_groups.append(group)

            for data in group:
                processed.add((
                    data["dag_path"].fullPathName(),
                    data["vertex_id"]
                ))

        if not matched_groups:
            cmds.warning(
                "指定距離内に一致する接合頂点が見つかりませんでした。"
            )
            return

        apply_average_normals(matched_groups)

        cmds.inViewMessage(
            amg=(
                "<hl>接合部法線を平均化しました</hl>"
                "<br/>一致グループ数: {}"
            ).format(len(matched_groups)),
            pos="midCenter",
            fade=True
        )

    finally:
        cmds.undoInfo(closeChunk=True)


def get_border_vertex_ids(dag_path):
    """
    メッシュの境界エッジに含まれる頂点番号を取得する。
    """
    border_vertex_ids = set()

    edge_iterator = om.MItMeshEdge(dag_path)

    while not edge_iterator.isDone():
        if edge_iterator.onBoundary():
            border_vertex_ids.add(
                edge_iterator.vertexId(0)
            )
            border_vertex_ids.add(
                edge_iterator.vertexId(1)
            )

        edge_iterator.next()

    return border_vertex_ids


def apply_average_normals(matched_groups):
    """
    法線グループごとに平均法線を計算し、
    各頂点へ適用する。
    """
    mesh_updates = {}

    for group in matched_groups:
        average_normal = om.MVector()

        for data in group:
            average_normal += data["normal"]

        if average_normal.length() < 0.000001:
            continue

        average_normal.normalize()

        for data in group:
            mesh_name = data["dag_path"].fullPathName()

            if mesh_name not in mesh_updates:
                mesh_updates[mesh_name] = {
                    "mesh_fn": data["mesh_fn"],
                    "vertex_ids": [],
                    "normals": []
                }

            mesh_updates[mesh_name]["vertex_ids"].append(
                data["vertex_id"]
            )

            mesh_updates[mesh_name]["normals"].append(
                om.MVector(
                    average_normal.x,
                    average_normal.y,
                    average_normal.z
                )
            )

    for update_data in mesh_updates.values():
        mesh_fn = update_data["mesh_fn"]

        mesh_fn.setVertexNormals(
            update_data["normals"],
            update_data["vertex_ids"],
            om.MSpace.kWorld
        )


def run_from_ui(*args):
    tolerance = cmds.floatField(
        "seamNormalToleranceField",
        query=True,
        value=True
    )

    border_only = cmds.checkBox(
        "seamNormalBorderOnlyCheckBox",
        query=True,
        value=True
    )

    if tolerance <= 0:
        cmds.warning("許容距離は0より大きくしてください。")
        return

    average_seam_normals(
        tolerance=tolerance,
        border_only=border_only
    )


def show_seam_normal_tool():
    if cmds.window(WINDOW_NAME, exists=True):
        cmds.deleteUI(WINDOW_NAME)

    window = cmds.window(
        WINDOW_NAME,
        title="Seam Normal Smoother",
        sizeable=False,
        widthHeight=(360, 190)
    )

    cmds.columnLayout(
        adjustableColumn=True,
        rowSpacing=8
    )

    cmds.separator(height=8, style="none")

    cmds.text(
        label="接合する2つ以上のメッシュを選択",
        align="center"
    )

    cmds.text(
        label="同じ位置にある境界頂点の法線を平均化します",
        align="center"
    )

    cmds.separator(height=8, style="in")

    cmds.floatFieldGrp(
        "seamNormalToleranceField",
        numberOfFields=1,
        label="許容距離",
        value1=0.001,
        precision=6,
        columnWidth2=(100, 220)
    )

    cmds.checkBox(
        "seamNormalBorderOnlyCheckBox",
        label="境界頂点のみを処理",
        value=True
    )

    cmds.separator(height=8, style="in")

    cmds.button(
        label="接合部の法線を滑らかにする",
        height=38,
        command=run_from_ui
    )

    cmds.separator(height=8, style="none")

    cmds.showWindow(window)


show_seam_normal_tool()

#UE4 #UE4Study Character #HairCloth #HairShadingModel #AdvancedLocomotionSystem ファサっと天使の輪と束感でUEのヘアーの使い方

服と髪をなびかせるHair Clothはこれをやった
https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/Physics/Cloth/Overview/
1, [Section Selection (セクション選択)]
2,左クリックを使って、クロスとして使用したいメッシュの一部を選択します。次に右クリックして、コンテキスト メニューを開いて Cloth アセットを作成します。
3,コンテキスト メニューから、[Create Cloth Asset from Selection (選択したものから Cloth アセットを作成)] を選びます。

 A,Asset Name – 後で簡単に見つけられるようにアセットに名前を付けてください。

 B,Remove from Mesh – クロスとして関連付けたい別個のジオメトリのメッシュの構成要素があれば、このオプションを有効にします。そうでなければ、チェックを入れずにこのままにします。

 C,Physics Asset – Cloth アセットがキャラクター用ならば、その Physics アセットを使ってクロス シミュレーションで適切なコリジョンが生じるようにします。

4,[Create] ボタンをクリックします。
5,セクションを再度右クリックして、コンテキスト メニューを表示させて、[Apply Clothing Asset] にマウスをかざし、利用可能なクロス アセットから適用するものを選択します。これで作成した任意のクロス アセットが選択したセクションに関連付けられます。

・ペイント – マウスの左ボタン
・消去 – Shift キー + マウスの左ボタン
・クロスのプレビュー – H キー

[Window] を選択し、リストから [Clothing] を選択します。
[Clothing Data] リストから選択します。

[Activate Cloth Paint] ボタンをクリックして、選択した Cloth アセットをペイントするために使用可能なプロパティを有効にします。

・ブラシの半径を5にして
・強度を0.2
・フォールオフ0.5
でヘアをクリックすると塗れる。ピンクが塗ってない場所

走ってみると

髪が前に来ちゃう

ので

物理ボディを修正した

全骨コリジョン作ったほうが調整しやすかった。(指は消した)

Spine5がこういった設定 Radius 10以下ぐらい

以下は昔の設定

で設定することが重要

でやっといい感じ

HairShadingModel 基本はこれをやった。

つまりこれで髪の天使の輪みたいなものができる。 (まわしてみないと分からない)

でも髪の束感は出したかったのでOpacityMaskもありにするとこうなる。

束感のために使ってるテクスチャはこれで

https://drive.google.com/file/d/11JcBV0dAtrexQew0fbKUZcLRfbYn7FuZ/view?usp=sharing

こんな感じにスキャッター、スペキュラ、ラフネスにMultyplyしたらいい感じになった。

使っているMIでの値はこんな感じ

最後にまたこれをやって

やっとこうなった

元のモデルはここ

https://www.deviantart.com/xcrofty/art/NieR-Automata-A2-678696387

[UE5.8.0][Maya][Retarget] TposeのキャラクターモデルをAposeのMannequinのモーションにリターゲットする方法

MM_T_Pose をエクスポート

Export Optionはこう

MayaにFBXインポートした状態で保存する SKM_Quinn_Simple_Tpose.ma

Tposeのキャラクターモデル(ここではfanModelのサイトからダウンロードしたものを用意した)

https://www.deviantart.com/d-presso/art/Nier-Re-in-carnation-Levannia-XPS-DL-880627377

を開いて


先ほどのTposeのFBXをインポートして

Root ( SKM_Quinn_Simple_Tpose:root_Quinn_T_pose ) のトップ のjointをスケールしてキャラクターにあわせる。

複製してフリーズする

以下ツールでスキンウェイトをコピー

SkinWeightCopy_First_Select_MeshGroup_to_Second Select_MeshGroup.mel

// SkinWeightCopy First Select MeshGroup to Second Select MeshGroup 
string $selectedArrFUllPath[] = `ls -long -sl`;
print($selectedArrFUllPath);

string $FirstSelect=$selectedArrFUllPath[0];

string $SecondSelect=$selectedArrFUllPath[1];

string $rejectArr[];

//string $inputNodes[] = `ls -type transform -long -dag $FirstSelect`;
//string $destNodes[] = `ls -type transform -long -dag $SecondSelect`;

string $inputNodes_mesh[] = `ls -type mesh -long -dag $FirstSelect`;
string $destNodes_mesh[] = `ls -type mesh -long -dag $SecondSelect`;

$inputNodes_meshlong=size($inputNodes_mesh);
$destNodes_meshlong=size($destNodes_mesh);

string $inputNodes[];
string $destNodes[];
clear $inputNodes;
clear $destNodes;
string $mesh;
string $parent;
for($d0 = 0; $d0 <$inputNodes_meshlong;$d0++){
    $mesh=$inputNodes_mesh[$d0];
    string $parentS[] = `listRelatives -parent -path -type transform $mesh`;
    $parent=$parentS[0];
    $inputNodes[size($inputNodes)] = $parent;
}
for($d0 = 0; $d0 <$destNodes_meshlong;$d0++){
    $mesh=$destNodes_mesh[$d0];
    string $parentS[] = `listRelatives -parent -path -type transform $mesh`;
    $parent=$parentS[0];
    $destNodes[size($destNodes)] = $parent;
}

print("$inputNodes= ------------------------------------------------ \n");
print($inputNodes);

print("$destNodes= ------------------------------------------------ \n");
print($destNodes);



$inputNodeslong=size($inputNodes);
$destNodeslong=size($destNodes);

string $buffer[];
string $buffer_i[];
$finded = 0;
for($d = 0; $d <$destNodeslong;$d++){
    string $destNode_name=$destNodes[$d];
    
    if($destNode_name==$SecondSelect){
            
    }else{
        print("$destNode_name= "+$destNode_name+"\n");
        
        $numTokens = `tokenize $destNode_name "|" $buffer`;
        $bufferlong=size($buffer);
        string $destNode_name_Short = $buffer[$bufferlong-1]+"";
        print("$destNode_name_Short= "+$destNode_name_Short+"\n");
        $finded = 0;
        for($i = 0; $i <$inputNodeslong;$i++){
            string $inputNode_name=$inputNodes[$i];
            $numTokens_i = `tokenize $inputNode_name "|" $buffer_i`;
            $bufferlong_i=size($buffer_i);
            string $inputNode_name_Short = $buffer_i[$bufferlong_i-1]+"";
            print("$inputNode_name_Short= "+$inputNode_name_Short+"\n");
                
            //$index=`gmatch $inputNode_name $destNode_name_Short`;
            //if($index > 0){
            if($inputNode_name_Short==$destNode_name_Short){
                //$blendShapeName = $blendshapeRet_name;
                print("$destNode_name= "+$destNode_name+" isHit!! "+"\n");
                select -r $inputNode_name;
                select -add $destNode_name;
                copySkinWeights  -noMirror -surfaceAssociation closestPoint -influenceAssociation closestJoint;
                //copySkinWeights -noMirror -surfaceAssociation closestPoint -influenceAssociation closestJoint -sourceSkin $inputNode_name -destinationSkin $destNode_name;
                $finded = 1;
            }//if
        }//for
        if($finded==0){
            $rejectArr[size($rejectArr)] = $destNode_name;
        }//if
    }//if
}//for
print("----rejected_list---start"+"\n");
print($rejectArr);
print("----rejected_list---end"+"\n");
select -r $FirstSelect ;
select -add $SecondSelect ;

適当にスキンウェイトを確認

rootとMesh 選択してFBX出力

UEへインポート

IKRig作成

IKRig設定

マネキンのほうはこう

IK Retargeter 作成

SounceにCreate>Import From Animation Sequence

MM_T_Pose選択

ポーズがそろう

Op Stack >Add Default Ops 押す とそろった

あとは必要なアニメーションを選択してエクスポート

追加で調整した作業 RootMotionを切ってエクスポートしないとだめだった

そのあとは以下を参考にしてください。

TexturePath Replace Python Script for Maya

Select HyperShade Textures And Do Script
import maya.cmds as cmds
import pymel.core as pm
import os

def remap_selected_texture(z_path):
    
    # figure out what's selected
    selected = pm.ls(sl=True)
    workspace_path = cmds.workspace(query=True, rootDirectory=True)
    print("workspace_path= "+workspace_path)
    for item in selected:
        test_path = pm.getAttr(item+".ftn")
        fileName = test_path.split('/')[-1]
        fileName = fileName.replace('//', '')
        fileName = fileName.replace('/', '')
        print("fileName2= "+fileName)
        #if ':' not in test_path:
        if(fileName=="GraceYong_ArmsColorD1001.jpg"):
            fileName="GraceYong_ArmsColorD_1001.jpg"
        if (1==1):   
            
            #print("fileName1= "+fileName)
            
            
            #new_path = os.path.join(z_path, 'cartoon_room', second)
            new_path = os.path.join(workspace_path,z_path, fileName)
            new_path = new_path.replace('\\', '/')
            new_path = new_path.replace('//', '/')
            relative_path = os.path.join(z_path, fileName)
            relative_path = relative_path.replace('\\', '/')
            new_path = new_path.replace('//', '/')
            if os.path.exists(new_path):
                if(fileName==""):
                    print("item= "+str(item)+" is No fileName= "+ fileName)
                    cmds.select( str(item), r=True )
                    cmds.delete( str(item) )
                else:
                    print("new_path= "+new_path+' is exists! OK')
                    pm.setAttr(item+'.ftn', new_path)
                #pm.setAttr(item+'.ftn', relative_path)
            else:
                print("new_path= "+new_path+ ' not exists NG' )
        if os.path.exists(test_path):
            pass
        else:
            PersonalIndex=test_path.find("Personal")
            if(PersonalIndex==-1):
                pass
            else:
                print("test_path= "+test_path+ ' not exists NG' )
                pm.setAttr(item+'.ftn', "")
                cmds.select( str(item), r=True )
                cmds.delete( str(item) )
         
    cmds.select(selected)
    print("--------------remap_selected_texture----------END")
    
remap_selected_texture("GraceYong.images")

モデルフォルダの自動認識と出力フォルダ引数のパターン

#remap_selected_filenode_texture_path
import maya.cmds as cmds
import pymel.core as pm
import os

def remap_selected_texture(z_path):
    
    # figure out what's selected
    selected = pm.ls(sl=True)
    # workspace_project_path
    workspace_path = cmds.workspace(query=True, rootDirectory=True)
    print("workspace_path= "+workspace_path)
    # ma_file_path
    ma_file_path=cmds.file(q=True, sn=True)
    print("ma_file_path= "+ma_file_path)
    ma_file_path_arr = ma_file_path.split('/')
    ma_Folder=ma_file_path_arr[-2]
    
    print("ma_Folder= "+ma_Folder)
    for item in selected:
        item_path = pm.getAttr(item+".ftn")
        print("item_path= "+item_path)
        item_path_arr = item_path.split('/')
        texFolderName = item_path_arr[-2]
        print("texFolderName= "+texFolderName)
        fileName = item_path_arr[-1]
        print("fileName= "+fileName)
        fileName = fileName.replace('//', '')
        fileName = fileName.replace('/', '')
        print("fileName2= "+fileName)
        
        
        
        #if ':' not in test_path:

        if (1==1):   
            
            #print("fileName1= "+fileName)
            
            
            #new_path = os.path.join(z_path, 'cartoon_room', second)
            new_path = os.path.join(workspace_path,z_path, fileName)
            new_path = new_path.replace('\\', '/')
            new_path = new_path.replace('//', '/')
            #relative_path = os.path.join(z_path, fileName)
            #====================================================
            relative_path = ma_Folder+"/"+z_path+"/"+fileName
            #====================================================
            print("relative_path= "+relative_path)
            #relative_path = relative_path.replace('\\', '/')
            abs_path = os.path.abspath(workspace_path+"/"+relative_path)
            abs_path = abs_path.replace('//', '/')
            print(" abs_path= "+abs_path)
            new_path= abs_path
            print(" new_path= "+new_path)
            print("----------------------------end--------------------------")
            
            if os.path.exists(new_path):
                if(fileName==""):
                    print("item= "+str(item)+" is No fileName= "+ fileName)
                    cmds.select( str(item), r=True )
                    cmds.delete( str(item) )
                else:
                    print("new_path= "+new_path+' is exists! OK')
                    pm.setAttr(item+'.ftn', new_path)
                
            else:
                print("new_path= "+new_path+ ' not exists NG' )

    cmds.select(selected)
    print("--------------remap_selected_texture----------END")
    
remap_selected_texture("texture")

Scene押してシーンディレクトリ取得、押してWalkでテクスチャファイルをwalkで自動検索

#remap_selected_filenode_texture_path
import maya.cmds as cmds
import pymel.core as pm
import os
   
def dir_walk(walkDir,TextureFileName):
    resultPath=""
    for curDir, dirs, files in os.walk(walkDir):
        print('===================')
        print("現在のディレクトリ: " + curDir)
        curDir=curDir.replace("\\","/")
        curDir_arr=curDir.split("/")
        
        curDir_endDirName = curDir_arr[-1]
        print("curDir_endDirName= "+curDir_endDirName)
        if(curDir_endDirName==".mayaSwatches"):
            print("処理をスキップします。。。")
            pass
        else:
            
            print("内包するディレクトリ:" + str(dirs))
            print("内包するファイル: " + str(files))
            for fileName in files: 
                if(fileName==TextureFileName):
                    print("Hit : fileName= "+fileName+ "== TextureFileName= "+TextureFileName)
                    resultPath=curDir+"/"+fileName
        print('===================')
    return resultPath
     
def remap_selected_texture2(sceme_path):
    
    # figure out what's selected
    selected = cmds.ls(sl=True)
    # workspace_project_path
    workspace_path = cmds.workspace(query=True, rootDirectory=True)
    print("workspace_path= "+workspace_path)
    # ma_file_path
    ma_file_path=cmds.file(q=True, sn=True)
    print("ma_file_path= "+ma_file_path)
    ma_file_path_arr = ma_file_path.split('/')
    ma_Folder=ma_file_path_arr[-2]
    
    print("ma_Folder= "+ma_Folder)
    for item in selected:
        item_path = cmds.getAttr(item+".ftn")
        print("item_path= "+item_path)
        item_path_arr = item_path.split('/')
        texFolderName = item_path_arr[-2]
        print("texFolderName= "+texFolderName)
        fileName = item_path_arr[-1]
        print("fileName= "+fileName)
        fileName = fileName.replace('//', '')
        fileName = fileName.replace('/', '')
        print("fileName2= "+fileName)
        
        fixTexturePath=dir_walk(sceme_path,fileName)
        print("fixTexturePath= "+fixTexturePath)
        #if ':' not in test_path:

        if (1==1):   
            
            #print("fileName1= "+fileName)
            
            
            #new_path = os.path.join(z_path, 'cartoon_room', second)
            #new_path = os.path.join(workspace_path,z_path, fileName)
            new_path = fixTexturePath
            new_path = new_path.replace('\\', '/')
            new_path = new_path.replace('//', '/')
            #relative_path = os.path.join(z_path, fileName)
            #====================================================
            #relative_path = ma_Folder+"/"+z_path+"/"+fileName
            #====================================================
            #print("relative_path= "+relative_path)
            #relative_path = relative_path.replace('\\', '/')
            #abs_path = os.path.abspath(workspace_path+"/"+relative_path)
            #abs_path = abs_path.replace('//', '/')
            #print(" abs_path= "+abs_path)
            #new_path= abs_path
            print(" new_path= "+new_path)
            print("----------------------------end--------------------------")
            
            if os.path.exists(new_path):
                if(fileName==""):
                    print("item= "+str(item)+" is No fileName= "+ fileName)
                    cmds.select( str(item), r=True )
                    cmds.delete( str(item) )
                else:
                    print("new_path= "+new_path+' is exists! OK')
                    #cmds.setAttr(item+'.ftn', new_path)
                    cmds.setAttr(item+'.fileTextureName', new_path,type='string')
            else:
                print("new_path= "+new_path+ ' not exists NG' )

    cmds.select(selected)
    print("--------------remap_selected_texture----------END")
    

def remap_fileNode_texture(self):
    text_Field_id="remap_selected_filenode_texture_path_Window|USD_layout|pathTxtFld"
    textField_outputFolder = cmds.textField(text_Field_id, q=True, text=True)
    remap_selected_texture2(textField_outputFolder)

def get_scenePath():
    scenefilePath = cmds.file(q=1, sceneName=1)
    mayaPath,mayaFile = os.path.split(scenefilePath)
    #mayaPath = mayaPath + "/Usd/"
    #mayaPath = mayaPath + "/"
    mayaPath=os.path.abspath(mayaPath)
    mayaPath=mayaPath.replace('\\', '/')
    print("mayaPath= "+mayaPath)
    mayaPath_len=len(mayaPath)
    last_str=mayaPath[mayaPath_len-1:]
    print("mayaPath= "+mayaPath+ " last_str= "+last_str)
    if(last_str=="/"):
        pass
    else:
        mayaPath=mayaPath+"/"
    
    return mayaPath

def btn_scene(self):
    scenePath = get_scenePath()
    
    #textureFolder= cmds.textField('textField_outputFolder', q=True, text=True)
    
    
    
    #lastExportDirPath=scenePath+textureFolder+"/"
    
    
    
    set_lastExportDirPath(scenePath)


def set_lastExportDirPath(lastExportDirPath):
    
    selectList=cmds.ls(sl=True)
    if(str(selectList)== "[]"):
        print("なにも選択されていません。0 set_lastExportDirPath")

            
    text_Field_id="remap_selected_filenode_texture_path_Window|USD_layout|pathTxtFld"
    cmds.textField(text_Field_id, edit=True, text=lastExportDirPath)
    cmds.select(selectList)



def createWindow():
    scenefilePath = cmds.file(q=1, sceneName=1)

    USD_window = cmds.window("remap_selected_filenode_texture_path_Window", widthHeight=(400, 200))
    USD_layout = cmds.columnLayout("USD_layout",adjustableColumn=True, parent=USD_window)
    cmds.text (label="選択したハイパーシェードのテクスチャのパスを置換するツールです。", align='left', parent=USD_layout)
    cmds.text (label="シーンのパス以下のフォルダーから検索して自動的に置換します。", align='left', parent=USD_layout)
    cmds.separator(parent=USD_layout)
    cmds.text (label="", align='left', parent=USD_layout)
    
    cmds.text (label="1、Sceneを押してください。", align='left', parent=USD_layout)
    cmds.button(label="Scene", command=btn_scene, parent=USD_layout)
    text_field = cmds.textField("pathTxtFld", parent="USD_layout",text="")
    #cmds.text (label="2、テクスチャフォルダ名を指定してください。", align='left', parent=USD_layout)
    #text_field = cmds.textField("textField_outputFolder", parent="USD_layout",text="texture")
    cmds.separator(parent=USD_layout)
    cmds.text (label="", align='left', parent=USD_layout)

    
    
    
    cmds.text (label="2、HyperShadeのテクスチャTabで(例:file1,file2,複数可)を選択して、ボタンを押してください。", align='left', parent=USD_layout)
    #CheckBox_Absolute_Path_Bool = cmds.checkBox('CheckBox_Absolute_Path', q=True, value=True)
    cmds.button(label="Remap FileNode Texture ", command=remap_fileNode_texture, parent=USD_layout)
    
    #cmds.separator(parent=USD_layout)

    #cmds.button(label="Add USD Referernce/Payload... ", command=add_prim_xform_for_stage_layer, parent=USD_layout)

    cmds.showWindow(USD_window)
    return None

    
def remap_selected_filenode_texture_path():
    if cmds.window("remap_selected_filenode_texture_path_Window",exists=True):
        cmds.deleteUI("remap_selected_filenode_texture_path_Window")
    createWindow()



remap_selected_filenode_texture_path()

fileノードのパスのバックスラッシュに対応しました。

#remap_selected_filenode_texture_path
import maya.cmds as cmds
import pymel.core as pm
import os
   
def dir_walk(walkDir,TextureFileName):
    resultPath=""
    for curDir, dirs, files in os.walk(walkDir):
        print('===================')
        print("現在のディレクトリ: " + curDir)
        curDir=curDir.replace("\\","/")
        curDir_arr=curDir.split("/")
        
        curDir_endDirName = curDir_arr[-1]
        print("curDir_endDirName= "+curDir_endDirName)
        if(curDir_endDirName==".mayaSwatches"):
            print("処理をスキップします。。。")
            pass
        else:
            
            print("内包するディレクトリ:" + str(dirs))
            print("内包するファイル: " + str(files))
            for fileName in files: 
                if(fileName==TextureFileName):
                    print("Hit : fileName= "+fileName+ "== TextureFileName= "+TextureFileName)
                    resultPath=curDir+"/"+fileName
        print('===================')
    return resultPath
     
def remap_selected_texture2(sceme_path):
    
    # figure out what's selected
    selected = cmds.ls(sl=True)
    # workspace_project_path
    workspace_path = cmds.workspace(query=True, rootDirectory=True)
    print("workspace_path= "+workspace_path)
    # ma_file_path
    ma_file_path=cmds.file(q=True, sn=True)
    print("ma_file_path= "+ma_file_path)
    ma_file_path_arr = ma_file_path.split('/')
    ma_Folder=ma_file_path_arr[-2]
    
    print("ma_Folder= "+ma_Folder)
    for item in selected:
        item_path = cmds.getAttr(item+".ftn")
        print("item_path= "+item_path)
        safe_path = item_path.replace("\\", "/")
        print("safe_path= "+safe_path)
        item_path_arr = safe_path.split('/')
        print("item_path_arr[0]= "+item_path_arr[0])
        
        print("item_path_arr[-1]= "+item_path_arr[-1])
        print("item_path_arr[-2]= "+item_path_arr[-2])
        texFolderName = item_path_arr[-2]
        print("texFolderName= "+texFolderName)
        fileName = item_path_arr[-1]
        print("fileName= "+fileName)
        fileName = fileName.replace('//', '')
        fileName = fileName.replace('/', '')
        print("fileName2= "+fileName)
        
        fixTexturePath=dir_walk(sceme_path,fileName)
        print("fixTexturePath= "+fixTexturePath)
        #if ':' not in test_path:

        if (1==1):   
            
            #print("fileName1= "+fileName)
            
            
            #new_path = os.path.join(z_path, 'cartoon_room', second)
            #new_path = os.path.join(workspace_path,z_path, fileName)
            new_path = fixTexturePath
            new_path = new_path.replace('\\', '/')
            new_path = new_path.replace('//', '/')
            #relative_path = os.path.join(z_path, fileName)
            #====================================================
            #relative_path = ma_Folder+"/"+z_path+"/"+fileName
            #====================================================
            #print("relative_path= "+relative_path)
            #relative_path = relative_path.replace('\\', '/')
            #abs_path = os.path.abspath(workspace_path+"/"+relative_path)
            #abs_path = abs_path.replace('//', '/')
            #print(" abs_path= "+abs_path)
            #new_path= abs_path
            print(" new_path= "+new_path)
            print("----------------------------end--------------------------")
            
            if os.path.exists(new_path):
                if(fileName==""):
                    print("item= "+str(item)+" is No fileName= "+ fileName)
                    cmds.select( str(item), r=True )
                    cmds.delete( str(item) )
                else:
                    print("new_path= "+new_path+' is exists! OK')
                    #cmds.setAttr(item+'.ftn', new_path)
                    cmds.setAttr(item+'.fileTextureName', new_path,type='string')
            else:
                print("new_path= "+new_path+ ' not exists NG' )

    cmds.select(selected)
    print("--------------remap_selected_texture----------END")
    

def remap_fileNode_texture(self):
    text_Field_id="remap_selected_filenode_texture_path_Window|USD_layout|pathTxtFld"
    textField_outputFolder = cmds.textField(text_Field_id, q=True, text=True)
    remap_selected_texture2(textField_outputFolder)

def get_scenePath():
    scenefilePath = cmds.file(q=1, sceneName=1)
    mayaPath,mayaFile = os.path.split(scenefilePath)
    #mayaPath = mayaPath + "/Usd/"
    #mayaPath = mayaPath + "/"
    mayaPath=os.path.abspath(mayaPath)
    mayaPath=mayaPath.replace('\\', '/')
    print("mayaPath= "+mayaPath)
    mayaPath_len=len(mayaPath)
    last_str=mayaPath[mayaPath_len-1:]
    print("mayaPath= "+mayaPath+ " last_str= "+last_str)
    if(last_str=="/"):
        pass
    else:
        mayaPath=mayaPath+"/"
    
    return mayaPath

def btn_scene(self):
    scenePath = get_scenePath()
    
    #textureFolder= cmds.textField('textField_outputFolder', q=True, text=True)
    
    
    
    #lastExportDirPath=scenePath+textureFolder+"/"
    
    
    
    set_lastExportDirPath(scenePath)


def set_lastExportDirPath(lastExportDirPath):
    
    selectList=cmds.ls(sl=True)
    if(str(selectList)== "[]"):
        print("なにも選択されていません。0 set_lastExportDirPath")

            
    text_Field_id="remap_selected_filenode_texture_path_Window|USD_layout|pathTxtFld"
    cmds.textField(text_Field_id, edit=True, text=lastExportDirPath)
    cmds.select(selectList)



def createWindow():
    scenefilePath = cmds.file(q=1, sceneName=1)

    USD_window = cmds.window("remap_selected_filenode_texture_path_Window", widthHeight=(400, 200))
    USD_layout = cmds.columnLayout("USD_layout",adjustableColumn=True, parent=USD_window)
    cmds.text (label="選択したハイパーシェードのテクスチャのパスを置換するツールです。", align='left', parent=USD_layout)
    cmds.text (label="シーンのパス以下のフォルダーから検索して自動的に置換します。", align='left', parent=USD_layout)
    cmds.separator(parent=USD_layout)
    cmds.text (label="", align='left', parent=USD_layout)
    
    cmds.text (label="1、Sceneを押してください。", align='left', parent=USD_layout)
    cmds.button(label="Scene", command=btn_scene, parent=USD_layout)
    text_field = cmds.textField("pathTxtFld", parent="USD_layout",text="")
    #cmds.text (label="2、テクスチャフォルダ名を指定してください。", align='left', parent=USD_layout)
    #text_field = cmds.textField("textField_outputFolder", parent="USD_layout",text="texture")
    cmds.separator(parent=USD_layout)
    cmds.text (label="", align='left', parent=USD_layout)

    
    
    
    cmds.text (label="2、HyperShadeのテクスチャTabで(例:file1,file2,複数可)を選択して、ボタンを押してください。", align='left', parent=USD_layout)
    #CheckBox_Absolute_Path_Bool = cmds.checkBox('CheckBox_Absolute_Path', q=True, value=True)
    cmds.button(label="Remap FileNode Texture ", command=remap_fileNode_texture, parent=USD_layout)
    
    #cmds.separator(parent=USD_layout)

    #cmds.button(label="Add USD Referernce/Payload... ", command=add_prim_xform_for_stage_layer, parent=USD_layout)

    cmds.showWindow(USD_window)
    return None

    
def remap_selected_filenode_texture_path():
    if cmds.window("remap_selected_filenode_texture_path_Window",exists=True):
        cmds.deleteUI("remap_selected_filenode_texture_path_Window")
    createWindow()



remap_selected_filenode_texture_path()

[maya][UE5.7.3]USD workflow pipline tool :レイヤー持ち背景のインポート

[Maya][UE5.7.3] USD workflow pipeline tool: Importing backgrounds with layers

https://github.com/nobolu-ootsuka-unrealengine/furcraeaMayaTool

kind を設定版を作ってUE で毎回オプションを触らなくて済むようにします。

USD Export Selection

Window > USD Stage Windowを開き


Openから S_Stg_Furcraea_Gate_Com_Geom.usda を選択


USD Stage Editor >option>collapsing>Use prim kinds for collapsingをoffにしたらStaticMeshが分かれた

Import

インポートフォルダ選択

インポートオプションにも Kind To Collapseがあるがこの設定にした。

一番上のGeom以下の全部選択して>Level>Create Packed Actor

Level Instance

BPP

BPP

Level Instance

レイヤー持ち背景のインポートできあがり。

[maya][mel]選択したグループに入った大量のメッシュのUVを自動配置するselect_Mesh_Group_To_replace_UV.mel

[maya][mel] select_Mesh_Group_To_replace_UV.mel – Automatically places UVs on a large number of meshes in a group.

ひさしぶりに楽しい楽しいmel script codingした。

Nurvs モデリングしたあとUVスケールが1×1になって全メッシュのUVが重なってる状態から

スクリプト実行で

自動配置できる。


select_Mesh_Group_To_replace_UV.mel

//実行
float $widthU = 0.05; //幅
float $heightV = 0.05; //高さ
float $ScaleU_ = 0.0625; //スケールX
float $ScaleV_ = 0.0625; //スケールY
float $newLineU_ =0.4; //改行位置
select_Mesh_Group_To_replace_UV($widthU,$heightV,$ScaleU_,$ScaleV_,$newLineU_);

//select Mesh Group To replace UV
global proc select_Mesh_Group_To_replace_UV(float $width,float $height,float $ScaleU,float $ScaleV ,float $newLineU)
{
    string $selectedArrFUllPath[] = `ls -long -sl`;
    print($selectedArrFUllPath);
    
    string $FirstSelect=$selectedArrFUllPath[0];
    string $inputNodes_mesh[] = `ls -type mesh -long -dag $FirstSelect`;
    $inputNodes_meshlong=size($inputNodes_mesh);
    string $inputNodes[];
    clear $inputNodes;
    string $mesh;
    for($d0 = 0; $d0 <$inputNodes_meshlong;$d0++){
        $mesh=$inputNodes_mesh[$d0];
        //string $parentS[] = `listRelatives -parent -path -type transform $mesh`;
        //$parent=$parentS[0];
        //$inputNodes[size($inputNodes)] = $parent;
        $bool=`gmatch $mesh "*Orig"`;
        if($bool==0){
            $inputNodes[size($inputNodes)] = $mesh;
        }
    }
    
    
    
    print("$inputNodes= ------------------------------------------------ \n");
    print($inputNodes);
    print("------------------------------------------------------------- \n");
    
    $inputNodeslong=size($inputNodes);
    
    float $buildX=0;
    float $buildY=0;
    //float $width=0.05;
    //float $height=0.05;
    for($f = 0; $f <$inputNodeslong;$f++){
        $mesh=$inputNodes[$f];
        $face=$mesh+".f[0:]";
        
        select -r $mesh;
        polyAutoProjection -lm 0 -pb 0 -ibd 1 -cm 0 -l 2 -sc 1 -o 1 -p 6 -ps 0.2 -ws 0 $face;
        select -r $face;
        
        $uvPivot=$mesh+".uvPivot";
        //setAttr $uvPivot -type double2 0.5 0.5 ;
        
        $U=$buildX*1-0.0;
        $V=$buildY*1-0.0;
        print("U:"+$U+" V:"+$V+"\n");
        //polyEditUV -relative false -u $buildX -v $buildY;
        $PivotU=$U+$width/2;
        $PivotV=$V+$height/2;
        polyEditUV -relative false -pu $PivotU -pv $PivotV -su $ScaleU -sv $ScaleU -u $buildX -v $buildY;
        //polyEditUV -relative false -pu $PivotU -pv $PivotV -su 0.8 -sv 0.8 ;
        //polyEditUV -relative false -pu $U -pv $V -su 0.2 -sv 0.2 ;
        if($buildX>$newLineU){
            print("new y----------Line\n");
            $buildY = $buildY+$width;
            $buildX = -$width;
        }
        $buildX = $buildX + $width;
    }
}
float $widthU = 0.05; //幅
float $heightV = 0.05; //高さ
float $ScaleU_ = 0.0625; //スケールX
float $ScaleV_ = 0.0625; //スケールY
float $newLineU_ =0.4; //改行位置
select_Mesh_Group_To_replace_UV($widthU,$heightV,$ScaleU_,$ScaleV_,$newLineU_);
/*
参考スクリプト
select -r group24_rearBoost_Up2.f[0:1601] group4_rear_Body1.f[0:7323] ;
setAttr "group24_rearBoost_UpShape2.uvPivot" -type double2 0.5 2.504208 ;
polyEditUV -u 0 -v 2.004208 ;
polyEditUV -pu 0.5 -pv 2.504208 -su 0.25 -sv 0.25 ;
*/

わかった事
polyEditUV コマンドは一発で6パラメータ入れないとだめ、

polyEditUV -relative false -pu $PivotU -pv $PivotV -su $ScaleU -sv $ScaleU -u $buildX -v $buildY;

polyEditUVコマンドは絶対スケール値を指定しても相対スケールが入る

polyEditUV -relative false -pu $PivotU -pv $PivotV -su $ScaleU -sv $ScaleU -u $buildX -v $buildY;

[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出力
おしまい。

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

[maya2025]animImportExportがインポートメニューからなくなった様子なのだがmelでやると動くので置いておく。

■アニメーションのエクスポート&インポート機能を有効にする

  • プラグインマネージャーを開いて「animImportExport.mll」の「Loaded」「Auto load」をチェック

■アニメーションのエクスポート

  1. アニメーションをコピーしたいリグのトップノードを選択してツールバーの「File」→「Export Selection(オプション)」を実行する。 
  2. 出力時の設定で「General Option」→「File type」を「animExport」に設定する。  
  3.  「File Type Specific Options」→「Hierarchy階層」→「Below(下位)」に設定する。 

※階層ではなくそれぞれのノードを選択して出力しようとするとインポートの際に失敗することが多いので注意。

select -r Root ;
file -force -options “precision=8;intValue=17;nodeNames=1;verboseUnits=0;whichRange=1;range=0:10;options=keys;hierarchy=below;controlPoints=0;shapes=1;helpPictures=0;useChannelBox=0;copyKeyCmd=-animation objects -option keys -hierarchy below -controlPoints 0 -shape 1 ” -typ “animExport” -pr -es “D:/work/right.anim”;

■アニメーションのインポート

  1. アニメーションをロードしたい階層構造のトップノードを選択。
  2. 「File」→「Import(オプション)」で、先ほど出力したファイルを選択する。 
  3. 「File Type」を「animImport」にして、必要に応じて「Paste Method」の「Insert」「Replace」「Merge」を選択する。
select -r Root ;
file -import -type “animImport”  -ignoreVersion -ra true -mergeNamespacesOnClash false -namespace “right” -options “;targetTime=4;copies=1;option=insert;pictures=0;connect=0;”  -pr “D:/work/right.anim”;
 

成功した。

[maya]法線の仕上げ方法

maya sphere normal to mesh

Mayaで球状の法線をメッシュに反映するには?

Mayaで球状の法線をメッシュに反映させるには、法線転送機能を使用します。まず、球状の法線を持つメッシュ(転送元)と、法線を反映させたいメッシュ(転送先)を用意します。次に、転送元と転送先のメッシュを選択し、「メッシュ>アトリビュートの転送」を選択、頂点法線のみを転送するように設定して「転送」を実行します。これにより、転送元の法線情報が転送先のメッシュに適用され、球状の滑らかな陰影が再現されます

そんなことをしなくてもよかった

を直すには

Mesh Display > Set Vertex Normal

Match face normal

Apply

でOKだった。