console_python.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8-80 compliant>
  19. import sys
  20. import bpy
  21. language_id = "python"
  22. # store our own __main__ module, not 100% needed
  23. # but python expects this in some places
  24. _BPY_MAIN_OWN = True
  25. def add_scrollback(text, text_type):
  26. for l in text.split("\n"):
  27. bpy.ops.console.scrollback_append(text=l.replace("\t", " "),
  28. type=text_type)
  29. def replace_help(namespace):
  30. def _help(*args):
  31. # because of how the console works. we need our own help() pager func.
  32. # replace the bold function because it adds crazy chars
  33. import pydoc
  34. pydoc.getpager = lambda: pydoc.plainpager
  35. pydoc.Helper.getline = lambda self, prompt: None
  36. pydoc.TextDoc.use_bold = lambda self, text: text
  37. pydoc.help(*args)
  38. namespace["help"] = _help
  39. def get_console(console_id):
  40. """
  41. helper function for console operators
  42. currently each text data block gets its own
  43. console - code.InteractiveConsole()
  44. ...which is stored in this function.
  45. console_id can be any hashable type
  46. """
  47. from code import InteractiveConsole
  48. consoles = getattr(get_console, "consoles", None)
  49. hash_next = hash(bpy.context.window_manager)
  50. if consoles is None:
  51. consoles = get_console.consoles = {}
  52. get_console.consoles_namespace_hash = hash_next
  53. else:
  54. # check if clearing the namespace is needed to avoid a memory leak.
  55. # the window manager is normally loaded with new blend files
  56. # so this is a reasonable way to deal with namespace clearing.
  57. # bpy.data hashing is reset by undo so can't be used.
  58. hash_prev = getattr(get_console, "consoles_namespace_hash", 0)
  59. if hash_prev != hash_next:
  60. get_console.consoles_namespace_hash = hash_next
  61. consoles.clear()
  62. console_data = consoles.get(console_id)
  63. if console_data:
  64. console, stdout, stderr = console_data
  65. # XXX, bug in python 3.1.2, 3.2 ? (worked in 3.1.1)
  66. # seems there is no way to clear StringIO objects for writing, have to
  67. # make new ones each time.
  68. import io
  69. stdout = io.StringIO()
  70. stderr = io.StringIO()
  71. else:
  72. if _BPY_MAIN_OWN:
  73. import types
  74. bpy_main_mod = types.ModuleType("__main__")
  75. namespace = bpy_main_mod.__dict__
  76. else:
  77. namespace = {}
  78. namespace["__builtins__"] = sys.modules["builtins"]
  79. namespace["bpy"] = bpy
  80. # weak! - but highly convenient
  81. namespace["C"] = bpy.context
  82. namespace["D"] = bpy.data
  83. replace_help(namespace)
  84. console = InteractiveConsole(locals=namespace,
  85. filename="<blender_console>")
  86. console.push("from mathutils import *")
  87. console.push("from math import *")
  88. if _BPY_MAIN_OWN:
  89. console._bpy_main_mod = bpy_main_mod
  90. import io
  91. stdout = io.StringIO()
  92. stderr = io.StringIO()
  93. consoles[console_id] = console, stdout, stderr
  94. return console, stdout, stderr
  95. # Both prompts must be the same length
  96. PROMPT = '>>> '
  97. PROMPT_MULTI = '... '
  98. def execute(context, is_interactive):
  99. sc = context.space_data
  100. try:
  101. line_object = sc.history[-1]
  102. except:
  103. return {'CANCELLED'}
  104. console, stdout, stderr = get_console(hash(context.region))
  105. if _BPY_MAIN_OWN:
  106. main_mod_back = sys.modules["__main__"]
  107. sys.modules["__main__"] = console._bpy_main_mod
  108. # redirect output
  109. from contextlib import (
  110. redirect_stdout,
  111. redirect_stderr,
  112. )
  113. # not included with Python
  114. class redirect_stdin(redirect_stdout.__base__):
  115. _stream = "stdin"
  116. # don't allow the stdin to be used, can lock blender.
  117. with redirect_stdout(stdout), \
  118. redirect_stderr(stderr), \
  119. redirect_stdin(None):
  120. # in case exception happens
  121. line = "" # in case of encoding error
  122. is_multiline = False
  123. try:
  124. line = line_object.body
  125. # run the console, "\n" executes a multi line statement
  126. line_exec = line if line.strip() else "\n"
  127. is_multiline = console.push(line_exec)
  128. except:
  129. # unlikely, but this can happen with unicode errors for example.
  130. import traceback
  131. stderr.write(traceback.format_exc())
  132. if _BPY_MAIN_OWN:
  133. sys.modules["__main__"] = main_mod_back
  134. stdout.seek(0)
  135. stderr.seek(0)
  136. output = stdout.read()
  137. output_err = stderr.read()
  138. # cleanup
  139. sys.last_traceback = None
  140. # So we can reuse, clear all data
  141. stdout.truncate(0)
  142. stderr.truncate(0)
  143. # special exception. its possible the command loaded a new user interface
  144. if hash(sc) != hash(context.space_data):
  145. return {'FINISHED'}
  146. bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
  147. if is_multiline:
  148. sc.prompt = PROMPT_MULTI
  149. if is_interactive:
  150. indent = line[:len(line) - len(line.lstrip())]
  151. if line.rstrip().endswith(":"):
  152. indent += " "
  153. else:
  154. indent = ""
  155. else:
  156. sc.prompt = PROMPT
  157. indent = ""
  158. # insert a new blank line
  159. bpy.ops.console.history_append(text=indent, current_character=0,
  160. remove_duplicates=True)
  161. sc.history[-1].current_character = len(indent)
  162. # Insert the output into the editor
  163. # not quite correct because the order might have changed,
  164. # but ok 99% of the time.
  165. if output:
  166. add_scrollback(output, 'OUTPUT')
  167. if output_err:
  168. add_scrollback(output_err, 'ERROR')
  169. # execute any hooks
  170. for func, args in execute.hooks:
  171. func(*args)
  172. return {'FINISHED'}
  173. execute.hooks = []
  174. def autocomplete(context):
  175. _readline_bypass()
  176. from console import intellisense
  177. sc = context.space_data
  178. console = get_console(hash(context.region))[0]
  179. if not console:
  180. return {'CANCELLED'}
  181. # don't allow the stdin to be used, can lock blender.
  182. # note: unlikely stdin would be used for autocomplete. but its possible.
  183. stdin_backup = sys.stdin
  184. sys.stdin = None
  185. scrollback = ""
  186. scrollback_error = ""
  187. if _BPY_MAIN_OWN:
  188. main_mod_back = sys.modules["__main__"]
  189. sys.modules["__main__"] = console._bpy_main_mod
  190. try:
  191. current_line = sc.history[-1]
  192. line = current_line.body
  193. # This function isn't aware of the text editor or being an operator
  194. # just does the autocomplete then copy its results back
  195. result = intellisense.expand(
  196. line=line,
  197. cursor=current_line.current_character,
  198. namespace=console.locals,
  199. private=bpy.app.debug_python)
  200. line_new = result[0]
  201. current_line.body, current_line.current_character, scrollback = result
  202. del result
  203. # update selection. setting body should really do this!
  204. ofs = len(line_new) - len(line)
  205. sc.select_start += ofs
  206. sc.select_end += ofs
  207. except:
  208. # unlikely, but this can happen with unicode errors for example.
  209. # or if the api attribute access its self causes an error.
  210. import traceback
  211. scrollback_error = traceback.format_exc()
  212. if _BPY_MAIN_OWN:
  213. sys.modules["__main__"] = main_mod_back
  214. # Separate autocomplete output by command prompts
  215. if scrollback != '':
  216. bpy.ops.console.scrollback_append(text=sc.prompt + current_line.body,
  217. type='INPUT')
  218. # Now we need to copy back the line from blender back into the
  219. # text editor. This will change when we don't use the text editor
  220. # anymore
  221. if scrollback:
  222. add_scrollback(scrollback, 'INFO')
  223. if scrollback_error:
  224. add_scrollback(scrollback_error, 'ERROR')
  225. # restore the stdin
  226. sys.stdin = stdin_backup
  227. context.area.tag_redraw()
  228. return {'FINISHED'}
  229. def copy_as_script(context):
  230. sc = context.space_data
  231. lines = [
  232. "import bpy",
  233. "from bpy import data as D",
  234. "from bpy import context as C",
  235. "from mathutils import *",
  236. "from math import *",
  237. "",
  238. ]
  239. for line in sc.scrollback:
  240. text = line.body
  241. type = line.type
  242. if type == 'INFO': # ignore autocomp.
  243. continue
  244. if type == 'INPUT':
  245. if text.startswith(PROMPT):
  246. text = text[len(PROMPT):]
  247. elif text.startswith(PROMPT_MULTI):
  248. text = text[len(PROMPT_MULTI):]
  249. elif type == 'OUTPUT':
  250. text = "#~ " + text
  251. elif type == 'ERROR':
  252. text = "#! " + text
  253. lines.append(text)
  254. context.window_manager.clipboard = "\n".join(lines)
  255. return {'FINISHED'}
  256. def banner(context):
  257. sc = context.space_data
  258. version_string = sys.version.strip().replace('\n', ' ')
  259. add_scrollback("PYTHON INTERACTIVE CONSOLE %s" % version_string, 'OUTPUT')
  260. add_scrollback("", 'OUTPUT')
  261. add_scrollback("Command History: Up/Down Arrow", 'OUTPUT')
  262. add_scrollback("Cursor: Left/Right Home/End", 'OUTPUT')
  263. add_scrollback("Remove: Backspace/Delete", 'OUTPUT')
  264. add_scrollback("Execute: Enter", 'OUTPUT')
  265. add_scrollback("Autocomplete: Ctrl-Space", 'OUTPUT')
  266. add_scrollback("Zoom: Ctrl +/-, Ctrl-Wheel", 'OUTPUT')
  267. add_scrollback("Builtin Modules: bpy, bpy.data, bpy.ops, "
  268. "bpy.props, bpy.types, bpy.context, bpy.utils, "
  269. "bgl, blf, mathutils",
  270. 'OUTPUT')
  271. add_scrollback("Convenience Imports: from mathutils import *; "
  272. "from math import *", 'OUTPUT')
  273. add_scrollback("Convenience Variables: C = bpy.context, D = bpy.data",
  274. 'OUTPUT')
  275. add_scrollback("", 'OUTPUT')
  276. sc.prompt = PROMPT
  277. return {'FINISHED'}
  278. # workaround for readline crashing, see: T43491
  279. def _readline_bypass():
  280. if "rlcompleter" in sys.modules or "readline" in sys.modules:
  281. return
  282. # prevent 'rlcompleter' from loading the real 'readline' module.
  283. sys.modules["readline"] = None
  284. import rlcompleter
  285. del sys.modules["readline"]