2014年10月27日月曜日

Maxscript その16「if式 条件分岐」

if式 <if_expr> 「プログラム フロー制御>if式」

これもプログラムでは代表的な処理のようだ。条件によって処理を変える事が出来る。条件分岐。概念などは、これまた色んなサイトで紹介されているので「if 文」などで検索。

構文

if <条件式> then <式>
if <条件式> then <式> else <式>
if <条件式> then <式 > else if <式> then <式>  else if  <・・・・許す限り続く>

もしくは

if <条件式> do <式> ※doの時はelseを使えないらしい

thenとdoの使い分けについて、リファレンスでは「doの場合elseがないと認識するので即座に式が実行される。」とある。処理が若干早くなるんすかね?どれくらい早いのかは不明・・・。

(※以下非常に申し訳ないのですが、チェックをしっかり行っていないため記事の精度が低いかもしれません。特にコードのスペルミス、構文ミスがある可能性が高いです。)
-----------
if式について

if の後の条件式部分は最終的な値がboolean値となれば、どのような式であっても問題ないようだ。もっとも最小構成のif式は以下のようになると思う。

if <boolean値> then <式>
if <boolean値> then <式> else <式>
if <boolean値> then <式> else if <式>

if式である意味はないけど

if true then print "yamagishi" -- 実行される
if false then print "yamagishi" --実行されない
if false then print "yamagishi" else print "false" -- elseの式が実行される

なので、次のような記述が可能のようだ。

--オムニライトのオン/オフによって処理を変える
if $omni001.enabled then print "OmniLightはon" else print "OmniLightはoff"

条件式部分を
$omni001.enabled == trueと記述しなくてもよいらしい。

各式部分にブロック式を使って見やすく書くと以下の感じ。
if ($omni001.enabled) then (print "OmniLightはon") else (print "OmniLightはoff")

もしくは

if ($omni001.enabled) then (
    print "OmniLightはon"
)
else (
    print "OmniLightはoff"
)

複数の式をまとめて処理したい時もブロック式を用いて・・・

obj = $Sphere001
r = 10

if classof obj == Sphere then (
    format "[original radius]\nname:% radius:%\n" obj.name obj.radius
    obj.radius = r
    format "[new radius]\nname:% radius:%\n" obj.name obj.radius
)
else (
    messagebox "オブジェクトが見つからないか球体ではない"
)

この辺りから急にスクリプトっぽくなってきて目がチカチカしてくる・・・。
-----------
例:その1 基本

球体オブジェクトを1つ作って選択して実行。半径がxより大きかったら半径をxにする

obj = $
x = 10

if  obj.radius > x then obj.radius = x

--
if式の使い方は様々だが、代表的な使い方の1にエラーを回避するというのがあると思う。
上のスクリプトは「選択しているオブジェクトが1つで球体」でないとエラーとなってしまう。エラーが出る際は「どの部分がどのように問題なのか?」をみる。

スクリプトの処理の順番を考える。

obj = $で選択しているオブジェクトし、変数xを初期化する。
次にif式が実行される。まず、条件式部分の評価がされるようだが・・・

obj.radius > x

この際、選択しているオブジェクトが「立方体」だった場合、立方体には「radius」というプロパティがないためエラーとなる。

複数選択している場合も同じで、複数選択の場合は$の値は「$Selection(Selection値)」となる。selection値も「radius」というプロパティを持っていないため、やはり同じ部分でエラーとなってしまう。

(ただし、選択しているオブジェクトが全て「radius」というパラメーターを持っていれば「selection.radius = 10」のような処理は出来る)

オブジェクトを何も選択してない場合も、objの中身が「undefined」となるためやはりエラー

そこで、処理をする前に条件を付ける事で条件が一致したときのみ処理が実行されるように(エラー回避)してあげる。
-----------
例:その2 基本

オブジェクトを1つ作って選択し、球体かボックスかで処理を変えるスクリプト

obj = $
x = 10

if classof obj == Sphere then (
    obj.radius = x
)
else if classof obj == Box then (
    obj.width = x
    obj.height = x
)
else messagebox "test"

などすれば各「classof == <クラス名>」の結果が一致した時にしか処理がされないのでエラーが出ないようだ。

$の中身は3通りだと思う。
・選択が1つ
・選択が複数
・何も選択されていない

複数選択している場合の$は$selection。

Classof $selection

はObjectSet値。

選択されていない場合は「obj = undefined」となるため
Classof undefiend
はundefined値。

1つだけ選択は各オブジェクトとなるので上の書き方で問題ないらしい。
-----------
tips:if式を使う際も変数のスコープに気を付ける
(
    obj = $omni001
    s = undefiend

    if obj.enabled then (
        print "オムニライトはオン"
        s = true
    )
    else(
        print "オムニライトはオフ"
        s = false
    )

    print s
)

ifでブロック式を用いた場合も例外なく変数のスコープが存在するので、条件で変数に値を代入し、その後もその変数を使いたい場合は、変数の初期化を適切な場所で行ってあげる必要がある。

この例の場合、s = undefined などの一文がないと、各ブロックの処理が終わった後にローカル変数であるsは破棄されprint sの結果がundefinedとなってしまう。
-----------
例:if式の多重構造

if式内でif式を使う事で色んな条件分岐が可能となるようだ。この辺りの考え方等はとにかく沢山書いて反復あるのみ。

--選択しているオブジェクトを取得
obj = $
x = 10

--選択している数が1つだったらブロック内の式を実行
if  selection.count == 1 then (
  --選択しているオブジェクトが球体だったらブロック内の式を実行
    if classof obj == Sphere then (
        --選択している球体の半径がxより大きかったらブロック内の式を実行
        if obj.radius > x then (
      --球体の半径をrに設定
            obj.radius = x
        )
    )
   --選択しているオブジェクトが球体以外でボックスだったら
   else if classof obj == Box then (
        --ボックスの幅か高さがxより小さかったら
        if obj.width > x or obj.height > x then (
            obj.width = x
            obj.height = x
        )
    )
    --それら以外
    else messagebox "test"
)

このスクリプトの場合は、最初のifで
selection.count == 1 としているので、選択しているオブジェクトの数が1つでなければ処理が実行されないスクリプトとなる。似たような処理として

obj = $Sphere001
if obj != undefined then (
)

というのもある。これはオブジェクトが取得できた場合のみ処理がされる仕組みとなるようだ。
obj != undefined ・・・ オブジェクトが未定義ではない=オブジェクトが存在する場合Trueになりthen以降が実行されるようだ。
----------------
tips:「短絡論理式?」
以下のスクリプトを見てみる

--選択しているオブジェクトを取得
obj = $
x = 10

--選択している数が1つだったらブロック内の式を実行
if  selection.count == 1 then (
  --選択しているオブジェクトが球体だったらブロック内の式を実行
    if classof obj == Sphere then (
        --選択している球体の半径がrより大きかったらブロック内の式を実行
        if obj.radius > x then (
           --処理内容
      --球体の半径をrに設定
            obj.radius = x
        )
    )
)

このスクリプトは「選択しているオブジェクトが1つ」で「球体」で「xより半径が大きいとき」に「球体の半径をxにする」というスクリプトになる。エラーが起きないように段階的に判定を進めていってるのだが・・・。

obj = $
x = 10

if (selection.count == 1) and (classof obj == Sphere) and (obj.radius > x) then obj.radius = x

としても問題なく実行される。また、余分な処理がされないという事もあるらしい。

【短絡論理式】
以下の場合は2番目以降の評価がされないらしい。

・最初のand式の左辺がfalseの場合。
・最初のor式の左辺がtrueの場合。

「false and <値>」「ture or <値>」は何が来ても結果は同じなので後の式の評価をしない(処理しない)という事らしい。賢いっすね。

(selection.count == 1) and (classof obj == Sphere) and (obj.radius > x)

上の論理式は全てandなので、左から順に評価をしていき、1つでもfalseとなれば以降処理はされないっぽい。省エネ設計。
-----------
tips:if式の戻り値
「Maxscriptでは全ての命令は式である」。ifも例外なくmaxscriptではif式と呼ばれ、式としての戻り値があるようだ。なので以下のように記述する事も出来るようだ。

s = if $omni001.enabled then "オムにライトはオン" else "オムニライトはオフ"
print s

この場合のif式の戻り値はthen、else各式の戻り値となる。

s = if $omni001.enabled then true else false
print s

オムニライトがオンだとtrue、オフだとfalseが返される。
また以下の場合は、else式がないため$omni001.enabledがfalseの場合、戻り値はundefinedとなるようだ。

s = if $omni001.enabled then "yamagishi"

ブロック式の場合はブロック式の戻り値がif式の戻り値となるようだ。

obj = $omni001
s = if obj.enabled then (
    print "オムニライトはオン"
    true
)
else(
    else "オムニライトはオフ"
    false
)

ただし、このような使い方はmaxscriptの独自性が際立つ気もするので・・・。

obj = $omni001
s = undefiend

if obj.enabled then (
    print "オムニライトはオン"
    s = true
)
else(
    print "オムニライトはオフ"
    s = false
)

とかの方が見やすいですかね?ブロック内の行数が長い時は下まで見に行かないと戻り値が不明だったりする時もありますし・・・。これはあくまでも個人的な考えですが。

-----------
tips:if式等の書き方の例
これは初心者用のアドバイス。先に仕組みを記述し、後に処理部分を記述するような感じで書いていくとスムースにコードが書ける。また、優先順位が高い順にブロックを構成していく。
また、print等を使って問題点がないかの動作チェックも出来るだけ先にしてしまう。後からだと色んな問題が多発的に出た場合、原因の特定に労力を使う事になってしまうので。

obj = $
x = 10

if  selection.count == 1 then (
    if classof obj == Sphere then (
        if obj.radius > x then (
            obj.radius = x
        )
    )
   else if classof obj == Box then (
        if obj.width > x or obj.height > x or obj.length  > x then (
            obj.width = x
            obj.height = x
            obj.length = x
        )
    )
    else messagebox "test"
)

の場合。
---------------
【ステップ1】おっきな仕組み
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"
)
---------------
【ステップ2】細部、レベル1。多分自動でなるとは思うけどレベル毎にインデント(字下げ=段落)を揃える。
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
          print "球体"
    )
   else if classof obj == Box then (
          print "立方体"
    )
    else messagebox "test"
)
---------------
【ステップ3】細部、レベル2
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
        print "球体"
        if obj.radius > x then (
             format "name:% radius:%\n" obj.name obj.radius
           print x
        )
    )
   else if classof obj == Box then (
        print "立方体"
        print x
        if obj.width > x or obj.height > x then (
             format "name:% width:% width:% length:% height:%\n" obj.name obj.width obj.length obje.height
              print x
        )
    )
    else messagebox "test"
)
---------------
【ステップ4】処理を書き足し動作確認。最後に必要のない処理はコメントアウトか削除して完成。変数のスコープの問題があるので、最終的な動作確認は一度maxを再起動し、初回実行で動作確認をする。俗に言うmaxscript検便検査。朝一が大事。(一度位はふざけず真面目に記事が書けないのだろうか・・・)

if  selection.count == 1 then (
    --print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
        --print "球体"
        --print x
        if obj.radius > x then (
             obj.radius = x
             --format "name:% radius:%\n" obj.name obj.radius
        )
    )
   else if classof obj == Box then (
        --print "立方体"
        --print x
        if obj.width > x or obj.height > x then (
             obj.width = x
             obj.height = x
             obj.length = x
             --format "name:% width:% width:% length:% height:%\n" obj.name obj.width obj.length obje.height
        )
    )
    else messagebox "test"
)

※スクリプトなのであまり処理速度を気にしてもあれなのだが、大量のオブジェクトを捌く場合、printやformatなどはリスナーの更新に時間がかかり、処理速度に大幅な違いが出てくるので必要のないアウトプット処理は削除するかコメントアウトする方がいいようだ。
----------------
tips:条件式の関数化

条件が長くなりそうな場合や後で条件を変えたい時などは、関数にして別処理とか。

fn selectIsSphere o = (
    (selection.count == 1) and (classof o == Sphere)
)

obj = $
r = 10

if  (selectIsSphere obj) then ()
-----------
tips:try式 例外処理

try式というのがある。

try()catch()

エラーによって処理を変えたい。エラー回避を考えたりするのが面倒な時などに。
obj = Plate()
try(obj.radius = 20)catch()

obj = Plate()
try(obj.radius = 20)catch(print "Unknown property [radius]")

0 件のコメント:

コメントを投稿