Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Скриптуємо Блендер (частина 2)

leave a comment »

Друга частина про скриптування Блендера. Все читайте на вікіпідручнику.

Тут буде продовжена розповідь про способи процедурного створення об’єктів, та розкажуть про графічний інтерфейс!

Карта висот

Тепер давайте зробимо щось поскладніше. Наприклад згенеруємо карту висот за функцією. Вони бувають доволі симпатичні:

Параболоїд

Зразу покажу скрипт який це намалював:

#!BPY

"""
Name: 'Function heightmap'
Blender: 244
Group: 'Mesh'
Tooltip: 'Draw function heightmap mesh'
"""

import Blender
from Blender import NMesh
import bpy
import math

def f(x,y):
	return (x*x+y*y)/5

def genheightmap(f,minx=-5,miny=-5,maxx=5,maxy=5,resolution=0.1,name="heightmap"):
	width=int( (maxx-minx)/resolution)+1
	height = int( (maxy - miny) / resolution)+1
	maxx=minx+resolution*width
	maxy=miny+resolution*height

	obj=NMesh.GetRaw()
	y=miny
	obj=NMesh.GetRaw()
	for y in range(height):
		for x in range(width):
			fx=minx+x*resolution
			fy=miny+y*resolution
			z=f(fx,fy)
			v=NMesh.Vert(fx,fy,z)
			obj.verts.append(v)
	for y in range(height-1):
		for x in range(width-1):
			f=NMesh.Face()
			f.v.append(obj.verts[y*width + x])
			f.v.append(obj.verts[(y+1)*width + x])
			f.v.append(obj.verts[y*width + x + 1])
			obj.faces.append(f)

			f=NMesh.Face()
			f.v.append(obj.verts[y*width + x + 1])
			f.v.append(obj.verts[(y+1)*width + x + 1])
			f.v.append(obj.verts[(y+1)*width + x])
			obj.faces.append(f)
	NMesh.PutRaw(obj,name,1)


genheightmap(f,-5,-5,5,5,0.7)

Він майже аналогічний скрипту що малював трикутник, з тією відмінністю, що трикутників тут набагато більше. Принцип роботи дуже простий. Подивимось на нашу карту висот в режимі дротяної моделі. Функцію я регулярно змінював, бо цікаво ж.

Дротяна модель сідла

Може здатись що топологія дуже складна, та насправді, якщо глянути зверху, ми побачим звичайну однорідну сітку:

Сідло, вигляд зверху

Сідло, вигляд зверху

Тому, нам спочатку треба обчислити координати вершин на однорідному розбитті, та заповнити ними модель:

for y in range(height):
		for x in range(width):
			fx=minx+resolution*x
			fy=miny+resolution*y
			z=f(fx,fy)
			v=NMesh.Vert(fx,fy,z)
			obj.verts.append(v)

А потім, об’єднати їх в трикутники, по два трикутники на клітинку, як на попередній ілюстрації. Зауважте, що якщо ми маємо width вершин в ряді, то клітинок в ряді буде width-1.

Наш скрипт чудово працює, але є одне але. Щоб змінити функцію нам треба міняти його код. А це незручно. Зручніше було б, якщо б скрипт давав нам можливість ввести свою функцію, точність, межі на яких її обчислювати. А для цього нам потрібний якийсь інтерфейс.

Створення графічного інтерфейсу

Графічний інтерфейс у блендері створюється ну просто дуже просто. Простіше хіба що в Delphi. За створення інтерфейсу відповідає модуль Blender.Draw. І одна команда:

Blender.Draw.Register(draw,event,button)

Ця команда приймає в параметрах три функції, які вона запускає в разі необхідності:
*draw – функція що перемальовує інтерфейс.
*event – функція що обробляє події, такі як рухи миші, та натиснення клавіш клавіатури.
*button – функція що обробляє події натискання кнопок графічного інтерфейсу.

Давайте поглянемо одразу на можливий інтерфейс для нашого скрипта:

Інтерфейс нашого скрипта (внизу) та результат його роботи (вгорі)

Інтерфейс нашого скрипта (внизу) та результат його роботи (вгорі)

Його малює така функція:

def draw():
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
	Blender.Draw.Toggle("Create given surface",1,10,10,150,20,0)
	Blender.Draw.Toggle("Create sample surface",2,10,30,150,20,0)
	Blender.Draw.String("f(x,y)=",3,160,50,180,20,functiontext,200,"Input surface function here",changestring)
	Blender.Draw.String("Surface name: ",4,10,50,150,20,surfacename,100,"What name will be given to created object.",changestring)
	Blender.Draw.String("min_x: ",5,160,30,125,20,str(minx),10,"Minimal x value",changestring)
	Blender.Draw.String("min_y: ",6,285,30,125,20,str(miny),10,"Minimal y value",changestring)
	Blender.Draw.String("max_x: ",7,160,10,125,20,str(maxx),10,"Maximal x value",changestring)
	Blender.Draw.String("max_y: ",8,285,10,125,20,str(maxy),10,"Maximal y value",changestring)
	Blender.Draw.String("d: ",9,340,50,70,20,str(resolution),10,"Size of one grid cell",changestring)

Перший рядок функції

Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)

очищає все поле для малювання. Якщо ви програмували в OpenGL – ця функція може видатись вам знайомою. А все тому, що інтерфейс блендера виводиться з допомогою OpenGL. І таке рішення видається досить вдалим.

Наступна функція

Blender.Draw.Toggle("Create given surface",1,10,10,150,20,0)

Ось так

Ось так

Створює кнопку – переключатель. Перший параметр – напис на кнопці. Другий – її унікальний номер. За цим номером обробник події дізнається від якої кнопки прийшов сигнал. Тому бажано щоб кожен елемент інтерфейсу мав унікальний номер, якщо він звісно не дублює функцію якогось іншого. Два інші числа – координати кнопки в пікселях. Що цікаво, тут на відміну від інших фреймворків інтерфейсу координати математичні. Тобто нуль внизу, та ігрик направлений вверх.

Далі ширина та висота кнопки, Останнє число – початковий стан кнопки. 1 – натиснута, 0 – відпущена. В кінці ще можна додавати рядок з спливаючою підказкою, та якщо підпис достатньо інформативний в цьому немає необхідності.

Наступний оператор аналогічно створює подібну кнопку. Але ми малювали ще елемент іншого виду – для вводу тексту:

Blender.Draw.String("f(x,y)=",3,160,50,180,20,functiontext,200,"Input surface function here",changestring)

Перший параметр – підпис. Цей підпис буде стояти зліва від тексту що ми будемо вводити, і буде займати ширину нашого компонента. Тому упевніться що і для вводу виділено достатньо. Наступні п’ять параметрів як і у кнопки – переключателя: ідентифікатор події, координати, розмір. Після них йде рядок (functiontext) – у якому вказують текст що буде стояти в полі для вводу за замовчуванням. Після нього – число (200) – максимальна кількість символів для вводу (не більше 399). Потім підказка, і останнє – функція зворотнього виклику changestring. Ця функція викликається після введення тексту, і їй передаються два аргументи – спершу ідентифікатор події, а потім параметр.

Стандартний обробник button, що передавався Blender.Draw.Register нам не підійшов, бо в його параметрах є тільки event – ідентифікатор компонента що запустив подію. А нам ще треба отримувати введений текст.

Розглянемо спочатку обробник button:

def button(evt):    
	if evt == 1: # Якщо натиснута кнопка з ідентифікатором 1
		genheightmap(f,minx,miny,maxx,maxy,resolution,functiontext) # Створити карту висот, з заданими параметрами
		Blender.Window.Redraw() # Та обновити вміст вікон блендера

Ось так просто. Так само туди вписуємо обробку подій для інших кнопок. if evt == 2 і так далі.

Функція – обробник спеціально для текстів нічим не складніша. Текст передається у змінну val. Єдиний недолік – вивід проводиться через глобальні змінні, та я не знаю як можна зробити інакше.

def changestring(event,val):
	if event==3:
		global functiontext
		functiontext=val

Якщо кнопка для вводу тексту мала ідентифікатор 3, і функцію обробник – changestring, то з її допомогою можна було б змінювати вміст глобальної змінної functiontext.

Нам залишилось розглянути тільки функцію:

def event(evt,val):  
	if evt == Blender.Draw.ESCKEY:
		Blender.Draw.Exit()   
		return    

Вона майже нічого не робить, окрім того, що при натисненні клавіші Esc припиняє роботу скрипта.

Список всіх констант для клавіш, та інших подій можна знайти на сторінках документації.

Ну, і звісно спочатку варто було зареєструвати глобальні змінні, та створити функцію що малює поверхню:

functiontext="0"
surfacename="heightmap1"
minx=-5
miny=-5
maxx=5
maxy=5
resolution=0.2

def f(x,y):
	return eval(functiontext,{"x":x,"y":y,"sin":math.sin,"atan2":math.atan2})

Увесь скрипт цілком виглядає так:

#!BPY

"""
Name: 'Function heightmap'
Blender: 244
Group: 'Mesh'
Tooltip: 'Draw function heightmap mesh'
"""

import Blender
from Blender import NMesh
import bpy
import math

functiontext="0"
surfacename="heightmap1"
minx=-5
miny=-5
maxx=5
maxy=5
resolution=0.2

def f(x,y):
	return eval(functiontext,{"x":x,"y":y,"sin":math.sin,"atan2":math.atan2})
def f2(x,y):
	r=math.sqrt(x*x+y*y)
	return 5*math.sin(r)/(r+1)

def genheightmap(f,minx=-5,miny=-5,maxx=5,maxy=5,resolution=0.1,name="heightmap"):
	width=int( (maxx-minx)/resolution)+1
	height = int( (maxy - miny) / resolution)+1
	maxx=minx+resolution*width
	maxy=miny+resolution*height

	obj=NMesh.GetRaw()
	y=miny
	obj=NMesh.GetRaw()
	for y in range(height):
		for x in range(width):
			fx=minx+x*resolution
			fy=miny+y*resolution
			z=f(fx,fy)
			v=NMesh.Vert(fx,fy,z)
			obj.verts.append(v)
	for y in range(height-1):
		for x in range(width-1):
			f=NMesh.Face()
			f.v.append(obj.verts[y*width + x])
			f.v.append(obj.verts[(y+1)*width + x])
			f.v.append(obj.verts[y*width + x + 1])
			obj.faces.append(f)

			f=NMesh.Face()
			f.v.append(obj.verts[y*width + x + 1])
			f.v.append(obj.verts[(y+1)*width + x + 1])
			f.v.append(obj.verts[(y+1)*width + x])
			obj.faces.append(f)
	NMesh.PutRaw(obj,name,1)

def changestring(event,val):
	if event==3:
		global functiontext
		functiontext=val	
	if event==4:
		global surfacename
		surfacename=val
	if event==5:
		global minx
		minx=float(val)
	if event==6:
		global miny
		miny=float(val)
	if event==7:
		global maxx
		maxx=float(val)
	if event==8:
		global maxy
		maxy=float(val)
	if event==9:
		global resolution
		resolution=float(val)

def draw():
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
	Blender.Draw.Toggle("Create given surface",1,10,10,150,20,0,)
	Blender.Draw.Toggle("Create sample surface",2,10,30,150,20,0,)
	Blender.Draw.String("f(x,y)=",3,160,50,180,20,functiontext,200,"Input surface function here",changestring)
	Blender.Draw.String("Surface name: ",4,10,50,150,20,surfacename,100,"What name will be given to created object.",changestring)
	Blender.Draw.String("min_x: ",5,160,30,125,20,str(minx),10,"Minimal x value",changestring)
	Blender.Draw.String("min_y: ",6,285,30,125,20,str(miny),10,"Minimal y value",changestring)
	Blender.Draw.String("max_x: ",7,160,10,125,20,str(maxx),10,"Maximal x value",changestring)
	Blender.Draw.String("max_y: ",8,285,10,125,20,str(maxy),10,"Maximal y value",changestring)
	Blender.Draw.String("d: ",9,340,50,70,20,str(resolution),10,"Size of one grid cell",changestring) 
def event(evt,val):  
	if evt == Blender.Draw.ESCKEY:
		Blender.Draw.Exit()   
		return                
 
def button(evt):    
	if evt == 1: 
		genheightmap(f,minx,miny,maxx,maxy,resolution,functiontext)
		Blender.Window.Redraw()
	if evt == 2: 
		genheightmap(f2,-5,-5,5,5,0.1,"surface2")
		Blender.Window.Redraw() 
 
Blender.Draw.Register(draw,event,button)

Посилання

  1. http://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro/Python_Scripting
  2. Документація по Blender API
Advertisements

Written by bunyk

Травень 5, 2010 at 22:45

Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: