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