Tuesday, July 17, 2012

Google Blockly in Blender

Hacking around with Google Blockly and Blender. Blockly inside of Blender could be useful for game play scripting and other things. At the moment this is just a proof of concept how to get WebKit and Blockly to load inside of Blender and have simple two-way communication.

This hack is standalone, and already includes Blender2.63 compiled by natewiebe13 from Graphicall.org. To get this running, all you need to do is: "sudo apt-get install libwebkitgtk-3.0-dev" download here

Hello World Source Code

import bpy

def myprocedure():
 '''
 user defined function, in blockly just define an empty function,
 and give it the name "myprocedure"
 '''
 bpy.ops.mesh.primitive_monkey_add()





###############################################
import os, sys, time, ctypes

sys.path.append( os.path.abspath('.') )
import webkitgtk as webkit
import Blender # brett's ctypes wrapper to libblender

gtk = glib = webkit # webkit links to gtk and glib
gtk.init()


def get_html():
 dom = view.get_dom_document()
 html = webkit.dom_html_element_get_inner_html( dom )
 return html

def call_javascript( script ):
 '''
 this won't work because it kills newlines!
  view.execute_script('document.title=%s;' %script)
 '''
 view.execute_script(
  "document.getElementsByTagName('text_hack')[0].setAttribute('x',%s);"%script
 )
 result = get_html()
 result = result.split('text_hack x="')[-1]
 result = result.split('"')[0]
 return result

def hack_code( script ):
 '''
 need to hack the script a bit, blockly generates python2,
 and blender needs python3!
 '''
 a = []
 for line in script.splitlines():
  if "print '" in line:
   line = line.replace("print '", "print('") + ')'
  if line == 'null': continue # blockly bug?
  elif line.strip() == 'passnull': # check for an undefined function and remove it
   a.pop()
   continue
  a.append(line)
 script = '\n'.join(a)
 print('----------- python code -------------')
 print(script)
 return script

def execute_python( script ):
 script = hack_code( script )
 print('----------- exec python code -------------')
 exec( script )


################### WebKitGTK ####################
view = webkit.webkit_web_view_new()
print(view)


settings = webkit.web_settings_new()
for prop in 'enable-webaudio enable-file-access-from-file-uris enable-universal-access-from-file-uris enable-developer-extras enable-accelerated-compositing enable-webgl'.split():
 gval = glib.GValue(True)
 glib.g_object_set_property( settings, prop, gval )
view.set_settings( settings )

view.load_uri( 'file://%s/test-blockly.html'%os.path.abspath('.'))


win = gtk.Window()
root = gtk.VBox()
win.add( root )

header = gtk.HBox()
root.pack_start( header, expand=False )


button = gtk.Button('print html')
button.connect('clicked', lambda b: get_html() )
header.pack_start( button, expand=False )

header.pack_start( gtk.Label() )

button = gtk.Button('print python')
button.connect('clicked', lambda b: hack_code(call_javascript("Blockly.Generator.workspaceToCode('Python')")) )
header.pack_start( button, expand=False )

button = gtk.Button('run python')
button.connect('clicked', lambda b: execute_python(call_javascript("Blockly.Generator.workspaceToCode('Python')")) )
header.pack_start( button, expand=False )


root.pack_start( view, expand=True )

win.set_default_size( 800, 600 )
win.show_all()

class BlenderHack(object):
 def update_gtk(self, region):
  while gtk.gtk_events_pending():
   gtk.gtk_main_iteration()


 def setup_blender_hack(self, context):
  self._sync_hack_handles = {} # region : handle
  self.default_blender_screen = context.screen.name
  self.evil_C = Blender.Context( context )

  for area in context.screen.areas:
   if area.type == 'VIEW_3D':
    for reg in area.regions:
     if reg.type == 'WINDOW':
      handle = reg.callback_add( self.update_gtk, (reg,), 'POST_PIXEL' )
      self._sync_hack_handles[ reg ] = handle

  return self._sync_hack_handles

 def mainloop(self):
  self.active = True
  while self.active:
   screen = bpy.data.screens[ self.default_blender_screen ]
   ## force a redraw on the 3d view
   for area in screen.areas:
    if area.type == 'VIEW_3D':
     for reg in area.regions:
      if reg.type == 'WINDOW':
       reg.tag_redraw()
       break
   ## iterate blender's mainloop from ctypes
   Blender.iterate( self.evil_C )
   time.sleep(0.01)


hack = BlenderHack()
hack.setup_blender_hack( bpy.context )
hack.mainloop()
print('exit to normal blender mainloop')