TR_ShellSwitch 配布

2月 23, 2016 0

TR_ShellSwitchは最大10個までのマテリアルを接続出来るよう拡張した、シェルマテリアルです。

マルチマテリアルのようにマテリアル毎にラベルを付けられる他、ビューポート、レンダリング用マテリアルを個別に切り替える事が出来ます。
また、シーン内のShellSwitchを一括で切り替える事ができる、グローバススイッチャーを搭載しています。

スクリーンショット

ダウンロード

Download (Ver 1.11)
Old Versions

更新履歴

16/02/23 (ver1.00)
ファーストリリース。
16/03/01 (ver1.01)
インストール先を$startupScriptsから$userStartupScriptsに変更。
($startupScriptsでは管理者権限が必要だった為)
16/11/13 (ver1.02)
UI言語を英語に変更、及び英語版Maxでの文字化け解消。
16/11/13 (ver1.10)
グローバルスイッチャーの搭載。
16/12/07 (ver1.11)
グローバルスイッチャーが起動出来ない問題を解消。
その他軽微な修正。

インストール

ダウンロードしたmzpパッケージを3dsMax上にドラッグ&ドロップすると自動的にインストールが実行されます。
もしくは、必要に応じてmseファイルを手動でコピーしてインストールする事も出来ます。
詳しくはReadme参照。

その他

現状では10個までのマテリアル接続に対応しています。
本来なら可変長にしたかったのですが、テスト時にSME上でどうしてもスロットに反映されず・・・。
どなたか、スレートマテリアルエディタ上で、マテリアルスロットを可変長に対応させる方法を教えていただけると幸いです。(通常のプラグインではなく、ScriptedPluginに限る。)

MaxScriptTip: SubRolloutに合わせて自動的にサイズを変えるロールアウト

2月 21, 2016 0
今回は前回の記事に続いてRolloutについての記事です。

UIオブジェクトの一つに、「SubRollout」というコントロールがありますが、今回はこのSubRolloutに合わせて動的にサイズを変更するロールアウトを作りたいと思います。


サンプルロールアウト

起動直後。


ボタンスイッチを押すとそれに対応するサブロールアウトが追加されます。
親ロールアウトは、自動的に子ロールアウトのサイズに合わせます。


全てのサブロールアウトを表示した状態。


ロールアウトを畳んだ時も自動でサイズを変更します。



サブロールアウト作成

初めに、子ロールアウトを作成します。
rollout rltSub1 "Sub1" width:190 height:32 category:1  --- 1. カテゴリの指定
(
    local resizeCallback  -- 2. コールバック変数

    checkbox chkOpt "Option" pos:[8,8] width:152 height:16
    
    -- 3. サイズ変更イベント
    on rltSub1 rolledUp isOpen do
    (
        if resizeCallback != undefined do
            resizeCallback()
    )
)

rollout rltSub2 "Sub2" width:192 height:80 category:2
(
    local resizeCallback
    radiobuttons rdoItems "Items" pos:[8,8] width:96 height:36 \
        labels:#("Item1", "Item2", "Item3")
        
    on rltSub2 rolledUp isOpen do
    (
        if resizeCallback != undefined do
            resizeCallback()
    )
)

rollout rltSub3 "Sub3" width:192 height:112 category:3
(
    local resizeCallback
    editText edtMsg "" pos:[4,8] width:180 height:96
    
    on rltSub3 rolledUp isOpen do
    (
        if resizeCallback != undefined do
            resizeCallback()
    )
)
3つのロールアウトを定義していますが、基本的に3つとも内容は同じです。
要点だけ説明します。
  1. カテゴリの指定
    ロールアウト作成時にcategoryパラメータを設定します。
    こうすることで、サブロールアウトが追加された時に自動的に順番を整列してくれるようになります。例えば、category:3のロールアウトを追加した後からcategory:1のロールアウトを追加した場合でも、category:1が先に来るようになります。
  2. コールバック変数
    この変数は親ロールアウトのresize関数を格納するための変数です。
    サブロールアウト作成時に、親からresize関数オブジェクトを受け取って格納します。
  3. サイズ変更イベント
    サブロールアウトが畳まれたり開かれたりした時に発生します。
    親のresize関数を実行してサイズを変更させます。
以上です。

コールバックの概念が解ってないと少々難解かもしれませんが、基本的にrolledUpイベントを親に伝搬する以外は、特別な機能は持っていません

親ロールアウト作成

次に親ロールアウトを作成します。
rollout rltMain "Main Rollout" width:200 height:180
(
    local self
    local subRollouts
    local btmControls
    subRollout srFloater "" pos:[-1,40] width:206 height:102
    checkbutton ckbSub1 "Sub1" pos:[10,10] width:60 height:20
    checkbutton ckbSub2 "Sub2" pos:[70,10] width:60 height:20
    checkbutton ckbSub3 "Sub3" pos:[130,10] width:60 height:20
    button btnGet "Get" pos:[10,150] width:90 height:20
    button btnClose "Close" pos:[100,150] width:90 height:20
    
    -- 3. サイズ変更関数
    fn resize =
    (
        local lastHeight = srFloater.height
        local maxHeight = 1000
        local subsHeight = 4
        
        for rlt in srFloater.rollouts do
        (
            if rlt.open then
                subsHeight += (rlt.height + 25)
            else
                subsHeight += 21
        )
        subsHeight = amin subsHeight maxHeight
        srFloater.height = subsHeight
        
        local offsetY = subsHeight - lastHeight
        self.height += offsetY
        for c in btmControls do
            c.pos.y += offsetY
    )
    
    -- 2. サブロールアウト追加・削除関数
    fn addSub rlt =
    (
        addSubRollout srFloater rlt
        rlt.resizeCallback = resize
    )
    
    fn removeSub rlt =
    (
        removeSubRollout srFloater rlt
    )
    
    -- 1. ロールアウト初期化
    on rltMain open  do
    (
        local btmY = srFloater.pos.y + srFloater.height
        self = rltMain
        subRollouts = #(rltSub1, rltSub2, rltSub3)
        btmControls = (for c in self.controls where c.pos.y > btmY collect c)
        resize()
    )
    
    -- 4. サブロールアウトON/OFFボタン変更イベント
    on ckbSub1 changed state do
    (
        if state then
            addSub subRollouts[1]
        else
            removeSub subRollouts[1]
        resize()
    )
    
    on ckbSub2 changed state do
    (
        if state then
            addSub subRollouts[2]
        else
            removeSub subRollouts[2]
        resize()
    )
    
    on ckbSub3 changed state do
    (
        if state then
            addSub subRollouts[3]
        else
            removeSub subRollouts[3]
        resize()
    )
    
    -- 5. パラメータ取得ボタンクリックイベント
    on btnGet pressed do
    (
        local opt = subRollouts[1].chkOpt.checked
        local items = subRollouts[2].rdoItems.state
        local msg = try subRollouts[3].edtMsg.text catch ""
        
        ss = StringStream ""
        format "Option: %\n\n" opt to:ss
        format "Items: %\n\n" items to:ss
        format "Text: '%'" msg to:ss
        
        messageBox (ss as string) title:"Result" beep:false
    )
    
    on btnClose pressed do
    (
        destroyDialog self
    )
)

createDialog rltMain

少し順番が前後してしまって見づらいかもしれませんが、番号の順に説明していきます。

  1. ロールアウト初期化
    主にロールアウト変数の初期化を行っています。
    self: このロールアウト自身のインスタンスを保持する為の変数です。理由は前回の記事を参照してください。
    subRollouts: サブロールアウトのインスタンスを保持する配列です。こちらも理由は前回の記事で説明した通りです。
    btmControls: サブロールアウトより下にあるコントロールの配列を格納しています。リサイズ時に、サブロールアウトの高さに合わせて位置を移動させる必要が有るため、予めリスト化しておきます。
    最後にresize()関数を呼び出してウィンドウサイズを初期化します。
  2. サブロールアウト追加・削除関数
    単純にサブロールアウトの追加と削除を行う関数ですが、追加後にサブロールアウトのresizeCallbackに親のresize関数オブジェクトを代入しています。
    こちらも前回の記事で説明していますが、ロールアウトは実体化のタイミングで変数が全てクリアされてしまうので、このタイミングで代入する必要があります。
  3. サイズ変更関数
    全ての子ロールアウトの高さを計算し、SubRolloutと親ロールアウトの高さ、それからbtmControlsに格納したコントロールのY位置を調整しています。
    各ロールアウトの高さを1px単位で微調整していますが、Maxのバージョンによってはこの辺りの細かい数値は変わってきてしまうかもしれません。この値は2014に最適化しています。
    maxHeightの値を変えることによってサブロールアウトの最大高さを変える事ができます。
  4. サブロールアウトON/OFFボタン変更イベント
    ボタンのON/OFF状態を見て、サブロールアウトを追加したり削除したりしています。
    最後にresize()を呼び出して、ロールアウトサイズを調整しています。
  5. パラメータ取得ボタンクリックイベント
    現在のサブロールアウトのパラメータを取得してメッセージボックスに出力します。
self変数とsubRollouts変数は一見意味が無いように見えますが、前回の記事のとおり、ロールアウトを多重起動した時に、確実に自分自身のインスタンスを参照する為に必要な変数です。
これをやっておかないと、ロールアウト多重起動時に正しく処理できなくなってしまいます。

ちょっと長くなってしまいましたが、こんな感じです。

最後に

どちらかというと、MaxよりはMayaっぽい見た目のウィンドウになりますが、パラメータをロールアウト毎にグループ化しておいて、最下部の"実行"ボタンで処理を実行したりと、何かと便利に使える形式だと思います。
使う側から見ても、視線を上から下に移動しながら最後に実行ボタンを押せるので、UIの基本として理にかなっているかと思います。

今回作成したコードはこちらからダウンロードする事ができます。
コードに特に使用制限などは設定しませんので、自由に改変等して使って頂ければ幸いです。

MaxScriptTip: ロールアウト挙動メモ

2月 21, 2016 0
ロールアウトの挙動がわりと意味不明だったので、勉強も兼ねて簡単に調べてみました。
  1. ロールアウトは定義されたタイミングで単一のオブジェクトとなる。
    クラス・インスタンスという概念はなく、プロセス内で1つの実体しか持てない。
    また、1つのオブジェクトにつき1つのウィンドウしか作れない。
    試しにcreateDialogを2度実行しても、作成されるダイアログは1つのみ。
    複数のSubRolloutやFloaterで実体を作成しようとしても、既に別の場所で実体化されているロールアウトは作成されない。
  2. 作成されたロールアウトは、同名の変数に格納される。
    例えばTestRolloutならTestRollout変数に格納される。
    普通の変数なので代入などで上書きする事ができる。
    また、作成したロールアウトオブジェクトを別の変数に入れ替える事もできる。
  3. 複数の実体を作りたい時には、再度定義コードを実行しなければならない。
    定義を実行すると、新規にロールアウトオブジェクトが作成され、同名の変数に上書きされる。
    前に作成したオブジェクトへの参照が失われる為、後から参照したいときは、予め変数からオブジェクトを取り出しておく必要がある。
    例えばSubRollout内の子要素を参照したいとき等、新しく作成した方のロールアウトを参照してしまい、意図しない挙動をする事がある。
  4. copy関数等でオブジェクトを複製する事は出来るが、それらは同一のロールアウトを参照しており、やはり多重起動できない。
  5. ロールアウトはcreateDialog等で実体化する時に、再度初期化される。
    実体化前に変数や関数にアクセスする事は出来るが、実体化時に再度初期化される。
全体的にかなりよく分からない仕様になっていますが、重要なのはオブジェクトと変数を切り離して考える事、クラス・インスタンスの概念が存在しない事を把握する事かと思います。
Max自体が20年以上も前に設計されたソフトだという事もあり、いわゆるオブジェクト指向的な考え方には則っていないのだと思います。

MaxScriptTip: Rolloutでのドラッグ&ドロップの受け取りについて

2月 17, 2016 0
今回はRollout内での外部からのドラッグ&ドロップ受け取りについて。

MaxScriptのRolloutは、残念ながらファイルのドラッグ&ドロップがサポートされていません。
仕方がないので、代わりに.NETコントロールを作成し、.NET側で発生したイベントを受け取ってこの機能を実装します。

.NETでのドラッグ&ドロップ

.NETは本来Windowsアプリを開発する為に設計されたフレームワークなので、全ての種類のコントロールでドロップ受け取りが可能です。
例えばラベル、ボタン、パネル系コントロールやリストボックス等でも受け取る事ができます。

今回はラベルを使用します。

rollout rltDropTest "DropDialog" width:160 height:72
(
    -- 1. コントロール作成
    dotNetControl dotNetLabel "label" pos:[0,0] width:160 height:72 
 
    -- 2. ロールアウト初期化イベント
    on rltDropTest open do
    (
        local Color = dotnetclass "System.Drawing.Color"
        local Alignment = dotNetClass "System.Drawing.ContentAlignment"
        dotNetLabel.allowdrop = true
        dotNetLabel.text = "Drop files here."
        dotNetLabel.backcolor = Color.FromArgb 255 64 64 64
        dotNetLabel.forecolor = Color.white
        dotNetLabel.textAlign = Alignment.MiddleCenter
    )
    
    -- 3. ドラッグカーソル受け入れイベント
    on dotNetLabel DragEnter sender args do
    (
        local Formats = dotnetclass "DataFormats"
        local Effects = dotnetclass "DragDropEffects"
        if args.Data.GetDataPresent Formats.FileDrop then
            args.Effect = Effects.Copy
        else
            args.Effect = Effects.None
    )
    
    -- 4. ドロップイベント
    on dotNetLabel DragDrop sender args do
    (
        local Formats = dotnetclass "DataFormats"
        local fileNames = args.Data.GetData Formats.FileDrop false
        print fileNames
    )
)

createDialog rltDropTest

実行すると以下の様なウィンドウが表示されます。
このウィンドウにファイルをドロップすると、そのファイルパスがリスナーに出力されます。

スクリプト説明

  1. コントロール作成
    .NETラベルコントロールを作成しています。
    この時、ラベルがRollout全体を覆うようにサイズ調整しています。
  2. ロールアウト初期化イベント
    ロールアウトopenイベント内でラベルコントロールの設定をしています。
    特にallowdropはtrueにしておかないとドロップイベントが発生しないので要注意です。
    その他プロパティは特に必要では無いのですが、コントロールの見栄えを良くするために設定しています。
    FromArgbの引数は、字のごとくAlpha, Red, Green, Blueの順です。
  3. ドラッグカーソル受け入れイベント
    このハンドラは.NETコントロールのDragEnterイベントを受け取っています。
    引数のsenderはイベントを発生させたコントロール(この場合はdotNetLabelのインスタンス)、argsはDragEventArgsクラスのインスタンスを格納しています。
    DragEnterイベントは、ドラッグ中のカーソルがコントロール上に侵入した時に発生し、そのドラッグを受け入れ可能かをユーザーに示す為に使用します。
    ここではドラッグのデータタイプをチェックし、ファイルであれば受け入れるように処理しています。
    その他に、ビットマップやテキストデータ等の受け取りも可能です。
  4. ドロップイベント
    ユーザーが最終的にドロップした時に発生します。
    イベント引数はDragEnterと同じです。
    ここではDragEventArgsからドロップされたファイルリストを取得し、fileNamesに格納しています。
    fileNamesは受け取った全てのファイルのフルパスを含む配列です。

情報

その他、各クラス詳細は.NETのリファレンスを参照してください。

Label クラス
Color 構造体
ContentAlignment 列挙体
DataFormats クラス
DragDropEffects 列挙体
DragEventArgs クラス

おまけ

.msファイル暗号化ツール作ってみました。

TR_EncryptScript.ms ver1.00

MaxScriptTip: MaxScriptEditorで等幅フォントを使う

2月 07, 2016 0
MaxScriptエディターでスクリプトを書いていて最も気になるのは、やはりフォントが等幅になっていない事でしょう。
メニューには一応「等幅フォントを使用」がありますが、実際には日本語環境では正しく表示してくれません。

というわけで、今回は等幅フォントの導入法について。

基本的に、MaxScriptエディタで等幅フォントを利用するには、グローバル設定ファイルでフォントを等幅フォントに変更するだけですが、問題はどのフォントを利用するのかという事です。


オススメフォント

個人的にオススメなのは、メイリオを等幅化したMeiryoKeフォントです。

日本語環境での等幅フォントで人気があるのはコーディングに特化したRictySource Han Code JP等ですが、どちらも標準のWindowsレンダリングエンジンでは美しくフォントをレンダリング出来ず、所々欠けた汚いフォントが出力されてしまいます。

その点、MeiryoKeはメイリオをベースにしているので、Windows環境でも完璧なフォントをレンダリングしてくれるのが嬉しい所です。

MeiryoKeはライセンス諸問題でフォントファイルを自力で出力せねばならず、少々面倒な部分もありますが、マニュアルにしたがって作業すれば簡単に出力できるので、とくに困ることもないでしょう。

というわけで、実際にMSゴシックとMeiryoKeを比較してみた画像がこちらです。



好みの問題もあるかもしれませんが、少なくとも日本語含めて等幅になっているのが分かります。
MeiryoKeの方はフォントの特性上、9ポイント以下に設定すると少々見づらいので10ポイントに設定していますが、好みでサイズを調整してもいいと思います。


フォントの導入

導入手順は以下のようになります。
  1. MeiryoKeの出力スクリプトを入手し、自力でフォントを出力する。
    ダウンロード先はこちら
    Windows10の場合は試して無いので分かりませんが、恐らく8.1用で大丈夫なんじゃないかなぁと思います。もしくは過去のバージョンのメイリオをダウンロードしてきてもいいかと。
  2. 3dsMaxのインストールフォルダの使用者権限を「フルコントロール」に設定する。
    もしくはMaxを管理者権限で起動してもいいのですが、いちいち面倒なので私はフォルダの権限の方を変更してしまいます。
    1. Maxのインストールフォルダのプロパティを開きセキュリティタブに移動。
    2. 「編集」ウィンドウを開きユーザーを選択。
    3. 「フルコントロール」にチェックを入れ「OK」
  3. Maxを起動し、MSエディタのグローバル設定ファイルを変更する。
    1. MaxScriptエディタのメニューから「ツール」→「グローバル オプション ファイルを開く」。
    2. font.base=font:Verdana,size:10~といった部分を探し、「Verdana」部分をMeiryoKe_Gothicに変更する。
    3. そこから数行にわたってのフォント設定も全てMeiryoKe_Gothicに変更する。
    4. フォントサイズも全て10に変更する。
    5. 上書き保存。
      (ファイル変更権限を取得してないとここで失敗する。)
これでフォントが変更されます。

グローバル設定ファイルは細かく弄っていくともっといろいろなカスタマイズが出来るので、気が向いたら色々触ってみてもいいかもしれません。



MaxScriptTip: skinOpsをUndoに記録する

2月 07, 2016 0
MaxScriptを使っていると、たまにUndoに記録されないコマンドがある事に気づくと思います。
例えばタイトルにあるようにskinOps。(3dsMax2014及び2015で確認)

某所ではskinOpsはAutodeskがインターンに作らせた欠陥コマンドなんて言われてたりしますが(実際問題多い)、今回はそのコマンドをUndoに記録する方法について紹介します。

結論から言うとUndoに記録されるコマンドと一緒に使って、undo on ()で括ってしまえば記録されるようになります。

undo "SetWeight" on
(
    theSkin = $.modifiers[#skin]
    skinOps.ReplaceVertexWeights theSkin 1 #(1) #(1.0)
)
記録されない。

undo "SetWeight" on 
(
    theSkin = $.modifiers[#skin]
    skinOps.bakeSelectedVerts theSkin  -- Undo記録用
    skinOps.ReplaceVertexWeights theSkin 1 #(1) #(1.0)
)
記録される。

う~ん、この欠陥っぷり・・・。

ちなみに今回はUndo記録用にbakeSelectedVertsを使っていますが、これはUndoに記録されるskinOps内のコマンドで最も高速に動作するからだそうです。

今回はbakeSelectedVertsを使いましたが、他にUndoに記録されるコマンドを既に使っているのであれば、わざわざ使う必要はありません。

自分もこの問題でかなり悩まされたので、誰かの参考になればいいなと。