Давайте пограємо зі змією/Скриптуємо Блендер: відмінності між версіями

Вилучено вміст Додано вміст
Bunyk (обговорення | внесок)
Bunyk (обговорення | внесок)
Рядок 267:
 
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
Рядок 275:
y=miny
obj=NMesh.GetRaw()
for y in range(height):
while y<=maxy:
for x in range(width):
x=minx
fx=minx+x*resolution
while x<=maxx:
zfy=f(x,miny+y)*resolution
vz=NMesh.Vertf(xfx,y,zfy)
v=NMesh.Vert(fx,fy,z)
obj.verts.append(v)
x+=resolution
y+=resolution
for y in range(height-1):
for x in range(width-1):
Рядок 297 ⟶ 296:
obj.faces.append(f)
NMesh.PutRaw(obj,name,1)
 
 
genheightmap(f,-5,-5,5,5,0.7)
</source>
 
Він майже аналогічний скрипту що малював трикутник, з тією відмінністю, що трикутників тут набагато більше. Принцип роботи дуже простий. Подивимось на нашу карту висот в режимі дротяної моделі. Функцію я регулярно змінював, бо цікаво ж.
 
[[Дротяна модель.]]
 
Може здатись що топологія дуже складна, та насправді, якщо глянути зверху, ми побачим звичайну однорідну сітку:
 
[[Карта висот, вигляд зверху.]]
 
Тому, нам спочатку треба обчислити координати вершин на однорідному розбитті, та заповнити ними модель:
 
<source lang="python">
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)
</source>
 
А потім, об'єднати їх в трикутники, по два трикутники на клітинку, як на попередній ілюстрації. Зауважте, що якщо ми маємо <code>width</code> вершин в ряді, то клітинок в ряді буде <code>width-1</code>.
 
Наш скрипт чудово працює, але є одне але. Щоб змінити функцію нам треба міняти його код. А це незручно. Зручніше було б, якщо б скрипт давав нам можливість ввести свою функцію, точність, межі на яких її обчислювати. А для цього нам потрібний якийсь інтерфейс.
 
== Створення графічного інтерфейсу ==
Графічний інтерфейс у блендері створюється ну просто дуже просто. Простіше хіба що в Delphi. За створення інтерфейсу відповідає модуль <code>Blender.Draw</code>. І одна команда:
 
<source lang="python">
Blender.Draw.Register(draw,event,button)
</source>
 
Ця команда приймає в параметрах три функції, які вона запускає в разі необхідності:
*<code>draw</code> - функція що перемальовує інтерфейс.
*<code>event</code> - функція що обробляє події, такі як рухи миші, та натиснення клавіш клавіатури.
*<code>button</code> - функція що обробляє події натискання кнопок графічного інтерфейсу.
 
Давайте поглянемо одразу на можливий інтерфейс для нашого скрипта:
 
[[Інтерфейс скрипта]]
 
Його малює така функція:
 
<source lang="python">
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)
</source>
 
Перший рядок функції
 
<source lang="python">
Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
</source>
 
очищає все поле для малювання. Якщо ви програмували в OpenGL - ця функція може видатись вам знайомою. А все тому, що інтерфейс блендера виводиться з допомогою OpenGL. І таке рішення видається досить вдалим.
 
Наступна функція
 
<source lang="python">
Blender.Draw.Toggle("Create given surface",1,10,10,150,20,0)
</source>
 
Створює кнопку - переключатель. Перший параметр - напис на кнопці. Другий - її унікальний номер. За цим номером обробник події дізнається від якої кнопки прийшов сигнал. Тому бажано щоб кожен елемент інтерфейсу мав унікальний номер, якщо він звісно не дублює функцію якогось іншого. Два інші числа - координати кнопки в пікселях. Що цікаво, тут на відміну від інших фреймворків інтерфейсу координати математичні. Тобто нуль внизу, та ігрик направлений вверх.
 
[[Координати]]
 
Далі ширина та висота кнопки, Останнє число - початковий стан кнопки. 1 - натиснута, 0 - відпущена. В кінці ще можна додавати рядок з спливаючою підказкою, та якщо підпис достатньо інформативний в цьому немає необхідності.
 
Наступний оператор аналогічно створює подібну кнопку. Але ми малювали ще елемент іншого виду - для вводу тексту:
 
<source lang="python">
Blender.Draw.String("f(x,y)=",3,160,50,180,20,functiontext,200,"Input surface function here",changestring)
</source>
 
Перший параметр - підпис. Цей підпис буде стояти зліва від тексту що ми будемо вводити, і буде займати ширину нашого компонента. Тому упевніться що і для вводу виділено достатньо. Наступні п'ять параметрів як і у кнопки - переключателя: ідентифікатор події, координати, розмір. Після них йде рядок (<code>functiontext</code>) - у якому вказують текст що буде стояти в полі для вводу за замовчуванням. Після нього - число (200) - максимальна кількість символів для вводу (не більше 399). Потім підказка, і останнє - функція зворотнього виклику <code>changestring</code>. Ця функція викликається після введення тексту, і їй передаються два аргументи - спершу ідентифікатор події, а потім параметр.
 
Стандартний обробник <code>button</code>, що передавався <code>Blender.Draw.Register</code> нам не підійшов, бо в його параметрах є тільки <code>event</code> - ідентифікатор компонента що запустив подію. А нам ще треба отримувати введений текст.
 
Розглянемо спочатку обробник <code>button</code>:
 
<source lang="python">
def button(evt):
if evt == 1: # Якщо натиснута кнопка з ідентифікатором 1
genheightmap(f,minx,miny,maxx,maxy,resolution,functiontext) # Створити карту висот, з заданими параметрами
Blender.Window.Redraw() # Та обновити вміст вікон блендера
</source>
 
Ось так просто. Так само туди вписуємо обробку подій для інших кнопок. <code>if evt == 2</code> і так далі.
 
Функція - обробник спеціально для текстів нічим не складніша. Текст передається у змінну <code>val</code>. Єдиний недолік - вивід проводиться через глобальні змінні, та я не знаю як можна зробити інакше.
 
<source lang="python">
def changestring(event,val):
if event==3:
global functiontext
functiontext=val
</source>
 
Якщо кнопка для вводу тексту мала ідентифікатор 3, і функцію обробник - <code>changestring</code>, то з її допомогою можна було б змінювати вміст глобальної змінної <code>functiontext</code>.
 
Нам залишилось розглянути тільки функцію:
 
<source lang="python">
def event(evt,val):
if evt == Blender.Draw.ESCKEY:
Blender.Draw.Exit()
return
</source>
 
Вона майже нічого не робить, окрім того, що при натисненні клавіші Esc припиняє роботу скрипта.
 
Список всіх констант для клавіш, та інших подій можна знайти на [http://www.blender.org/documentation/249PythonDoc/Draw-module.html сторінках документації].
 
Ну, і звісно спочатку варто було зареєструвати глобальні змінні, та створити функцію що малює поверхню:
 
<source lang="python">
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})
</source>
 
Увесь скрипт цілком виглядає так:
 
<source lang="python">
#!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)
</source>
 
== Посилання ==
#[http://www.blender.org/documentation/249PythonDoc/index.html Документація по Blender API]
 
== Посилання ==