Line drawing function/jp

このページでは高度な機能を簡単にPythonで構築する方法を説明しています。このエクササイズではラインを描画する新しいツールを作ることにします。このツールはFreeCADコマンドに関連付けすることが可能で、そのコマンドはメニューアイテムやツールバーボタンなどのインターフェイスの任意の要素から呼び出すことができます。

メインスクリプト

まず必要な機能を全て持つスクリプトを書きましょう。それをファイルに保存してFreeCADでインポートすると書いたクラスと関数の全てをFreeCADで利用できるようになります。あなたのおこのみのテキストエディタを起動して次の様に入力してください:

import FreeCADGui, Part
from pivy.coin import *

class line:
    "this class will create a line after the user clicked 2 points on the screen"
    def __init__(self):
        self.view = FreeCADGui.ActiveDocument.ActiveView
        self.stack = []
        self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)  

    def getpoint(self,event_cb):
        event = event_cb.getEvent()
        if event.getState() == SoMouseButtonEvent.DOWN:
            pos = event.getPosition()
            point = self.view.getPoint(pos[0],pos[1])
            self.stack.append(point)
            if len(self.stack) == 2:
                l = Part.Line(self.stack[0],self.stack[1])
                shape = l.toShape()
                Part.show(shape)
                self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)

詳しい説明

import Part, FreeCADGui
from pivy.coin import *

Pythonでは別のモジュールの関数を使いたい場合にはそれをインポートする必要があります。今回はラインを作るためのPartモジュールの関数と3DビューにアクセスするためのGUIモジュール(FreeCADGui)の関数が必要になります。またSoMouseButtonEventなどのCoinオブジェクト全てを直接使用するのでCoinライブラリの全機能も必要になります。

class line:

ここでメインクラスを定義しています。なぜ関数でなくクラスを使うのでしょうか?それは私たちのツールはユーザーが画面をクリックするの待つ間、"生きて"いなければならないからです。関数はその処理が終わると終了してしまいますがオブジェクト(クラスによってオブジェクトが定義されます)は破棄されるまで生き続けます。

"this class will create a line after the user clicked 2 points on the screen"

Pythonでは全てのクラス、関数に説明文を設定することができます。これはFreeCADでは特に便利です。クラスをインタプリタで呼ぶとその説明文がツールチップ表示されるからです。

def __init__(self):

Pythonのクラスはいつでも__init__を持つことができます。__init__はオブジェクトを作成するためにクラスが呼ばれた時に実行されます。従って私たちのラインツールが開始された時にやりたいことは全てここに書きます。

self.view = FreeCADGui.ActiveDocument.ActiveView

クラスの中では通常は変数名の前にself.と付けた方がいいでしょう。そうすることでクラスの内外の全関数がわかりやすくなります。ここではself.viewを使ってアクティブな3Dビューへのアクセスと操作を行います。

self.stack = []

ここではgetpoint関数によって送信される3D上の点を入れる空のリストを作成しています。

self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)

重要な部分です。実際のCoin3Dシーンだからです。FreeCADは特定のシーンイベントが起きるたびに関数を呼び出すためのCoinのコールバックの仕組みを使っています。今回はSoMouseButtonイベント用のコールバックを作成し、それをgetpoint関数と関連付けています。これによってマウスボタンが押されたり離されたりするたびにgetpoint関数が実行されます。

またaddEventCallbackPivy()の代わりにaddEventCallback()を呼べばpivyを利用しなくとも済みます。ただしpivyはCoinシーンの任意の部分にアクセスする非常に効率的で自然な方法なので、できるだけ使った方がいいでしょう!

def getpoint(self,event_cb):

次に3Dビューでマウスボタンが押されるたびに実行されるgetpoint関数を定義します。この関数はevent_cbと呼ばれる引数を受け取ります。このイベントコールバックから複数の情報が格納されているイベントオブジェクトにアクセスすることが可能です(モード情報はこちら)。

if event.getState() == SoMouseButtonEvent.DOWN:

getpoint関数はマウスボタンが押されるか離される時に呼ばれます。しかし私たちは押された時にだけ3D上の点をピックしたいのです(そうしないと互いに非常に近い二つの3D上の点を取得してしまうことになります)。そこでそれをチェックしなければなりません。

pos = event.getPosition()

ここではマウスカーソルのスクリーン座標を取得しています。

point = self.view.getPoint(pos[0],pos[1])

この関数はマウスカーソル下の焦点面の上にある3D上の点が格納されたFreeCADのVector (x,y,z)を返します。カメラ視点で言うとカメラから伸びる光線とマウスカーソル位置を通過し焦点面にぶつかるところを想像してください。それが取得される3D上の点です。正射影表示の場合にはその光線は視線方向と平行になります。

self.stack.append(point)

新しい点をスタックに追加しています。

if len(self.stack) == 2:

必要なだけの点はありますか?もしあればラインを描画しましょう!

l = Part.Line(self.stack[0],self.stack[1])

ここではFreeCADのVectro二つからラインを作成する関数であるPartモジュールのLine()関数を使用しています。Partモジュールの中で作ったり変更したりしたものは全てPartモジュールが保持します。ここまでで私たちはラインパートを作成しました。しかしまだアクティブなドキュメント上のオブジェクトに結び付けられていないので画面上には何も表示されません。

shape = l.toShape()

FreeCADドキュメントはPartモジュールから作成されたシェイプだけを受け取ることができます。シェイプはPartモジュールの最も一般的な型です。従って私たちはドキュメントに追加する前にラインをシェイプに変換しなければなりません。

Part.show(shape)

Partモジュールには非常に便利なshow()関数があり、それを使ってドキュメントに新しいオブジェクトを作ってそこにシェイプを結びつけることができます。あるいはまずドキュメントに新しいオブジェクトを作った後、手作業でシェイプを結びつけることもできます。

self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)

ラインを使った作業が終わったので貴重なCPUサイクルを消費してしまうコールバックのための仕組みを取り除きましょう。

スクリプトのテストと使用

さて、それではスクリプトをFreeCADのPythonインタプリタが見える場所に保存しましょう。モジュールインポート時にインタプリタは次の場所を検索します:Pythonのインストールパス、FreeCADのbinディレクトリ、FreeCADモジュールディレクトリの全て。最もいい方法はFreeCADのModディレクトリの一つに新しいディレクトリを作成してそこにスクリプトを保存するというものです。例えば"MyScripts"ディレクトリをを作成してスクリプトを"exercise.py"として保存してみましょう。

これで準備万端です。FreeCADを起動して新しいドキュメントを作りPythonインタプリタで次のコマンドを実行しましょう:

import exercise

エラーメッセージが表示されなければ私たちの練習用スクリプトがロードされているはずです。次の様にしてその内容を調べて見ることができます:

dir(exercise)

dir()コマンドは組み込みのPythonコマンドでモジュールの内容をリスト表示することができます。私たちのline()クラスが待機しているのが確認できるはずです。それではテストしてみましょう:

exercise.line()

さらに3Dビューを二回クリックしてみると・・・ビンゴ。ラインが表示されます!もう一度行うには再度exercise.line()と入力します。何度でも繰り返せます・・・すごいと思いませんか?

FreeCADインターフェイスでのスクリプトの登録

さて私たちの新しいラインツールをもっとクールにするためにはインターフェイス上にボタンを付けるべきです。そうすれば毎回、キー入力を行う必要がなくなります。最も簡単な方法は新しく作ったMyScriptsディレクトリをちゃんとしたFreeCADワークベンチに作り変えるというものです。簡単にできます。必要なのはInitGui.pyというファイルをMyScriptsディレクトリの中に置くことだけです。InitGui.pyには新しいワークベンチを作り、そこに私たちの新しいツールを追加しろ、という命令を書きます。それから私たちの練習用コードをほんの少しだけ書き換えてline() ツールが公式なFreeCADコマンドとして認識されるようにする必要もあります。それではInitGui.pyファイルの作成からはじめましょう。次のコードをInitGui.pyファイルに書き込んでください:

class MyWorkbench (Workbench): 
   MenuText = "MyScripts"
   def Initialize(self):
       import exercise
       commandslist = ["line"]
       self.appendToolbar("My Scripts",commandslist)
Gui.addWorkbench(MyWorkbench())

もうあなたも上記のスクリプトが理解できるのではないかと思います。MyWorkbenchという名前の新しいクラスを作成してそこにタイトル(MenuText)を指定し、ワークベンチがFreeCADに読み込まれた時に実行されるInitialize()関数を定義しています。またその関数の中で私たちの練習用ファイルの内容を読み込み、内部で見つけたFreeCADコマンドをコマンドリストに追加します。それが終わったら"My Scripts"という名前のツールバーを作成し、コマンドリストをそこに代入するのです。もちろん今のところはツールは一つだけなので、コマンドリストに入っている要素は一つだけです。ワークベンチの用意が整ったらそれをメインインターフェイスに追加します。

ただしまだ動作を行うことはできません。FreeCADコマンドを動作されるためには特定のやり方でフォーマットする必要があるからです。つまり私たちのline()ツールを少しだけ変更する必要があるのです。新しいexercise.pyは次のようになります:

import FreeCADGui, Part
from pivy.coin import *
class line:
 "this class will create a line after the user clicked 2 points on the screen"
 def Activated(self):
   self.view = FreeCADGui.ActiveDocument.ActiveView
   self.stack = []
   self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint) 
 def getpoint(self,event_cb):
   event = event_cb.getEvent()
   if event.getState() == SoMouseButtonEvent.DOWN:
     pos = event.getPosition()
     point = self.view.getPoint(pos[0],pos[1])
     self.stack.append(point)
     if len(self.stack) == 2:
       l = Part.Line(self.stack[0],self.stack[1])
       shape = l.toShape()
       Part.show(shape)
       self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)
 def GetResources(self): 
     return {'Pixmap' : 'path_to_an_icon/line_icon.png', 'MenuText': 'Line', 'ToolTip': 'Creates a line by clicking 2 points on the screen'} 
FreeCADGui.addCommand('line', line())

ここでは__init__()関数をActivated()関数に変更しています。FreeCADコマンドが実行されるときには自動でActivated()関数が実行されるためです。またGetResources()を追加しています。この関数はFreeCADにどこにツールのアイコンがあるのかとその名前、そして私たちのツールのツールチップを通知します。アイコンには任意のサイズのjpg、png、svgの画像が使用できますが最終的な外観に近い16x16、24x24、32x32といったサイズを使用するのが最も好ましいです。 最後にaddCommand()メソッドを使ってline()クラスを公式なFreeCADコマンドとして追加しています。

これで終わりです。FreeCADを再起動すれば真新しいラインツールのついたすてきな新しいワークベンチが使えます!

もっと知りたいですか?

このエクササイズが気にいったらこの小さなツールを改良してみましょう。できることはたくさんあります。例えば次のようなものがあります:

疑問やアイデアがあれば遠慮せずにforumに書き込んでください!


Online version: "http://www.freecadweb.org/wiki/index.php?title=Line_drawing_function/jp&oldid=211455"

Navigation menu