[Cinema 4D] スプラインのバウンディングボックスを作成してみる

[前回の記事でポリゴンオブジェクトからワールド座標に平行なバウンディングボックスを作成する](https://qiita.com/Makoto-T/items/ebff9c341a2da7cd7483)ことができましたが，今回は前回のスクリプトを少し拡張してCinema 4Dでよく使うスプラインオブジェクトからバウンディングボックスを作成してみようと思います．

スプラインはポリゴンオブジェクトとは異なるタイプのオブジェクトなので，if文で条件分岐させてあげますが，少し注意が必要です．

例えば，ポリゴンオブジェクトの場合はobj.IsInstanceOf(c4d.Opolygon)などでタイプをチェックできます．

[InstanceOfのマニュアル]("https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d/C4DAtom/index.html?highlight=isinstanceof#C4DAtom.IsInstanceOf")

これでIsInstanceOf(c4d.Ospline)でスプラインタイプがチェックできるかと思いきやこれはできません．

スプラインタイプの場合は，次のようにしてあげます．他にGetType() == c4d.Osplineでもできます．InstanceOfだとできないんですね．


```
if obj.GetInfo() & c4d.OBJECT_ISSPLINE:
    realSpline = obj.GetRealSpline()
    if realSpline is False:
        return
    # Do something
```

2行目のGetRealSpline()でプリミティブスプラインから編集可能にした状態のスプラインを取得します．

[GetRealSpline()](https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d/C4DAtom/GeListNode/BaseList2D/BaseObject/index.html?highlight=getrealspline#BaseObject.GetRealSpline)

これで準備ができたので，あとはポリゴンオブジェクトと同様にrealSpline.GetClone()でクローンを取得してそれに対して逆行列をかけてワールド座標に平行にしたのち，各ポイントに回転行列を乗算して元の位置にもどしてあげます．前回と全く同じ手順です．

できました
![20190303-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/195698/7d93fe2c-05d8-28df-c82c-edc9da89cc35.jpeg)

と言いたいところですが，接線の端までバウンディングボックスができてしまっています．これはちょっとよくないですね．どうやらGetRad()は接線の端まで含んだ半径を返すようです．

そこで，このクローンスプラインに対して〈現在の状態をオブジェクト化〉をして内部で生成している実際のポイントを取得していきます．英語ではCurrent State to Objectです．Plugincafeでもこのトピックは沢山みつかるので，検索してみると良いでしょう．

オブジェクトに対してutils.SendModelingCommandを実行するわけですが，先にモジュールをインポートしておきます．

```
from c4d import utils
```

```

rs = obj.GetRealSpline()
if rs is False:
    return

clone = rs.GetClone()
clone.SetMg(~obj.GetMg() * obj.GetMg())
settings = c4d.BaseContainer()
res = utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                list = [clone],
                                mode = c4d.MODELINGCOMMANDMODE_ALL,
                                bc = settings,
                                doc = doc)
c4d.EventAdd()

if res is False:
    print "Something went wrong."
elif res is True:
    print "Command successful."
elif isinstance(res, list):
    print "Here you get the newly created object(s)."
```

[utils.SendModelingCommand]のマニュアル(https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.utils/index.html?highlight=sendmodelingcommand#c4d.utils.SendModelingCommand)

commandは実行するコマンドです．
引数のオブジェクトはリストになっているので，[clone]としています．
[modeはALLのほか，選択範囲に限定したりできます．](https://developers.maxon.net/docs/Cinema4DPythonSDK/html/consts/MODELINGCOMMANDMODE.html?highlight=modelingcommandmode_all)
settingsは実行コマンドのパラメータを指定できます．例えばID_MODELING_EXTRUDE_TOOLを使う場合に50cm押し出したい時などです．〈現在の状態をオブジェクト化〉では不要ですが．

注意点としては，複製したオブジェクトを渡さないとシーン内のスプラインに対してコマンドが実行されてしまいます．したがって，この時[clone]を渡しています．

また，返り値はリストになるので，実行後のオブジェクトへはres[0].GetPointCount()などとしてアクセスします．

関数は次のようにしてみました．引数のobjはすでに記事冒頭のobj.GetInfo() & c4d.OBJECT_ISSPLINE:でスプラインオブジェクトだけを渡すようにしていますので，関数内でスプラインかどうかのチェックはしていません．

```
def createBoundingBoxFromSpline(obj):
    print "Call createBoundingBox from Spline"

    # もしスプラインプリミティブならスプライン状態を取得する（編集可能にした状態）
    rs    = obj.GetRealSpline()
    if rs is False:
        return

    # クローンをとる
    clone = rs.GetClone()
    clone.SetMg(~obj.GetMg() * obj.GetMg())

    # 現在の状態をオブジェクト化
    settings = c4d.BaseContainer()
    res = utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [clone],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    bc = settings,
                                    doc = doc)
    c4d.EventAdd()

    if res is False:
        print "Something went wrong."
    elif res is True:
        print "Command successful."
    elif isinstance(res, list):
        print "Here you get the newly created object(s)."

    # ポイントに元の行列を乗算してポイントだけ元の位置に戻す
    if res[0].GetPointCount():
        pcount = res[0].GetPointCount()
        points = res[0].GetAllPoints()
        for p in xrange(pcount):
            res[0].SetPoint(p, obj.GetMg() * points[p])
            print points[p]
        res[0].Message(c4d.MSG_UPDATE)
        c4d.EventAdd()

    # XYZの最小，最大値を取得する
    center = res[0].GetMp()
    xMin = center.x + res[0].GetRad().x * -1
    xMax = center.x + res[0].GetRad().x
    yMin = center.y + res[0].GetRad().y * -1
    yMax = center.y + res[0].GetRad().y
    zMin = center.z + res[0].GetRad().z * -1
    zMax = center.z + res[0].GetRad().z
    print xMin
    print xMax
    print yMin
    print yMax
    print zMin
    print zMax

    # XYZの最小・最大値からバウンディングボックスを生成する
    box = c4d.BaseObject(c4d.Opolygon)
    box.ResizeObject(8,6)
    box.SetPoint(0,c4d.Vector(xMin, yMin, zMin))
    box.SetPoint(1,c4d.Vector(xMin, yMax, zMin))
    box.SetPoint(2,c4d.Vector(xMax, yMin, zMin))
    box.SetPoint(3,c4d.Vector(xMax, yMax, zMin))
    box.SetPoint(4,c4d.Vector(xMax, yMin, zMax))
    box.SetPoint(5,c4d.Vector(xMax, yMax, zMax))
    box.SetPoint(6,c4d.Vector(xMin, yMin, zMax))
    box.SetPoint(7,c4d.Vector(xMin, yMax, zMax))
    box.SetPolygon(0, c4d.CPolygon(0,1,3,2))
    box.SetPolygon(1, c4d.CPolygon(2,3,5,4))
    box.SetPolygon(2, c4d.CPolygon(4,5,7,6))
    box.SetPolygon(3, c4d.CPolygon(6,7,1,0))
    box.SetPolygon(4, c4d.CPolygon(1,7,5,3))
    box.SetPolygon(5, c4d.CPolygon(6,0,2,4))
    doc.InsertObject(box)
    
    tag = c4d.BaseTag(c4d.Tdisplay)
    tag[c4d.DISPLAYTAG_AFFECT_DISPLAYMODE] = True
    tag[c4d.DISPLAYTAG_SDISPLAYMODE] = 6
    tag[c4d.DISPLAYTAG_WDISPLAYMODE] = 0
    box.InsertTag(tag)

    box.Message(c4d.MSG_UPDATE)
    doc.AddUndo(c4d.UNDOTYPE_NEW,box)
    doc.EndUndo()
    c4d.EventAdd()
    return
```

できました
![20190303-2.jpg](https://qiita-image-store.s3.amazonaws.com/0/195698/a11eb92b-1dd9-45d5-f81b-ae01467a17b3.jpeg)