テクスチャエディタで選んだらコンポーネント(ポリゴン)が選択される
こちら、Texture Editorの設定画面。Sync MethodでデフォルトはSamplesのはずなんだけど、Componetsを選べたということを今日初めて知りました。これにより、
Texture Edtiro側で選択した部分がビュー上ではポリゴン選択されてきました。
この機能知らずに、ホントよく仕事してきましたよね、オレ。そして@JunkiTheJunkieに感謝です。
…しかし検証を自宅環境のParallels上で動くWindowsでやったのだけど、23インチ4Kモニタに表示されているSoftimageは文字が小さすぎてとても辛かった。AutodeskさまはHiDPIに対応させてからSoftimageの開発を止めて欲しかったと、今はそう思います(切実に)。
なーぶす
Softimageのスクリプトでカーブを作ろうとしたんですよ。
スクリプトとしては
NurbsCurveList.AddCurve( ControlPoints, [Knots], [Closed], [Degree], [Parameterization], [NurbsFormat] )
です。
それじゃ、ControlPointsとはなんぞや、Knotsとはなんぞや、Degreeとは?とかなりますわな。
ControlPointsはX,y,z,w 値を指定することになります。xyzは制御点の座標として、wはウェイトでいいのかな。つまりその制御点にどんだけ引き寄せられた線を引くかをここで決めている感じでしょうか。よくわからないので、とりあえずwは1としときます。
Knotsは「ノット値の配列」とあります。Degreeが「NURBS サーフェイスの U 方向の次数」となっていて大ざっぱに何次曲線かって事ですね。直線なら1で3DCGで使うNurbsならデフォルトは3って感じ。で、この次数と制御点の数からKnotsは一意に決まってくるものだそうですが、どうも、制御点 + 1の数でいいようです。で、均等に並べるには均等な配列にします。
それ以外のParameterization(デフォルト値1)とかNurbsFormat(デフォルト値0)はとりあえずデフォルトでいいんで無視します(^^;
というわけで、
制御点を4つ用意し
[ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 2.0, 1.0, -1.0, 0.0, 1.0, 1.0 ]
として、次数を1(直線)とするなら Knotsは 1+4=5個の要素の配列と言うことで
[0,1,2,3,4]
となりましょうか。
結果としてスクリプトはこんな風になります。
oRoot = Application.ActiveProject.ActiveScene.Root cp = [ 0.0, 0.0, 0.0, 1.0,\ 1.0, 0.0, 1.0, 1.0,\ 0.0, 0.0, 2.0, 1.0,\ -1.0, 0.0, 1.0, 1.0 ] knots = [0,1,2,3,4] oCrv = oRoot.AddNurbsCurveList() oCrv.ActivePrimitive.Geometry.AddCurve( cp, knots , True, 1, 0, 0 )
次数をデフォルトの3にするにはoCrv.ActivePrimitive.Geometry.AddCurve( cp, knots , True, 1, 0, 0 )の部分をoCrv.ActivePrimitive.Geometry.AddCurve( cp, knots , True, 3, 0, 0 )にするだけです。
ちなみに、Knotsの値を [ 0,0,1,2,2 ] のようにすると、いびつな形になりますね。
というわけで、最低限これだけ知ってれば曲線はひけるのかなぁ…
昔作ったスクリプトがSIをたたき落とした
昔作ったスクリプトがありまして、なにせ昔だからぱっと見ただけだとよく分からないのだけど、とりあえず実行したらPPGを開こうとしてSoftimageごとたたき落とされてる、というような感じになりました。 同じスクリプトを自宅環境で試したら落ちなかったので、環境依存の部分もあるのでしょうが、よくよく確認してみると構文的に正しくない書き方をしている事が原因だったようです。
チェックボックスのあるPPGを作りました。例えばこんなスクリプト。
from win32com.client import constants as c app = Application oProp = XSIFactory.CreateObject( "CustomProperty" ) oProp.name = "TEST PPG" boolcheckbox = oProp.AddParameter2( "boolcheckbox", c.siBool, 1) oLayout = oProp.PPGLayout oLayout.AddEnumControl( "boolcheckbox", ["", 0] , "test", c.siControlBoolean ) Err = app.InspectObj(oProp, "", "", c.siModal, False) if Err == False: LogMessage( boolcheckbox.Value ) else: LogMessage("Cancel")
問題は、AddEnumControlの部分のリスト部分(["", 0] の所)の書き方にありました。
ここのリストは[ "String", Value, "String", Value ... ] というように文字列のラベルと値の組み合わせを羅列していかなければならないのですけど、これを [ 0, 0 ] って書いていたのですね。チェックボックスだとこのリスト部分ってあんまり意味が無いみたいなのですけど(だから [0,0] にも特に意味が無い?)、環境によってSoftimageごとたたき落とす原因となってしまったようです。
正しく書かないとダメですなぁってことで。
メルカトル図法へ
それじゃ、メルカトル図法に行ってみましょう。
メルカトル図法の式はシンプル。Wikipediaによると、地図中央の経度をλ0とし、与えられる経緯度がλ、φである場合、
x = λ - λ0
で、yは sinφ をtanhの逆関数にかけたもの、という事になるそうです。
tanhってなんぞや、となるわけですが、pythonなら大丈夫。mathモジュールを読み込めば、その理屈を知らずとも、tanhを適用すればいいだけで、逆関数もatanhとして用意されています(^^;
import math app = Application oObj = app.Selection(0) oPntArr = oObj.ActivePrimitive.Geometry.Points.PositionArray newPosArr = [[],[],[]] lamda0 = 139.741389 for i in range(len(oPntArr[0])): x = oPntArr[0][i] - lamda0 phy = math.radians( -oPntArr[2][i] ) y = math.degrees( math.atanh( math.sin( math.fabs(phy) ) ) ) if phy < 0: y = -y newPosArr[0].append(x) newPosArr[2].append(-y) newPosArr[1].append(0.0) oObj.ActivePrimitive.Geometry.Points.PositionArray = newPosArr
理屈は理解せずともw、簡単でいいですね。ただし、極点付近は極端に伸びるので、極点付近は削除の上、実行したほうがよいでしょう。
今回は80度線でぶった切ってみました。
そうすると、実行するとこんな風になります。
重ねてあるのはgoole mapから。低緯度の地域では一致しています。
高緯度の所は、なにせ
こんなふうにメッシュが引き伸ばされている為に、結果として高緯度側のメッシュ分割が粗く、精度が悪くなっております。
でも、対世界地図で活躍してくれそうですな。
正距方位図法に変換
CG上で地球のテクスチャとしてよく使われる正距円筒図法は、経度、緯度をそれぞれx座標、y座標に読み替えたものの為、緯度経度位置の情報(分かりやすいのは標高とか)から作図しやすい反面、既存の地図に重ね合わせるには、図法の変換をせねばなりません。で、その変換をせねばならない用事が生まれましてね。やってみるわけです。
欲しいのは数式なんですけど、正距方位図法で検索してもなかなか該当する物が見つからず、azimuthal equidistant projectionで検索したら
Azimuthal Equidistant Projection [Wolfram MathWorld]
というドンピシャなページが引っかかりました。φとλはそれぞれ緯度、経度に相当する値で、それをx,yに変換したり、x,yから緯度・経度を求める式も載っています。すばらしいです。ということで、これを単純に実装するだけででき上がりました。
緯度・経度が-z座標、x座標になっているメッシュに対して適用します。
import math app = Application log = app.Logmessage oObj = app.Selection(0) pntPosArr = oObj.ActivePrimitive.Geometry.Points.PositionArray newPosArr = [[],[],[]] phi1 = XSIMath.DegreesToRadians(35.658056) lamda0 = XSIMath.DegreesToRadians(139.741389) for i in range(len(pntPosArr[0])): phi = XSIMath.DegreesToRadians(-pntPosArr[2][i]) #latitude lamda = XSIMath.DegreesToRadians(pntPosArr[0][i]) #longitude c = math.acos( math.sin(phi1) * math.sin(phi) + math.cos(phi1) * math.cos(phi) * math.cos(lamda - lamda0) ) k = c/math.sin(c) x = k * math.cos(phi) * math.sin(lamda - lamda0) z = k * (math.cos(phi1) * math.sin(phi) - math.sin(phi1) * math.cos(phi) * math.cos( lamda - lamda0 )) newPosArr[0].append( x ) newPosArr[2].append( -z ) newPosArr[1].append( 0.0 ) oObj.ActivePrimitive.Geometry.Points.PositionArray = newPosArr
上図の色がついている範囲の板(センターは原点にある)に適用すると
となる。見やすくするために明るさを持ち上げてあります。
形状も、冒頭にリンクを貼った日本周辺にほぼあっているかな、と思いますです、はい。
ポリゴン法線
Softimageのスクリプトからポリゴンの法線を求めるときはどうすんだ?といういまさらな疑問です。
どうやらFacetCollectionにアクセスすると良いようでございます。そこにNormalArrayというプロパティがあるそうで。
ポリゴンを形成する頂点(Point)の持つ法線の平均でいいやと思って、ポリゴンノード(ActivePrimitive.Geometry.Polygons.Nodes)のNormalプロパティの平均を出そうとしました。これは、ビューポートの法線表示と同じものだから、下図のような平面となっているポリゴンの隣のポリゴンが傾いていると、その接線上の頂点の法線は当然傾いているから、平面となっているポリゴンの頂点の法線の平均をとると、傾いた値が出てきてしまうわけです。
というわけで、困っていたのですが、FacetCollectionのNormalArrayから取得してやれば何とかなりそうでした。
以下のスクリプトは各ノードの法線の値と、FacetCollectionにアクセスするとことで得られる法線の値と、それぞれのポリゴンのポイントとサンプルポイントのインデックスを出してみたものです。
app = Application log = app.Logmessage def nodeNomarl( oObj ): oPolys = oObj.ActivePrimitive.Geometry.Polygons vn = XSIMath.CreateVector3() for oPoly in oPolys: oNodes = oPoly.Nodes i = 0 for oNode in oNodes: vn = oNode.Normal log("Node %d: %f\t%f\t%f"%(oNode.Index, vn.X, vn.Y, vn.Z)) def polygonNormal( oObj ) : vn = XSIMath.CreateVector3() vy = XSIMath.CreateVector3( 0.0, 1.0, 0.0) oFacets = oObj.ActivePrimitive.Geometry.Facets oNormals = oFacets.NormalArray for i in range(oFacets.Count): vn.Set( oNormals[0][i] , oNormals[1][i], oNormals[2][i] ) log("Facet ID: %d --- %f\t%f\t%f"%( oFacets.IndexArray[i], vn.X , vn.Y, vn.Z )) log("dot product: %f"%vn.Dot( vy )) oPnts = oFacets[i].Points oSamples = oFacets[i].Samples pntsIndices = [] smpIndices = [] for oPnt in oPnts: pntsIndices.append(oPnt.Index) for oSmp in oSamples: smpIndices.append(oSmp.Index) log("Point Index: %s"%str(pntsIndices)) log("Sample Index: %s"%str(smpIndices)) oObj = app.Selection(0) nodeNomarl( oObj ) polygonNormal( oObj )
この結果が、例えば
# INFO : Node 0: 0.000000 1.000000 0.000000
# INFO : Node 1: 0.000000 1.000000 0.000000
# INFO : Node 5: 0.087156 0.996195 0.000000
# INFO : Node 4: 0.087156 0.996195 0.000000
# INFO : Node 2: 0.173648 0.984808 0.000000
# INFO : Node 3: 0.173648 0.984808 0.000000
# INFO : Node 7: 0.087156 0.996195 0.000000
# INFO : Node 6: 0.087156 0.996195 0.000000
# INFO : Facet ID: 0 --- 0.000000 1.000000 0.000000
# INFO : dot product: 1.000000
# INFO : Point Index: [0, 1, 5, 4]
# INFO : Sample Index: [0, 1, 4, 5]
# INFO : Facet ID: 1 --- 0.173648 0.984808 0.000000
# INFO : dot product: 0.984808
# INFO : Point Index: [3, 2, 4, 5]
# INFO : Sample Index: [2, 3, 6, 7]
というようになります。ちょっと賢くなったような気がします。
XYZファイルをPointCloudに、再び
Softimageにてxyzファイルを読み込むにあたり、それが250万行とかある場合に、ICEのString to Arrayノードに座標をぶち込んで、ってのは無理があるようでした。
ということで、Scriptからパーティクルを発生させる方法を考えるわけですけど、例えばMayaなら座標の配列をコマンドに渡してやればパーティクルが作成されるわけですが、それに相当するものを見つけられていません。まぁ、Mayaでその方法を試したら、やっぱり処理が帰ってこなかったんですけどね。たぶん別のやり方があるのだとは思うけど。というわけで、以下の方法を試してみました。
スクリプトでPointCloudのプリミティブオブジェクトを出すことは出来ました。つまり、グリッドとかキューブとか球とかのプリセットから生成されるPointCloudは作成可能という事です。ということで、グリッドのPointCloudを作成し、必要なポイント数に近い数字になるように分割数を設定してやって、で、パーティクルポイントの位置をxyzファイルから読み込んだ位置情報に設定してやる、という手順を踏んでみることにしました。
import math from win32com.client import constants as c app = Application oRoot = app.ActiveSceneRoot xyzFile = "xyz file path" #eg: "c:\file.xyz" def readXYZFile(): f = open(xyzFile, 'r') str = f.read() f.close() lines = str.split('\n') posList = [[],[],[]] improvisedValueArray = [] i = 0 for line in lines: temp = line.split(' ') try: posList[0].append(float(temp[0])) posList[1].append(float(temp[1])) posList[2].append(-float(temp[2])) improvisedValueArray.append(True) except: LogMessage("NOT Params" ) i += 1 return posList, improvisedValueArray posList, improvisedValueArray = readXYZFile() pntCnt = len(posList[0]) uvCnt = math.ceil(math.sqrt( pntCnt )) subCnt = uvCnt**2 - pntCnt LogMessage("U V:" + str(uvCnt)) posList[0] = posList[0] + [0.0]*int(subCnt) posList[1] = posList[1] + [0.0]*int(subCnt) posList[2] = posList[2] + [0.0]*int(subCnt) improvisedValueArray = improvisedValueArray + [False]*int(subCnt) oObj = oRoot.AddGeometry("Grid","PointCloud","") oParams = oObj.Parameters oParams["subdivu"].Value = oParams["subdivv"].Value = uvCnt - 1 app.FreezeObj(oObj) oPnts = oObj.ActivePrimitive.Geometry.Points oPnts.PositionArray = posList attr = oObj.GetActivePrimitive3().AddICEAttribute( "improvisedValue", c.siICENodeDataBool, c.siICENodeStructureSingle, c.siICENodeContextComponent0D ); attr.DataArray = improvisedValueArray
グリッドから生成してるから、必要数よりも多くのパーティクルができ上がっており、そこで、improvisedValue っていうICEアトリビュートを設定して、必要のないパーティクルにはFalseを、それ以外はTrueの値を持つようにお尻で設定しています。で、ICE上でその値をみて、Falseの物は削除する、というようにしてやりました。
上図は以前挑戦した時にいつまでたっても読み込みが完了しなかったデータでの結果なんですけど、1分もかからずに読み込みが終了し、上のICE Treeの設定もさくさくできました(動作も軽かった)。xyzファイルの読み込みではないけど、この方法で可能かどうか、適当に1000万個の座標を与えてみたところ、1、2分で結果が返ってきたし、割と現実的な物が出来たかもしれないとか思っているところです。
とか、なんかSoftimage使いの方々がたまにみてくださっているようなので、たまにはSIの話題もといったところでございました。
Softimage感謝祭、楽しかったです。
Softimage 2015 SP1 が出たよー
mayaと比較するのもなんだけど、そっちは既にSP5が出ています。しかもSI 2015 SP1における修正された不都合数はわずか9。ちなみにmaya 2015 SP5は30ぐらいリストアップされているようです。これはあれだね、Softimageの完成度の高さを示しているね、と言っていいのかね?
それにしても不思議なのは
Softimage 2015 SP1 は、Softimage 2015 と共存させることはできません。お使いのコンピュータに Softimage 2015 がインストールされている場合は、アンインストールしてから Softimage 2015 SP1 のインストーラを実行してください。という仕様です。同一バージョンのSP違いの共存が出来ないというのは、他のAutodeskソフト、例えばmayaやmaxではその通りなんだけど、Softimageについては全バージョンが共存出来ていたのが、ここに来て出来ないようになっている。他のソフトウェアとポリシーを合わせてきたようにも見えます。
Softimage 2015ってそこはいじらなくてもいいだろう、って部分を触ってきている例もあるように見受けられる事からや、SP違いを共存させる事が出来なくなったという事から、2015開発当初はまだこれを最終バージョンにするという決定は下っていなかったんじゃないかって想像したりしますが、どうなんでしょうね。
ともあれ、2016年4月までサポートする事になっているのだから、徹底的にバグを潰していただき、あと、HiDPIへの対応やWindows 10への対応あたりもしていっていただけると、サポート終了後も5年ぐらいは使えるんじゃないでしょうか? Autodeskがそれを望まないであろう事は想像しますが…
そいえばダイキンさんがSoftimage感謝祭とやらを開くそうで。とりあえず登録してみたよ。
位置をプロット
というわけで、それを地図上にプロットするというのは、正距円筒図法に対しては非常に容易だったりします。
まずはエクセルファイルを読むためのモジュールをインストールです。xlrdってモジュールを入れればいいようです。使い方はネット上にいろいろあるんで省略。僕も良くわかってないし。ということで、これを使って、プロットしてやります。
from win32com.client import constants as c import xlrd app = Application xlsfilePath = 'D:\Users\whc-sites-2014.xls' #ダウンロードしたエクセルファイルのパス xLen = 200.0 book = xlrd.open_workbook( xlsfilePath ) sheet0 = book.sheet_by_index(0) for col in range( sheet0.ncols ): if str( sheet0.cell( 0, col ).value ) == 'longitude': colLog = col elif str( sheet0.cell( 0, col ).value ) == 'latitude': colLat = col posList = [] for row in range( 1, sheet0.nrows ): LogMessage(sheet0.cell( row, colLog ).value) xpos = (xLen * 0.5) * sheet0.cell( row, colLog ).value / 180.0 zpos = (xLen * 0.5 * 0.5 ) * sheet0.cell( row, colLat ).value / -90.0 posList.append( xpos ) posList.append( 0.0 ) posList.append( zpos ) oPntCloud = app.GetPrim("PointCloud", "", "", "") oICETrees = app.ApplyOp("ICETree",oPntCloud,c.siNode) oICETree = oICETrees(0) oAddPoint = app.AddICENode("$XSI_DSPRESETS\ICENodes\AddPointNode.Preset", oICETree ) app.ConnectICENodes(oICETree.FullName + ".port1", oAddPoint.FullName + ".add") oStrToArr = app.AddICENode("StringToArray", oICETree) app.SetValue(oStrToArr.FullName + ".Value_string", str(posList)[1:-1], "") app.ConnectICENodes(oAddPoint.FullName + ".positions1", oStrToArr.FullName + ".Result")
ここまでで、ICEツリー下図の赤四角で囲まれた範囲が作成されます。ポイントクラウドを作成するわけっすね。
で、その後、ポリゴンのコピーをそのポイント位置に置いていく予定ながら、面積を持ったものをおくと、隣と干渉しまくる密度の濃い地域があるんで、干渉を出来るだけ回避するために、赤四角以外の範囲のICEツリーを手動で組んでいます。
でもって、空のポリゴンメッシュに対してICEツリーで下図を作成するだけ。形状のソースとなるものは、単なる板ポリでございます。
これだけでいいんだもんなぁ。SI、楽だよねぇ。
Parallels 上でのSoftimage 2015の挙動の問題
ということで、現在、うちのバーチャルマシンにはSI 2011から2015までの各Softimageが入っております。2011の時代から引き継いだものというわけでは必ずしも無いのですけど、2011年(SI 2012の時代)ぐらいからは軽く引き継いだ環境になっている事と思います。
さて、そんなわけで、今回も嬉々としてあるいは大変な悲しみの気持ちとともにSI 2015をインストールし、ICEのエディタが便利になってるうひょぉとかやっていたわけですが、コンポーネントを触ろうとして問題に気付きます。レクタングルでタグやエッジを選ぶと、そのオブジェクトの全てのポイントやエッジが選ばれてしまう、という問題です。
ログを見ると(球のポイントをタグ付けするためにRectangleのモードで球の任意の範囲を囲ったら)
Application.SelectGeometryComponents("sphere.pnt[*,975]")
となっておりました。*がある事から分かる通り、これでは球のポイント全部がタグ付けされてしまいます。
ということで困ったなぁってTwitterでつぶやいていたら、ある方が素敵に解決方法を教えてくれました。ということで、それをメモっときます。その方法とは setenv.bat に XSI_DISABLE_NEW_PICK=1 という環境変数を書き加える、というものです。その方曰く、2015で選択の反応速度等の改善を施したものの、それを従来の方法に戻すための環境変数ということでした。どうやってこの変数を見つけるんでしょうね(^^)
と言う事で、とりあえずこれまで通りParallels上でSIを触っていけそうです。感謝でございます。