MayaScripts_DeleteConstraints : 任意のノードのコンストレイントノードをリスト
ノードにどんなコンストレイントがかかっているか、パっと確認したいことってないっすか?
標準機能でも、そんなのあったような気がするんですけど、簡易的なやつ作りました。
DeleteConstraints
選択ノードの子供にあるコンストレイントノードをリストで表示し、選択と削除が可能なUIを生成します。
ダウンロード
使い方
ダウンロードしたファイルから、DeleteConstraintsフォルダを以下の場所に配置してください。
C:\Users\"ユーザー名"\Documents\maya\scripts
実行するには以下の2行。
import DeleteConstraints
DeleteConstraints.main()
被コンストレイントノードを選択して、実行すると下図のようなウィンドウが立ち上がります。
該当ノードの子供に存在するコンストレイントノードをリストで表示しており、各ノード名をクリックすると、シーン上でも該当ノードを選択します。
Delete
キーを押すと、選択している項目のノードをシーン上から削除します。
現状、それだけです。
MayaScripts_AttachTransform : TransformのAttach
Maya2023からですかね?ようやく任意のTransformノード同士の位置合わせが標準で出来るようになったっぽいですね。 ・・・いや、遅いだろて!
もう作ってるて!
AttachTransform
# encoding=utf8 import pymel.core as pm VERSION = "0.1" print("\n*import {0}.{1}".format(__name__,VERSION)) def getTranslate (target): # type(pm.PyNode) -> list[float, float, float] return pm.xform(target, q = True, ws = True, t = True) def getRotate (target): # type(pm.PyNode) -> list[float, float, float] return pm.xform(target, q = True, ws = True, ro = True) def setTranslate (target, vector): # type(pm.PyNode, list[float, float, float]) -> None pm.xform(target, ws = True, t = vector) def setRotate (target, angle): # type(pm.PyNode, list[float, float, float]) -> None pm.xform(target, ws = True, ro = angle) def attachTranslate (master, slave): # type(pm.PyNode, pm.PyNode) -> None vector = getTranslate(master) setTranslate(slave, vector) def attachRotate (master, slave): # type(pm.PyNode, pm.PyNode) -> None angle = getRotate(master) setRotate(slave, angle) def attachTransform (master, slave): # type(pm.PyNode, pm.PyNode) -> None attachTranslate(master, slave) attachRotate(master, slave) def getMasterAndSlavesFromSelected (): # type() -> list[pm.PyNode] target = pm.selected() if len(target) < 2: return None return target def main (): target = getMasterAndSlavesFromSelected() if not target : return master = target[-1] modifier = pm.getModifiers() attachFuncs = { 0:attachTransform,#none 4:attachTranslate,#ctrl 8:attachRotate#alt } for i in target[:-1]: attachFuncs[modifier](master,i) pm.headsUpMessage("*{0}* AttachTransform done *{0}*".format("-"*50))
使い方
Attachしたいノードを選択し、実行すると、最後に選択されたノードをターゲットとして、位置と回転を合わせます。
Ctrl
を押しながら実行すると、位置のみをAttachします。
Alt
を押しながら実行すると、回転のみをAttachします。
以上。
MayaScripts_AddAnimatedAttrToAnimLayer : アニメーションレイヤーへのAttr追加補助
AnimationLayerを利用する際、アニメートされているAttributeだけをAnimationLayerに追加したいと思ったことはありますか?
AnimationLayerに対してノードを追加する際、いつも対象のノードを選択した状態でAnimationLayerEditor上のLayerを右クリックして表示されるコンテキストメニューから「選択したオブジェクトの追加」を実行しているのですが、この機能だとアニメート可能な全てのTransformAttributeとVisiblityがLayerに追加されてしまい・・・後にAnimationLayerをマージする際にLayerに追加された全てのAttributeがベイクされてしまいます。マージ時にスマートベイク等を利用すれば結果は変わりますが、AnimationLayerの利用目的にもよりますが、基本的にはベースのアニメーションに対して加算的にLayerを使いたいことの方が多いように思います。よって、そもそもアニメートされていないAttributeがLayerに追加される必要はないことが殆どではないでしょうか。
任意のAttributeだけをAnimationLayerに追加するには、チャンネルボックス上でAttributeを選択し、右クリックすると、コンテキストメニューに「選択したレイヤに追加」とありますので、これを実行すると良いのですが・・・対象のAnimationLayerを選択して、Attributeを選択して・・・と少々面倒に感じます。
なので、自動化するスクリプトを作りました。
AddAnimatedAttrToAnimLayer
# encoding:utf-8 import pymel.core as pm VERSION = "0.2" print ("\n-*- import {0}.{1} -*-".format(__name__,VERSION)) # 選択しているレイヤーがなかった場合の挙動 WITH_CREATE_NEWLAYER = True# 新規レイヤーの作成を行う PRIORITY_CREATE_LAYER = False# 新規レイヤーの作成を優先する PRIORITY_EXIST_LAYER = True# 既存レイヤーを優先する SKIP_BASE_LAYER = True# Baseレイヤーを対象から除外する ANIM_LAYER = ["animLayer"]# 対象とするAnimLayerノード TARGET_NODE_TYPES = ["animCurve","animBlendNodeBase"]# Animatedと判断する接続ノードの種類 def __listAnimatableAttrsFromNode (target_nodes): """任意のノードから、AnimatableなAttrを返す""" # type:(list[pm.PyNode]) -> list[pm.general.Attribute] animatable_attrs = list() for n in target_nodes: animatable_attrs.extend(n.listAnimatable()) return animatable_attrs def __popAnimatedAttrs (target_attributes): """Attrのリストから、TARGET_NODE_TYPESに接続されているAttrだけを抽出""" # type:(list[pm.general.Attribute]) -> list[pm.general.Attribute] animated_attrs = list() for a in target_attributes: if a.connections(t = TARGET_NODE_TYPES, d = False): animated_attrs.append(a) print ("Animated attributes = {0}".format(animated_attrs)) return animated_attrs def __listAllExistAnimLayers (): """存在するAnimLayerを全て返す""" # type:() -> list[pm.nodetypes.AnimLayer] return [pm.nodetypes.AnimLayer(n) for n in pm.ls(type = ANIM_LAYER)] def __getSelectedAnimLayersFromEditor (): """AnimLayerEditorで選択されているAnimLayerを返す""" # type:() -> list[pm.nodetypes.AnimLayer] selected_animlayers = list() base_layer = pm.animLayer(q = True, r = True) for al in __listAllExistAnimLayers(): if SKIP_BASE_LAYER and al == base_layer : continue if pm.animLayer(al, q = True, sel = True): selected_animlayers.append(al) print ("Selected AnimLayers = {0}".format(selected_animlayers)) return selected_animlayers def __addAttrToAnimLayers (target_attrs, target_animlayers): """任意のAttrを任意のAnimLayerに追加する""" # type:(list[pm.general.Attribute],list[pm.nodetypes.AnimLayer]) -> None for al in target_animlayers: pm.animLayer(al, e = True, at = target_attrs) print ("Add Attrs:\n{0} \n\tto {1}".format([a.name() for a in target_attrs],target_animlayers)) def __createNewAnimLayer (name = "NewAnimLayer"): """新規AnimLayerを作成する(または同名のAnimLayerを返す)""" # type:(str) -> list[pm.nodetypes.AnimLayer] animLayer = None isAlreadyExist = pm.animLayer(name, q = True, ex = True) if isAlreadyExist and PRIORITY_CREATE_LAYER: animLayer = pm.animLayer(name + "_new")# 必ず新規Layerを作成 elif not isAlreadyExist: animLayer = pm.animLayer(name)# 新規Layerを作成 else: animLayer = pm.nodetypes.AnimLayer(name)# 既存の同名Layerを取得 print ("Create or ExistAnimLayer = {0}".format(animLayer)) return [animLayer] def __findAnimLayerWithAttrs (target_attrs): """任意のAttrがIncludeされているAnimLayerを返す""" # type:(list[pm.general.Attribute]) -> list[pm.nodetypes.AnimLayer] found_animLayers = list() for al in __listAllExistAnimLayers(): if al == pm.animLayer(q = True, r = True):continue layered_attrs = al.getAttributes() for a in target_attrs: if a in layered_attrs and al not in found_animLayers: found_animLayers.append(al) return found_animLayers def __getValidAnimLayers(target_attrs): """有効なAnimLayerを返す""" # type:(list[pm.general.Attribute]) -> list[pm.nodetype.AnimLayer] animLayers = __getSelectedAnimLayersFromEditor() if animLayers: return animLayers if PRIORITY_EXIST_LAYER: animLayers = __findAnimLayerWithAttrs(target_attrs) if not animLayers and WITH_CREATE_NEWLAYER: animLayers = __createNewAnimLayer() return animLayers def __addAnimatedAttrToLayer (target_nodes): """選択しているノードのAnimatedなAttrをAnimLayerへ追加する""" # type:(list[pm.PyNode]) -> None animatable_attrs = __listAnimatableAttrsFromNode(target_nodes) target_attrs = __popAnimatedAttrs(animatable_attrs) if not target_attrs : return target_animLayers = __getValidAnimLayers(target_attrs) if not target_animLayers : return __addAttrToAnimLayers(target_attrs, target_animLayers) def main (): __addAnimatedAttrToLayer(pm.selected())
実行するには下記を参照。
import AddAnimatedAttrToAnimLayer
AddAnimatedAttrToAnimLayer.main()
実行すると、選択しているAnimationLayerに対して、シーン上で選択中のノードのアニメートされているAttributeだけが追加されます。 選択しているAnimationLayerがない場合は、新規でAnimationLayerを生成し、そのLayerを対象とします。
余談
ずっと疑問なんですけど、Mayaコマンドの「animLayer」の引数のひとつに「bestLayer」というものがありますけど、何をもってベストとするのか、イマイチ理解できていません。 公式のコマンドに「ベスト」という単語が使われているのも珍しくないっすか?そうでもない? しかし、ベストて・・・なんかのコマーシャル以外で聞かない単語ですよね。
もうひとつ疑問。 AnimationLayerEditorの各Layer名の右横にある、緑とか赤色のドットインジケーター。
それぞれ、なにを意味しているかは理解できているつもりなんですが、このインジケーターの正式名称て何? 公式のリファレンスには「アクティブなキーイング フィードバック」とか書いてあるけど、ではこれをコマンドから参照するには、なんという名前なの?って話。
MayaScripts_TimeJumper : 任意の時間をリマインドする。
Maya2020辺りですかね、TimeSliderBookmarksっていう機能ができましたよね。
あれ、便利なんですけど、自分にとってはちょっと多機能すぎるんすよね。
純粋にアニメーション中の特定のフレームをリマインドできればいいなと思うことが多々あります。
そんな隙間を埋めるものを作りました。
TimeJumper
仰々しい名前ですが、UIに記述した特定のフレームへのジャンプを簡単にするものになります。
ダウンロード
使い方
ダウンロードしたファイルから、TimeJumperフォルダを以下の場所に配置してください。
C:\Users\"ユーザー名"\Documents\maya\scripts
実行するには以下の2行。
import TimeJumper
TimeJumper.main()
実行すると下図のようなウィンドウが立ち上がります。
- テキストフィールド
- ここにリマインドしたいフレームを記述できます。
- 「0,10,15」みたいにカンマで区切って入力してください。
- その後、Enterを叩けば入力内容が以下の処理で整理されます。
- 値のソート、無効な文字列の除去等
- また隣のチェックボックスにチェックを入れると、入力内容をロックできます。
- Clear
- 入力内容を抹消します。
- 左右の不等号記号ボタン
- 現在の時間を基準として、入力内容の値の時間に飛びます。
- Nearestボタン
- 入力内容から現在の時間にもっとも近い時間に飛びます。
このようにボタンを用意しているんですが、基本的には下記のコードを適当なホットキーに登録して使う前提です。
- UIを立ち上げます(既に立ち上がっていれば、Nearestボタンを押した時と同様の機能が働きます)
import TimeJumper TimeJumper.main() # UIを立ち上げない場合はこっち import TimeJumper TimeJumper.jumpToNearest()
- 左右の不等号記号ボタンを押した時と同じ機能が働きます。
# > import TimeJumper TimeJumper.jumpToNext() # < import TimeJumper TimeJumper.jumpToPrev()
設定用ノード
UIに入力した内容は、シーン内に生成される「okTimeJumperSetting」というTransformノードに保存されます。
設定内容を完全に破棄してシーンを綺麗にしたい場合は、UIを閉じてこのノードを削除してください。
更新履歴
ver 1.1
Lock機能を追加。ver 1.0
配布開始。
余談
- アニメーションの途中からループになる。
- 任意フレームのポーズが他のアニメーションデータに繋がるポーズである。
等々、編集中に無意識変えてしまうとマズいフレームが存在していることって結構あります。
とは言え、編集中はアニメーション作業に没頭したいので、そういう面倒事を意識したくないんですよ。
編集中のふとしたタイミングで実行して、登録したフレーム位置にジャンプし、「あぁそうそう、ここは編集しないよう気を付けなアカンね」って具合です。
TimeSliderBookmarksで全然代用可能なんで、この思想に共感できない方は素直にそっちをお使いください。
MayaScripts_CameraCirculation:カメラ切り替え補助ツール
こんにちは。
今回も小スクリプト紹介です。
2023/02/20 Update
CameraCirculation
アクティブなビューポートのカメラをサイクル的に切り替えます。
実行する度にシーン内のカメラをリスト化し、上から順にスイッチイングします。
■ ダウンロード
ダウンロードしたファイルから、CameraCirculationフォルダを以下の場所に配置してください。
C:\Users\"ユーザー名"\Documents\maya\scripts
で、実行するには以下の2行をシェルフ等に登録してお使いください。
import CameraCirculation
CameraCirculation.main()
■ 使い方
実行する度に「camera1 -> camera2 -> front -> persp」のようにアクティブなビューポートのカメラをサイクル的に切り替えます。
「Alt」を押しながら実行すると、Hideされているカメラを対象から除外してサイクルします。
※最初にビューポートのカメラがHideされていない状態でないとエラーとなりますのでご注意。
また、オプションとして「Shift + Ctrl」を押しながら実行すると、以下のようなウィンドウが立ち上がります。
各項目について
- Ignore Hides
- Reload時にHideされているカメラを除外します。
- 正投影カメラリストとAddボタン
- シーン内に新規で任意の方向の正投影カメラを追加します。
- Mayaデフォルトの正投影カメラと大体内容は同じです。
- カメラ名リスト
- シーン内に存在するカメラのリストです。
- ビューポートのカメラを選択したカメラに切り替えます。
- ダブルクリックをすると該当カメラのNearClipPlaneを「1」にFarClipPlaneを「1000000」に変更します。
- Reload
- シーン内のカメラを再度検索し、リストを更新します。
- Close
- ウィンドウを閉じます。
■ 更新履歴
ver 1.3.2
- UIで選択中のカメラをDeleteKeyで削除できる機能を追加。
ver 1.3.0
- 正投影カメラの生成機能を追加。
ver 1.2.0
- Maya2022以降でも利用できるように修正。
- VisiblityがFalseのカメラは無視するオプションを追加。
- 新規シーン以外でシーンを開いた際にUIを更新する機能を追加。
ver 1.1.1
- カメラ取得をTransformからShapeに変更。
ver 1.1
- Near/FarClipPlane値を規定値へ変更する機能を追加。
ver 1.0
- 配布開始
余談
アニメーション制作においても、制作用とレンダリング用の2つのカメラを切り替えながら作業することって多いんですよね~。
作業中に何度も行う面倒なカメラ切り替え作業を短縮する為に作りました。
以上です。
宜しくお願い致します。
MayaScripts_FollowerGenerator:選択ノードに追従するノードを生成
ニッチなことかもしれませんが、とあるTransformノードに対して位置や回転を追従するTransformノードが欲しいことってありませんか?
私はあるんすよ。
FollowerGenerator
Transformノードの位置と回転に追従するTransformノードを生成する。
そういうスクリプトです。
コード
# encoding:utf-8 import pymel.core as pm VERSION = "1.1" print ("-*- import {0}.{1} -*-".format(__name__,VERSION)) SUFIX = "_Follower" VISIBLE = False LOCKATTR = True LOCKATTR_TARGET = ["translate", "rotate", "scale", "visibility"] STRIP_NAMESPACE = True LOCATOR_NODE = False WINDOWNAME = "FollowerGen" WINDOWSIZE = [160,78] def __createFollowerNode (name): """Follower用のTransformノードを生成""" # type:(str) -> pm.nodetypes.Transform follower = None if LOCATOR_NODE: follower = pm.spaceLocator(n = name) follower.getShape().setAttr("v", VISIBLE) else: follower = pm.createNode("transform", n = name) return follower def __createFollowerName (target_node): """Followerノード用の名前を生成""" # type:(pm.nodetypes.Transform) -> str return target_node.name(stripNamespace = STRIP_NAMESPACE) + SUFIX def __generateFollower (target_node, const_flags): # type:(pm.nodetypes.Transform, list[bool]) -> pm.nodetypes.Transform follower = __createFollowerNode(__createFollowerName(target_node)) if const_flags[0] : pm.pointConstraint(target_node, follower) if const_flags[1] : pm.orientConstraint(target_node, follower) for a in LOCKATTR_TARGET : follower.setAttr(a, lock = LOCKATTR) return follower def __generateFollowers (target_nodes, const_flags = [True, True]): """Targetノードに対してFollowerを生成""" # type:(list[pm.nodetypes.Transform], list[bool]) -> list[pm.nodetypes.Transform] if not target_nodes : return None followers = list() for n in target_nodes: if not isinstance(n, pm.nodetypes.Transform) : continue followers.append(__generateFollower(n, const_flags)) return followers def __deleteExistUI (*arg): # type:(tuple) -> None if pm.window(WINDOWNAME, q = True, ex = True): pm.deleteUI(WINDOWNAME) def __buildUI (): # type:() -> None __deleteExistUI() with pm.window(WINDOWNAME, mnb = False, mxb = False, s = False) as w: w.setWidthHeight(WINDOWSIZE) with pm.columnLayout(adj = True, rs = 2, co = ["both", 5]): pm.separator(st = "none", h = 1) pos_flag = pm.checkBox(l = "Position", v = True) rot_flag = pm.checkBox(l = "Rotation", v = True) cmd = lambda *arg:__generateFollowers(pm.selected(), [pos_flag.getValue(), rot_flag.getValue()]) with pm.rowLayout(nc = 2): button_width = (WINDOWSIZE[0] * 0.5) - 6 pm.button(l = "Generate", w = button_width, c = cmd) pm.button(l = "Close", w = button_width, c = __deleteExistUI) def main (): if pm.getModifiers() == 5: __buildUI() return __generateFollowers(pm.selected())
インストール
上記コードを「FollowerGenerator.py」という名前のファイルに保存して、下記の場所においてください。
C:\Users\ユーザー名\Documents\maya\scripts
実行するには下記のコード。
import FollowerGenerator
FollowerGenerator.main()
使い方
追従したいノードを選択した状態で実行すると、下図のように「Transformノード」が生成されます。
選択したノードに対して位置と回転のコンストレイントが接続されていますので追従して動きます。
また、「Shift+Ctrl」を押しながら実行すると下図のUIが立ち上がります。
追従するTransformAttributeを位置と回転のいずれか、またはその両方をチェックボックスから選択できます。
「Generateボタン」を押すと選択中のTransformノードに対して、チェックした内容で追従するノードを生成します。
オプション
コード中の定数を変更することで、ちょっとしたオプションが利用可能です。
SUFIX = "_Follower" # 生成するノード名のサフィックス VISIBLE = False # LOCATOR_NODEがTrueになっている場合、その可視性をこの値に設定します。 LOCKATTR = True # 生成したノードに不要なキーフレームを打てないように、下記のAttrをロックします。 LOCKATTR_TARGET = ["translate", "rotate", "scale", "visibility"] STRIP_NAMESPACE = True # 生成するノード名に選択ノードのネームスペースを含めるか LOCATOR_NODE = False # 生成するノードをLocatorにする
以上。
MayaScripts_NoiseAnimGenerator : 自動でノイズアニメーション
2023/02/20 Update
今回も自作スクリプトの紹介。
例によってPymelです。
Download
NoiseAnimGenerator
なんてことはない、ノイズアニメーションを自動生成するものです。
アニメーション作業において、必ずといっていいほどノイズアニメーションを作る機会があるでしょう?
毎回毎回、適当なフレームごとに適当なキーを打つのはめんどうだよね。
短いものならいいけど、数百フレームにおいてそれを実行するのは地獄。
悪いCGアニメータはノイズアニメーションを無限に作成する地獄か、ポリゴンの埋まりを解消する作業だけをさせられる地獄に落とされます。
たとえそんな地獄に落ちても、このスクリプトがあればノイズアニメーション地獄だけは楽に過ごせるようになります。
これでなにができるのか?
- チャネルボックス上で選択された任意のアトリビュートに対して 、自動でノイズアニメーションを生成します。
- 生成するアニメーションは、自動的にアニメーションレイヤに追加されるので、既存のアニメーションを破壊しません。
- 単なるランダム値の他に、複数の種類のオフセットタイプがあります。
使用方法
ダウンロードしたフォルダの中から、NoiseAnimGeneratorフォルダを以下に配置。
C:\Users\ユーザー名\Documents\maya\scripts
Mayaのスクリプトエディタ等に以下の内容を記述して実行。
import NoiseAnimGenerator
NoiseAnimGenerator.main()
UIについて
実行すると以下のようなUIが表示されます。
それぞれの設定項目の詳細
- Amount Value
- ノイズの強度
- Frame Range
- ノイズを生成するフレーム範囲
- Frame Step
- ノイズの間隔
- Anim Layer
- ノイズを生成するアニメーションレイヤを選択
- 基本的にはAutoで問題ないです。
- Load
- アニメーションレイヤリストをリロードします。
- Offset Type
- ノイズアニメーションの強度を オフセットする種類 。※後述
- Regenerate
- オンにしていると、ノイズ生成時に、既にアニメーションレイヤ内に存在するノイズアニメーションを削除して新規でノイズを生成します。
- オフにしていると、ノイズ生成時に、アニメーションレイヤ内にあるアニメーションを削除しません。
- Generate
- ノイズ生成開始する
- Cancel
- ウィンドウを閉じる
AnimLayerについて
AnimLayerの項目が Auto になっている状態で、ノイズを生成すると
okNoiseAnim という名のアニメーションレイヤがシーン内に追加され、ターゲットのアトリビュートのノイズアニメーションは全てこのレイヤに追加されます。
既にこのレイヤがシーン内に存在する場合は、ターゲットアトリビュートの追加のみが行われます。
また、ノイズを生成するフレーム範囲の両端は、必ず 「0」 の値が入りますので、既存のアニメーションに 不要なオフセットが入ることがないようにしています。
AnimLayerを設定するプルダウンメニューにはシーン内に存在する全てのアニメーションレイヤが表示されます※ので、任意のレイヤにノイズを生成することもできますが、Auto にして置くことをお勧めします。
※表示されない場合は、Loadボタン 押してみてください。
Offset Type
このスクリプトの推しポイントは、ノイズの強度をオフセットできる 点です。
具体的にいうと、選択した項目名ごとに以下のようなオフセットがかかります。
- RANDOM
- 単純なランダム値に基づいてノイズを生成。
- ZIGZAG
- ジグザグになるようにランダム値を生成。
- INCREASE
- 徐々に大きくなるノイズを生成。
- DECREASE
- 徐々に小さくなるノイズを生成。
- INCREASE WAVE
- 指定フレーム範囲の中間部分が、最も大きくなるようにノイズを生成。
- DECREASE WAVE
- 指定フレーム範囲の中間部分が、最も小さくなるようにノイズを生成。
画像乗っけときますね?
更新履歴
ver 2.2 不具合を修正。
ver 2.0
キーフレームを正しく打てない不具合を修正。ver 2.0.beta
Maya2022以降に対応する為、Python3でも動くように修正。
RegenerateフラグとLoadボタンを追加。ver 1.0
配布開始。
制作話
ノイズアニメーションはとにかく作るのがめんどう。
なのに必ずどっかでノイズを作らなきゃいけないこともある。
でも未だにMayaにはそのような機能はない。次のバージョンでは入っているのかな。
最近は朝晩と冷えるので、裸でノイズアニメーション作ってたら風邪引いたなんて話もよく聞きます。
そんな哀れな人を救いたい。そんな一心でつくりました。
それでは。