text_edit_object_with_tkinter.py を Blender 2.62 で動かす
リソース・チュートリアル : 最近の Blender 2.5の Text オブジェクトの日本語対応 Blender.jp
この記事で紹介されているスクリプトを修正し 2.62 で動作するところまでこぎつけました。
動作確認をしたのは Windows XP 32bit、Windows 7 64bit のみです。
Python は 3.2 です。
text_edit_object_with_tkinter_2_62.py
※この記事の最後にスクリプトの全文を記載しています。
インストール
Python のパスがうまく解決できなかったため、とりあえずファイルをコピーして動作するのを確認しています。このあたりは出来れば解決したい……
次のファイルを Blender の python ディレクトリにコピーしてください。
(\Blender Foundation\Blender\2.62\python 以下)
- Python32\DLLs
- Python32\tcl
- Python32\Lib\tkinter
スクリプトは普通にアドオンのインストールの手順でインストールしてください。
アドオンのカテゴリーは Text です。
名称は Edit text object with Tkinter となっていて元のスクリプトから変更しておりません。
使用方法
ウィンドウを表示するには、Text Object を追加し、選択し、Properties -> Object Data を選択し、一番下の Edit Text Object ボタンをクリックしてください。
Load Fonts ボタンの方は何も変更を加えていないので動かないかもしれません。
その場合フォントは適切なフォントを選択しておいてください。
デフォルトのフォントでは日本語が入っていない(?)ため、日本語の文字が表示されません。
ウィンドウを表示したときにランタイムエラーが発生する場合、次のファイルをリネーム等して読み込まれない状態にすればエラーは表示されなくなると思います。
\Blender Foundation\Blender\msvcr90.dll
ウィンドウが表示されている状態でも Blender 上で操作は可能です。
ただし、ウィンドウを表示するときに選択されていたオブジェクトを参照しているので、別の Text Object を選択しテキストを編集しても最初に選択していた Text Object に変更が反映されます。
また、最初に選択していた Text Object を削除した場合正しく動作しません。
ウィンドウ内でテキストを書き換えるとリアルタイムに 3D View 上の Text Object に反映します。
Apply ボタンを押下するか、×ボタンでウィンドウを閉じて編集を完了してください。
Cancel ボタンを押下すると、ウィンドウを立ち上げたときの内容のテキストに差し戻します。
text_edit_object_with_tkinter_2_62.py
# coding: utf-8 # ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { 'name': 'Edit text object with Tkinter', 'author': 'chromoly', 'version': (0, 0, 1), 'blender': (2, 6, 2), 'api': 32385, 'location': 'Properties -> ObjectData', 'description': '', 'warning': '', 'category': 'Text'} ''' 日本語入力の為、TkinterのGUIを呼び出します。GUIを呼び出している間、Blenderは止まります。 参考: http://docs.python.org/py3k/library/tkinter.html Pythonプログラミング入門 他 ''' import os import sys import threading #### 環境に合わせて修正してください ############################################# ## Tkinter path ''' importエラーを吐くようなら、モジュールパスを修正してください。 ''' try: import tkinter as tk except: if sys.platform == 'win32': # Windows sys.path.append('C:\\Python32\\Lib') sys.path.append('C:\\Python32\\DLLs') os.environ['TCL_LIBRARY'] = 'C:\\Python32\\tcl\\tcl8.5' else: # sys.platform == 'linux2' etc... prefix = '/usr' dirname = 'python' + sys.version[:3] # python3.1 sys.path.append(os.path.join(prefix, 'lib', dirname)) sys.path.append(os.path.join(prefix, 'lib', dirname, 'lib-dynload')) ## Load Fonts ''' 'Load Fonts'ボタンで複数のフォントを一度に読み込みます。 ''' if sys.platform == 'win32': # Windows font_paths = ('C:\WINDOWS\Fonts\msgoth04.ttc', 'C:\WINDOWS\Fonts\msmin04.ttc') else: # Linux etc. font_paths = ('/home/hoge/.fonts/meiryo/meiryo.ttc', '/home/hoge/.fonts/meiryo/meiryob.ttc') ## Tkinter GUI ''' FONTNAMEは、利用可能なフォント名を指定してください。 読み込めない場合はデフォルトのフォントが使われます。 ''' FONTNAME = 'Meiryo' FONTSIZE = 11 ############################################################################### import tkinter as tk from tkinter.scrolledtext import ScrolledText from tkinter.filedialog import askopenfilename, asksaveasfilename import bpy from bpy.props import * import time class Scroller(tk.Frame): def __init__(self, master=None, textobj=None): tk.Frame.__init__(self, master) self.pack() self.cached_text = "" # <Title> master.title('Edit TextObject') # <Menu> self.mb = tk.Menu(master, font=(FONTNAME, FONTSIZE)) self.fm = tk.Menu(self.mb, font=(FONTNAME, FONTSIZE), tearoff=0) self.fm.add_command(label='開く', command=self.openFile) self.fm.add_command(label='別名で保存', command=self.saveFile) self.fm.add_separator() self.fm.add_command(label='終了', command=self.quit) # master.quit self.mb.add_cascade(label='ファイル', menu=self.fm) self.em = tk.Menu(self.mb, font=(FONTNAME, FONTSIZE), tearoff=0) self.em.add_command(label='切り取り', command=self.cutSelection) self.em.add_command(label='複製', command=self.copySelection) self.em.add_command(label='貼り付け', command=self.pasteClip) self.mb.add_cascade(label='編集', menu=self.em) master.config(menu=self.mb) # <Shortcut> master.bindだと二回繰り返す事に self.bind('<Control-KeyPress-x>', self.cutSelection) self.bind('<Control-KeyPress-c>', self.copySelection) self.bind('<Control-KeyPress-v>', self.pasteClip) # <Button> f1 = tk.Frame(master, relief=tk.SUNKEN, bd=1) f1.pack() self.bpy_clear = tk.Button(f1, text='Clear', \ font=(FONTNAME, FONTSIZE), \ command=self.bpyClear, width=12) self.bpy_clear.pack(side='left') ''' self.bpy_read = tk.Button(f1, text='Read', \ command=self.bpyRead, width=12) self.bpy_read.pack(side='left') self.bpy_apply = tk.Button(f1, text='Apply', width=12) self.bpy_apply['command'] = self.bpyApply # こんな書き方も self.bpy_apply.pack(side='left') ''' self.bpy_applyquit = tk.Button(f1, text='Apply', \ font=(FONTNAME, FONTSIZE),\ command=self.bpyApplyQuit, width=12) self.bpy_applyquit.pack(side='left') self.bpy_quit = tk.Button(f1, text='Cancel', \ font=(FONTNAME, FONTSIZE), \ command=self.bpyCancel, width=12) self.bpy_quit.pack(side='right') # <Text> self.st = ScrolledText(master, font=(FONTNAME, FONTSIZE)) self.st.pack(fill=tk.BOTH, expand=1) self.st.focus_set() # <Read from bpy.context.active_object> self.setTextObject(textobj) def setTextObject(self, txtobj): self.text_active_object = txtobj def getTextObject(self): ob = self.text_active_object if ob and ob.type == 'FONT': return ob return None def bpyClear(self): self.st.delete('1.0', tk.END) def bpyRead(self): ob = self.getTextObject() if ob: editmode = ob.mode if editmode == 'EDIT': bpy.ops.object.mode_set(mode='OBJECT') text = ob.data.body self.cached_text = text self.st.delete('1.0', tk.END) self.st.insert(tk.END, text) if editmode == 'EDIT': bpy.ops.object.mode_set(mode='EDIT') def setText(self, str): ob = self.getTextObject() if ob: editmode = ob.mode if editmode == 'EDIT': bpy.ops.object.mode_set(mode='OBJECT') ob.data.body = str if editmode == 'EDIT': bpy.ops.object.mode_set(mode='EDIT') def bpyApply(self): self.setText(self.st.get('1.0', tk.END)) def bpyApplyQuit(self): self.bpyApply() self.quit() def bpyCancel(self): self.setText(self.cached_text) self.quit() def openFile(self): fn = askopenfilename() if fn: fi = open(fn, 'r') b = fi.read() fi.close() self.st.delete('1.0', tk.END) self.st.insert(tk.END, b) def saveFile(self): fn = asksaveasfilename() if fn: fo = open(fn, 'w') fo.write(self.st.get('1.0', tk.END)) fo.close() def cutSelection(self, *tmp): if self.st.tag_ranges(tk.SEL): self.copySelection() self.st.delete(tk.SEL_FIRST, tk.SEL_LAST) def copySelection(self, *tmp): if self.st.tag_ranges(tk.SEL): t = self.st.get(tk.SEL_FIRST, tk.SEL_LAST) self.st.clipboard_clear() self.st.clipboard_append(t) def pasteClip(self, *tmp): if self.st.tag_ranges(tk.SEL): self.st.delete(tk.SEL_FIRST, tk.SEL_LAST) t = self.st.selection_get(selection='CLIPBOARD') self.st.insert(tk.INSERT, t) class Invoker(threading.Thread): def __init__(self, m): threading.Thread.__init__(self) self.method = m def run(self): self.method() class TEXT_OT_edit_object_with_tkinter(bpy.types.Operator): '''Call Tkinter GUI''' bl_description = 'Call Tkinter GUI and Blender stop' bl_idname = 'text.edit_object_with_tkinter' bl_label = 'Call Tkinter GUI' @classmethod def poll(cls, context): actob = context.active_object return actob and actob.type == 'FONT' def pre_call(self): self.root = tk.Tk() self.app = Scroller(master=self.root, textobj=bpy.context.active_object) self.app.st.bind("<ButtonRelease>", self.callback) self.app.st.bind("<KeyRelease>", self.callback) def callback(self, event): self.app.bpyApply() def call_tk(self): self.app.bpyRead() self.app.mainloop() try: self.root.destroy() except: pass del self.app del self.root del self.t self._timer = bpy.context.window_manager.event_timer_add(0.1, bpy.context.window) def execute(self, context): context.window_manager.modal_handler_add(self) self.pre_call() self.t = Invoker(self.call_tk) self.t.start() return {'RUNNING_MODAL'} def cancel(self, context): return {'CANCELLED'} def modal(self, context, event): if event.type == 'ESC': # Cancel return self.cancel(context) if event.type == 'TIMER': context.window_manager.event_timer_remove(self._timer) del self._timer return {'CANCELLED'} return {'PASS_THROUGH'} class TEXT_OT_load_fonts(bpy.types.Operator): '''Load fonts''' bl_description = 'Load fonts. ' + \ '(edit "text_edit_object_with_tkinter.py":62~76:font_paths)' bl_idname = 'text.load_fonts' bl_label = 'Load Fonts' setactob = BoolProperty(default=False) ''' @classmethod def poll(cls, context): actob = context.active_object return actob and actob.type == 'FONT' ''' def execute(self, context=None): if not font_paths: return {'FINISHED'} loadedpaths = [f.filepath for f in bpy.data.fonts] for path in font_paths: if not path in loadedpaths: bpy.ops.font.open(filepath=path) if self.setactob: actob = bpy.context.active_object if actob and actob.type == 'FONT': for font in bpy.data.fonts: if font.filepath == font_paths[0]: actob.data.font = font break return {'FINISHED'} class DATA_PT_call_tkinter(bpy.types.Panel): bl_label = "Edit Text with Tkinter" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = 'data' @classmethod def poll(cls, context): '''engine = context.scene.render.engine actob = context.active_object return (actob and actob.type == 'FONT' and \ (engine in cls.COMPAT_ENGINES)) ''' actob = context.active_object return (actob and actob.type == 'FONT') def draw(self, context): layout = self.layout row = layout.row() row = row.split(percentage=0.7) row.operator_context = 'INVOKE_DEFAULT' op = row.operator('text.edit_object_with_tkinter', text='Edit Text Object') op = row.operator('text.load_fonts', text='Load Fonts') op.setactob = True # Register def register(): bpy.utils.register_class(DATA_PT_call_tkinter) bpy.utils.register_class(TEXT_OT_edit_object_with_tkinter) bpy.utils.register_class(TEXT_OT_load_fonts) def unregister(): bpy.utils.unregister_class(DATA_PT_call_tkinter) bpy.utils.unregister_class(TEXT_OT_edit_object_with_tkinter) bpy.utils.unregister_class(TEXT_OT_load_fonts) if __name__ == '__main__': register()