mieki256's diary



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にコピーして実行してみたところ、以下のエラーが表示されて実行できず。
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ループ再生をするスクリプト。
#!/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
が入れてある。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を使った学習等には向いているような気もしたり。一々コンパイルをしないで済むし。

ただ、速度その他の面では、微妙…。

以上、1 日分です。

過去ログ表示

Prev - 2013/05 - Next
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project