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. Нельзя изменять разом параметры нескольких сплайнов (тоже изначально не планировалось и вообще говоря для этого у меня есть другой скрипт).

572 0 850 18
12
2008-11-10
Мдяя.... буду первым, кто отпишется) Я бы сказал, если у того, кто читает нету никаких знаний МАХ скрипта (это я про себя) то сложно в чём-то разобраться)
2008-11-10
Прямо сильно хардкорный урок :) А вообще скрипты это конечно хорошо, но конкретно этот пример уж слижком специфичный. 4/5
2008-11-10
Естественно в таком тексте разобраться невозможно, даже если знания макскрипта есть. Обращаюсь к администрации: Ну господа, ну так же нельзя, выкладывать урок и рушить все таблицы стилей, которые я пыхтел и делал для удобочитаемости. Смотреть же невозможно. Ну предупредите меня, что нельзя сохранить мои стили, но не выкладывайте сразу же в таком убогом виде. Для тех, кому будет читать урок: вот так [url]http://www.scriptattack.com/ArchedSpline_renderru.html[/url] он должен выглядеть.
2008-11-10
ну а картинку что должно получиться? =)
2008-11-10
Брутально! 5/5
2008-11-10
скрипт работающий должен получиться, а картинка у каждого будет своя.
2008-11-10
Зачет, полезный урок.
2008-11-10
Да, блин, пользователь, хоть и продвинутый тем от профи и отличается, что знает "потроха" подопытного, в данном случае max'a. Но круче динамики 4-х тактного двигателя лично я здесь не встречал. Все, иду изучать книгу по MEL ...
2008-11-10
Если дойдет до следующего урока, то это будет либо брутальное моделирование, либо хардкорная анимация, которые по другому сделать вообще нельзя. Между тем выяснилось, что рендер.ру не отображает цветной текст, жаль я узнал об этом только после публикации урока.
2008-11-10
Привет 1acc Неплохой урок но слишком много текста, даже стили не спасают. Меньше програмирования интерфеса, если этот урок не по UI конечно. Больше наглядной информации в виде картинок. С этой точки зрения очень понравился урок по Шейдерам (Гудини).
2008-11-10
Интерфейса тут итак по минимуму. А картинок откуда взять больше я даже затрудняюсь придумать, это же все-таки скрипты, а не шейдеры. Разве что глюки отрисовать :)))
2008-11-26
spasobo vam interesno
RENDER.RU