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
[ ツッコむ ]
#2 [prog] pygame+PyOpenGLのスクリプトを別PCで動かしてみたのだけど
録画用PCに持っていって動作確認したものの。なんだか微妙な結果に。描画内容その他、PCのスペックは、先日試していたスクリプトと同じ。
そしてもちろん、Python + PyOpenGL 等には、環境さえ整っていれば WindowsでもMacでもLinuxでもどれでもヌルヌル動くスクリプトがLLの気楽さで書ける、という大きなメリットがあるわけで。OpenGLを使った学習等には向いているような気もしたり。一々コンパイルをしないで済むし。
ただ、速度その他の面では、微妙…。
- pygame(= SDL)で描画した場合は、20FPSしか出なかったけど、PyOpenGL を使ったら58FPS前後出た。
- 64x64ドット x 512枚の描画をスキップすると、60FPSを少し超えた表示がチラチラ見えるので、60FPSに間に合ってない可能性がありそう。
- フルスクリーン表示をしたら、ウインドウ枠がずっと残り続けたり、画面上部だけが黒くなったりした。
- exe化したファイルを配布しようとすると、3,215ファイル、108フォルダを渡すことに。
- PyOpenGL は pygame (= SDL)より圧倒的に速い。
- けれど、HSP や DXRuby よりはちょっと遅い。
- ソースも長くなりがち。
- 環境によって、フルスクリーン表示に不具合が出る。
- 配布ファイル数、多過ぎ。
そしてもちろん、Python + PyOpenGL 等には、環境さえ整っていれば WindowsでもMacでもLinuxでもどれでもヌルヌル動くスクリプトがLLの気楽さで書ける、という大きなメリットがあるわけで。OpenGLを使った学習等には向いているような気もしたり。一々コンパイルをしないで済むし。
ただ、速度その他の面では、微妙…。
[ ツッコむ ]
以上、1 日分です。