C4D.Python: CSVファイルから円グラフを作成するスクリプト

Cinema 4Dで円グラフを作る場合、プリミティブの円柱にあるスライスというパラメータで比較的簡単に作成できます。ただこのスライスのパラメータは、角度での入力でしかも角度0の位置が水平位置から始まり、角度が増えると反時計回りに増える仕様です。

そのため、円グラフを作る場合は、パーセンテージを角度に変換してそれをマイナスの角度にする必要があります。これでは計算が面倒なので、csv形式(コンマ区切り)のファイルを読み込んで一発で円グラフができるスクリプトを作成します。

なお、csvかコンマ区切りのテキストデータでないと動作しませんので注意してください。

最初はPythonオブジェクトで作っていましたが、円グラフを作ったに編集可能な状態のほうが円グラフを加工しやすいので、今回はスクリプトにしました。



スクリプトの仕様

まず読み込むファイルですが、Excelなど書き出し可能なcsv形式にします。csvはコンマ区切りのテキストファイルです。データとしては

60,120,50,20,30

というような内容のファイルを読み込むようにします。この値はあらかじめパーセンテージで入力ではなく、どんな数字でもよいものにしました。

次に、読み込んだデータが値順に並んでいない場合、ソートして値の大きい順(一般的な円グラフ)にするか、入力順にするかユーザーに指定してもらいます。

最後に分かりやすいように作成した円グラフの各要素の色がランダムになるようにして、オブジェクト名もパーセンテージになるようにします。

スクリプト作成の準備

まずは、スクリプトメニュー/スクリプトマネージャを開きます。そして、スクリプトマネージャのファイルメニュー/新規を選び、新規スクリプトを作成します。これでスクリプト作成の準備が完了です。

完成したスクリプト

上の仕様に合わせて作ったスクリプトがこちらです。基本的にはこちらをコピーしてスクリプトマネージャにペーストして保存してもらえば、すぐに使えます。

import c4d
from c4d import gui
from c4d import utils
from c4d import storage
import random


def main():
    #ダイアログを開いてcsvファイルを選択
    csv_path = storage.LoadDialog(title="Select CSV file")
    if not csv_path:return

    #ファイルを開いてデータの読み込み
    csv = open(csv_path,'r') 
    text = csv.read()
    vl = text.split(",")
    csv.close()


    pslice = []    #入力の値の数値に変換したリスト用変数
    pslice_start = []    #円グラフ開始角度変換用変数
    pslice_end = []    #円グラフ終了角度変換用変数
    pslice_par = []    #入力値のパーセントに変換用変数

    #リスト化したCSVのデータを文字列から数値に変換
    for i in xrange(0,len(vl)):
        pslice.append(float(vl[i]))
    
    #リストの総合計
    list_total = sum(pslice)
    
    #データをソートするか確認のダイアログ
    rvalue = gui.QuestionDialog("データをソートしますか?")
    
    #イエスの場合ソートを実行
    if rvalue == True:
        pslice.sort(reverse=True)
    
    #ヌルオブジェクトの作成
    null = c4d.BaseObject(c4d.Onull)
    null[c4d.ID_BASELIST_NAME] = "Pie Chart"
    

    #各円グラフ要素を円柱で作成
    for x in xrange(0,len(vl)):
        #リストの値を割合に変換
        pslice_par.append(pslice[x]/list_total)
        #割合から円グラフの開始角度に変換さらにその角度の値をラジアン角に変換。ただし、スライスの角度が反時計回りなので-1を掛けている
        pslice_start.append(utils.Rad(360*sum(pslice[0:x])/list_total)*-1)
        
        #割合から円グラフの終了角度に変換さらにその角度の値をラジアン角に変換。ただし、スライスの角度が反時計回りなので-1を掛けている
        pslice_end.append(utils.Rad(360*sum(pslice[0:x+1])/list_total)*-1)
        
        #円グラフの円柱の全体の分割数を80として、それに合わせた各要素の分割数を判定
        segment = 80*pslice_par[x]

        #円グラフ用の円柱のパラメータを設定
        cylinder = c4d.BaseObject(c4d.Ocylinder)    #円柱の作成
        cylinder[c4d.PRIM_SLICE] = True    #円柱のスライスを有効にする
        cylinder[c4d.PRIM_SLICEFROM] = pslice_start[x]    #スライスのからに入力
        cylinder[c4d.PRIM_SLICETO] = pslice_end[x]    #スライスのまでに入力
        cylinder[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = c4d.utils.Rad(90)    #円柱の0°が3時の位置からスタートしているので90°回転
        cylinder[c4d.PRIM_CYLINDER_SEG] = int(segment)    #円柱の分割数を入力
        cylinder[c4d.PRIM_CYLINDER_HEIGHT] = 10    #円柱の高さを指定
        cylinder[c4d.PRIM_CYLINDER_FILLET] = True    #円柱のフィレットを有効にする
        cylinder[c4d.PRIM_CYLINDER_FILLETRADIUS] = 1    #円柱のフィレットの半径を入力
        cylinder[c4d.ID_BASELIST_NAME] = str(int(pslice_par[x]*100)) + "%"    #円柱の名前を割合に設定
        cylinder[c4d.ID_BASEOBJECT_USECOLOR] = True    #円柱の色を指定をオン
        cylinder[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(random.random(),random.random(),random.random())    #円柱の表示色をランダムに生成

        #スムースタグを追加
        cylinder.MakeTag(c4d.Tphong)

        #ヌルオブジェクトに円柱を挿入
        cylinder.InsertUnder(null)
    
    #ヌルをシーンに挿入
    doc.InsertObject(null)
    c4d.EventAdd()

if __name__=='__main__':
    main()

スクリプトの解説

それではスクリプトを見ていきましょう。

モジュールの読み込み

import c4d
from c4d import gui
from c4d import utils
from c4d import storage
import random

最初は、モジュールの読み込みです。c4dはCinema 4Dを操作するためのモジュールですね。さらにその中からgui、utils、storageを読み込みます。

guiは、GUI操作のためでデータをソートするかどうかの確認のダイアログ表示のために使います。

utilsは、Cinema 4Dで使うユーティリティ機能で、角度をラジアンに変換するユーティリティを使うために読み込みます。

storageは、外部ファイルの読み込みのためつかいます。

randomは、乱数を生成する一般的なモジュールで、今回はオブジェクトの色をランダムにするために使います。

csvファイルの選択

def main():
    #ダイアログを開いてcsvファイルを選択
    csv_path = storage.LoadDialog(title="Select CSV file")
    if not csv_path:return

まずは、スクリプトが実行されたcsvファイルを選択するためのダイアログを開くようにします。この時に使う関数がstorage.LoadDialogです。ここでcsvファイルが選択されるとcsv_pathという変数にファイルの保存パスが入力されます。括弧内の引数「title=”Select CSV file”」は、ダイアログのタイトルになります。

キャンセルされ場合の処理として、「if not csv_path:return」はcsv_pathに値がなければ、returnつまりこのスクリプトを終了します。

ファイルのデータをリストとして読み込む

    #ファイルを開いてデータの読み込み
    csv = open(csv_path,'r') 
    text = csv.read()
    vl = text.split(",")
    csv.close()

open関数でこれをファイルオブジェクトとして変数csvに入れます。open関数の引数はopen(読み込むファイル,”r”)なので、読み込むファイルとcsv_pathを入れ、第2引数は読み込み「r」にしています。まだ、この段階ではファイルオブジェクトです。

次に、このファイルオブジェクトをテキストデータにします。テキストデータにするには、readというメソッドを使います。「変数.read()」でテキストとして読み込んでくれますので、変数textにテキストの内容が1つのテキストとして入力されました。

このテキストデータはコンマ区切りになっています。splitというメソッドでこれをリスト型のデータに変えます。括弧内の引数で区切りの文字種を指定します。「vl = text.split(“,”)」これで変数vlにテキスト変数textの中身をコンマごとにリストとして入力できました。ただ、注意が必要なのは、ここで入っている値は文字列状態になっています。そのため、120という値が入っていてもプログラムとしては数字として扱えない状態です。

最後に開いたファイルオブジェクトを閉じるためにcloseメソッドを使います。記述は「閉じたいファイルオブジェクトの変数.close()」になります。

後で使用する空のリストを作成しておく

    pslice = []    #入力の値の数値に変換したリスト用変数
    pslice_start = []    #円グラフ開始角度変換用変数
    pslice_end = []    #円グラフ終了角度変換用変数
    pslice_par = []    #入力値のパーセントに変換用変数

ここでこれから使用するリスト型の変数をあらかじめ作成しておきます。使用目的は上記の通りです。

文字列を数値に変換する

    #リスト化したCSVのデータを文字列から数値に変換
    for i in xrange(0,len(vl)):
        pslice.append(float(vl[i]))

「ファイルのデータをリストとして読み込む」で、リストはまだ文字列だという話をしました。pythonは数字でもデータの型文字列だと数字として計算できません。これをfloatデータ(数値)に変えます。流れとしてはxrangeでくり返し回数を指定しますが、回数はリストの数だけ行いたいので、lenメソッドでリストの数を取得します。

次に、appendメソッドで、変数vlの各リストをfloat関数でfloat型に変換して、作っておいた空のリストpsliceに追加します。

リストの総合計を求める

    #リストの総合計
    list_total = sum(pslice)

リストの値の割合を割り出すために、リストのデータの総合計を求めておきます。sum関数で引数にリストを入れるとリストの値をすべて合算されます。

データのソートを確認

    #データをソートするか確認のダイアログ
    rvalue = gui.QuestionDialog("データをソートしますか?")
    
    #イエスの場合ソートを実行
    if rvalue == True:
        pslice.sort(reverse=True)

取り込んだデータが、値が大きい順に並んでいるとは限りません。円グラフの場合大きい順に並べたいので、ユーザーに確認します。「はい」ならソートを行い、「いいえ」ならファイルに書かれた順番でグラフを作ります。

「はい」か「いいえ」は、gui.QuestionDialogを使います。括弧内の引数は質問文になります。これでダイアログが開き、はいかいいえを押した結果を変数rvalueに入れます。はいならTure、いいえならFalseになります。

次に、if文で「はい」の場合だけ、ソートを実行させます。ソートはsortメソッドを使います。大きい順にしたいので、降順にするための引数「reverse=True」を入れています。

ヌルオブジェクトの作成

    #ヌルオブジェクトの作成
    null = c4d.BaseObject(c4d.Onull)
    null[c4d.ID_BASELIST_NAME] = "Pie Chart"

円柱オブジェクトを入れる親オブジェクトのヌルオブジェクトを作成します。名前は英語の円グラフ「Pie Chart」にしています。

各円グラフの要素のくり返し

    #各円グラフ要素を円柱で作成
    for x in xrange(0,len(vl)):
        #リストの値を割合に変換
        pslice_par.append(pslice[x]/list_total)
        #割合から円グラフの開始角度に変換さらにその角度の値をラジアン角に変換。ただし、スライスの角度が反時計回りなので-1を掛けている
        pslice_start.append(utils.Rad(360*sum(pslice[0:x])/list_total)*-1)
        
        #割合から円グラフの終了角度に変換さらにその角度の値をラジアン角に変換。ただし、スライスの角度が反時計回りなので-1を掛けている
        pslice_end.append(utils.Rad(360*sum(pslice[0:x+1])/list_total)*-1)
        
        #円グラフの円柱の全体の分割数を80として、それに合わせた各要素の分割数を判定
        segment = 80*pslice_par[x]

ここで円グラフの各要素をfor文を使って作成します。

まず、くり返しの作業としてリストの数値をパーセンテージにして、pslice_parという空のリストに追加しています。パーセンテージの計算は元の数値が入っているpsliceの個別の値を、psliceの合算した変数list_totalで割っています。その結果をpslice_par変数に入れています。

つぎに、円グラフの開始角度を求める計算です。計算は「360°×psliceの値を累積÷psliceの総計」です。これは、円グラフの2つ目以降の要素の開始位置の計算は、その前までの値を累積する必要があるため、「sum(変数[0:変数x])」で累積値を計算しています。sum関数は「sum(リスト型[始めるリスト番号:終わりのリスト番号]で、加算する範囲を設定できます。さらに求めた値は、通常角度なのでその角度の値をc4d.utilsのRad()関数でラジアン角に変換します。ただし、スライスの角度が反時計回りなので-1を掛けています。

終了角度は、sum関数のリスト番号を一つ増やせば終わりの値が求められます。

「segment = 80*pslice_par[x]」は、要素ごとの円柱の分割数を計算しています。円の分割数を80として、それを各要素の割合を掛けています。これを行うことにより、円柱がグラフになったときに分割数が均等になります。これをしないとポリゴンの密度が不均等になる。

円柱オブジェクトの作成

        #円グラフ用の円柱のパラメータを設定
        cylinder = c4d.BaseObject(c4d.Ocylinder)    #円柱の作成
        cylinder[c4d.PRIM_SLICE] = True    #円柱のスライスを有効にする
        cylinder[c4d.PRIM_SLICEFROM] = pslice_start[x]    #スライスのからに入力
        cylinder[c4d.PRIM_SLICETO] = pslice_end[x]    #スライスのまでに入力
        cylinder[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = c4d.utils.Rad(90)    #円柱の0°が3時の位置からスタートしているので90°回転
        cylinder[c4d.PRIM_CYLINDER_SEG] = int(segment)    #円柱の分割数を入力
        cylinder[c4d.PRIM_CYLINDER_HEIGHT] = 10    #円柱の高さを指定
        cylinder[c4d.PRIM_CYLINDER_FILLET] = True    #円柱のフィレットを有効にする
        cylinder[c4d.PRIM_CYLINDER_FILLETRADIUS] = 1    #円柱のフィレットの半径を入力
        cylinder[c4d.ID_BASELIST_NAME] = str(int(pslice_par[x]*100)) + "%"    #円柱の名前を割合に設定
        cylinder[c4d.ID_BASEOBJECT_USECOLOR] = True    #円柱の色を指定をオン
        cylinder[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(random.random(),random.random(),random.random())    #円柱の表示色をランダムに生成

円柱オブジェクトの作成です。円柱のパラメータをそれぞれ入れていきます。

「cylinder[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = c4d.utils.Rad(90) 」は、円柱の0°が時計で言うと3時の位置からスタートしているので90°回転させています。

「cylinder[c4d.ID_BASELIST_NAME] = str(int(pslice_par[x]*100)) + “%”」は、円柱の名前としてパーセンテージを入れています。

作成したオブジェクトがすべて同じ色だとわかりにくいので、表示色をランダムになるようにしています。「cylinder[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(random.random(),random.random(),random.random())」は、0.0から1.0間でランダムになる値をc4d.Vectorの各ベクトルに入れることで、RGBカラーをランダムにしています。これは本当にランダムなので、どんな色が出るか作ってみないとわかりません。

スムースタグの作成と円柱をヌルの子にする

        #スムースタグを追加
        cylinder.MakeTag(c4d.Tphong)

        #ヌルオブジェクトに円柱を挿入
        cylinder.InsertUnder(null)

円柱の設定ができたので、スムースタグを円柱に追加します。タグを追加するにはMakeTagを使います。引数に追加したいタグの種類をいれます。

そして、ヌルの子として円柱を作成します。子にするには、InsertUnder関数です。

オブジェクトをシーンに生成

    #ヌルをシーンに挿入
    doc.InsertObject(null)
    c4d.EventAdd()

最後に、ヌルをシーンに挿入して、Cinema 4Dを更新して完了です。

 

完成したスクリプトもダウンロードできるようにしておきます。解凍したPie_chart.pyを一般設定フォルダのlibrary/scriptsに入れると、スクリプトメニュー/ユーザースクリプトに追加されます。

Pie_chartダウンロード

Download Pie chart script English version

This script works with CSV file or txt file spit each number with “,”.