From BlenderWiki

Jump to: navigation, search
Note: This is an archived version of the Blender Developer Wiki. The current and active wiki is available on wiki.blender.org.

Blender and GTK

Python gtk-ctypes can be mixed together with Blender, this is for experimental development. Gtk can be imported and run directly inside of Blender's Python, however to make callbacks thread-safe, the Gtk callback must wait before touching any of Blender's object using locks.

	def threadsafe( self, reg ):
		if not self.ready.locked():
			self.ready.acquire()
			time.sleep(0.0001)
			while self.ready.locked():	# must block to be safe
				time.sleep(0.0001)
		else: pass #time.sleep(0.033) dont block

	_handle = None
	def setup_callback( self, context ):
		if self._handle: return self._handle
		for area in bpy.context.window.screen.areas:
			if area.type == 'VIEW_3D':
				for reg in area.regions:
					if reg.type == 'WINDOW':
						# PRE_VIEW, POST_VIEW, POST_PIXEL
						self._handle = reg.callback_add(
							self.threadsafe, (reg,), 'PRE_VIEW' )
						self._area = area
						self._region = reg
						break


Dev-GtkBlender-simple.png

Above two instances of Blender are embedded inside a Gtk window with a tabbed interface. The code below also uses the Wnck library to get the XID and "shade" the Blender windows before they are embedded by the GtkSocket. Gtk appears to be unable to embed into multiple sockets at once, so most of the work this script is doing is to create the sockets one at a time.

(This example only works in Linux because it uses the XEMBED protocol)

#!/usr/bin/python
import os,sys, time, subprocess
import wnck	# apt-get install gnome-python-extras

if '..' not in sys.path: sys.path.append( '..' )
import rpythonic
gtk = rpythonic.module( 'gtk' )
assert gtk
gtk.init()

class MultiBlender(object):
	def __init__(self, blenders=2):
		self._num_blenders = blenders
		self._not_ready = self._num_blenders
		self._waiting = False
		self._window = win = gtk.Window()	#gtk.window_new( gtk.GTK_WINDOW_TOPLEVEL )
		self._screen = wnck.screen_get_default()
		print( win )
		#win.set_title( 'ctypes_ooapi_gtk_test')	# segfaults, name too long?
		win.set_title('GtkBlender')
		#win.set_default_size( 640, 480 )
		win.set_border_width( 2 )
		win.connect( 'destroy', lambda *args: gtk.main_quit() )

		frame = gtk.Frame()
		win.add( frame )
		frame.set_border_width( 4 )

		self._notebook = note = gtk.Notebook()
		frame.add( note )
		self._pages = []
		self._sockets = []
		self._procs = []
		self._plugs = 0
		for i in range( blenders ):
			exe = os.path.expanduser('~/Blender25/blender')
			self._procs.append(
				subprocess.Popen( [exe]+ '--window-geometry 0 0 640 480'.split() )
			)
			frame = gtk.Frame(); self._pages.append( frame )
			frame.set_size_request( 640, 480 )
			note.append_page( frame, gtk.Label( 'blender %s' %i ) )

		win.show_all()
		self._blender_windows = []



	def add_blender_page(self):
		self._waiting = True
		self._not_ready -= 1
		wins = None
		for i in range( 10 ): 	# only try this ten times
			time.sleep( 0.25 )
			wins = self.get_blender_windows()
			if wins: break
			if gtk.gtk_events_pending(): gtk.gtk_main_iteration()	# required
		if not wins: assert 0	# this should never happen
		else:
			w = wins[0]
			index = len(self._sockets)
			frame = self._pages[ index ]
			soc = gtk.gtk_socket_new()
			self._sockets.append( soc )
			soc.connect('plug-added', self.on_plug)
			frame.add( soc )
			frame.show_all()

			wid = w.get_xid()
			w.shade()	# shade is the trick
			w.make_below()
			w.set_skip_pager(True)
			self._blender_windows.append( w )

			assert index == self._notebook.get_current_page()
			print( 'socket add id', wid )
			soc.gtk_socket_add_id( wid )	# on_plug is called right after
			print( '----------ok---------' )

			if index+1 < len(self._pages):
				self._notebook.set_current_page( index+1 )

	def get_blender_windows(self):
		screen = self._screen
		screen.force_update()
		bwindows = []
		for w in screen.get_windows():
			wname = w.get_name()
			if w.get_name() == 'Blender':
				print( 'FOUND BLENDER WINDOW', w )
				bwindows.append( w )
		return bwindows

	def on_plug(self, *args):
		self._waiting = False
		self._plugs += 1
		print( 'on plug:', self._plugs )
		for i,soc in enumerate(self._sockets):
			sub = soc.gtk_socket_get_plug_window()
			sub.set_title('hack%s' %i)
			bwin = self._blender_windows[ i ]
			bwin.unshade()

		if self._plugs == self._num_blenders:
			print( '---embed complete---' )
		else:
			print( '...waiting for other blenders to embed...' )

mb = MultiBlender()
while True:
	if gtk.gtk_events_pending(): gtk.gtk_main_iteration()
	elif mb._not_ready and not mb._waiting:
		mb.add_blender_page()

print('exit')