Pythonlópes - El Python que yo sé

Parte III: Toqueteando un poco para hacernos los amos del corral

El alcohol disminuye el campo visual.
Por tanto, afina la puntería

El módulo Window
Escribo estas líneas recién salida la versión 2.30 de Blender, con el nuevo API de Python ya muy depurado, funcional y documentado, y bastantes novedades implementadas. En este capítulo veremos cómo podemos tontear con el interface de usuario (GUI) de Blender y creernos que somos Blender en persona.
Para ello echamos un vistazo a lo que nos ofrece el módulo llamado Window:
>import Blender
>print dir(Blender.Window)

['DrawProgressBar', 'FileSelector', 'GetCursorPos', 'GetViewMatrix',
'GetViewVector', 'ImageSelector', 'QRedrawAll', 'Redraw', 'RedrawAll',
'SetCursorPos', 'Types', '__doc__', '__name__', 'drawProgressBar']
	

Esto es, cositas que tienen relación con el GUI y que forman un cajoncito de sestrecito muy mono.

Estas funciones se dividen en 3 grupos:
*Acceso a ficheros
*Barra de progreso
*Orientación de la vista 3D y Cursor3D

¡Veámoslos!
¡YAA!


1. Acceso al selector de ficheros con Python
Como en cualquier lenguaje con acceso a rutinas gráficas, el API de Python dispone de órdenes ('FileSelector' e 'ImageSelector') que nos abren una ventana estándar que permite al usuario navegar por los directorios y buscar archivos. Sin embargo, este acceso no se hace de la forma habitual: estas órdenes no devuelven una 'trayectoria a fichero' como cadena, ni tan siquiera como objeto. Lo que hacen es llamar a otra función donde se deberían realizar las operaciones deseadas sobre este fichero. Así, los formatos respectivos son:
	Blender.Window.FileSelector (func , titulo)
	Blender.Window.ImageSelector (func , titulo)
Donde titulo es una cadena que aparecerá en la ventana de selección de archivos, como "Elige fichero", "LOAD FILE" y esas cosas; y func es el nombre de una función de un parámetro a la que se llama, que debe ser de esta guisa:
	def func(nombre_archivo):
		print "El archivo se llama" , nombre_archivo
		f=open(nombre_archivo)
		...
Es decir, FileSelector ejecuta la función func y le pasa directamente la ruta al archivo como parámetro.
Se ve enseguida que esto es muy incómodo y dificulta el hilo "lineal" del pensamiento del programador, pues aunque nos ahorra trabajo en ciertas situaciones, no siempre es deseable esta estructura para acceder a un fichero. Tampoco podemos hacer que esta func nos devuelva el nombre con un return ya que no la llamamos nosotros, sino FileSelector.
¿Qué devuelven pues estas funciones? Fácil: devuelven 'None'.
Lo ideal, digamos, sería disponer de una orden directa: nombre_archivo=FileSelector("Busca Archivo"). Así que, a poco que nos estrujemos el celebro, encontramos la solución a través de una variable global:
	global nom_arch

	def func(ARCH):
		nom_arch=ARCH
Y nada más. Ya está.


2. Como hacer una barra de estado
Esto sí que es sencillo, tan fácil como introducir un número entre 0 y 1 como primer parámetro de la función DrawProgressBar, y se dibuja una barrita de color en la ventana de información, sobre el logo de Blender. Así, en un bucle de 100 pasos:
	from Blender.Window import DrawProgressBar
	for i in range(100):
		DrawProgressBar(float(i)/100,"Haciendo algo...")
Fijémonos en el indicador i, que hemos transformado a tipo coma flotante con float(), ya que parece más rápido que multiplicarlo por '1.0'.


3. ¿Para dónde estoy mirando?
Cuando creamos un objeto nuevo, este aparece en el lugar donde está el "Cursor3D" (el punto de mira) y orientado de tal manera que sus ejes XY son paralelos a los lados de la ventana, y el Z "sale para afuera" de la pantalla.
Podemos darnos cuenta, pues, de que el punto de vista de cada ventana3D, funciona en ese sentido como un objeto: tiene su posición (el Cursor3D) y su orientación (la que tendría el objeto que creamos sobre ella). También podemos decir que tiene asociado un vector escala que está dado por la longitud focal (Parámetro Lens, tanto en las Viewport Properties, como si es una cámara).
Recordamos ahora que, cada vez que creamos un objeto desde Python, por ejemplo con NMesh.PutRaw('mesh'), aparecía en el origen y orientado sobre los ejes globales, o sea, su matriz de transformación era la identidad. Vamos a ver cómo simular exactamente el comportamiento del menú "Add" de Blender, con un script en Python. Así conseguiremos que, al crear un Objeto3D, nos aparezca según la vista de la ventana y centrado en el cursor.
Simulación de la función ADD de Blender

import Blender
from Blender.Window import *

sc=Blender.Scene.GetCurrent()	# Creamos un objeto
o=Blender.Object.New('Empty')	# de tipo 'Empty'
sc.link(o)		# según el método ya visto

Loc3D=GetCursorPos()	# Lee la posición del cursor
o.setLocation(Loc3D)	# y la copia al objeto

#Rot3D=GetViewMatrix()	# Lee la orientación del
#Eu=mat2euler(Rot3D)	# punto de vista y la
#o.setEuler(Eu)		# copia al objeto (1)

Redraw()		# Redibuja la ventana
			# para efectuar setLocation()
	

Vale, pero ¿qué son esas líneas comentadas en (1)?
Resulta que, aunque sabemos que un objeto3D está dado por:
(a) un vector posición (x,y,z),
(b) una orientación (los 3 ángulos de Euler, [A,B,C]) y
(c) un vector escala (X,Y,Z),
resulta que todo ello se almacena internamente en la matriz de transformación, de dimensiones 4x4, y legible con el método 'objeto.getMatrix()'.
Sin embargo, de momento no hay método 'objeto.setMatrix()' para aplicar una transformación alobjeto, sino que hay que hacerlo según los pasos (a), (b), (c) de arriba. Aplicar la posición y la escala no es problema con 'o.setLocation', o escribiendo directamente en 'o.loc' y 'o.size', pero en cuanto a su orientación, sólo podemos asignar los ángulos de rotación, y en este ejemplo sólo disponemos de la matriz.
¿Hay alguna forma entonces de extraer estos ángulos de Euler de tal matriz? Sí, pero no es obvio. Así que mientras no hay una solución mejor, os remitimos al archivo funciones.py, donde se encuentra la función de Alfredo de Greef, mcc eeshlo, quien después de muchas aventuras matemáticas hizo las funciones mat2euler y euler2mat.


Menu desplegable
En el módulo Draw encontramos un caramelo que nos permitirá mostrar menús, Preguntas del tipo 'estás seguro' o simples mensajes de advertencia o error, en una sola línea y sin necesidad de definir un entorno gráfico.
Con el método PupMenu y con un formato que describiremos en el capítulo siguiente pero podemos adelantar ahora, se nos despliega el menú Blender de toda la vida, con el título y las opciones que deseemos. Su formato es como se muestra en el ejemplo, y la función devuelve como entero el número de opción elegida por el usuario.
	opcion=PupMenu('Choose language%t|Espanol%x1|English%x2')
He incluído este método aquí por ser este un capítulo dedicado al interface nativo de Blender (el GHOST), y no a los entornos gráficos creados con órdenes OpenGL mediante Python y que describiremos en el siguiente capítulo.
V.18,11,3 - © 2003 Carlos López
Escríbeme y dime algo... ¡tú puedes hacerlo!
Gracias a todos los reporteros de fallos que hacen que esto se vaya corrigiendo ;-)