| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
Практическое применение макроязыка программирования 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 Наиболее важным, в плане познания макроязыка, является
строка Написать окончательно программу по этому уроку невозможно, так как здесь нет описания события к кнопке <Записать данные в файл>, но думаю, что это не проблема, потому что основной целью данного урока была попытка описать те или иные возможности макроязыка для последующего применения в других областях. Удачи! |
|
| ||||||||||||||||
|