2009/12/24

Real Time Ambient Occlusion via ICE II

メリークリスマスイブっ!

すごいひさしぶりにICEをいじってみました。

以前、WeightMapにしか適用でき無かったAOですが、VertexColorに再現出来る方法が分かったのでやってみました。

こちらの方のサイトを参考にしました。
http://coralocean.sakura.ne.jp/2008/09/20080901020653757

こちらがICE Tree。



では、つたない解説。



まず、初めに、ポイントごとに必要なランダムベクトルを作るために配列を用意します。
0~1までの、配列を作れるとわかりやすくていいかもです。
5個の配列なら

[ 0, 0.25, 0.5, 0.75, 1 ]

って感じ。
一番左端のIntegerノードがランダムベクトルの数になります。
この数字を増やせば増やすほど高精細な結果になるけど、重くなるってところです。



次は、キレイにならんでいる配列にランダム感を与えます。

ランダムノードは、一律に同じ値が入ってしまうので、使えません。
なので、Modulo(剰余)を使ってちょっと細工をします。
入ってきた配列に対して、適当な値を掛け算します。この値は、整数では駄目です。

123.4818

などのような値が好ましいようです。1でModuloするので少数が入ってりゃいいです。
欲しいのは、半球のランダムベクトルなので、XとZだけ、レンジを-1~1にします。
Yは、0~1までです。

最後に、Point毎にNormalの方向にProject Vectorを使ってランダムベクトルを向けます。



Scalar to 3D Vector をプレビューすると、こんなベクトルの花が咲きました。



次は、このベクトルフラワーをPointごとに配置して、このベクトルが、自分自身に突き刺さるかを判定するのに、Raycastを使います。遮蔽率を求めるためです。
PointPositionに、PointNormalをちょっと加えているのは、フラワーをサーフェイスから少し浮かすためです。
でないと、まったいらな平面でも遮蔽してしまうことになってしまうからです。
Dirtmapは実はそこが弱点だったりしますが・・・。



ベクトルがヒットしたか否かの配列の平均を取ることで、そのPointの遮蔽率を出します。
Gradientノードに一度さして、PointColorに焼き込みます。
その後、改めてVertexColorにさします。
直接させないのが、なんで?って感じなんですけど・・・。
教えて欲しいものです。

以上でざーっとですが、ICE Treeの解説でした。

もっといろいろやりたいんですが、いかんせんアルゴリズムが分かりません。
数学Iしかやってないのが、ここに来て響いたなぁ(笑)

ま、自業自得ですねー。もっと勉強します。

そして、ワタクシ、来年より無職になります。
半年くらい仕事しない予定。
だけど、ブログはちまちまやっていこうかなと思ってます。

では、早いですが良いお年をお過ごしください。

来年も良い年でありますように。

2009/09/19

ヘアースタイリング - Hair2Curve

ヘアースタイリングツールを作ってみました。
初めに断っておきますが、このツールは、スクリプテッドオペレータを使用しています。
で、そのスクリプテッドオペレータが存在している状態で、保存すると二度とデータは復帰出来ません
という超危険な仕様なので、使い方は気をつけてくださいw
保存するときは、ヘアについてる、Hair2Curveをすべて消してからにしましょう。

使い方は、下記画像さんしょ。



ヘアを選択して実行するやつ

Hair2Curve.pys
#---------------------------------------------------
app=Application;log=app.LogMessage;sel=app.Selection
import win32com.client
#---------------------------------------------------
if sel.Count==0 or sel(0).type!="hair":
 raise

sCode = """from win32com.client.dynamic import Dispatch as d
def Hair2Curve_Update(ctx,OutPrim,InPrim):
 oCrvCol = InPrim.Value.Geometry.Curves
 lHairPos = [[],[],[]]
 for oCrv in oCrvCol:
  for fHairPer in [f/13.000*99.999+0.001 for f in range(14)]:
   vPos = d(oCrv.EvaluatePositionFromPercentage(fHairPer)[0])
   lHairPos[0].append(vPos(0))
   lHairPos[1].append(vPos(1))
   lHairPos[2].append(vPos(2))
 OutPrim.Value.GetGeometry2(ctx.CurrentFrame).Points.PositionArray = lHairPos
"""

oCrvCol = win32com.client.Dispatch("XSI.Collection")
for oHair in sel:
 oHair.AllowStretch.Value = True
 lPos=[[f for f in l] for l in oHair.ActivePrimitive.Geometry.Points.PositionArray]
 iStrNmb=len(lPos[0])/14
 lPos.append(list([1])*len(lPos[0]))
 oCrv = oHair.Parent.AddNurbsCurveList()
 for i in range(iStrNmb):
  oCrv.ActivePrimitive.Geometry.AddCurve(
   [ lPos[0][i*14:i*14+14],
    lPos[1][i*14:i*14+14],
    lPos[2][i*14:i*14+14],
    lPos[3][i*14:i*14+14] ],
   range(14),
   False,
   1,
   1
  )
 oFitCrvOp = app.ApplyGenOp("CrvFit", "", oCrv, 3, "siPersistentOperation", "siKeepGenOpInputs", "")(0)
 oFitCrvOp.points.Value = 3
 log(oFitCrvOp.Parent3DObject)
 oFitCrv = oFitCrvOp.Parent3DObject
 oHair.Parent.AddChild(oFitCrv)

 oTransfo = oHair.Kinematics.Global.Transform
 oFitCrv.Kinematics.Global.Transform = oTransfo
 app.FreezeModeling(oFitCrv)
 app.DeleteObj(oCrv)
 #app.ToggleVisibility(oFitCrv)
 oCrvCol.Add(oFitCrv)
 app.Refresh()

 oOpr = oHair.ActivePrimitive.AddScriptedOp(sCode,oFitCrv.ActivePrimitive,"Hair2Curve","Python",1)

sel.SetAsText(oCrvCol.GetAsText())



そんでもって、Hair2CurveOpを消すスクリプトは、これです。

FreezeHair2Curve.pys
#---------------------------------------------------
app=Application;log=app.LogMessage;sel=app.Selection
import win32com.client
from win32com.client import constants as c
from win32com.client.dynamic import Dispatch as d
#---------------------------------------------------
app.ActivateObjectSelTool()
oHairPrimCol = app.FindObjects("","{D5C3CDBA-B361-4A11-9582-91DBA0ECBB49}")
for oPrim in oHairPrimCol:
 oOp = oPrim.NestedObjects("Hair2Curve")
 if oOp:
  log(oOp)
  oCrv = d(oOp).InputPorts(0).target2.Parent3DObject
  app.FreezeObj(oPrim.Parent3DObject)
  app.DeleteObj(oCrv)


いやいや、おっかねぇスクリプトですね。
会社でなんて使わせられません。

そもそも、Hairにスクリプテッドオペレータが適用出来ないXSI様が悪いんですけどね。
HairにICE適用出来ないのも悪いですね。

そいや、Softimage2010を使い始めましたわ。
2010になって、HairにICE適用出来るようになったのかしら。
後でやってみよーっと。

FaceRobotは、なかなか素晴らしいものがありますね。
顎とか、口まわりとか、目まわりとか。
顔は、モーションキャプチャの場合、Envelopeのほうが断然楽ですな。
ただし、後付け修正したいので、Shapeで加えるとかですかね。
もちろん、移動差分の骨のクリップを並べて、MixerのNormalizeをオフって加算でやるのもいいですね。
どっちも得手不得手があるので、それぞれ選ぶべきですなー。

データは軽く作れよっ。

2009/07/26

Softimage : Sukio Sukio Sukio Python X

遂に10回目になりました。
しかし、かなり時間を空けてしまいましたね・・・。
サボタージュ期間が長かったかもです。
でも、海の日にヴぁかんスに行ってきたのでちょっと回復。
さぁ、行きましょうか。

では、今回は、よく使うPythonの関数を紹介しましょう。
いろいろ便利なものが揃っています。
取りあえずは、こんなのあるなーくらいに思うくらいにしておいて、確かこんなのあったな。
って時に使うのがベストですね。


map(関数,シーケンス)

これは、シーケンスのひとつずつのアイテムを関数にかまします。
戻り値は、関数のreturn値を介してリストで戻します。

mapを使ったもの

def fct(p):
    return p
lRtn = map(fct,range(10000000))

for文を使ったもの

lRtn = list()
for i in range(10000000):
    lRtn.append(i)

内容はほぼ一緒です。(あんまり意味の無いサンプルのサンプルですが・・・)
ただmapを使ったほうがちょびっとだけ早く動作するみたいですね。
XSIでの使い方は、大量のオブジェクトがあったとして、そのひとつひとつに同じ関数をかましたいときに使います。
例えば、null群があって、そのsizeを0.1にしてさらに形状をBoxにしたいときは

def SetDefaultNull(pObj):
    pObj.size.Value = 0.1
    pObj.primary_icon.Value = 4
map(SetDefaultNull,Application.Selection)

こんな風に使います。
やりたいことをファンクションにしておいて、シーケンスをかます。
とてもシンプルに書けます。


enumerate(シーケンス)

enumerateは、for文と共に使います。
インデックスとアイテムを同時に発生させることが出来ます。

for i,o in enumerate(list):

iにインデックスが入って、oにリストの中身が入ります。
for文だけでリストなどを回すとリストに入っているアイテムしか取得出来ず、それが何番目のオブジェクトなのかは分かりません。
使いどころは例えば、envelopeのweightsとdeformersをシンクロしたいときに効果を発揮します。weightsは、デフォーマの順番に各ポイントが割り振られている2次元配列で戻って来ます。

((0.0,50.0,100.0),(100.0,50.0,0.0))←こんな感じに。

つまり、weightsの0番目に処理しているときに、deformersの0番目を取得したいときなどに使えますね。

import win32com.client
app=Application;log=app.LogMessage;sel=app.Selection
oDfm = sel(0).Envelopes(0).Deformers
tWgt = sel(0).Envelopes(0).Weights.Array
oBoneCol = win32com.client.Dispatch("XSI.Collection")
for i,w in enumerate(tWgt):
    if max(w):oBoneCol.add(oDfm(i))
log(oBoneCol.GetAsText())

このサンプルは、エンベロープのウェイトが0のオブジェクトを弾くXSICollectionを作成します。
max()も使っていますが後述します。
maxの値が0ってことは、weightがすべて割り振られてない0 weiht bone なので、スルーです。


sum(シーケンス)

リスト、タプルの足し算を返します。
sum(range(10))は、0から9を足しているので45を返します。
ただし、数字しか受け付けません。文字列の場合は、joinを使いましょう。

",".join(string_list)

よく使うとか言っておきながらサンプルを思いつけませんでした(ギャボンヌ)
うーん。使わないかなw


max(シーケンス),min(シーケンス)

maxは、リストなどの最大値を取得。minは、同様に最小値取得です。
FCurveの最大値とか取れそうですね(取ったこと無いけど・・・)


app=Application;log=app.LogMessage;sel=app.Selection
log(max([k.Value for k in sel(0).posy.Source.Keys]))

open(ファイルパス,モード(読み取りとか書き込みとか))

結構良く使います。
weight情報を吐き出したり、ログファイルを生成したいときとか、テキスト情報を読みに行ったりとか。
読み込みたいときはコレです。stripは、末尾の改行を示す"\n"を取り除いてくれます。
pathになんか入れてください。

app=Application;log=app.LogMessage
f=open(path,"r")
while 1:
    s = f.readline()
    if not s:break
    log(s.strip())
f.close()

逆に書き込みたいときは

app=Application;log=app.LogMessage;sel=app.Selection
f=open(path,"w")
f.write(sel.GetAsText())
f.close()

pathになんかパス。選択したもののリストを書き出します。
と、Openには、いろいろあるんですが、使うのはこれくらいで十分じゃないでしょうかね。


今回は、こんな感じです。
だいぶ、Pythonの海泳げるようになって来たんじゃないでしょうか?
そいや、初心者の講座って始めたんですが、全然初心者向けにならなくなってきた感じですよね。
一度初心者の道を通り終わると、初心にかえるのが難しいことを意味しているんですが、ということは、ワタクシもまだまだひよっこなんだなぁと思い知らされます。

絶えずもどかしい。ああもどかしい。もどかしい。

でも、ライブラリとして、とって置けたのでなんとなくはいいかな。と。
完全なる自己満足。

ってなわけで、次回は、HairをCurveに変換するPythonでも公開しましょうかね。
Curveに変換して、Scripted Operatorにつなぎます。
すると、Curveで、Hairをスタイリング出来ます。

Softimageは、Hairスタイリングツールは、開発者のためにあるようなものですから、ちょっとは楽にしてほしいところなんですけどねぇ・・・。
とはいえ、この辺は難しいところですから、デザイナーがどう使うかにかかっているのですが。

では、近々公開しますね。

2009/04/06

Softimage : Sukio Sukio Sukio Python - IX

さて、9回目です。
ちょっと難しい問題に取り組んでいきます。

それでは、正規表現2回目。
defを用いた置換を紹介します。

app = Application;log = app.LogMessage
import re
def repl(m):
  dictRepl = {
    "Model":"A",
    "Model1":"B",
    "Model2":"C",
    "sphere":"maru"
  }
  try:
    return dictRepl[m.group()]
  except:
    return m.group()

for oObj in app.Selection:
  s = "%s -> %s"%( oObj.FullName,re.sub("\w+",repl,oObj.FullName) )
  log(s)

こんな感じの時に実行しています。



このコードは、辞書を使ったパターン置換です。

dictRepl = {
 "Model":"A",
 "Model1":"B",
 "Model2":"C",
 "sphere":"maru"
}

この辞書の部分で、

Model  → A
Model1 → B
Model2 → C
sphere → maru

という置換の定義をする事にしています。
法則というか決まりごとですね。

そして、置換するメソッドとしてsubを使用しています。
re.sub("\w+",repl,oObj.FullName)

第1引数は、拾うパターン。
第2引数は、置換する文字列。またはファンクション。
第3引数は、置換する対象です。

第1引数の \w+
これは、[A-Za-z0-9_]と同じになります。
A~Z、a~z、0~9と_にヒットします。
つまり、XSIの場合"."ドットまで、ひとくくりです。

第2引数は repl
通常は文字列ですが、defを使用出来ます。
上記は、replファンクションが入ります。
このファンクションには、引数として、マッチオブジェクトが入る事になっています。
1でヒットしたマッチオブジェクトです。

return dictRepl[m.group()]

そして、上記replのmに引数が渡され、辞書の値を返します。

例えばこのようにキーを入力すると、値を取ることが出来ます。
dictRepl["Model"] -> "A"

Model なら A が返ってきます。

それと、新たにtry except文を入れてみました。
これは、エラーを吐いても例外に飛ぶことが出来る文です。
try内で、エラーが発生したら、except(例外)に飛びます。
マッチオブジェクトが、Model3というものにヒットした場合、無い辞書のキーを取ってしまうとエラーになります。

dictRepl["Model3"]

これは、エラーですので、回避して何も変換しないという

m.group()

を返すことにしています。

今回は、あまり利用価値が無いものかもですが、一応メモ的な感じです。
応用としては、ターゲットの指定のときに役に立つかなぁとも思ってます。
コンストとか、Definition指定するの面倒ですものね。

とはいえ、正規表現はこの位で終わりますね。
そういえば、JScriptでも同じようなことが出来ますから、別にPythonでなくてもいいんですけどね。
連想配列で検索です。
VBは知りません。

ではでは。

2009/02/16

Sukio Sukio Sukio Python - VIII

第8回目は、正規表現に行きたいと思います。
正規表現は、とても難しいですので、ワタクシも全て理解しているとは言いがたいです。
ですが、少しでも学ぶことが出来れば、強力なツールになること間違いなしです。
正規表現は、パターンマッチをするには、最強です。
では、そのほんのちょっとしたさわりをご紹介します。

例えば、duplicateなどした場合には、cube1など、お尻に数字が入ります。
さらに100個くらい増やした後、幾つかのcubeをABCやguideなどと数字は生かしたままりネームしてしまったとします。
最悪な事に、幾つかのオブジェクトはDeleteしてあります。
その後、そのオブジェクト達の番号に対応するnullを用意しなくてはならない場合があるとします。
さらに、nullをリグとするposeコンストをしたいです。
cubeとABCとguideは、番号続きであるもののごちゃ混ぜ。
さらにnullには、その番号に対応した数字を入れなくてはなりません。

cube1
guide3
ABC24
cube54
guide98
ABC145
cube203
...

数字を取得するのなら、cubeやABCやguideを抜いたら良いですね。
ですが、この場合の発想は逆で、数字だけを抜き取ります。
こういったときには、正規表現を使うのが一番です。

まず、正規表現を使うことが出来るreモジュールをインポートします。

import re

これで、reモジュールを使うことが出来ます。
reには幾つかのメソッドが存在します。
まずは、compile
パターンオブジェクトを作成します。

reD = re.compile(r'\d+$')

\d+

ここで使われている特殊な文字『\d』は数字を意味します。
digitのdですね。
+を付けると、0や567など1以上の複数にマッチします。

$

これは、末尾を意味します。

\d+$

としておけば、末尾が数字のものという意味になります。
次にパターンにひっかかるかどうかのマッチオブジェクトを作成します。
ここでsearchを使います。

m = reD.search('cube54')

mにマッチオブジェクトが入りました。
そして、実際にマッチングしている文字列を取り出すには

m.group()

とします。
これで

54

が取得出来ます。
選択して、それに対応するnullを作成しposeコンストするにはこんな感じにします。



これにより、数字に対応したnullがposeコンスト付きで出来ることでしょう。

もう少し複雑にしましょう。
例えば、カメラモデル名にシーン番号カット番号イン点アウト点などを表記させておきます。

s021c156_Camera_231_310

このパターンは、かなりの数に上ります。
通常の考え方で、カット番号を取得したい場合は
カメラのモデルをoCamMdlとした場合

oCamMdl.Name[5:8]

とすれば『156』を取得出来ます。
しかし、こうなればどうでしょう?

s1c56_Camera_231_310

『_Ca』が取得されてしまいます。
これではあまりに情けないので、正規表現を使って取得してみることにしましょう。

パターンオブジェクトを作成します。

reCam = re.compile(r'(s)(\d+)(c)(\d+)(_Camera_)(\d+)(_)(\d+)')

()を付けるとくくられて、グルーピングされます。これは、後述します。
上記を日本語で書くとこんな感じでしょうか。

(sにマッチ)(数字にマッチ,+で複数も可)(cにマッチ)(数字にマッチ,+で複数も可)(_Camera_にマッチ)(数字にマッチ,+で複数も

可)(_にマッチ)(数字にマッチ,+で複数も可)

次に、パターンオブジェクトに対して、実際に判定する文字列を入れマッチオブジェクトを作成してみます。

reCam = re.compile(r'(s)(\d+)(c)(\d+)(_Camera_)(\d+)(_)(\d+)')
m = reCam.search('s021c156_Camera_231_310')

mにマッチオブジェクトが入りました。
そして、実際にマッチングしている文字列を取り出すには

m.group()

とします。
これで

s021c156_Camera_231_310

が取得出来ます。
ここまではあまり意味無いですが今度は

'Camera_root'

を入れてみます。

m = reCam.search('Camera_root')
m.group()

これはエラーになってしまいます。
パターンにひっかから無かったからです。
searchで、パターンにマッチ出来ないと、Noneオブジェクトが返ってくる仕組みになっています。
ですので

if m:
m.group()

などとすれば、マッチしているかどうか判定出来ます。
ifで、Noneは、Falseと判定されます。
次に、マッチオブジェクトに対して、groups()としてみましょう。

import re
app = Application
log = app.LogMessage
reCam = re.compile(r'(s)(\d+)(c)(\d+)(_Camera_)(\d+)(_)(\d+)')
m = reCam.search('s021c156_Camera_231_310')
if m:
 log(m.groups())


結果は、()でくくったグルーピングをタプルとして返します。

('s', '021', 'c', '156', '_Camera_', '231', '_', '310')

カット番号を取りたい場合は決まって4番目の値をとりたいので

m.group(4)

とすれば、目的のカット番号

156

が取得出来ますね。

ここでもう少し便利にすることが出来ます。
パターンに名前を付けることが出来たりもします。

import re
app = Application
log = app.LogMessage
reCam = re.compile(r'(s)(?P<scn>\d+)(c)(?P<cut>\d+)(_Camera_)(?P<in>\d+)(_)(?P<out>\d+)')
m = reCam.search('s021c156_Camera_231_310')
if m:
 log(m.group('scn'))
 log(m.group('cut'))
 log(m.group('in'))
 log(m.group('out'))
このように

(?P<scn>\d+)

とすればscnという名前から取得出来るようにタグを付けておいて、後で呼び出すときに指定できるようにすることも可能です。

このように、正規表現は1冊の本として出ることが許されるほど複雑怪奇です。
ですが、webには、沢山の例がありますので、少しずつ覚えて行けばOKです。
使えなくても対した問題にはなりませんが、使えるようになれば、更なる進歩が望めます。

次回ももう少し正規表現の世界にダイブしてみることにしましょう。
では、しばらくの沈黙の後に。

2009/01/05

Sukio Sukio Sukio Python - VII

今年もよろしくおねがいします。
7回目になりました。はじめるときは、ココまで続けると思ってませんでした・・・。
教えるとなるといろいろあるものですね。
そうそう、Pythonのことをもっと勉強しようと思いまして、Pythonクックブックという本を買ってみました。
で・す・が、物凄く難しいですねぇ。
というか、本当に一部分の機能しか使っていないんだなということを、痛感させられます。
Pythonが使用されているのは、多岐多用に渡っていますからそれはそうなんですけどね。
CGで使うのは、本当に基本的な機能だけです。
ですが、基本的な機能さえ覚えてしまえば、ほとんど応用できますね。
あとは、CGのアルゴリズムをPythonに組み込めば、立派なスクリプターになれると思います。
バネ係数とか、波紋の伝達の式とかとかとか。
まだまだ勉強しなくてはならないものは、たくさんあります。

ではでは、前回の回答からしますね。
モデルの名前は、LR反転することなくオブジェクトの名前だけで反転するコードです。


テキスト処理する際、最速になる傾向は、分解・変換・結合です。
細かく割って、変換した後、結合させる。
という方法が、早く動作する傾向にあります。

for oObj in app.Selection:

この行で、セレクションのリストを順々にoObjに代入します。
選択しているものが

ROB.IK_Rarm, ROB.IK_Rfoot, ROB.IK_Rhand

だとすると一番最初に代入されるのは

ROB.IK_Rarm

処理が終わって、2番目に代入されるのは

ROB.IK_Rfoot

となります。

sMdl = oObj.Model

モデルの名前は、変換させたくないので、一度sMdlという変数にモデルをキープしておきます。

ROB.IK_Rarm

というオブジェクトでしたら、ROBという文字列が入る事になります。

sName = oObj.Name.encode().translate(table)

次にモデルの名前だけ前回のやり方で翻訳します。
上記でしたら

"IK_Larm"

に変換されているはずですね。

lSel = list()
lSel.append('%s.%s'%(sMdl,sName))

一度、for文の前に、lSelという空のリストを作っておきます。
その後、appendメソッドで、リストに追加します。
その前にPython特有の文字列処理があります。

'%s.%s'%(sMdl,sName)

上記の例ですと

sMdl="ROB"
sName="IK_Larm"

ですので"."(ドット)が挟まり

"ROB.IK_Larm"

になります。
%sに、文字列が次々に代入されるという書き方です。

sMdl+'.'+sName

と書きたくなりますが、%sを使った書き方のほうが、高速に動作します。
覚えておいて損は無いですよ。

'%s'*3%('a','b','c')

と書けば

"abc"

となります。文字列にも掛け算が出来るのもPythonの特徴です。
ひとつの文字列を入れたい場合は、タプルにしなくてもOKです。

s='XSI'
'I love %s' % s



"I love XSI"

こんな感じですね。

そして、次々に処理をしていき、リストlSelにappendで追加していきます。
例えば

lSel=[ "ROB.IK_Larm" ]
lSel.append("ROB.IK_Lfoot")



[ "ROB.IK_Larm", "ROB.IK_Lfoot" ]

こんな感じです。
最後には、内部的には、こんな感じで格納されているはずです。

[ "ROB.IK_Larm", "ROB.IK_Lfoot", "ROB.IK_Lhand" ]

最後にリストを単一の文字列に結合です。

app.Selection.SetAsText(','.join(lSel))

string.join(list)という形で、listの間にstringを挟んで行ってね。
と命令します。
上記は、リストの間に、','を挟みます。結果は

"ROB.IK_Larm,ROB.IK_Lfoot,ROB.IK_Lhand"

という単一の文字列になり、app.Selection.SetAsTextでテキスト通りに選択しなさいという命令文で完成です。

モデルが無い場合でも、Scene_Rootというモデルがあるので、安全に動作するはずです。
しかし、まだまだ落とし穴はあるもので、NameにもLRが入っているものには、使うことが出来ません・・・。
例えば

ROB.ArmOffset_Rot_R

だとか

ROB.ROB_Rarm

など名前を付けられてしまうともうお手上げですね。
なので、名前の仕様を決めたほうがいいでしょう。
LR以外は小文字でお願いね。とかですね。

という感じで、今年もがんばって更新したいと思います。
もうそろそろネタ切れかなーと思いきや、実はまだあったりするのでちょっと難しいところに行ってみたいと思います。
正規表現をちょろりと。
これは、本当に難しいので、一度では全然理解できないと思いますが、少しでも使えるようになるとかなり強力な力を発揮するので、少しずつ覚えておくのが、◎です。
いろいろなサイトもありますが、まずはさわりだけやりたいと思います。

ではでは、しばらくの時を経て、次回をおたのしみに☆

Perforce: 複数のワークスペースを更新するバッチ

batを叩けば全部更新。 @echo off set P4PORT=x.x.x.x:xxxx set P4USER=user set P4PASSWD=password echo %P4PORT% echo %P4USER% echo %P4PASSWD% echo %P4PAS...