Уроки: Общие принципы

MAXScript. Параметрический арочный сплайн

Макроскрипт ArchedSpline: параметрический арочный сплайн

Мой псевдоним 1acc, я пишу скрипты в 3dsmax и в данном уроке хочу продемонстрировать, как это делать быстро, просто и эффективно. На примере ArchedSpline постараюсь охватить все стадии скриптопроизводства, от идеи до реализации. Скриптовый элемент урока состоит из 3-х частей - независимых фрагментов общего кода, которые можно копировать, запускать и смотреть, что получилось. Финальный скрипт нужно устанавливать, поэтому его кода в уроке нет - желающие могут его посмотреть уже в 3dsmax. В конце урока приведен скриптовый фрагмент с комментариями, который объясняет, что нужно добавить до финала.

Идея: оптимизировать создание и редактирование сплайна - основы для арочного проема. Сплайн такого вида создавать стандартными средствами слишком долго и очень неудобно, особенно, если нужна точность, хочется максимально оптимизировать процесс, чем я сейчас и займусь.

Структура кода и интерфейс: финальный скрипт будет макроскриптом, то есть кнопка на панели, при нажатии на которую он запускается. При запуске открывается окно с четырьмя спиннерами, отвечающими за параметры сплайна и кнопкой Create, создающей сплайн. Параметры: общая длина, общая высота, высота арки, радиус арки - считаю их необходимыми и достаточными. По-английски в интерфейсе они будут в том же порядке называться соответственно: Full Length, Full Height, Arc Height, Arc Radius.

Входные данные: прежде чем создавать скриптом любой объект, необходимо определиться, в какой системе координат (СК) он будет находиться: в любой или заранее предопределенной. В моем случае СК неопределена, так как положение плоскости стены неизвестно и она может располагаться как угодно в пространстве. Значит, при создании сплайна необходимо задать СК и его параметры. СК задается тремя точками A B C, определяющими плоскость XY, направление оси Z для меня в данном случае неважно. Сэкономлю время и введу начальные габариты сплайна одновременно с указанием точек, учитывая то, что исходная дуга составляет половину окружности (180 градусов) с диаметром, равным длине вектора AB. Точки A и B - крайние точки основания сплайна. Точка C может находиться в любом месте на прямой n и определяет высоту проема, то есть длина перпендикуляра CN, опущенного из нее на прямую AB равна либо полной высоте сплайна (вариант 1), либо высоте сегмента до дуги (вариант 2 - в случае, когда невозможно начертить 180-градусную дугу с максимумом в точке C). После создания сплайна, соответствующего габаритам, его можно отредактировать по 4-м параметрам: изменить длину, общую высоту, высоту арки и радиус арки. Последние два параметра взаимозависимые, т.е. при изменении одного меняется и другой. Возможность редактировать параметры должна быть доступна всегда, поэтому их для каждого сплайна нужно где-то хранить, но об этом после.

Итак, определился с геометрией, входными данными, параметрами, теперь немного математики. Я не буду описывать решение, кто захочет - может сам посчитать, это задача для старшего класса средней школы. Один очень важный момент - задача решается в системе координат сплайна с центром в точке O [0,0,0], вектор AB (или BA, мне неважно, т.к. сплайн симметричный) - направление оси x, вектор NC - направление оси y. Как этот сплайн поставить в мировую СК, т.е. в соответствии с реальными координатами точек ABC - уже задача университета :-) и ее решение вы узнаете дальше, когда я буду описывать код.

Сейчас привожу только конечные формулы в СК сплайна (сразу в синтаксисе MAXScript):

R=(l^2+h^2)/(2*h)
h=R-sqrt(R^2-l^2)
a=asin(l/R)
c=[0, H, 0]-[0, R, 0]


Здесь: R - радиус арки, h - высота арки, H - общая высота, l - половина общей длины, a - половина угла дуги (необходимо знать для построения дуги), c - координата центра дуги, sqrt - квадратный корень, asin - арксинус, ^2 - возведение в квадрат.

Ну все, теперь наконец можно приступать непосредственно к программированию.

Я оставляю "на потом" все детали, связанные с описанием интерфейса и создаю две основных функции.

Первая: getcoordmatrix - получает СК по трем точкам. Точки для функции либо задаются пользователем при создании сплайна, либо берутся из вершин сплайна, в случае, когда сплайн уже создан и надо получить его СК для редактирования сплайна.

Вторая: editarchedspline - создает арочный сплайн по параметрам.

--СКРИПТОВАЯ ЧАСТЬ 1
fn getcoordmatrix pp = ( --НА ВХОД ФУНКЦИИ ПОСТУПАЕТ МАССИВ ИЗ ТРЕХ ТОЧЕК (КООРДИНАТЫ ABC), ЕСЛИ СПЛАЙНА ЕЩЕ НЕТ ИЛИ 0, ЕСЛИ СПЛАЙН УЖЕ ЕСТЬ
    if pp==0 then ( --ЕСЛИ ПОСТУПАЕТ 0, ТО ЗНАЧИТ СПЛАЙН УЖЕ СОЗДАН И МАССИВ НАДО ЗАПОЛНИТЬ ОПРЕДЕЛЕННЫМИ КООРДИНАТАМИ ВЕРШИН СПЛАЙНА
    pp=#(0,0,0) --СПЛАЙН СОЗДАЕТСЯ ТАКИМ ОБРАЗОМ, ЧТО НОМЕРА ВЕРШИН ИДУТ СТРОГО ПО ПОРЯДКУ И СООТВЕТСТВУЮТ ИМЕННО НУЖНЫМ МНЕ ТОЧКАМ
    pp[1]=getKnotPoint currentarchedspline 1 2 --ТОЧКА A
    pp[2]=getKnotPoint currentarchedspline 1 3 --ТОЧКА B
    pp[3]=getKnotPoint currentarchedspline 1 1 --ТОЧКА C
    )
    in coordsys world ( --РАСЧЕТ В МИРОВОЙ СИСТЕМЕ КООРДИНАТ И НЕВАЖЕН ПОРЯДОК ТОЧЕК ОСНОВАНИЯ, Т.Е. МОЖНО И ABC И BAC
    ppc=(pp[1]+(pp[2]-pp[1])/2) --КООРДИНАТА ТОЧКИ O
    win_length=(distance pp[2] pp[1])/2 --ПОЛОВИНА ДЛИНЫ AB
    v1=(pp[3]-ppc) --ВЕКТОР OC
    vx=normalize (pp[1]-ppc) --НОРМИРОВАННЫЙ (ДЛИНА РАВНА 1) ВЕКТОР OA, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ x СК СПЛАЙНА
    vdot=dot v1 vx --ДЛИНА ПРОЕКЦИИ ВЕКТОРА OC НА ОСЬ x
    pp4=ppc+vx*vdot --КООРДИНАТА ТОЧКИ N
    win_height=distance pp[3] pp4 --ОБЩАЯ ВЫСОТА (РАССТОЯНИЕ МЕЖДУ ТОЧКАМИ C И N)
    vy=normalize (pp[3]-pp4) --НОРМИРОВАННЫЙ ВЕКТОР NC, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ y СК СПЛАЙНА
    vz=normalize (cross vx vy) --НОРМИРОВАННЫЙ ВЕКТОР, ПЕРПЕНДИКУЛЯРНЫЙ ОСЯМ x И y, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ z СК СПЛАЙНА
    m=matrix3 vx vy vz ppc --СОЗДАЮ МАТРИЦУ 4x3 ИЗ ПОЛУЧЕННЫХ ЕДИНИЧНЫХ ВЕКТОРОВ И КООРДИНАТЫ ЦЕНТРА
    )
    m --ВОЗВРАЩАЮ МАТРИЦУ
)   
   
fn editarchedspline wLen wHei wARad wAHei = ( --НА ВХОД ФУНКЦИИ ПОСТУПАЮТ 4 ПАРАМЕТРА СПЛАЙНА
    l=wLen/2.0 --ПОЛОВИНА ОСНОВАНИЯ
    anglealpha=asin(l/wARad) --ПОЛОВИНА УГЛА ДУГИ
    arc_center=[0, wHei, 0]-[0, wARad, 0] --ЦЕНТР ДУГИ
       
    ca=arc pos: [0,0,0] radius: wARad from: (90-anglealpha) to: (90+anglealpha) --СОЗДАЮ ДУГУ С ЦЕНТРОМ В НАЧАЛЕ КООРДИНАТ
    aw=SplineShape() --СОЗДАЮ ОСТАВШУЮСЯ ЧАСТЬ СПЛАЙНА (ПЕРЕВЕРНУТАЯ БУКВА П)
    addnewspline aw --ДОБАВЛЯЮ НОВЫЙ СПЛАЙН
    addKnot aw 1 #corner #line [-l, wHei-wAHei,0] --ДОБАВЛЯЮ 1 ВЕРШИНУ
    addKnot aw 1 #corner #line [-l,0,0] --ДОБАВЛЯЮ 2 ВЕРШИНУ
    addKnot aw 1 #corner #line [l,0,0] --ДОБАВЛЯЮ 3 ВЕРШИНУ
    addKnot aw 1 #corner #line [l, wHei-wAHei,0] --ДОБАВЛЯЮ 4 ВЕРШИНУ
    updateShape aw --ОБНОВЛЯЮ СПЛАЙН

    ca.pos=arc_center --ПЕРЕМЕЩАЮ ДУГУ В ЦЕНТР
    convertToSplineShape ca --КОНВЕРТИРУЮ ДУГУ В EDITABLE SPLINE
    addAndWeld aw ca 0.001 --ПРИСОЕДИНЯЮ ДУГУ К СПЛАЙНУ
    updateShape aw --ОБНОВЛЯЮ СПЛАЙН
    aw.wireColor=color 255 0 0 --ЗАДАЮ КРАСНЫЙ ЦВЕТ ДЛЯ СПЛАЙНА (ЧТОБЫ НЕ МОРГАЛО ПРИ РЕДАКТИРОВАНИИ)
    setUserProp aw "Length" wLen --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "Height" wHei --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "ARadius" wARad --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "AHeight" wAHei --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
   
    aw --ВОЗВРАЩАЮ СПЛАЙН КАК РЕЗУЛЬТАТ ФУНКЦИИ
    )

--ПРОВЕРОЧНЫЙ КОД
m=getcoordmatrix #([0,0,0], [100,100,0], [0 ,100,200])
currentarchedspline=editarchedspline 100 200 50 50
currentarchedspline.transform=m
--КОНЕЦ СКРИПТОВОЙ ЧАСТИ 1

Скопируйте в MAXScript/MAXScript Editor и выполните вышеприведенный код - создастся арочный сплайн по параметрам в заданной СК.

Теперь самое время заняться интерфейсом скрипта.

Интерфейс удобнее всего создавать и редактировать при помощи Visual MAXScript Editor (VME), но сохранять в отдельном файле и редактировать отдельно (Tools/Edit Rollout), а потом копировать в общий файл. Если редактировать в общем файле - существует опасность удаления или многократного дублирования кода событий для элементов интерфейса - потом замучаетесь править. Из-за этого глюка даже существует мнение, что лучше не использовать VME и делать все ручками без GUI - размещать элементы, двигать кнопки и т.д. Я сохраняю в отдельном файле и не имею проблем с интерфейсами и VME.

Запустите нижеприведенный код - это "чистый" интерфейс, вот такой можно и нужно сохранять отдельно, безболезненно редактировать посредством VME, а потом копировать в код свитка, в котором описываются переменные, функции и события элементов интерфейса.

Интерфейс

--СКРИПТОВАЯ ЧАСТЬ 2
rollout floaterarchedspline "ArchedSpline 1.0" width:138 height:118
(
label lbl_fulllength "Full Length:" pos:[9,8] width:58 height:13
label lblfullheight "Full Height:" pos:[11,28] width:54 height:13
label lbl_archeight "Arc Height:" pos:[11,52] width:54 height:13
label lbl_arcradius "Arc Radius:" pos:[9,72] width:56 height:13

spinner spn_fulllength "" pos:[70,5] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_fullheight "" pos:[70,25] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_archeight "" pos:[70,49] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_arcradius "" pos:[70,69] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
checkbutton btn_create "Create" pos:[8,91] width:118 height:19 enabled: false checked: true
)
CreateDialog floaterarchedspline
--КОНЕЦ СКРИПТОВОЙ ЧАСТИ 2

Добавляю в интерфейс две функции, с которых начал, пишу события для элементов и т.д.

Код свитка:

--СКРИПТОВАЯ ЧАСТЬ 3
rollout floaterarchedspline "ArchedSpline 1.0" width:138 height:118 --ОПРЕДЕЛЯЮ СВИТОК
(
--МЕТКИ
label lbl_fulllength "Full Length:" pos:[9,8] width:58 height:13
label lblfullheight "Full Height:" pos:[11,28] width:54 height:13
label lbl_archeight "Arc Height:" pos:[11,52] width:54 height:13
label lbl_arcradius "Arc Radius:" pos:[9,72] width:56 height:13

--СПИННЕРЫ. В НАЧАЛЬНОМ СОСТОЯНИИ ИХ ИЗМЕНЯТЬ НЕЛЬЗЯ (enabled: false), Т.К. СПЛАЙН СОЗДАЕТСЯ ПО ТРЕМ ТОЧКАМ, А НЕ ПО ЗНАЧЕНИЯМ СПИННЕРОВ
spinner spn_fulllength "" pos:[70,5] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_fullheight "" pos:[70,25] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_archeight "" pos:[70,49] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
spinner spn_arcradius "" pos:[70,69] width:56 height:16 range:[0.001, 100000.0, 100.0] type:#float enabled: false
checkbutton btn_create "Create" pos:[8,91] width:118 height:19 enabled: false checked: true --КНОПКА-ФЛАЖОК, ПО РУССКИ НАЗОВУ "ВКЛЮЧАТЕЛЬ"

--ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ
local isOpen=off --ОТВЕЧАЕТ ЗА СОСТОЯНИЕ ОКНА ИНТЕРФЕЙСА (ОТКРЫТО/ЗАКРЫТО)
local currentarchedspline=0 --ХРАНИТ ТЕКУЩИЙ АРОЧНЫЙ СПЛАЙН
local win_length, win_height --ХРАНЯТ ДЛИНУ И ШИРИНУ ТЕКУЩЕГО СПЛАЙНА

--ФУНКЦИИ, К КОТОРЫМ БУДУТ ОБРАЩАТЬСЯ ЭЛЕМЕНТЫ ИНТЕРФЕЙСА
fn onoffspinners arg = (--ВКЛЮЧАЕТ/ВЫКЛЮЧАЕТ ВОЗМОЖНОСТЬ РЕДАКТИРОВАНИЯ СПИННЕРОВ В ЗАВИСИМОСТИ ОТ АРГУМЕНТА
    floaterarchedspline.spn_fulllength.enabled=arg
    floaterarchedspline.spn_fullheight.enabled=arg
    floaterarchedspline.spn_arcradius.enabled=arg
    floaterarchedspline.spn_archeight.enabled=arg
    )
   
fn setcorrectvalues = (--ОБНОВЛЯЕТ ЗНАЧЕНИЯ СПИННЕРОВ В ЗАВИСИМОСТИ ОТ ТЕКУЩЕГО СПЛАЙНА
    if isValidNode currentarchedspline then (--ЕСЛИ СПЛАЙН ЕСТЬ
    floaterarchedspline.spn_fulllength.value=getUserProp currentarchedspline "Length"
    floaterarchedspline.spn_fullheight.value=getUserProp currentarchedspline "Height"
    floaterarchedspline.spn_arcradius.value=getUserProp currentarchedspline "ARadius"
    floaterarchedspline.spn_archeight.value=getUserProp currentarchedspline "AHeight"
    )
    )
   
fn editarchedspline wLen wHei wARad wAHei = ( --НА ВХОД ФУНКЦИИ ПОСТУПАЮТ 4 ПАРАМЕТРА СПЛАЙНА   
    l=wLen/2.0 --ПОЛОВИНА ОСНОВАНИЯ
    anglealpha=asin(l/wARad) --ПОЛОВИНА УГЛА ДУГИ
    arc_center=[0, wHei, 0]-[0, wARad, 0] --ЦЕНТР ДУГИ

    ca=arc pos: [0,0,0] radius: wARad from: (90-anglealpha) to: (90+anglealpha) --СОЗДАЮ ДУГУ С ЦЕНТРОМ В НАЧАЛЕ КООРДИНАТ
    aw=SplineShape() --СОЗДАЮ ОСТАВШУЮСЯ ЧАСТЬ СПЛАЙНА (ПЕРЕВЕРНУТАЯ БУКВА П)
    addnewspline aw --ДОБАВЛЯЮ НОВЫЙ СПЛАЙН
    addKnot aw 1 #corner #line [-l, wHei-wAHei,0] --ДОБАВЛЯЮ 1 ВЕРШИНУ
    addKnot aw 1 #corner #line [-l,0,0] --ДОБАВЛЯЮ 2 ВЕРШИНУ
    addKnot aw 1 #corner #line [l,0,0] --ДОБАВЛЯЮ 3 ВЕРШИНУ
    addKnot aw 1 #corner #line [l, wHei-wAHei,0] --ДОБАВЛЯЮ 4 ВЕРШИНУ
    updateShape aw --ОБНОВЛЯЮ СПЛАЙН

    ca.pos=arc_center --ПЕРЕМЕЩАЮ ДУГУ В ЦЕНТР
    convertToSplineShape ca --КОНВЕРТИРУЮ ДУГУ В EDITABLE SPLINE
    addAndWeld aw ca 0.001 --ПРИСОЕДИНЯЮ ДУГУ К СПЛАЙНУ
    updateShape aw --ОБНОВЛЯЮ СПЛАЙН
    aw.wireColor=color 255 0 0 --ЗАДАЮ КРАСНЫЙ ЦВЕТ ДЛЯ СПЛАЙНА (ЧТОБЫ НЕ МОРГАЛО ПРИ РЕДАКТИРОВАНИИ)
    setUserProp aw "Length" wLen --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "Height" wHei --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "ARadius" wARad --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    setUserProp aw "AHeight" wAHei --СОХРАНЯЮ ПАРАМЕТРЫ В СВОЙСТВАХ ОБЪЕКТА
    aw --ВОЗВРАЩАЮ СПЛАЙН КАК РЕЗУЛЬТАТ ФУНКЦИИ
    )

fn getcoordmatrix pp = ( --НА ВХОД ФУНКЦИИ ПОСТУПАЕТ МАССИВ ИЗ ТРЕХ ТОЧЕК (КООРДИНАТЫ ABC), ЕСЛИ СПЛАЙНА ЕЩЕ НЕТ ИЛИ 0, ЕСЛИ СПЛАЙН УЖЕ ЕСТЬ
    if pp==0 then ( --ЕСЛИ ПОСТУПАЕТ 0, ТО ЗНАЧИТ СПЛАЙН УЖЕ СОЗДАН И МАССИВ НАДО ЗАПОЛНИТЬ ОПРЕДЕЛЕННЫМИ КООРДИНАТАМИ ВЕРШИН СПЛАЙНА
    pp=#(0,0,0) --СПЛАЙН СОЗДАЕТСЯ ТАКИМ ОБРАЗОМ, ЧТО НОМЕРА ВЕРШИН ИДУТ СТРОГО ПО ПОРЯДКУ И СООТВЕТСТВУЮТ ИМЕННО НУЖНЫМ МНЕ ТОЧКАМ
    pp[1]=getKnotPoint currentarchedspline 1 2 --ТОЧКА A
    pp[2]=getKnotPoint currentarchedspline 1 3 --ТОЧКА B
    pp[3]=getKnotPoint currentarchedspline 1 1 --ТОЧКА C
    )
    in coordsys world ( --РАСЧЕТ В МИРОВОЙ СИСТЕМЕ КООРДИНАТ И НЕВАЖЕН ПОРЯДОК ТОЧЕК ОСНОВАНИЯ, Т.Е. МОЖНО И ABC И BAC
    ppc=(pp[1]+(pp[2]-pp[1])/2) --КООРДИНАТА ТОЧКИ O
    win_length=(distance pp[2] pp[1])/2 --ПОЛОВИНА ДЛИНЫ AB
    v1=(pp[3]-ppc) --ВЕКТОР OC
    vx=normalize (pp[1]-ppc) --НОРМИРОВАННЫЙ (ДЛИНА РАВНА 1) ВЕКТОР OA, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ x СК СПЛАЙНА
    vdot=dot v1 vx --ДЛИНА ПРОЕКЦИИ ВЕКТОРА OC НА ОСЬ x
    pp4=ppc+vx*vdot --КООРДИНАТА ТОЧКИ N
    win_height=distance pp[3] pp4 --ОБЩАЯ ВЫСОТА (РАССТОЯНИЕ МЕЖДУ ТОЧКАМИ C И N)
    vy=normalize (pp[3]-pp4) --НОРМИРОВАННЫЙ ВЕКТОР NC, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ y СК СПЛАЙНА
    vz=normalize (cross vx vy) --НОРМИРОВАННЫЙ ВЕКТОР, ПЕРПЕНДИКУЛЯРНЫЙ ОСЯМ x И y, ЭТО ЕДИНИЧНЫЙ ВЕКТОР ОСИ z СК СПЛАЙНА
    m=matrix3 vx vy vz ppc --СОЗДАЮ МАТРИЦУ 4x3 ИЗ ПОЛУЧЕННЫХ ЕДИНИЧНЫХ ВЕКТОРОВ И КООРДИНАТЫ ЦЕНТРА
    )
    m --ВОЗВРАЩАЮ МАТРИЦУ
    )   
   
fn createarchedspline = ( --СОЗДАЮ СПЛАЙН   
    pp=#(0,0,0); checkpoints=0 --МАССИВ ТОЧЕК И ПРОВЕРОЧНАЯ ПЕРЕМЕННАЯ
    for i=1 to 3 do ( --ЦИКЛ ОТ ПЕРВОЙ ДО ТРЕТЬЕЙ ТОЧКИ
    t=case i of (
        1: " First "
        2: " Second "
        3: " Third "
        )
    txt="Pick"+ t+ "Point" --ЛЮБЛЮ АНГЛИЙСКИЙ :)
    floaterarchedspline.title=txt --МЕНЯЮ ЗАГОЛОВОК ОКНА ДЛЯ КРАСОТЫ И ПОНЯТНОСТИ
    txt="\n"+txt --ПЕЧАТАЮ В ЛИСТЕНЕР
   
    --УКАЗЫВАЮ ПЕРВУЮ ТОЧКУ (МОЖНО ЩЕЛКАТЬ МЫШКОЙ В ОКНАХ ПРОЕКЦИИ, ИЛИ ЗАДАВАТЬ КООРДИНАТЫ В ЛИСТЕНЕРЕ)
    if i==1 then point_pos = pickPoint prompt: txt snap: #3D
   
    --ЕСЛИ 1 ТОЧКА ОПРЕДЕЛЕНА, ТО БЕРУ ВТОРУЮ ТОЧКУ И ВЕДУ ПУНКТИРНУЮ ЛИНИЮ ОТ ПЕРВОЙ ДО ВТОРОЙ
    if i==2 and pp[1]!=0 then point_pos = pickPoint prompt: txt snap: #3D rubberBand: pp[1]
   
    --ЕСЛИ 1 И 2 ТОЧКИ ОПРЕДЕЛАНЫ, ТО БЕРУ ТРЕТЬЮ ТОЧКУ И ВЕДУ ПУНКТИРНУЮ ЛИНИЮ ОТ СЕРЕДИНЫ ОТРЕЗКА АВ ДО ТРЕТЬЕЙ
    if i==3 and pp[2]!=0 and pp[1]!=0 then point_pos = pickPoint prompt: txt snap: #3D rubberBand: (pp[1]+(pp[2]-pp[1])/2)
   
    --ПРОВЕРКА НА ПРАВИЛЬНОСТЬ ТОЧЕК (НЕ ОТМЕНИЛИ, НЕ ЩЕЛКНУЛИ ПРАВОЙ КНОПКОЙ - ЗАЩИТА "ОТ ДУРАКА")
    if (classOf point_pos)==Point3 then pp[i]=point_pos else checkpoints=1
    ) --КОНЕЦ ЦИКЛА
   
    if checkpoints==0 then (--ЕСЛИ ВСЕ ТРИ ТОЧКИ ОПРЕДЕЛЕНЫ, ТО СКРИПТ РАБОТАЕТ ДАЛЬШЕ
    m=getcoordmatrix pp--ПОЛУЧАЮ МАТРИЦУ, ВЫЗЫВАЯ ЗАРАНЕЕ ОПРЕДЕЛЕННУЮ ФУНКЦИЮ (ОДНА ФУНКЦИЯ ЗАПУСКАЕТСЯ ИЗ ДРУГОЙ)
    l=win_length --ПОЛОВИНА ДЛИНЫ АВ
    h=win_length --ВЫСОТА АРКИ ПО УМОЛЧАНИЮ
    if win_height<win_length then win_height+=h --МЕНЯЮ ВЫСОТУ ДЛЯ ВТОРОГО ВАРИАНТА (СМ. РИС)
    win_radius=(l^2+h^2)/(2*h) --РАДИУС АРКИ
    currentarchedspline=editarchedspline (distance pp[2] pp[1]) win_height win_radius h--СОЗДАЮ СПЛАЙН (ОДНА ФУНКЦИЯ ЗАПУСКАЕТСЯ ИЗ ДРУГОЙ)
    onoffspinners true--ЗАПУСКАЮ ФУНКЦИЮ И ТЕПЕРЬ МОЖНО МЕНЯТЬ ЗНАЧЕНИЯ СПИННЕРОВ
    currentarchedspline.transform=m --СТАВЛЮ СПЛАЙН В ПРАВИЛЬНОЕ МЕСТО
    )
    floaterarchedspline.btn_create.checked=false --ОТЖИМАЮ КНОПКУ И ТЕПЕРЬ НА НЕЕ МОЖНО ЖАТЬ И СОЗДАВАТЬ НОВЫЙ СПЛАЙН
    floaterarchedspline.title="ArchedSpline 1.0" --МЕНЯЮ ЗАГОЛОВОК ОКНА
)
--ФУНКЦИИ ЗАКОНЧИЛИСЬ

--СОБЫТИЯ, КОТОРЫЕ ВЫПОЛНЯЮТСЯ В ИНТЕРФЕЙСЕ
on floaterarchedspline open do ( --ПРИ ОТКРЫТИИ ОКНА
    createarchedspline() --СРАЗУ ЗАПУСКАЮ ФУНКЦИЮ СОЗДАНИЯ СПЛАЙНА
    setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
    btn_create.enabled=true --ВКЛЮЧАЮ ВКЛЮЧАТЕЛЬ
)
   
on btn_create changed arg do ( --ЕСЛИ ВКЛЮЧАТЕЛЬ НАЖИМАЕТСЯ
    onoffspinners false --НЕЛЬЗЯ ЗАДАВАТЬ ЗНАЧЕНИЯ СПИННЕРОВ
    createarchedspline() --СОЗДАЮ СПЛАЙН
    setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
    onoffspinners true --ТЕПЕРЬ МОЖНО МЕНЯТЬ ЗНАЧЕНИЯ СПИННЕРОВ
    )
   
on spn_fulllength changed arg do if isValidNode currentarchedspline then (--ПРИ ИЗМЕНЕНИИ ДЛИНЫ ТЕКУЩЕГО СПЛАЙНА
    if spn_archeight.value<=spn_arcradius.value then (--ЕСЛИ НЕ ВЫХОДИТ ЗА ПРЕДЕЛЫ
    m=getcoordmatrix 0 --ПОЛУЧАЮ МАТРИЦУ ИЗ СПЛАЙНА
    win_height=getUserProp currentarchedspline "Height" --ВЫСОТА
    h=getUserProp currentarchedspline "AHeight" --ВЫСОТА АРКИ
    l=arg/2 --ПОЛОВИНА ДЛИНЫ
    win_radius=(l^2+h^2)/(2*h) --РАДИУС
    delete currentarchedspline --УДАЛЯЮ СПЛАЙН
    currentarchedspline=editarchedspline arg win_height win_radius h --СОЗДАЮ ТУТ ЖЕ НОВЫЙ
    currentarchedspline.transform=m --СТАВЛЮ СПЛАЙН НА МЕСТО
    )
    setcorrectvalues() --ОБНОВЛЯЮ ЗНАЧЕНИЯ СПИННЕРОВ
)
   
on spn_fullheight changed arg do if isValidNode currentarchedspline then (--ПРИ ИЗМЕНЕНИИ ВЫСОТЫ ТЕКУЩЕГО СПЛАЙНА
    if arg>=spn_archeight.value then (--ЕСЛИ НЕ ВЫХОДИТ ЗА ПРЕДЕЛЫ
    m=getcoordmatrix 0 --ПОЛУЧАЮ МАТРИЦУ ИЗ СПЛАЙНА
    win_length=getUserProp currentarchedspline "Length" --ДЛИНА
    win_radius=getUserProp currentarchedspline "ARadius" --РАДИУС АРКИ
    h=getUserProp currentarchedspline "AHeight" --ВЫСОТА АРКИ
    l=win_length/2 --ПОЛОВИНА ДЛИНЫ
    delete currentarchedspline --УДАЛЯЮ СПЛАЙН
    currentarchedspline=editarchedspline win_length arg win_radius h --СОЗДАЮ НОВЫЙ
    currentarchedspline.transform=m --СТАВЛЮ СПЛАЙН НА МЕСТО
    )
    setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
)

on spn_archeight changed arg do if isValidNode currentarchedspline then (--ПРИ ИЗМЕНЕНИИ ВЫСОТЫ АРКИ ТЕКУЩЕГО СПЛАЙНА
    if arg<=spn_arcradius.value then (--ЕСЛИ НЕ ВЫХОДИТ ЗА ПРЕДЕЛЫ
    m=getcoordmatrix 0 --ПОЛУЧАЮ МАТРИЦУ ИЗ СПЛАЙНА
    win_length=getUserProp currentarchedspline "Length" --ДЛИНА
    win_height=getUserProp currentarchedspline "Height" --ВЫСОТА
    h=arg --ВЫСОТА АРКИ
    l=win_length/2 --ПОЛОВИНА ДЛИНЫ
    win_radius=(l^2+h^2)/(2*h) --РАДИУС АРКИ
    delete currentarchedspline --УДАЛЯЮ СПЛАЙН
    currentarchedspline=editarchedspline win_length win_height win_radius arg --СОЗДАЮ НОВЫЙ
    currentarchedspline.transform=m --СТАВЛЮ НА МЕСТО
    )
    setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
)
   
on spn_arcradius changed arg do if isValidNode currentarchedspline then (--ПРИ ИЗМЕНЕНИИ РАДИУСА АРКИ ТЕКУЩЕГО СПЛАЙНА
    if arg>=(spn_fulllength.value/2) then (--ЕСЛИ НЕ ВЫХОДИТ ЗА ПРЕДЕЛЫ
    m=getcoordmatrix 0 --ДОСТАЮ МАТРИЦУ
    len=getUserProp currentarchedspline "Length" --ДЛИНА
    win_height=getUserProp currentarchedspline "Height" --ВЫСОТА
    l=len/2 --ПОЛОВИНА ДЛИНЫ
    h=arg-sqrt(arg^2-l^2) --ВЫСОТА АРКИ
    delete currentarchedspline --УДАЛЯЮ СПЛАЙН
    currentarchedspline=editarchedspline len win_height arg h --СОЗДАЮ НОВЫЙ
    currentarchedspline.transform=m --СТАВЛЮ НА МЕСТО
    )
    setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
)

)--СВИТОК ГОТОВ
CreateDialog floaterarchedspline --СОЗДАЮ СВИТОК
--КОНЕЦ СКРИПТОВОЙ ЧАСТИ 3

Обратите внимание, точки A B и C можно задавать не только щелкая мышкой в окнах проекций, но и вводя абсолютные или относительные координаты в MAXScript Listener. Функция pickPoint поддерживает для этого синтаксис командной строки AutoCAD.

До финала осталось реализовать следующее:

I. Возможность редактирования любого арочного сплайна, в том числе при его выборе каждый раз обновлять спиннеры из свойств объекта (здесь нужен коллбэк).
II. Сделать из скрипта макроскрипт, с запоминанием положения окна интерфейса и кнопкой включения/выключения.
III. Нарисовать картинки на кнопку макроскрипта и написать readme по установке (от этого я вас избавлю, сделаю сам :-D).

I. Меняю скриптовую часть 3 следующим образом.

I.1. Добавляю новую функцию whenchangeselection

fn whenchangeselection = (--ФУНКЦИЯ ЗАПУСКАЕТСЯ КОЛЛБЭКОМ, КОГДА МЕНЯЕТСЯ ВЫДЕЛЕНИЕ
    --ЕСЛИ ВЫДЕЛЕН ОДИН ОБЪЕКТ И У НЕГО ЕСТЬ СВОЙСТВО AHeight, ТО ТЕКУЩИЙ АРОЧНЫЙ СПЛАЙН ИМЕЕТ МЕСТО БЫТЬ И Я ЗАПИСЫВАЮ ЕГО В ПЕРЕМЕННУЮ
    if selection.count==1 and (getUserProp selection[1] "AHeight")!=undefined then floaterarchedspline.currentarchedspline=selection[1]
    --ЕСТЬ СПЛАЙН ИЛИ НЕТ, ВЫДЕЛЕНО БОЛЕЕ ОДНОГО ОБЪЕКТА - ВСЕ РАВНО ЗАПИСЫВАЮ ЗНАЧЕНИЕ ПЕРЕМЕННОЙ В a
    a=floaterarchedspline.currentarchedspline
    if isValidNode a then (floaterarchedspline.setcorrectvalues() --ЕСЛИ ОБЪЕКТ СПЛАЙНА ЕСТЬ, ОБНОВЛЯЮ И ВКЛЮЧАЮ СПИННЕРЫ
        floaterarchedspline.onoffspinners true
        )
    else (floaterarchedspline.currentarchedspline=0; floaterarchedspline.onoffspinners false) --ЕСЛИ ОБЪЕКТА НЕТ - ОТКЛЮЧАЮ СПИННЕРЫ И ОБНУЛЯЮ ПЕРЕМЕННУЮ СПЛАЙНА
)

I.2. Изменяю событие открытия окна и добавляю новое событие закрытия окна

on floaterarchedspline open do ( --ПРИ ОТКРЫТИИ ОКНА
    isOpen=on --ЗАПОМИНАЮ, ЧТО ОКНО ОТКРЫВАЕТСЯ
    if selection.count==1 and (getUserProp selection[1] "AHeight")!=undefined then ( --ЕСЛИ КАКОЙ-ТО ОДИН АРОЧНЫЙ СПЛАЙН ВЫДЕЛЕН
        currentarchedspline=selection[1] --СОХРАНЯЮ В ПЕРЕМЕННУЮ
        onoffspinners true --ВКЛЮЧАЮ СПИННЕРЫ
        btn_create.checked=false --ОТЖИМАЮ ВКЛЮЧАТЕЛЬ, ЧТОБЫ ПОТОМ НА НЕГО МОЖНО БЫЛО НАЖАТЬ И СОЗДАТЬ НОВЫЙ СПЛАЙН
        )
        else createarchedspline() --ИНАЧЕ СРАЗУ ЗАПУСКАЮ ФУНКЦИЮ СОЗДАНИЯ СПЛАЙНА
        setcorrectvalues() --ОБНОВЛЯЮ СПИННЕРЫ
    btn_create.enabled=true --ВКЛЮЧАЮ ВКЛЮЧАТЕЛЬ
    callbacks.removeScripts #selectionSetChanged id: #AWINDOW --УДАЛЯЮ СТАРЫЙ КОЛЛБЭК
   
    --ДОБАВЛЯЮ КОЛЛБЭК, ЗАПУСКАЮЩИЙ ФУНКЦИЮ ПРИ ИЗМЕНЕНИИ ТЕКУЩЕГО ВЫДЕЛЕНИЯ
    callbacks.addscript #selectionSetChanged "floaterarchedspline.whenchangeselection()" id: #AWINDOW
)

on floaterarchedspline close do ( --ПРИ ЗАКРЫТИИ ОКНА
    isOpen=off --ЗАПОМИНАЮ, ЧТО ОКНО ЗАКРЫВАЕТСЯ
    archedsplinepos=GetDialogPos floaterarchedspline --ЗАПОМИНАЮ ПОЛОЖЕНИЕ ОКНА
    callbacks.removeScripts #selectionSetChanged id: #AWINDOW --УДАЛЯЮ КОЛЛБЭК
    updateToolbarButtons() --ОБНОВЛЯЮ КНОПКУ МАКРОСКРИПТА
)

I.3. Удаляю команду "CreateDialog floaterarchedspline", находящуюся на следующей строке после скобки и комментария "--СВИТОК ГОТОВ"

II. Создаю новый файл ArchedSpline.mcr со следующим содержанием:

--ОПРЕДЕЛЯЮ МАКРОСКРИПТ
macroScript ArchedSpline category:"ScriptAttack" tooltip:"ArchedSpline v1.0" Icon: #("ArchedSpline",1) --silentErrors: true
(
global floaterarchedspline --ХРАНИТ ОКНО ИНТЕРФЕЙСА
global archedsplinepos=[100,100] --ЗАПОМИНАЕТ ПОЛОЖЕНИЕ ОКНА
   
on ischecked return try(execute "floaterarchedspline.isOpen")catch(off) --ВЫКЛЮЧАЮ, ЕСЛИ ВКЛЮЧЕНО И НАОБОРОТ.
on execute do (--ЗАПУСК СКРИПТА
if (floaterarchedspline == undefined) then (--ЕСЛИ ОКНА СКРИПТА НЕТ НА ЭКРАНЕ, ТО СОЗДАЮ ЕГО СО ВСЕМИ ВЫТЕКАЮЩИМИ

--СЮДА ВСТАВЛЯЮ ИСПРАВЛЕННУЮ СКРИПТОВУЮ ЧАСТЬ 3

updateToolbarButtons() --ОБНОВЛЯЮ КНОПКУ МАКРОСКРИПТА
)
--ЕСЛИ ОКНО ОТКРЫТО И НАЖАЛИ КНОПКУ МАКРОСКРИПТА, ТО ЗАПОМИНАЮ ПОЛОЖЕНИЕ ОКНА, УДАЛЯЮ ЕГО И ВЫКЛЮЧАЮ КНОПКУ.
if floaterarchedspline.isOpen then (archedsplinepos=GetDialogPos floaterarchedspline; destroyDialog floaterarchedspline; updateToolbarButtons())
else CreateDialog floaterarchedspline pos: archedsplinepos --ЕСЛИ ОКНО ЗАКРЫТО - СОЗДАЮ ЕГО И СКРИПТ ЗАПУСКАЕТСЯ
) --КОНЕЦ ЗАПУСКА СКРИПТА (execute)
) --КОНЕЦ МАКРОСКРИПТА

III. Скачать готовый скрипт

Ну вот и все урок окончен, на сладкое опишу баги и недостатки ArchedSpline:

1. Нет проверки точек A B C на принадлежность одной конкретной плоскости, т.е. если точки будут лежать на одной линии - скрипт выдаст или ошибку или сплайн в непойми какой системе координат. Кто захочет - может внедрить проверку самостоятельно (должна быть ненулевая длина вектора AB и ненулевой синус угла между векторами AB и AC).

2. Иногда при изменении общей высоты дуга пропадает (почему - не ясно, м.б глюк макса) - в этом случае нужно просто "подергать" за какой-то другой параметр.

3. При уменьшении высоты дуги до нуля - дуга вырождается в прямую линию, в этом случае нужно увеличить высоту дуги или уменьшить радиус.

5. Нельзя изменять разом параметры нескольких сплайнов (тоже изначально не планировалось и вообще говоря для этого у меня есть другой скрипт).

20460 Автор:
Актуальность: 386
Качество: 582
Суммарный балл: 968
Голосов: 36 оценки

Отзывы посетителей:

аватар
 
hovhannes edigaryan 1 0
spasobo vam interesno
аватар
 
1acc 40 0
Ведущий Анимационных конкурсов
Интерфейса тут итак по минимуму. А картинок откуда взять больше я даже затрудняюсь придумать, это же все-таки скрипты, а не шейдеры. Разве что глюки отрисовать :)))
аватар
  vip
Savin Denis 210 0
Модератор форума
Привет 1acc Неплохой урок но слишком много текста, даже стили не спасают. Меньше програмирования интерфеса, если этот урок не по UI конечно. Больше наглядной информации в виде картинок. С этой точки зрения очень понравился урок по Шейдерам (Гудини).
аватар
 
1acc 40 0
Ведущий Анимационных конкурсов
Если дойдет до следующего урока, то это будет либо брутальное моделирование, либо хардкорная анимация, которые по другому сделать вообще нельзя.

Между тем выяснилось, что рендер.ру не отображает цветной текст, жаль я узнал об этом только после публикации урока.
аватар
 
naust 2 0
Да, блин, пользователь, хоть и продвинутый тем от профи и отличается, что знает "потроха" подопытного, в данном случае max'a. Но круче динамики 4-х тактного двигателя лично я здесь не встречал. Все, иду изучать книгу по MEL ...
аватар
 
MpaKo6ec 13 0
Зачет, полезный урок.
аватар
 
1acc 40 0
Ведущий Анимационных конкурсов
скрипт работающий должен получиться, а картинка у каждого будет своя.
аватар
 
ViewPort 2 0
Брутально! 5/5
аватар
 
khickma 141 0
ну а картинку что должно получиться? =)
аватар
 
1acc 40 0
Ведущий Анимационных конкурсов
Естественно в таком тексте разобраться невозможно, даже если знания макскрипта есть.

Обращаюсь к администрации:
Ну господа, ну так же нельзя, выкладывать урок и рушить все таблицы стилей, которые я пыхтел и делал для удобочитаемости. Смотреть же невозможно. Ну предупредите меня, что нельзя сохранить мои стили, но не выкладывайте сразу же в таком убогом виде.

Для тех, кому будет читать урок: вот так http://www.scriptattack.com/ArchedSpline_renderru.html он должен выглядеть.
аватар
 
Timur Mullayanov 128 0
Прямо сильно хардкорный урок :)
А вообще скрипты это конечно хорошо, но конкретно этот пример уж слижком специфичный.
4/5
аватар
 
kUkara4 51 0
Мдяя.... буду первым, кто отпишется) Я бы сказал, если у того, кто читает нету никаких знаний МАХ скрипта (это я про себя) то сложно в чём-то разобраться)
Зарегистрируйтесь, чтобы добавить комментарий.
Эту страницу просмотрели: 298 уникальных посетителей