2013/05/09(木) [n年前の日記]
#1 [prog] PyOpenGLを使ったスクリプトをpy2exeでexe化しようとしてハマる
環境は、Windows7 x64 + Python 2.6.6。
pygameだけを使ったスクリプトなら、すんなりexe化できたのだけど。PyOpenGLも使ったスクリプトをexe化したら、実行時に win32 だの OpenGL.GL がimport できないとエラーが表示されてしまって。
解決方法が載ってそうなページを検索。
_PyOpenGL - py2exe.org
_HOWTO: freeze PyOpenGL programs with py2exe - Jef Mangelschots
英語なので、何を書いてあるのかよくわかりません…が、おそらく、
_備忘録: windows7(32bit) pyOpenGLの実行ファイルをpy2exeで作成する に、解決策が全部記述されてました。ありがたや…。同じように作業したら、たしかにexe化できました。
しかし、dist/ フォルダの容量が50MBぐらいになってしまって、なんというか…。自分の場合、OpenGL フォルダだけコピーして、他のフォルダ・ファイルはコピーしなくても動いたけれど、それでも OpenGL フォルダが大き過ぎて…。プロパティを見たら 3,215ファイル、108フォルダ…。さすがに多過ぎ…。コレ、まとめる方法はないのかな…。
_PyOpenGL - py2exe.org には、「C:\python\Lib\ 以下からコピーしてきたファイルについては任意の .pyc と .pyo を残せる」と書いてあるようにも見えるので、試しに .py を削除してみたのだけど。.py を削除すると、その後しばらくして .py と対応していた .pyc が消滅しちゃう。何故? 何がそのへんを監視してるんだ? .pyc ファイルって、何なんだろう…。調べないと…。
できあがった dist/ 以下を別PCにコピーして実行してみたところ、以下のエラーが表示されて実行できず。
メインPC上で which glut32.dll をしてみたら、"C:\Gtk+\bin\glut32.dll" が表示された。お前…そんなところにあった glut32.dll を使ってたのか…。ファイルの日付を確認したら、 _Nate Robins - OpenGL - GLUT for Win32 で公開されてる glut-3.7.6-bin.zip 内の glut32.dll と同じだった。
スクリプトが入ってるフォルダにも glut32.dll をコピー、かつ、setup.py 内で、dist/以下にも glut32.dll をコピーするように指定。そうしてできた .exe + 大量のファイル群を別PCに持っていったところ、今度は実行することができた。
pygameだけを使ったスクリプトなら、すんなりexe化できたのだけど。PyOpenGLも使ったスクリプトをexe化したら、実行時に win32 だの OpenGL.GL がimport できないとエラーが表示されてしまって。
解決方法が載ってそうなページを検索。
_PyOpenGL - py2exe.org
_HOWTO: freeze PyOpenGL programs with py2exe - Jef Mangelschots
英語なので、何を書いてあるのかよくわかりません…が、おそらく、
- スクリプトの頭にいくつか記述をして、
- setup.py 中で OpenGL 関係は別ファイルで扱うように指定して、
- Pythonインストールフォルダ中にある OpenGL関係のフォルダその他を exe の入ってるフォルダにコピーしなさい、
_備忘録: windows7(32bit) pyOpenGLの実行ファイルをpy2exeで作成する に、解決策が全部記述されてました。ありがたや…。同じように作業したら、たしかにexe化できました。
しかし、dist/ フォルダの容量が50MBぐらいになってしまって、なんというか…。自分の場合、OpenGL フォルダだけコピーして、他のフォルダ・ファイルはコピーしなくても動いたけれど、それでも OpenGL フォルダが大き過ぎて…。プロパティを見たら 3,215ファイル、108フォルダ…。さすがに多過ぎ…。コレ、まとめる方法はないのかな…。
_PyOpenGL - py2exe.org には、「C:\python\Lib\ 以下からコピーしてきたファイルについては任意の .pyc と .pyo を残せる」と書いてあるようにも見えるので、試しに .py を削除してみたのだけど。.py を削除すると、その後しばらくして .py と対応していた .pyc が消滅しちゃう。何故? 何がそのへんを監視してるんだ? .pyc ファイルって、何なんだろう…。調べないと…。
できあがった dist/ 以下を別PCにコピーして実行してみたところ、以下のエラーが表示されて実行できず。
OpenGL.error.NullFunctionError: Attempt to call an undefined function glutBitmap Character, check for bool(glutBitmapCharacter) before callingビットマップフォント用の何かがどうのこうのと言われてるっぽいけど…。もしかして、glut32.dll が無い?
メインPC上で which glut32.dll をしてみたら、"C:\Gtk+\bin\glut32.dll" が表示された。お前…そんなところにあった glut32.dll を使ってたのか…。ファイルの日付を確認したら、 _Nate Robins - OpenGL - GLUT for Win32 で公開されてる glut-3.7.6-bin.zip 内の glut32.dll と同じだった。
スクリプトが入ってるフォルダにも glut32.dll をコピー、かつ、setup.py 内で、dist/以下にも glut32.dll をコピーするように指定。そうしてできた .exe + 大量のファイル群を別PCに持っていったところ、今度は実行することができた。
◎ 一応スクリプトその他をメモ。 :
fullscreenopengl.py。pygame + PyOpenGLで、BG2枚 + 64x64ドット512枚を描画 + oggループ再生をするスクリプト。
setup.py。py2exeに渡す設定が書いてある。
スクリプトと同じフォルダに、
mkexe.bat。exeを作成するためのバッチファイル。py2exeを呼び出している。
#!/usr/bin/env python # -*- coding: utf-8 -*- u""" pygame + PyOpenGLでスプライトの表示テスト """ import pygame from pygame.locals import * import os import sys sys.path += ['.'] # if hasattr(sys, "frozen"): # if sys.frozen == "windows_exe": # main_dir = os.path.dirname( # unicode(sys.executable, sys.getfilesystemencoding()) # ) # sys.path.append(main_dir) # os.chdir(main_dir) # else: # sys.path.insert(0, ".") import math # import OpenGL.arrays.ctypesarrays # import OpenGL.arrays.ctypesparameters # import OpenGL.arrays.ctypespointers # import OpenGL.arrays.lists # import OpenGL.arrays.nones # import OpenGL.arrays.numbers # import OpenGL.arrays.strings # import OpenGL.platform.win32 # import OpenGL.raw.GL # import dummy.Process # import email.Generator # import email.Iterators # import numpy import OpenGL.GL import OpenGL.GLU import OpenGL.GLUT from OpenGL.GL import * from OpenGL.GLUT import * from OpenGL.GLU import * from OpenGL.extensions import alternate from OpenGL.GL.ARB.point_parameters import * from OpenGL.GL.EXT.point_parameters import * from ctypes import util try: from OpenGL.platform import win32 except AttributeError: pass SCR_RECT = Rect(0, 0, 640, 480) tex = [] # 頂点配列を作成 vertices = [ [ # ufo.png用 0, 64, 0, # 左下 64, 64, 0, # 右下 64, 0, 0, # 右上 0, 0, 0 # 左上 ], [ # BG用 0, 512, 0, # 左下 512, 512, 0, # 右下 512, 0, 0, # 右上 0, 0, 0 # 左上 ] ] # テクスチャ座標配列を作成 texcoords = [0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0] class MySprite: u"""スプライト管理用クラス""" def __init__(self, x, y, vx, vy, width, height): self.w = width self.h = height self.whf = self.w / 2 self.hhf = self.h / 2 self.fpx = float(x) self.fpy = float(y) self.fpvx = float(vx) self.fpvy = float(vy) self.rect = Rect(int(self.fpx - self.whf), int(self.fpy - self.hhf), self.w, self.h) def update(self): self.fpx += self.fpvx self.fpy += self.fpvy self.rect = Rect(int(self.fpx - self.whf), int(self.fpy - self.hhf), self.w, self.h) if self.rect.left < 0 or self.rect.right > SCR_RECT.width: self.fpvx *= -1 if self.rect.top < 0 or self.rect.bottom > SCR_RECT.height: self.fpvy *= -1 def draw_bg(bx, by, texid): u"""BGを1枚分描画""" # 使うテクスチャを選択 glBindTexture(GL_TEXTURE_2D, tex[texid]) # 頂点配列、テクスチャ座標配列、カラー配列を指定 glVertexPointer(3, GL_FLOAT, 0, vertices[1]) glTexCoordPointer(2, GL_FLOAT, 0, texcoords) for ofs in [[0, 0], [512, 0], [0, -512], [512, -512]]: glLoadIdentity() glTranslatef(bx + ofs[0], by + ofs[1], -10.0) # 平行移動 glDrawArrays(GL_QUADS, 0, 4) # 四角形を描画 def init_gl(w, h, fullfg): """OpenGL関係の初期化""" # OPENGL向けに初期化する modev = pygame.OPENGL | pygame.DOUBLEBUF if fullfg: modev |= pygame.FULLSCREEN screen = pygame.display.set_mode((w, h), modev) if not screen: return False glViewport(0, 0, w, h) glClearColor(0.0, 0.0, 0.0, 1.0) # クリア色の設定 # 隠面消去、カリング、照明を設定 glDisable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) glDisable(GL_LIGHTING) glShadeModel(GL_FLAT) # フラットシェーディングを有効化 glEnable(GL_NORMALIZE) # 法線の自動正規化を有効化 # テクスチャの読み込み global tex glEnable(GL_TEXTURE_2D) # テクスチャ有効化 tex.append(loadImage('res/ufo.png')) tex.append(loadImage('res/bg1.png')) tex.append(loadImage('res/bg2.png')) # 座標系の設定 glMatrixMode(GL_PROJECTION) # 射影変換 glLoadIdentity() # 単位行列 # 以後、平行投影で描画するよう指定 glOrtho(0, 640.0, 480.0, 0.0, 0.1, 100.0) return True def loadImage(image_fila_path): """テクスチャ画像をロード""" # pygameを使って画像ロード textureSurface = pygame.image.load(image_fila_path) # 画像読み込み width = textureSurface.get_width() # 横幅取得 height = textureSurface.get_height() # 縦幅取得 # OpenGLに渡すために文字列化 textureData = pygame.image.tostring(textureSurface, "RGBA", False) texture = glGenTextures(1) # テクスチャを1枚生成 glBindTexture(GL_TEXTURE_2D, texture) # テクスチャとして登録 # _S は横方向、_T は縦方向 # テクスチャをリピートさせるかしないか glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) # _MAG_FILTER は拡大時のフィルタ種類, _MIN_ は縮小? glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL) # glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) # テクスチャを設定 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData) return texture def draw_text(x, y, st): """文字列をビットマップフォントで描画""" glRasterPos3f(x, y, -3.0) for s in st: glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ord(s)) def draw_info(cap): """現在の状態をテキスト描画する""" glLoadIdentity() glColor3f(1.0, 1.0, 1.0) lx = 16 ly = 16 lyadd = 20 for s in [cap, "F2: Full/Wdw", "F3: SPR ON/OFF", "ESC,Q: EXIT"]: draw_text(lx, ly, s) ly += lyadd def main(): fullscreen_flag = False pygame.init() pygame.display.set_caption("Full Screen Demo") init_gl(SCR_RECT.width, SCR_RECT.height, fullscreen_flag) # スプライトを作成 pmax = 512 x = SCR_RECT.width / 2 y = SCR_RECT.height / 2 sprgrp = [] for i in range(pmax): rad = math.radians(i * 360 / pmax) vx = 3.0 * math.cos(rad) vy = 3.0 * math.sin(rad) sprgrp.append(MySprite(x, y, vx, vy, 64, 64)) # BGM start pygame.mixer.music.load("res/bgm32.ogg") pygame.mixer.music.play(-1) clock = pygame.time.Clock() cap = "" frame_count = 0 bgx = [0, 0] bgy = [0, 0] sprdraw = True running = True # main loop while running: clock.tick(60) # 60fps # OpenGLバッファのクリア glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glFrontFace(GL_CCW) # 頂点反時計回りを表として扱う glEnable(GL_TEXTURE_2D) # テクスチャを有効に # テクスチャを透過にする glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); # 頂点配列、テクスチャ座標配列の有効化 glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) # BG bgang = math.radians(frame_count) bgx[0] = -64 + (64 * math.cos(bgang)) bgx[1] = -128 + (128 * math.cos(bgang)) bgy[0] = (bgy[0] + 2) % 512 bgy[1] = (bgy[1] + 6) % 512 draw_bg(bgx[0], bgy[0], 1) draw_bg(bgx[1], bgy[1], 2) # スプライト座標更新 for spr in sprgrp: spr.update() # スプライト描画 if sprdraw: # 使うテクスチャを選択 glBindTexture(GL_TEXTURE_2D, tex[0]) # 頂点配列、テクスチャ座標配列、カラー配列を指定 glVertexPointer(3, GL_FLOAT, 0, vertices[0]) glTexCoordPointer(2, GL_FLOAT, 0, texcoords) for spr in sprgrp: glLoadIdentity() glTranslatef(spr.rect.x, spr.rect.y, -10.0) # 平行移動 glDrawArrays(GL_QUADS, 0, 4) # ポリゴンとして描画 glDisable(GL_TEXTURE_2D) # テクスチャを無効に # 頂点配列、テクスチャ座標配列の無効化 glDisableClientState(GL_VERTEX_ARRAY) glDisableClientState(GL_TEXTURE_COORD_ARRAY) # テキスト描画 cap = '%5.2f FPS' % clock.get_fps() draw_info(cap) # pygameダブルバッファ交換 pygame.display.flip() # ウインドウタイトルの文字列を設定 pygame.display.set_caption(cap) # キー入力に対する処理 for event in pygame.event.get(): if event.type == QUIT: running = False if event.type == KEYDOWN: if event.key == K_ESCAPE or event.key == K_q: # ESCキーかqキーで終了 running = False elif event.key == K_F2: # F2キーでフルスクリーンモードへの切り替え fullscreen_flag = not fullscreen_flag init_gl(SCR_RECT.width, SCR_RECT.height, fullscreen_flag) elif event.key == K_F3: # F3キーでスプライト描画のon/off切り替え sprdraw = not sprdraw frame_count = frame_count + 1 pygame.quit() sys.exit() if __name__ == "__main__": main()
setup.py。py2exeに渡す設定が書いてある。
#! c:/Python26/python.exe # -*- coding: utf-8 -*- from distutils.core import setup import py2exe option = { "compressed" : 1, "optimize" : 2, "bundle_files" : 3 } setup( console = [ {"script" : "fullscreenopengl.py"} ], options = { "py2exe": { "includes": ["ctypes", "logging"], "excludes": ["OpenGL"], } }, data_files = [ ('res', ['res/bg1.png', 'res/bg2.png', 'res/ufo.png', 'res/bgm32.ogg', 'res/VeraMoBd.ttf']), ('', ['C:\Python26\Lib\site-packages\pygame\libvorbisfile.dll', 'C:\Python26\Lib\site-packages\pygame\libogg.dll', 'C:\Python26\Lib\site-packages\pygame\libvorbis.dll', 'glut32.dll', ]) ], # zipfile = "lib/library.zip" # zipfile = None )
スクリプトと同じフォルダに、
- glut32.dll
- res/bg1.png
- rse/bg2.png
- res/bgm32.ogg
- res/ufo.png
- res/VeraMoBd.ttf
mkexe.bat。exeを作成するためのバッチファイル。py2exeを呼び出している。
@rem make exe @echo フォルダを削除します rmdir /s build rmdir /s dist @echo dist\fullscreen.exe を作ります python setup.py py2exe pause @echo dist\fullscreenopengl.exe を作ります python setup2.py py2exe pause @echo 必要なファイルをコピーします mkdir .\dist\OpenGL xcopy C:\Python26\Lib\site-packages\OpenGL .\dist\OpenGL /e @rem mkdir .\dist\ctypes @rem xcopy C:\Python26\Lib\ctypes .\dist\ctypes /e @rem copy C:\Python26\Lib .\dist pause
[ ツッコむ ]
以上です。