Практическое применение макроязыка программирования Maxscript

Здравствуйте уважаемые читатели! Вашему вниманию предлагается урок, в котором будет рассмотрено множество интересных возможностей maxscript'а. Они, на мой взгляд, будут полезны не только 3д-художникам, желающим разнообразить свои познания, но и инженерам - программистам.
Основу урока составляет описание практического применения maxscript'а для расчета аэродинамических характеристик летательных аппаратов на гиперзвуковом участке полета. Реализованная программа была многократно испытана и ее результаты были подвергнуты тщательному анализу. Как выяснилось, относительная погрешность, при сравнении с экспериментальными данными, составляет всего 5-6%!
Заранее приношу свои извинения за возможные ошибки и упущения. Сам я не являюсь профессиональным программистом, но все же постараюсь описать элементы программы в максимально доступной форме.
Итак, начнем:
Как и во всех современных языках программирования в макроязыке применяется цветовая идентификация:
Зеленым - обозначаются, сопровождающие программу, пояснения
Синим - обозначаются различные функции Maxscript'a, коих бесчисленное множество!:)
Коричневым - обозначаются различные тексты, которые будут отображаться в интерфейсе программы
Черным - все остальное.
Так как наша программа будет заниматься не только расчетами, но и построением графиков, то нам пригодиться стандартная графическая функция, которую я выдернул из прилагающихся туториалов по maxscript'у:
-- создание графической функции <BLine> для построения прямых линий
function BLine bmp x1 y1 x2 y2 c =
(
local ary = #(c)
-- assign originals
local Xb = x1 as integer
local Yb = y1 as integer
-- build the line deltas
local dX = x2-x1 as float
local dY = y2-y1 as float
-- straight horiz
if dy == 0.0f do
(
local xsign = 1
if xb > x2 then xsign = 1 as integer
if x2 < xb then xsign = -1 as integer
setPixels bmp [xb,yb] ary
while xb != x2 do
(
xb += xsign
setPixels bmp [xb,yb] ary
)
return true
)
-- straight vertical
if dx == 0.0f do
(
local ysign = 1
if yb > y2 then ysign = 1 as integer
if y2 < yb then ysign = -1 as integer
setPixels bmp [xb,yb] ary
while yb != y2 do
(
yb += ysign
setPixels bmp [xb,yb] ary
)
return true
)
-- no straights, go for bresenham line slide
-- set up the movements
local xsign = 1
if xb > x2 then xsign = 1 as integer
if x2 < xb then xsign = -1 as integer
local ysign = 1
if yb > y2 then ysign = 1 as integer
if y2 < yb then ysign = -1 as integer
dx = abs(dx)
dy = abs(dy)
setPixels bmp [xb,yb] ary
-- line more vertical than horizontal
if dx < dy then
(
p = 2 * dx - dy
const1 = 2 * dx
const2 = 2 * (dx - dy)
while yb != y2 do
(
yb += ysign
if p < 0 then ( p = p + const1 )
else
(
p += const2
xb += xsign
)
setPixels bmp [xb,yb] ary
)
)
-- line more horizontal than vertical
else
(
p = 2 * dy - dx
const2 = 2 * (dy - dx)
const1 = 2 * dy
while xb != x2 do
(
xb = xb + xsign
if p < 0 then ( p = p + const1 )
else
(
p = p + const2
yb = yb + ysign
)
setPixels bmp [xb,yb] ary
)
)
)
При желании можно обойтись и без нее, но все-таки с этой функцией интерфейс программы легче воспринимается человеческим глазом.
Теперь приступим к созданию плавающего окна и его интерфейса:
Присвоим переменным параметры ширины и высоты плавающего окна и встроенной в него пиксельной карты, которая затем пригодится нам для построения графиков функций:
-- объявление переменных
M_width = 860 -- ширина плавающего окна
M_hight = 490 -- высота плавающего окна
R_width = 800 -- ширина пиксельной карты
R_hight = 400 -- высота пиксельной карты
Создадим плавающее окно, которое я назвал <Построитель аэродинамических функций>:
-- создание нового плавающего окна <Построитель аэродинамических функций>
Main = newRolloutFloater "Построитель аэродинамических функций" M_width M_hight
Далее:
-- объявление внутри плавающего окна <Построитель аэродинамических функций> локального свитка <Окно просмотра>
rollout Aero "Окно просмотра"
Любой локальный свиток должен иметь начало и конец, которые обозначаются соответственно <(>, <)>:
(-- начало свитка <Aero>
Теперь, когда мы объявили начало локального свитка, можно начинать вставлять различные элементы интерфейса:
-- создание пиксельной карты <gr>
bitmap gr width:R_width height:R_hight color: white
-- создание цифровых меток для маркировки оси ординат (левый край)
label lbl21 "-2.0" pos:[0,395] width:20 height:18
label lbl22 "-1.5" pos:[0,350] width:20 height:18
label lbl23 "-1.0" pos:[0,300] width:20 height:18
label lbl24 "-0.5" pos:[0,250] width:20 height:18
label lbl25 "0.0" pos:[0,200] width:20 height:18
label lbl26 "0.5" pos:[0,150] width:20 height:18
label lbl27 "1.0" pos:[0,100] width:20 height:18
label lbl28 "1.5" pos:[0,50] width:20 height:18
label lbl29 "2.0" pos:[0,0] width:20 height:18
-- создание цифровых меток для маркировки оси ординат (правый край)
label lbl51 "-2.0" pos:[825,395] width:20 height:18
label lbl52 "-1.5" pos:[825,350] width:20 height:18
label lbl53 "-1.0" pos:[825,300] width:20 height:18
label lbl54 "-0.5" pos:[825,250] width:20 height:18
label lbl55 "0.0" pos:[825,200] width:20 height:18
label lbl56 "0.5" pos:[825,150] width:20 height:18
label lbl57 "1.0" pos:[825,100] width:20 height:18
label lbl58 "1.5" pos:[825,50] width:20 height:18
label lbl59 "2.0" pos:[825,0] width:20 height:18
-- создание цифровых меток для маркировки оси абсцисс (горизонтальная ось)
label lbl30 "0" pos:[22,407] width:20 height:18
label lbl31 "10" pos:[60,407] width:20 height:18
label lbl32 "20" pos:[104,407] width:20 height:18
label lbl33 "30" pos:[148,407] width:20 height:18
label lbl34 "40" pos:[192,407] width:20 height:18
label lbl35 "50" pos:[240,407] width:20 height:18
label lbl36 "60" pos:[284,407] width:20 height:18
label lbl37 "70" pos:[329,407] width:20 height:18
label lbl38 "80" pos:[373,407] width:20 height:18
label lbl39 "90" pos:[418,407] width:20 height:18
label lbl40 "100" pos:[458,407] width:20 height:18
label lbl41 "110" pos:[500,407] width:20 height:18
label lbl42 "120" pos:[545,407] width:20 height:18
label lbl43 "130" pos:[590,407] width:20 height:18
label lbl44 "140" pos:[635,407] width:20 height:18
label lbl45 "150" pos:[679,407] width:20 height:18
label lbl46 "160" pos:[724,407] width:20 height:18
label lbl47 "170" pos:[768,407] width:20 height:18
label lbl48 "180" pos:[812,407] width:20 height:18
Цифровые метки, в данном случае, необходимы только для повышения удобства чтения полученных данных. Надеюсь, что пока понятно, что обозначают , :, если, все таки возникли вопросы, то просто поэкпериментируйте с параметрами и, думаю, все встанет на свои места.
Далее:
-- создание внутри локального свитка 4-х групп:
-- первая
groupBox grp1 "Построение аэродинамических коэффициентов в связанной и поточной системе координат" pos:[5,420] width:835 height:47
--(
radiobuttons copy_type
labels:#("Cx ", "Cy ", "Cz ", "Cxa ", "Cya ", "K = (Cya/Cxa) ") pos: [260,440]
default:1
pickbutton bn_Master "Выделить объект" pos:[10,435] width:130 height:27
button knopka "Построить" pos:[705,435] width:130 height:27
--)
-- вторая
group "Переменные величины"
(
spinner x1 "Аэродинамический коэффициент K :" range:[0,3,2] type:#float pos:[170,490] width:100
spinner x2 "Шаг:" range:[0.1,90,5] type:#float pos:[640,490] width:50
--spinner x3 "Number of points" range:[36,800,36] type:#integer pos:[580,490] width:80
label lbl1 "Площадь миделя:" pos:[340,490] width:100 height:18
editText edt1 text:"3.14159" pos:[430,490] width:100 height:18
)
-- третья
group "Быстрое построение простых объектов (площадь миделя: 3.14159; позиция центра: [0,0,0])"
(
button Constr_sphere "Создать сферу" pos:[10,535] width:100 height:27
spinner CS_rad "радиус:" range:[0,100,1] type:#float pos:[150,545] width:55
spinner CS_s "число сегментов:" range:[0,100,18] type:#integer pos:[285,545] width:75
button Constr_cone "Создать конус (высота h=1)" pos:[685,535] width:150 height:27
spinner C_rad "радиус:" range:[0,100,1] type:#float pos:[480,545] width:55
spinner C_s "число сторон:" range:[0,100,24] type:#integer pos:[600,545] width:65
)
-- четвертая
group "Вывод данных"
(
button Otp "Записать данные в файл" pos:[10,590] width:140 height:27
label lbl8 "по адресу:" pos:[155,597] width:70 height:18
editText edt2 text:"D:\\out1.txt" pos:[210,597] width:70 height:18
button disp "Отобразить и сохранить" pos:[685,590] width:150 height:27
button Vert "Записать в файл координаты вершин (по фэйсам)" pos:[290,590] width:260 height:27
label lbl9 "по адресу:" pos:[555,597] width:70 height:18
editText edt3 text:"D:\\out2.txt" pos:[610,597] width:70 height:18
)
Группы созданы двумя разными способами, но дают почти один и тот же результат. Какая же между ними разница?
С помощью переключателей
Но здесь может возникнуть небольшая трудность с отображением заданного интерфейса, чтобы ее избежать, просто добавьте ниже следующие строки:
)-- конец свитка <Aero>
Addrollout Aero main -- добавление свитка <Aero> к плавающему окну
Если все сделано правильно, то Вы должны получить следующий результат:
Изучив все параметры удалите строки:
)-- конец свитка <Aero>
Addrollout Aero main -- добавление свитка <Aero> к плавающему окну
Потому что они понадобятся нам позже.
Итак, создав необходимый интерфейс, можно приступить к описанию событий (т.е. что у нас произойдет при нажатии на ту или иную кнопку):
-- при нажатии на кнопку <Записать в файл координаты вершин (по фэйсам)> производится запись координат вершин каждого треугольника выделенного объекта в текстовый файл
on Vert pressed do
(
local o=convertToMesh $ -- локальной переменной присваивается, преобразованный в Mesh(сетку), выделенный объект
local s=edt3.text as string -- локальной переменной присваивается путь, по которому будет сохранен текстовый файл
local d=createFile s -- по указанному адресу создается файл (пока пустой)
for f in o.faces do -- объявление цикла по фэйсам (треугольникам, из которых состоит объект)
(
Vx=(meshop.getVertsUsingFace o f.index) as array -- получение массива фэйсов
for i=1 to 3 do -- цикл по трем вершинам рассматриваемого фэйса
(
local b=getVert o Vx[i] -- переменной "b" присваивается значение i-ой вершины фэйса
format "% % %" b.x b.y b.z to:d -- запись координат [x,y,z] i-ой вершины
format "\n" to:d -- запись перехода на следующую строку
)
)
close d -- закрывает созданный файл
Запись координат вершин(по фэйсам) необходима для того, чтобы появилась возможность обрабатывать данные не только в 3Dstudio Max'е, но и в других программах, таких как Маткад или Матлаб.
-- при нажатии на кнопку <Создать сферу> будет создана сфера с указанными в обработчике события параметрами
on Constr_sphere pressed do
(
local s = sphere radius: CS_rad.value segs: CS_s.value smooth:off
max tool zoomextents all -- показать сферу в области просмотра
)
Значения
-- при нажатии на кнопку <Создать конус> будет создан конус с указанными в обработчике события параметрами
on Constr_cone pressed do
(
local c = cone radius1: C_rad.value sides: C_s.value height: 1 heightsegs: 1 capsegs: 1 smooth:off
rotate c 90 [0,1,0] -- повернуть конус на 90 градусов относительно оси Y
move c [-0.3, 0, 0] -- передвинуть конус по оси X на -0.3 единицы
max tool zoomextents all -- показать конус в области просмотра
)
-- при нажатии на кнопку <Выделить объект> и после выделения объекта в сцене произойдет следующее событие
on bn_Master picked obj do
(
obj.name = "Выделен" -- кнопку <Выделить объект> переименовать в <Выделен>
bn_Master.text=obj.name -- присвоить объекту имя <Выделен>
select $Выделен -- выделить объект в сцене с именем <Выделен>
)
-- при открытии плавающего окна <Построитель аэродинамических функций> произойдет следующее событие
on Aero open do
(
-- оформление дизайна пиксельной карты
local bm = bitmap R_width R_hight color: white -- создание пиксельной карты
local col = color 220 220 220 -- создание локальной переменной со значением цвета в виде массива
for x=0 to R_width by 44.444444 do( -- создание вертикальных линий
BLine bm x 0 x R_hight col )
for y=0 to R_hight by 50 do( -- создание горизонтальных линий
BLine bm 0 y R_width y col )
BLine bm 0 ((R_hight)/2) R_width ((R_hight)/2) red -- создание оси абсцисс красного цвета
gr.bitmap = bm -- записать полученное изображение в пиксельную карту
)
Проще говоря, данное событие описывает то, что Вы увидите на месте пиксельной карты при первом запуске скрипта (Ctrl+e). Как же все таки создаются вертикальные и горизонтальные линии?
Эти строки
for x=0 to R_width by 44.444444 do( -- создание вертикальных линий
BLine bm x 0 x R_hight col )
означают примерно следующее:
Для x от 0 до "R_width" (значение было задано в начале) с интервалом 44.4444 выполнить функцию
Далее:
-- при нажатии на кнопку <Отобразить и сохранить> в группе <Вывод данных> произойдет следующее событие
on disp pressed do
(
display gr.bitmap -- отображение полученной пиксельной карты
)
Осталось описать события к двум кнопкам - это кнопки <Построить> и <Записать данные в файл>. Так как описания событий к этим кнопкам довольно похожи и, при этом, довольно объемны, то ограничусь описанием события только для кнопки <Построить>, а описание для кнопки <Записать данные в файл> можно, при желании, посмотреть в оригинале программы.
Событие для кнопки <Построить> интересно тем, что в нем производится расчет площади, как отдельных фэйсов, так и всего объекта, так же вычисляются нормали и вектора из центра глобальной системы координат к центрам фэйсов. Все эти операции можно выполнять с помощью стандартных функций макроязыка, а можно вычислять самим с помощью методов аналитической геометрии. В событии описываются, как тот, так и другой способ. Способы, которые дают быстрейший результат - активированы, а те, что замедляют расчеты приведены в виде подсказок:
-- при нажатии на кнопку <Построить> произойдет следующее событие
on knopka pressed do
( -- начало события <knopka>
clearlistener() -- отчистка "Слушателя" (вызывается клавишей F11 при активном главном окне 3dstudio MAX!)
-- оформление дизайна пиксельной карты
local bm = bitmap R_width R_hight color: white -- создание пиксельной карты
local col = color 220 220 220 -- создание локальной переменной со значением цвета линий в виде массива
for x=0 to R_width by 44.444444 do( -- создание вертикальных линий
BLine bm x 0 x R_hight col )
for y=0 to R_hight by 50 do( -- создание горизонтальных линий
BLine bm 0 y R_width y col )
BLine bm 0 ((R_hight)/2) R_width ((R_hight)/2) red -- создание оси абсцисс красного цвета
ary = #(blue) -- создание переменной <ary> со значением цвета построительных точек (цвет графика функции)
-- объявление переменных необходимых для расчета
o = converttoMesh $ -- преобразование выделенного объекта в доступную для редактирования сетку
Ka = x1.value -- переменной <Ka> присваивается значение счетчика <x1>
Smid = edt1.text as float -- переменной <Smid> присваивается значение в текстовом поле <edt1>
-- начало циклического изменения значений угла <i> от 0 до 180 градусов с шагом, указанным счетчиком <x2>
for i = 0 to 180 by x2.value do
( -- начало цикла по <i>
P1 = [0.0, 0.0, 0.0] -- объявление переменной <P1> для обнуления суммы <P1=P1+P> в цикле по фэйсам при первой итерации
potok = [cos i, 0, sin i] -- объявление переменной потока (обеспечивается вращение единичного вектора против часовой стрелки на 180 градусов)
-- начало цикла обращения к фэйсам объекта
for f in o.faces do
(-- начало цикла по <f>
-- получение массива фэйсов
index = (meshop.getVertsUsingFace o f.index) as array
-- получение координат каждой вершины рассматриваемого фэйса в виде векторов
a = getVert o index[1] -- координаты первой вершины
b = getVert o index[2] -- координаты второй вершины
c = getVert o index[3] -- координаты третьей вершины
-- расчет площади рассматриваемого фэйса (где функция <length> - вычисление длины вектора, а функция <cross> - векторное произведение двух векторов)
-- вычисление площади фэйса с помощью стандартной функции макроязыка
--local Square = meshop.getFaceArea o f.index
-- вычисление площади фэйса методом аналитической геометрии
vec1 = b - a
vec2 = c - a
Square = (length(cross vec1 vec2))/2
-- получение вектора нормали к данному фэйсу
local N = getFaceNormal o f.index
--an=(b.y-a.y)*(c.z-a.z)-(b.z-a.z)*(c.y-a.y)
--bn=(b.z-a.z)*(c.x-a.x)-(b.x-a.x)*(c.z-a.z)
--cn=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)
--N = [an, bn, cn]
Nn = normalize N -- нормализация(преобразование к величине от 0 до 1) нормали фэйса
-- вычисление вектора, направленного из центра глобальной системы координат к центру данного фэйса
local Cf = meshop.getFaceCenter o f.index
--amc = (a.x + b.x + c.x)/3
--bmc = (a.y + b.y + c.y)/3
--cmc = (a.z + b.z + c.z)/3
--Cf = [amc, bmc, cmc]
Cf_n = normalize Cf -- его нормализация
-- направляем все нормали в одну сторону - наружу объекта (где функция <dot> - это функция скалярного перемножения двух векторов)
N_edit = if (dot Nn Cf_n)>0 then Nn else -Nn
local Uv = (dot N_edit potok)^2
-- расчет значения вектора потока в данном фэйсе
P = if (dot N_edit potok)<0 then ((Uv*Ka*Square)*N_edit) else [0.0, 0.0, 0.0]
-- суммирование значений векторов потока по всем фэйсам
P1 = P1+P
)-- конец цикла по <f>
-- привидение к безразмерному виду суммарного значения вектора потока делением на площадь миделя (напомню, что Pd.x=Cx, Pd.y=Cy, Pd.z=Cz), знак минуса здесь необходим для зеркального отображения графика относительно оси X
Pd = P1/Smid
-- если убрать перед словами <print> знаки тире, то в "Слушатель" будут выведены значения <i> и <P_end>, это было удобно при отработке программы
--print "Угол"
--print i
--print "Значение потока"
-- вычисление значений аэродинамических коэффициентов <Cxa> и <Cya> в поточной системе координат
Cxa = (Pd.x)*cos(i)+(Pd.z)*sin(i)
Cya = (Pd.z)*cos(i)-(Pd.x)*sin(i)
Kachestvo = (-Cya)/(Cxa) -- вычисление аэродинамического качества
-- в зависимости от выбранного положения <radiobuttons copy_type> переменной <P_end> присваивается нужное значение
P_end = (if copy_type.state == 1 then Pd.x else
if copy_type.state == 2 then Pd.y else
if copy_type.state == 3 then Pd.z else
if copy_type.state == 4 then Cxa else
if copy_type.state == 5 then Cya else Kachestvo)
--print P_end
setpixels bm [4.4444*i, 100*P_end + 200] ary -- запись значений в переменную <bm>
gr.bitmap = bm -- запись значений переменной <bm> в пиксельную карту
) -- конец цикла по <i>
) -- конец события <knopka>
Хотелось бы обратить Ваше внимание на то, каким образом производится выбор с помощью
Ну вот, почти и все, осталось только закрыть свиток
)-- конец свитка <Aero>
Addrollout Aero main -- добавление свитка <Aero> к плавающему окну
-- объявление свитка <Aero2>
rollout Aero2 "Помощь"
(
label lbl1a "Данная программа производит расчет и построение, по уточненной теории Ньютона, сверхзвуковых стационарных аэродинамических характеристик элементов
летательных аппаратов. К таковым относятся: аэродинамические коэффициенты в связанной системе координат Cx, Cy, Cz; аэродинамические коэффициенты в
скоростной (поточной) системе координат Cxa, Cya; аэродинамическое качество K = Cya/Cxa.
Плюсы и недостатки программы:
Плюсами данной программы, несомненно, являются скорость получения результата с возможностью его последующего сохранения или в виде
изображения, в любом из распространенных сейчас форматов, или записи результатов расчета в текстовый файл в виде матрицы столбца.
К недостаткам относятся - а) направление осей связанной системы координат в 3DstudioMAX'е немного отличается от общепринятой в аэро-
динамике (ось Z в 3DMAX'е соответствует оси Y в аэродинамике), б)чтобы скопировать в буфер графические построения с цифровыми метками по осям -
- необходимо воспользоваться сочетанием клавиш <Alt+Print Screen>, при использовании же кнопки <Отобразить и сохранить> будет сохранено
только изображение построенной функции без цифровых меток!
Порядок проведения расчетов и пояснения к интерфейсу программы:
1. Необходимо создать объект и правильно расположить его в области просмотра. Для создания объектов сложной формы необходимо воспо-
льзоваться стандартными средствами построения объектов в 3DstudioMAX'е, а для демонстрационных расчетов можно воспользоваться кнопками
<Создать сферу> и <Создать конус> в группе <Быстрое построение простых объектов (площадь миделя: 3.14159; позиция центра: [0,0,0])>. Эти кнопки
создают, соответственно, сферу и конус в центре ГСК. Все параметры, в данном случае, заданные по умолчанию(радиус, площадь миделя)-
- заданы правильно.
2. Необходимо указать(выделить) объект кнопкой <Выделить объект>. После указания объекта кнопка изменит свое название на <Выделен>.
3. В группе <Переменные> задаются такие параметры, как аэродинамический коэффициент, площадь миделя и шаг, с которым будут про-
изводиться расчеты.
4. В группе <Построение аэродинамических коэффициентов в связанной и поточной системе координат>, для построения графика, необходимо
выбрать нужную функцию и нажать на кнопку <Построить>.
5. После проведенных исследований может понадобится сохранение результатов расчета или в виде текстового файла, или в виде изображения.
Для этого в группе <Вывод данных> есть, соответственно, две кнопки <Записать данные в файл> и <Отобразить и сохранить>, а если же понадобится
сохранить координаты вершин (по фэйсам) для последующих расчетах в других программах, то необходимо воспользоваться кнопкой
<Записать в файл координаты вершин (по фэйсам)>" pos:[10,10] width:860 height:390
)
Addrollout Aero2 main rolledUp:true
-- объявление свитка <Aero3>
rollout Aero3 "О программе"
(
label lbl1b "Автор программы: Александр Якушев
Принимали участие в создании программы: Дмитрий Агафонов
Дмитрий (DG) http://dgsd.nm.ru/
Фёдор Мосалов
Руководитель: Миненко Виктор Елисеевич" pos:[10,10] width:790 height:70
)
Addrollout Aero3 main rolledUp:true
Наиболее важным, в плане познания макроязыка, является строка
Написать окончательно программу по этому уроку невозможно, так как здесь нет описания события к кнопке <Записать данные в файл>, но думаю, что это не проблема, потому что основной целью данного урока была попытка описать те или иные возможности макроязыка для последующего применения в других областях.
Оригинал же скрипта можно скачать здесь.
Хочу выразить огромную благодарность за неоценимую помощь и поддержку Дмитрию (DJ), моим, уже бывшим :), одногрупникам Дмитрию Агафонову и Фёдору Мосалову, а также руководителю дипломного проекта в МГТУ им. Н.Э. Баумана Миненко Виктору Елисеевичу.
Если у Вас возникли вопросы, то пишите, постараюсь помочь.
Удачи!