Pythonlópes - El Python que yo sé

Parte II: la cultura malla :)

...¡Pero Einstein no sabía Python!

Yo, que sé. Yo qué sé.

El módulo NMesh
Sin duda el módulo más utilizado de los incluidos en el API de Blender es el que se ocupa de manipular mallas, que es de lo que está hecho el 90% de la producción 3D. Se llama NMesh y nadie sabe lo que significa la N. "Sabe Ton", dicen.
Si echamos un vistazo a lo que contiene el módulo encontramos esto:
>import Blender
>print dir(Blender.NMesh)

['Col', 'Face', 'FaceFlags', 'FaceModes', 'FaceTranspModes', 'GetRaw',
'GetRawFromObject', 'New', 'PutRaw', 'Vert', '__doc__', '__name__']
	

Debemos saber que 'dir' devuelve una lista, en la cual encontramos siempre los atributos '__doc__' y '__name__'. Estos contienen, respectivamente, unas líneas de documentación referidas a lo que hacemos 'dir' (o bien nada='None'), y el nombre, en este caso: 'NMesh'.

De entre estos, conozcamos unas de funciones:
New(), GetRaw(nombre) y PutRaw(obj[,nombre]) para llamar y devolver mallas,
y nuevas clases de PyObjetos, que no llegan a ser BlObjetos:
Col (color RGBA), Face (cara) y Vert (vértice)
que llegaremos a usar como nuestro mando a distancia.
Es conveniente aprender bien el formato y función de GetRaw, PutRaw y New, y cómo usarlas en cada caso. Para eso, nada mejor que estas
NOTAS:
*New() no tiene argumentos y crea una malla vacía.
*GetRaw() hace lo mismo.
*GetRaw('Nombre') crea una malla con nombre, y si ya hay una así, la toma.
*PutRaw(me) devuelve a la escena una el objeto me
*PutRaw(me,'Nombre') lo mismo, con nombre, y si ya existe lo sustituye. Más información en la tabla de debajo.
Así pues:
*Si sólo queremos MODIFICAR una malla existente, hay que hacer: (¡cuidado, perderemos la vieja!)
m=NMesh.GetRaw('Mesh')
	...
NMesh.PutRaw(m,'Mesh')

*Si queremos MODIFICAR una malla, y devolverla como otra nueva, hay que hacer:
m=NMesh.GetRaw('Mesh')
	...
NMesh.PutRaw(m)

*Si queremos crear una malla NUEVA, y SUSTITUIRLA por otra vieja, hay que hacer:
m=NMesh.GetRaw()
	...
NMesh.PutRaw(m,'Mesh')
*En todos los casos, al crear una malla, se crea además un objeto3D a no ser que ya haya alguno asociado a esta, con lo que no es necesario el proceso de la página anterior de enlazar el objeto a la escena actual.
*No os quejareis, ¿eh...? ¡Mascaditoo!

NOTA: Funcionamiento interno de NMesh.PutRaw() - API 2.29

NMesh.PurRaw() hace 4 cosas:

1.PutRaw(nmesh) sin 2º parámetro:
a) Crea objeto3D por defecto con malla por defecto, llamada 'Mesh'
b) Si el PyObjeto Mesh se llamó desde GetRaw() o GetRawFromObject(), se crea un BlOjeto Mesh nuevo que se enlaza al Objeto3D. Si el PyObjeto Mesh fue creado nuevo, se usa el objeto por defecto
c) Actualiza la malla

2.PutRaw(nmesh, 'datablock') donde 'datablock' EXISTE y YA está enlazado a Objetos3D:
a) ¿Existe 'datablock'? SI
b) ¿Está enlazado a objetos? SI
c) Actualiza la malla

3.PutRaw(nmesh, 'datablock') donde 'datablock' EXISTE y NO está enlazado a Objetos3D:
a) ¿Existe 'datablock'? SI
b) ¿Está enlazado a objetos? NO
c) Crea objeto3D por defecto con malla por defecto, llamada 'Mesh'
d) Enlaza Objeto3D nuevo a nmesh.
e) Actualiza la malla

3.PutRaw(nmesh, 'datablock') donde 'datablock' NO EXISTE:
a) ¿Existe 'datablock'? NO
b) Crea objeto3D por defecto con malla por defecto, llamada 'Mesh'
c) Usa el nombre como nombre global para mallas
d) Actualiza la malla


Estructura de una malla
Al igual que otros tipos de objeto3D de estructura similar (curvas, superficies, armatures...) que el lector tendrá que investigar por su cuenta, porque es más de lo mismo pero más fácil, un PyObjeto de clase Mesh está formado por subobjetos: vértices y caras. Así que para crear una malla desde cero, tenemos que imaginarnos:
*Una lista numerada desde 0, de vértices
*Una lista igual de caras
Cada vértice contiene 3 coordenadas, pero puede tener información de color y otros artilugios
Cara cara contiene la lista de vértices que la forman. De hecho. O sea, que no contiene números de orden de la lista de vértices sino LOS PROPIOS VÉRTICES. Esto nos da dos ventajas:

a)Podemos acceder a los vértices directamente a través de las caras, así:
vertice0=cara.v[0]
vertice1=cara.v[1]
vertice2=cara.v[2]
para una cara triangular.

b)No necesitamos redefinir un vértice para que pertenezca a dos caras. Así, podemos hacer:
Mi primer cuadrado
from Blender import NMesh

v1=NMesh.Vert(-1,0,0)	# v1,v2,v3,v3 son vértices
v2=NMesh.Vert(0,1,0)	# formando un cuadrado
v3=NMesh.Vert(0,-1,0)
v4=NMesh.Vert(1,0,0)

cara1=NMesh.Face()	# definimos 2 caras vacías
cara2=NMesh.Face()

cara1.v.append(v1)	# Llenamos las caras de vértices
cara1.v.append(v2)	# y así definimos dos
cara1.v.append(v3)	# triángulos unidos por
cara2.v.append(v4)	# los vértices v2 y v3
cara2.v.append(v3)
cara2.v.append(v2)

me=NMesh.New()		# Ahora podemos definir la malla:
me.verts.append(v1)	# Sus vértices...
me.verts.append(v2)
me.verts.append(v3)
me.verts.append(v4)
me.faces.append(cara1)	#...y sus caras
me.faces.append(cara2)

NMesh.PutRaw(me)	# Voilá. Un cuadrado
	


En serio: Representación de funciones con Blender
Vamos a hacer un script que creará una malla que representará una función matemática real de variable real, o sea, lo que de toda la vida ha sido "dibuja el seno", o "la recta y=2x+1", o la parábola "del buen modelador samaritano".
La estructura será la siguiente:
Definimos la función según el Python tradicional, como función Python, mandando valores en la variable 'x' y devolviendo f(x). (1)
y definimos unas variables que utilizaremos como parámetros: extremos, saltos...(2)
Leemos, o creamos nueva, la malla "Funcion" en la escena.
Añadimos el primer vértice (3)
Hacemos un bucle que añade en cada paso el siguiente vértice, y una cara que une el vértice anterior al nuevo (4)
Devolvemos la malla (5)
Mi primera raya
from Blender import NMesh
#from math import *		# Si hemos instalado Python
				# podremos acceder así a las
				# funciones de la librería math

def f(x):			# (1)
	return x**3-x**2		# Poner aquí la función

Ex0,Ex1=-1.,2.			# (2)
N=100
salto=(Ex1-Ex0)/N

me=NMesh.New("Funcion")

v0=NMesh.Vert(Ex0,f(Ex0))	# (3)
me.verts.append(v0)
for i in range(N-1):		# (4)
	x=(i+1)*salto+Ex0
	v1=NMesh.Vert(x,f(x),0)
	arista=NMesh.Face()
	arista.v.append(v0)
	arista.v.append(v1)
	me.verts.append(v1)
	me.faces.append(arista)
	v0=v1

NMesh.PutRaw(me,'Funcion')	# (5)

Como comentario, decir que las caras en Blender pueden unir sólo dos vértices (y tenemos un segmento, como en este ejemplo), 3 ó 4, y tenemos entonces triángulos y cuadriláteros en 3D. Esto significa que, cuando borramos en Blender una cara, o vértice, o cualquiera de las distintas modalidades de borrado que hay, en realidad pasan muchas más cosas de las que parece. Por ejemplo, borrando "Only Faces", cada cara se transforma en sus aristas, lo que significa que desparece la cara, pero se hacen nuevas caras (de dos vértices), cuidando de no repetirlas en cuando las viejas son adyacentes, y cosas más enrevesadas que no quiero saber.

V.16,4,4 - © 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 ;-)