カテゴリー: Programming

Programming, コンピュータ

Redmine の Workflow 定義を直感的におこなう

Redmine では、マトリックスにチェックを入れることで Workflow を定義します。

慣れればこれはこれでいいのですが、直感的ではないですし、編集した結果どのようになるのかもわかりづらいです。

ちょっと調べたところ、Google chart API を使って図にするプラグイン redmine_workflow_viz が見つかったものの古くて最近の Redmine で動かないようです。また、グラフを見るためには Save をしなければいけないため、やっぱりちょっと不便です。

そこで、編集中の Workflow の状態をリアルタイムに見ることのできるプラグイン、workflow_graph を作りました。ブラウザ上で描画をおこなっているため、チェックのオンオフに連動して表示が更新されますしレイアウトも好きに調整できます。

workflow_graph from JCGS on Vimeo.

参考にしたサイト:

Programming, Python

set なにそれおいしいの?

昨日、Python文法詳解を詳解する会 #3に参加してきました。第三回の主な内容は文字列と集合(set)だったんですが、 set ってよく知らないと何それおいしいの?状態だったりします。というか、自分も以前はそんな感じでした。

Python には配列として list があって、大抵のことはこれで事が済んでしまいます。list なら順番は保持してくれるし同じ値も格納することができます。それに対して set では順番が保持されないし同じ値を格納することもできません。一見するとすごく不便です。

set は、 l = list(set(l)) のように重複する要素を無くした list にするためにしか使わないという人は多いのではないでしょうか。

でも、実は set ってものすごく使える子なんですね。

set の何が嬉しいかっていうのを説明するには集合っていうものが何なのか?っていうことを説明したほうがいいんですが、細かい話をしだすとキリがないので気になる人は集合っていうキーワードで調べてみてください(丸投げ。

ここではあんまり詳しくなくても今日から使える set の使い方ということで、実例をあげて紹介します。

連番ファイルの抜けチェック

何かスクリプトを組む時に、あるデータ A と 別のデータ B が同じか、違うならどこが違うか?を比較したい時がよくあります。

映像業界では動画を扱う必要があるので画像を連番ファイルとして扱うことが日常的にあります。この時、一部のファイルが無くなってしまっているということが多々あります。

ファイルが 10 個とか 20 個とか少なければ目でも確認できますが、動画の連番ファイルは一秒で 24 個とか 30 個のファイルができます。10 秒で 240~300、一分なら 1440 とか 1800 個のファイルが一つのディレクトリ内にできます。ファイルの抜けがあるかどうかは総ファイル数を確認すればできます(後述の通り、本当はできないですが)。問題は、ファイルが少なかったり多かったりしたときです。

1440 個ファイルがなければいけないのに、1438 個しかファイルがなかった場合、どのファイルが無いのか確認しなければいけません。目視で確認?できなくはないですけど時間もかかるし見落としてしまったら再度チェックの必要があります。もっと悪いことに、正しいファイルは 1437 個で、本当はあってはいけないファイルが 1 個紛れ込んでいるかもしれません。こうなったらもう手あげです。こういうときはプログラムの出番です。

簡単に書くとこんな感じでしょうか。


import os
d = 'path/to/renban'
fname = 'hoge.%04d.exr'
nfile = 1000

for i in range(nfile):
    path = os.path.join(d, fname % i)
    if not os.path.exists(path):
        print 'missing file :', path

はい。これで一応抜けているファイルのチェックはできるようになりました。でも、これだと余分なファイルがあった時にきづけないです。細かいことをしようとすると os.listdir() でファイル一覧を取得して、えいやっといろんな処理を書かないといけないです。めんどくさいです。めんどくさすぎて禿げてしまいます。

そういうときに、集合の比較という問題に落とし込んでしまいます。

連番ファイルのチェックという問題は、結局のところ

  • 実際に存在するファイルの一覧
  • 本来存在すべきファイルの一覧

の二つを比較するということです。一覧を集合と読み替えると、まさに集合演算の最も得意とする処理内容になります。そこで、コードにしてみましょう。まずはそれぞれの一覧(集合)を作ります。


import os
d = 'path/to/renban'
fname = 'hoge.%04d.exr'
nfile = 1000

# 実際に存在するファイルの一覧
existingFiles = set(os.listdir(d))

# 本来存在すべきファイルの一覧
theoreticalFiles = set([fname % i for i in range(nfile)])

これで集合ができました。あとは、この二つを好きなように集合演算すればいいだけです。


#本来あるべきなのに無いファイルを表示
print theoreticalFiles - existingFiles 

#存在すべきでないファイルを表示
print existingFiles - theoreticalFiles 

どうでしょう。引き算をするだけで欲しい情報が得られてしまいました。プログラムで逐次処理する例と比べて段違いに簡単で、かつ処理内容をちょっと変えるだけでいろいろな情報を得ることができるようになっています。

集合演算

ここで何をやっているのかを簡単に説明します。

最初に作成した existingFiles と theoreticalFiles は下図のような関係にあります。赤い丸が existingFiles、青い丸が theoreticalFiles です。赤丸と青丸が重なっている部分は両方に含まれるファイル、重なっていない部分はどちらか片方にしか含まれないファイルになります。

集合演算では、この丸を使って足したり引いたりといった演算を行うことができます。今回おこなったのは下図の二つの演算です(existingFiles と theoreticalFiles だと長いので、A と B に置き換えています)。みての通り、それぞれにしか含まれない要素だけを取り出すことができています。

もちろん、これだけではなく他にもいろいろな演算をおこなうことができるので、目的に応じて必要な情報を得ることができます。

まとめ

今回のような内容は、集合論の勉強をしたことのある人なら当たり前だよっていう内容ですが、知っていると知らないとでやりたいことを実現するためのアプローチが全く異なってくるといういい例です。

もちろん、集合(set) を使った処理をおこなうためには集合を扱うためのお約束に則った形に問題を置き換えなければいけないですが、うまく問題を置き換えることができた時には強力な道具になってくれます。

皆さんも身の回りの問題を置き換えて簡単に表現できないか考えてみてください。

Blender, Programming

第1回 Blenderソースコードリーディング 準備:Blender2.6aのコードを読んでみる(1)

引き続き、ドキュメントのある Blender2.6a をベースにコードリーディングを進めます。

Blender 内の動作を確認する

デバッガで動作を確認する

  1. Blender を Debug 版でビルドする
  2. 起動
  3. Visual Studio(VS) で デバッグ/プロセスへアタッチ

これで Blender と VS が接続された。

ブレークポイントを張ってみる

とりあえず適当にブレークポイントを張って、Blender の挙動を確認してみる。
コードをざっと見て bf_blenkernel に mesh.c というのがあったので、そこの add_mesh が Blender でジオメトリを作成するときに呼ばれる関数だろうという目星をつけてブレークポイントを張る。

Space→Add Cube で Cube を作成しようとすると、ブレークポイントで処理が止まって各種情報を確認できるようになる。

call stack を辿っていくと add_primitive_cube_exec() が呼ばれていて、更に呼び出し元を辿ると wm_* 関数群まで戻る。このあたりが GUI のメニュー処理を行っている。
wm_* 内で引き継がれている引数 ot がメニューと実際の処理を紐付けるためのもので、この中にどのメニューの表示内容やメニューが選ばれたときに呼ばれる関数がまとめられている。

このようにして、特定の機能やコードを起点にその周囲の処理の流れを把握することができる。

このときの情報を元に、次にどの方向に進むかを考える。今回の情報だけでも

  • ジオメトリの生成や管理
  • GUI でのイベント処理
  • ot を中心としたイベント、メニュー、処理のテーブル管理
  • テーブル管理のためのアーキテクチャ

といった方向性に進んでいくことができる。

Blender 内の構造を確認する

Blender code layout を見ながらコードのレイアウトを確認する。

横方向の点線でレイヤが分けられていて、矢印でレイヤ間/レイヤ内の依存関係が示されている。

Application startup レイヤ

例として Application startup レイヤの説明をおこなう。Application startup レイヤは blender/source ディレクトリにあり、creator と blenderplayer モジュールから構成されている。この二つのモジュールは依存関係がなく、それぞれは “Editor definitions, drawing, interaction” 以下のレイヤの機能を呼び出している。

モジュールの複雑さ

全てのレイヤは基本的に下のレイヤに依存しているため、上位に行けば行くほど複雑に、逆に下位に行けば行くほどそれぞれのモジュールの機能はシンプルになっていることが想像できる。
図をボヤッと見ていてもどこから手をつけていけばわからないが、タイトル下部に書いてある矢印の図を参考に、下の方から見ていくのがコツ。

Utility Libraries(in own development)レイヤ

Utility Libraries(from external development)レイヤ以下は外部で開発されている共通ライブラリなので今回のコードリーディングの対象にはならない。興味があればそれぞれのライブラリのプロジェクトを調べ、個別に調査をする。

つまり、Blender が抱えるコードで最もシンプルなのはUtility Libraries(in own development)レイヤに所属するものということになるので、まずはここの構造を把握することから始める。

モジュール概観

このレイヤに所属するモジュールをざっと眺めてみる

  • メモリ管理
    • guardedalloc
    • memutil
  • 文字列処理
    • string
  • 数値解析
    • opennnl
  • シーンデータ管理
    • bsp
  • データ処理・ソルバー
    • audaspace
    • boolop
    • decimation
    • elbeem
    • iksolver
    • itasc
    • smoke
    • mikktspace
    • moto
  • システム・GUI
    • container
    • ghost

分類としては色々雑なものの、以上のような感じ。
このように同じレイヤ内でも全く異なる目的だったり、アプリケーション上での位置付けが異なるのものが並列して記述されているので要注意。ただ、このレイヤはあくまでもユーティリティライブラリなので直接 Blender の特定の機能と結びつくものではなく、汎用的な部品の扱いとなる。

また、モジュール一つ一つが VS のプロジェクトとして分けられている。例えば、smoke は bf_intern_smoke プロジェクトになっているので、ここのコードを辿れば Blender で使用している smoke solver の情報を得ることができる。

今回のまとめ

Blender の内部を解析するにあたって、実際の Blender の挙動から処理の流れを追う方法と、コードレイアウトのドキュメントから Blender の構造を把握する方法の二つの方法をおこなった。この二つの方法を用いることで、アプリケーションの構造を把握するための糸口を掴むことができるようになる。

引き続き、Blender Architecture を参考にしながら Blender の論理的な構造を探っていく。

3ds max, Programming

Py3dsMaxでmaxの特殊な引数への対応

max script で各種操作を行う際によく目にする、# 付きのパラメータ(named parameter? name type?)を Py3dsMax で扱うためのメモ。

本家のドキュメントの Type Conversion でも簡単には触れられているのですが、 “testing” という文字列を #testing という名前としての値に変換するには max script では


"testing" as name
#testing

とし、同様のことを Py3dsmax で行うには


>>> mxs.pyhelper.namify("testing")
#testing

とします。ここまでは OK。で、ここからが疑問で、関数への引数として渡すには? ということが書いてないです。どうすればいいんでしょう?

答えとしては単純で、namify された値をそのまま渡せばいけました。これに気づくまで、わざわざ max script のコードを生成してそれを実行してましたよ。。。。。

たとえば、確認ダイアログを出さないでシーンをリセットする場合。


noPrompt = mxs.pyhelper.namify('noPrompt')
mxs.resetMaxFile(noPrompt)

こんな感じです。メンドクサイですけど、しかたないですね、、、

3ds max, Programming

Py3dsMaxでKrakatoa PRTローダを操作する

Py3dsMax 関連でドハマりしたのでメモ。

Py3dsMax であるクラスに関連するオブジェクトを取得するのは、maxScript にある getClassInstances() 関数を使えば取得できます。が、どうやらモノによってはこれで取得したオブジェクトだと、親クラスで定義されているプロパティにアクセスできないようです。


from Py3dsMax import mxs 
for n in mxs.getClassInstances(mxs.KrakatoaPRTLoader): 
    print n.name

これは一見 OK そうですが、実はダメです。なーぜーーーーー!?

PRT Loader を選択した状態で selection から拾う、


from Py3dsMax import mxs 
for n in mxs.selection: 
    print n.name

これならイケるんですね。ぐぬぬぬ。何が違うのか全く不明です。

ちなみにどちらの例でも fileList のように KrakatoaPRTLoaderクラスで定義されているプロパティはアクセスできます。

でまあ、いろいろ試行錯誤した結果こんな感じに。ここで得られた objs なら、 show するなり何なりで通常のオブジェクトと同じように操作できます。


from Py3dsMax import mxs

objs = []
for n in mxs.getClassInstances(mxs.KrakatoaPRTLoader): 
    objs.append('selectMore ' + mxs.exprForMAXObject(n))

s = '''undo on
(
clearSelection()
%s
)''' % '\n'.join(objs)

mxs.execute(s)
objs = mxs.selection
mxs.execute('max undo')

for o in objs:
    print o.name

良い子はマネしちゃいけない、極悪非道なコードです。あまりにも酷すぎます。

全く、なんなんでしょうね、、、、、

Programming

ネットワークファイルへの stat() は、嘘をつかれることがあるよ!!

ネットワークファイル周りは結構地雷がたくさんあって、これも今年私がドハマりしたものです。
私が確認しているのはWindows7+Windows Server の組み合わせのみで XP などでは起きない症状ですが、ネットワーク上にファイルを作成/削除してもどこかでキャッシュが聞いているようで、数秒間はstat() が嘘情報を返すというものがあります。


import os
import time
import sys
import ctypes


def CreateHardLink(src, dest):
    if os.path.exists(dest):
        print 'file exist'
        raise FileExistError

    CreateHardLinkW = ctypes.windll.kernel32.CreateHardLinkW
    CreateHardLinkW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int]

    ret = CreateHardLinkW(dest, src, 0)
    if ret ==0:
        print ctypes.FormatError()
        print src
        print dest

        raise PlatformError

def clearData():
    for i in range(10):
        f = os.path.join(d, r'%s\%03d.txt' % (d,i))
        if os.path.exists(f):
            os.remove(f)

def testCreate(path):
    s = os.path.join(path, r'src.txt')
    open(s, 'w').close()

    for i in range(10):
        print ('create %d' % (i))

        f = os.path.join(path, r'%s\%03d.txt' % (path,i))

        CreateHardLink(s, f)

        count = 0
        while not os.path.exists(f):
            print ('file not found %d %d' % (i, count))
            time.sleep(0.1)
            count += 1

testCreate(sys.argv[1])
C:\temp>python linkPerformanceTest.py \\Nas01\PJ\tmp
create 0
file not found 0 0
file not found 0 1
file not found 0 2
file not found 0 3
file not found 0 4
file not found 0 5
file not found 0 6

(以下略)

これで不思議なのは、Explorer ではきちんとファイルがあると認識されていることなんですね。どうやったら正確な情報を得られるのか。。。謎です。

※。。。と書いている途中で、もしかしたらAPI 経由で取ればいけるのかも?と思えてきました。後から追試してみます。

この症状はネットを漁ってみると Windos に限って起こるわけではなく、NFS ベースの Linux システムでもオプションによっては起こるようです。ただ、各種対応策を取っても解決できなかったのでちょっと特殊な理由なのかもしれません。

こういうの、地味に困るんですよね、、、ある程度時間が経ってから確認するときちんと動くのに、ちょっとシビアなタイミング(テスト環境では4.5秒以内)でアクセスすると発動する地雷なのです。トホホホホ。。。。。

Programming

ネットワーク上のファイルのハードリンクのカウントは正確に取得できないことがあるみたいだよ!!

NTFSで一つのファイルに張れるハードリンクは1023個までだよ!! の続き記事です。

1023 個って数字な時点で SAN 値ダダ下がり、もう一歩でソウルジェム真っ黒!!ってところまで来てたものの、自分の用途ならば限界超えてリンクを張るなら一つファイルをコピーしてハードリンク先を半分そっちに割り振ればいいやと思っていたので、そのための検証をしていたらここでもドハマりしたのでこれ以上不幸な(ry

UN*X の場合、ファイルにハードリンクされた数は stat で取得できます。例えば、Python のドキュメントだとこのあたり。ところが、こいつは Windows では正しく動きません。 OKOK。これは想定内。これくらいで動じてたらソウルジェムが幾つあっても足りない。

Windows にはこれらの情報を得るための API 、GetFileInformationByHandle() があります。ハードリンク数を取得するのはこちらが正解。これを使ってリンク数を取得するテストプログラムは以下のとおり

import os
import sys
import stat
import ctypes

import win32file

def CreateHardLink(src, dest):
    CreateHardLinkW = ctypes.windll.kernel32.CreateHardLinkW
    CreateHardLinkW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int]

    ret = CreateHardLinkW(dest, src, 0)
    if ret ==0:
        print ctypes.FormatError()
        print src
        print dest

        raise

    return ret

def getNumHardLink(fileName):
    hFile = win32file.CreateFile (
        fileName,
        win32file.GENERIC_READ,
        win32file.FILE_SHARE_READ,
        None,
        win32file.OPEN_EXISTING,
        0,
        None
        )

    (attributes,
     created_at, accessed_at, written_at,
     volume,
     file_hi, file_lo,
     n_links,
     index_hi, index_lo
     ) = win32file.GetFileInformationByHandle (hFile)

    hFile.Close()

    return n_links

d = sys.argv[1]
print d
src = os.path.join(d, 'src.txt')
dest = os.path.join(d, 'dest.txt')

if not os.path.exists(src):
    open(src, 'w').close()

CreateHardLink(src, dest)

s = os.stat(src)
if s.st_nlink == 2:
    print 'OK by stat'
else:
    print ('invalid link count by stat : %d' % s.st_nlink)

n_links = getNumHardLink(src)
if n_links == 2:
    print 'OK by API'
else:
    print ('invalid link count by API : %d' % n_links)

比較のために stat でも情報を拾っています。

C:\temp>python countLink.py c:\temp
c:\temp
invalid link count by stat : 0
OK by API

ちゃんと動いています。

だがしかし!!ここで一筋縄でいかないのが Windows 様。ネットワーク上のファイルシステムを指定してみると。。。

C:\temp>python countLink.py \\nas01\PJ\test
\\nas01\PJ\test
invalid link count by stat : 0
invalid link count by API : 1

え。。。。

え。。。。。

はい、ちゃんと値が取れません。ちなみに、これもサーバーによるらしく某所ではこの症状が起きてドハマりしました。
ちなみに Windows7+QNAP(Linux) ではきちんと動いているっぽいです。

たまたま外れのサーバーだったのか、それとも Windows ベースのものだったからなのか。。。どなたか他の環境で検証していただけないでしょうか。
この数字が取れないと本当に困るんですけど。。。。

これ以外でハードリンクされているかどうかを確認するには、先ほどの GetFileInformationByHandle() でファイルの ID が取れるので、
それを比較するしかなさそうな予感がします。。。。

Programming

NTFSで一つのファイルに張れるハードリンクは1023個までだよ!!

こんなところにこんな罠があるとは夢にも思わず、ガッツリやられたのでこれ以上不幸な人を生み出さないためにメモ。

Windows の NTFS で作るハードリンクは、一つのファイルに対して 1023 個が MAX のようです(!!)
恐ろしいことに、これ、日本語で書かれたドキュメントが見つからないんですね、、、みんなそこまで使ってないんでしょうか?

一番詳しく書かれているっぽいのが英語の Wikipedia。

Hard link

The maximum number of hard links to a single file is limited by the size of the reference counter: with NTFS this is limited to 1023 because a 10 bit field is used for this purpose.

NTFSでは、ハードリンクを管理するための領域として 10bit しか確保してないから 1023 個しかリンクできないよ♪ってことらしいです。おおう、じーざす!!

本当かどうか、テストしてみました。

import os
import ctypes

def CreateHardLink(src, dest):
    CreateHardLinkW = ctypes.windll.kernel32.CreateHardLinkW
    CreateHardLinkW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int]

    ret = CreateHardLinkW(dest, src, 0)
    if ret ==0:
        print ctypes.FormatError()
        print src
        print dest

        raise

    return ret

src = r'c:\temp\src.txt'
if not os.path.exists(src):
    open(src, 'w').close()

for i in range(2048):
    print i
    CreateHardLink(src, os.path.join(r'c:\temp', '%04d.txt' % i))

C:\temp>python testLink.py
0
1
2
3
4
5
6
7
8
9
10

(中略)

1020
1021
1022
1023
ファイル システムでサポートされている以上の数のリンクをファイルに作成しようとしました。
c:\temp\src.txt
c:\temp\1023.txt
Traceback (most recent call last):
File “testLink.py”, line 24, in
CreateHardLink(src, os.path.join(r’c:\temp’, ‘%04d.txt’ % i))
File “testLink.py”, line 14, in CreateHardLink
raise
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType

C:\temp>

本当でした。。。

ちなみに、この制限は NTFS によるものなので Linux ベースのファイルサーバーに対して同様なことをしてもエラーにならないみたいです。
私の環境(Windows7+QNAP)では、2048 個のリンクを張ることもできました。

Windows でハードリンクを張るっていうことがかなり少ないのでハマることはあまりないのかもしれませんが、こういうことが想定されるシステムではちゃんとした OS で動いているファイルサーバーを用意した方がいいかもしれません。

Programming

Sphinx+翻訳 Hack-a-thon 2012.07

今回のお題は、Google site のドキュメントを MoinMoin に移行しようZE☆

html2rest

最初のアプローチは、html2rest を使って直接変換する方法→玉砕

  • ヘッダとかメニューとかフッタとか、余計なものまで変換されて鬱陶しい
  • そもそもいろいろ崩れる!!

Copy&Paste

Google Site の編集モードでコピー、MoinMoin の GUI モードでペースト
→結構いける。文章部分はほぼ完璧。画像も来るけど、元のサイトへのリンクになる

画像とリンクの差し替え

該当ページのディレクトリの attachments 以下に直接画像ファイルをコピー、MoinMoin にペーストしたソースの、リンクを添付ファイルへのリンクに差し替え
差し替え用のスクリプトはこんな感じ。


import sys
import re

fp = open(sys.argv[1])
lines = fp.readlines()
fp.close()

fp = open(sys.argv[1]+'_clean', 'w')

pat = re.compile('https://.*/([^/\?].*)\?.*\}\}')

for line in lines:
    line = line.rstrip()
    while True:
        #replace image
        if not re.search('\[\[([^\]]*)\]\]', line):
            break

        #replace image
        m = re.search('(\[\[https://[^[]*/([^/\?][^\[]*)\?[^\[]*\}\}\]\])', line)
        if m != None:
            print m.group(1), ' ==> ', m.group(2)
            line = line.replace(m.group(1), '{{attachment:'+m.group(2)+'}}')
            continue

        #replace internal link
        m = re.search('(\[\[(.*)\|([^\]]+)\s*]*\]\])', line)
        if m != None:
            if re.search('https://sites.google.com/a', m.group(2)):
                #Inter Wiki
                #print m.group(1), ' =|> ', m.group(3), m.group(3)
                line = line.replace(m.group(1), '_SB_'+m.group(3)+'|'+m.group(3)+'_EB_')
            else:
                #external link
                #print m.group(1), ' =|> ', m.group(2), m.group(3)
                line = line.replace(m.group(1), '_SB_'+m.group(2)+'|'+m.group(3)+'_EB_')

    fp.write(line.replace('_SB_', '[[').replace('_EB_', ']]')+'\n')

fp.close()

このスクリプトを通すことで、以下の処理を行う

  • アタッチされた画像を参照するようにする
  • InterWikiName を変換
  • 外部ページへのリンクを MoinMoin 形式に変換

途中で Copy&Paste が入るので自動化は難しいものの、いずれにしても変換後のチェックはしなければいけないのでまあいいかなぁと。

Programming, 雑記

GCS2012でお話をさせていただきます

@FumotoKZ さんのお誘いで、 GCS2012Technical Artist Jam でお話をさせていただくことになりました。

当日までにでっちあげた日々成長中のシステムの一端を披露できるよう、夜も寝ないで昼間寝て準備を進めていきます。今の予定では、「究極のバッチフレームワーク(予定)」と称して、自動処理マニアが半分実益、半分趣味でやるとこうなる!!という実例をお見せできればなーと考えています。ぜひご参加ください 🙂