描画した図形を操作する(5)
PythonのOpenGLライブラリで、図形の描画を行う練習をしている。とりあえずは、以下のプロセスに沿って、いろいろと機能を試して練習を進めていく。
- なんでもいいので3次元の図形を描画する(practice1.py)
- 描いた図形をマウスで回転できるようにする(practice2mod.py)
- 表面が連続した四角形からなる物体を描画する(practice3.py)
- それをマウスで回転できるようにする(practice3.py)
- wxPythonでGUIを作成し、図形を描画できるようにする <--今、ここ。
- 描いた物体をマウスで平行移動したり、拡大縮小したりする
- その他
今回は、http://d.hatena.ne.jp/Megumi221/20110408 で作成したpractice3.pyをwxPythonと組み合わせて使えるようにする。wxPythonでOpenGLを使うには、wx.glcanvasモジュールを利用する。wxPython demoのGLCanvas.pyに使用方法のデモがあるが、この場合、コールバック関数はglutDisplayFuncやglutMotionFuncで登録するのではなく、Bindを使うのが自然なようだ。
そこで、前回作成したpractice3.pyをGLCanvas.pyに基づいて、wxPython用に改良する。以下、改良したコードpractice4.py。
# practice4.py import wx import sys import math from wx import glcanvas from OpenGL.GL import * from OpenGL.GLUT import * ESCAPE = 27 class ButtonPanel(wx.Panel): #ボタンを置くだけのパネル。OpenGLとは関係ない def __init__(self, parent, log): wx.Panel.__init__(self, parent, -1) self.log = log box = wx.BoxSizer(wx.VERTICAL) box.Add((20, 30)) btn = wx.Button(self, 0, 'My') box.Add(btn, 0, wx.ALIGN_CENTER|wx.ALL, 15) self.Bind(wx.EVT_BUTTON, self.OnButton, btn) self.SetAutoLayout(True) self.SetSizer(box) def OnButton(self, evt): #このボタンを押すと描画画面が開く canvasClass = eval('MyCanvas') frame = wx.Frame(None, -1, 'OpenGL', size=(400,400)) canvas = canvasClass(frame) frame.Show(True) class MyCanvasBase(glcanvas.GLCanvas): def __init__(self, parent): glcanvas.GLCanvas.__init__(self, parent, -1) self.init = False self.lastx = self.x = 30 self.lasty = self.y = 30 self.size = None # いろいろなイベント self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp) self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseDown) self.Bind(wx.EVT_RIGHT_UP, self.OnMouseUp) self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMouseDown) self.Bind(wx.EVT_MIDDLE_UP, self.OnMouseUp) self.Bind(wx.EVT_MOTION, self.OnMouseMotion) self.Bind(wx.EVT_CHAR, self.OnKeyboard) #キーが押されたとき # self.Bind(wx.EVT_IDLE, self.OnIdle) # self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) def OnEraseBackground(self, event): pass # Do nothing, to avoid flashing on MSW (これがないとチラつくらしい) def OnSize(self, event): size = self.size = self.GetClientSize() if self.GetContext(): self.SetCurrent() glViewport(0, 0, size.width, size.height) event.Skip() def OnPaint(self, event): dc = wx.PaintDC(self) self.SetCurrent() if not self.init: self.InitGL() self.init = True self.OnDraw() def OnMouseDown(self, evt): self.CaptureMouse() self.x, self.y = self.lastx, self.lasty = evt.GetPosition() def OnMouseUp(self, evt): self.ReleaseMouse() def OnMouseMotion(self, evt): if evt.Dragging() and evt.LeftIsDown(): self.lastx, self.lasty = self.x, self.y self.x, self.y = evt.GetPosition() self.Refresh(False) def OnMouseWheel(self, evt): #今後の改良のための準備(まだ不完全) if evt.GetWheelRotation() > 0: self.zooming = self.zooming * 0.9 else: self.zooming = self.zooming * 1.1 def OnKeyboard(self, evt): #ESCAPEキーで終了する if evt.KeyCode == ESCAPE: sys.exit() class MyCanvas(MyCanvasBase): #OpenGLによる描画を行う def InitGL(self): glClearDepth(1.0) glEnable(GL_DEPTH_TEST) glClearColor(0.0, 0.5, 0.0, 0.0) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glFrustum(-0.5, 0.5, -0.5, 0.5, 1.0, 3.0) glMatrixMode(GL_MODELVIEW) glTranslatef(0.0, 0.0, -2.0) glRotatef(self.y, 1.0, 0.0, 0.0) glRotatef(self.x, 0.0, 1.0, 0.0) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE) glEnable(GL_COLOR_MATERIAL) self.vpos = list() self.readpos() self.displaylist() def readpos(self): fp = open('coordpos.txt', 'r') num = int(fp.readline()) poss = fp.read().splitlines() for i in xrange(num): self.vpos.append([float(x) for x in poss[i].split(',')]) fp.close() def crossproduct(self, a, b, c): cross = [0.0, 0.0, 0.0] cross[0] = (a[1]-b[1])*(c[2]-b[2]) - (a[2]-b[2])*(c[1]-b[1]) cross[1] = (a[2]-b[2])*(c[0]-b[0]) - (a[0]-b[0])*(c[2]-b[2]) cross[2] = (a[0]-b[0])*(c[1]-b[1]) - (a[1]-b[1])*(c[0]-b[0]) cabs = math.sqrt(cross[0]**2+cross[1]**2+cross[2]**2) cc = [x/cabs for x in cross] return cc def displaylist(self): ps = self.vpos self.index = glGenLists(1) glNewList(self.index, GL_COMPILE_AND_EXECUTE) glBegin(GL_QUADS) glNormal3fv(self.crossproduct(ps[4],ps[1],ps[0])) glVertex3fv(ps[0]) glVertex3fv(ps[1]) glVertex3fv(ps[4]) glVertex3fv(ps[3]) glNormal3fv(self.crossproduct(ps[5],ps[2],ps[1])) glVertex3fv(ps[1]) glVertex3fv(ps[2]) glVertex3fv(ps[5]) glVertex3fv(ps[4]) # 以下、続けて頂点を指定する。 # (中略) glEnd() glEndList() def OnDraw(self): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glPushMatrix() glCallList(self.index) glPopMatrix() if self.size is None: self.size = self.GetClientSize() w, h = self.size w = max(w, 1.0) h = max(h, 1.0) xScale = 180.0 / w yScale = 180.0 / h glRotatef((self.y - self.lasty) * yScale, 1.0, 0.0, 0.0); glRotatef((self.x - self.lastx) * xScale, 0.0, 1.0, 0.0); self.SwapBuffers() def runTest(frame, nb, log): win = ButtonPanel(nb, log) return win if __name__ == '__main__': import sys,os import run run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
実行方法は、wxPython demoのrun.pyを使って、
$ python run.py practice4.py
とする。
起動画面。
描画画面を開いたところ。現状でできることは、マウスでの回転操作、エスケープキ―を押したときに終了する、ことぐらい。
- 今後の課題
- 定義されているイベントの種類を検討