Render.ru

MEL-команды в выражениях. Почему плохо?

Артём Ягодин

Активный участник
Рейтинг
11
#1
Название темы должно отражать ее содержание.

Ну, собственно, я выполнил это требование. Суть вопроса в теме сообщения.
Все знают, что делать этого не рекомендуют. А кто-нибудь знает почему?
Пробовал ли кто этот прием испытывать на практике?
Меня, в часности, интересуют команды getAttr и setAttr.

Спасибо.
 

alex_alv

Активный участник
Рейтинг
11
#3
Использовать MEL в выражениях - это не плохо, а иногда и необходимо, но нужно это делать аккуратно.

Выражение - это нода, у которой есть входы и выходы.

Если выражение использует прямой доступ к атрибуту, то этот доступ оформляется в виде входа или выхода выражения и появляется связь в графе.
Кроме того, майка анализирует выражение и внутри этой ноды указывает, какие входы выражения на какие выходы влияют.


Вся эта информация используется при пересчете графа, который пересчитывается примерно по следующему алгоритму.

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

Когда майке требуется значение какого-нибудь атрибута (например, при прорисовке сцены), если он помечен как недействительный, майка пересчитывает этот атрибут с учетом алгоритма работы ноды, выход которой недействителен. Если это экспрешн, то соответственно, запускается экспрешн. Этот экспрешн может обращаться явным образом к другим атрибутам. Если один из них помечен как недействительный, соответственно запускается выполнение других нод, от которых зависит данный атрибут.

Такой механизм позволяет оптимизировать просчет графа и приводит к автоматическому запуску экспрешена при изменении его входных атрибутов. Но у этого механизма есть и недостатки.
1) При сложных связях невозможно предсказать порядок вычислений нод (т. е. например, невозможно сказать, в каком порядке будут запускаться экспрешены, вылияющие друг на друга)
2) Невозможность циклческих связей.
3) Если экспрешн меняет атрибут другой ноды, то в процессе его выполнения эта нода не запустится даже если потом экспрщн запросит выход этой ноды, так как в этой ситуации ее выход будет являться входом экспрешена, который в свою очередь на момент запуска экспрешна былпомечен действительным и именно значение атрибута на момент запуска экспрешна будет использоваться им.

Эти недостатки решаются доступом к атрибутом через MEL, но при этом, майка не будет знать о том, что имеются зависимости от этих атрибутов и не будет учитывать эти связи (так как их реально и нет) при перечете графа.

Чтобы было понятнее, приведу три примера, когда нужно использовать MEL, а когда прямой доступ.

Пример 1. Требуется, чтобы при перемещении сферы по Y ее размер по X менялся как синус координаты по Y.
Здесь нужен прямой доступ. Т. е. должно быть написано:
sx=sin(ty);
В этой ситуации даже если сферу переместить манипулятором, ее размер автоматически изменится.
В этой ситуации, есть нода экспрешна, у которого есть вход и есть выход. Вход подключен к ty трансформ-ноды сфера, а выход подключен к sx той же транформ-ноды.
При перемещении сферы по Y, майка сканирует граф и помечает вход экспрешна как недействительным.
Проанализировав экспрешн, майка помечает, что выход экспрешна зависит от входа. Соответственно, выход экспрешна тоже помечается как недействительный.
Выход подключен к трансформ-ноде. Соответственно, атрибут sx сферы тоже помечается как недействительный.
Когда майке требуется перерисовать экран, она в том числе перерисовывает и сферу. Для прорисовки сферы майке требуется в том числе и атрибут sx. Когда она его опрашивает, выячняется, что он недействителен. Тогда майка идет по связи и попадает в экспрешн, выход которого тоже недействиетелен. Майка запускает экспрешн. Но перед этим она проверяет все входы экспрешна. Выясняется, что вход тоже недействителен. Тогда она идет по связи и попадает в атрибут ty сферы, который действителен. Майка копирует значение ty на вход экспрешна и помечает его как действительным. Запускается эксрешн. Там используется значение ty, но это значение уже берется не с реального атрибута, а со входа экспрешена (иными словами, если теперь экспрешн изменит какой-нибудь атрбут третьей ноды, которая влияет на ty, а потом снова возьмет ty, то она получит старое значение ty до измеения). Далее, экспрешн вычисляет значение своего выхода и майка помечает выход действительным. Затем, майка копирует значение выхода экспрешна в атрибут sx трансформ-ноды сферы (так как в графе имеется соответствующая связь) и тоже помечает атрибут действительным. Теперь у майки есть вся необходимая информация для прорисовки сферы, и соответственно она ее прорисовывает.
Если в данном случае поменять прямой доступ на MEL, то майка не будет знать о зависимостях атрибутов, т. е. при изменении координаты Y манипулятором, сфера размера своего менять не будет. Но при изменении кадра, экспрешн все равно будет запускаться (если он помечен, что он должен запускаться каждый кадр).
Но. Допустим, мы проанимировали сферу по Y ключом. В этом случае, нельзя сказать, когда майка будет запускать экспрешн - до того, как атрибт ty сферы станет действительным или после этого, так как майка не знает, что экспрешн зависит от атрибута ty сферы. Это ознчает, что хоть экспрешн и будет выполняться в каждом кадре, нельзя сказать, из какого кадра будет браться информация этим экспрешном. Иногда он может запуститься до просчета графа, т. е. при чтении атрибута ty через getAttr будет получено недействительное значение предыдущего кадра. Иногда - после просчета графа, т. е. значение будет уже из этого кадра. Т. о., при анимации экспрешн работать будет, но иногда с задержкой на кадр, т. е. работа экспрешна будет нестабильной и непредсказуемой. Т. о., это - пример, когда использовать MEL недопустимо.

Пример 2. Требуется создать экспрешн, который с помощью ноды arcLength методом деления пополам будет по заданному расстоянию от начала кривой получать u-координату.
Здсь придется делать цикл, в котором несколько раз за одну работу экспрешна будет меняться входной атрибут ноды arcLength и потом опрашиваться ее выход, который зависит от изменяемого входа. В этой ситуации наоборот, недопустимо прямое обращение к атрибуту, так как при чтении выхода ноды arcLength экспрешн будет получать одно и тоже некорректное значение и в результате зависнет.
Но если для ноды arcLength менять ее вход через setAttr, а опрашивать выход через getAttr, все будет работать отлично.

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

В общем, запрещение использования MEL в экспрешн - это неправильно. Иногда наоборот, недопускается использование прямого доступа. Просто это нужно делать с умом и понимать, как майка будет воспринимать и обрабатывать данную ситуацию.
 

Артём Ягодин

Активный участник
Рейтинг
11
#4
Alex_alv, блестящий ответ! А можно ли избавится от зависимости вычисления выражения от времени? Но чтоб оно реагировало на входящие связи. Удаление связи с тайм1 не помогает.

DemX86, хочу написать урок, там встал этот вопрос. Зачем именно сможешь прочесть в уроке.
 

alex_alv

Активный участник
Рейтинг
11
#5
Избавиться от вычисления экспрешна в каждом кадре можно.
Для этого нужно в Expression Editor в поле Evaluation значение "Always" заменить на "On demand" (сам не пробовал, но по-идее, должно работать)
 

alex_alv

Активный участник
Рейтинг
11
#6
P.S. только в этом случе нужно иметь в виду, что экспрешн скорее всего будет запускаться не по изменению входных атрибутов, а тогда, когда майке потребуются значения выходов экспрешна, помеченных недействительными (см. выше). Т. е. если данный экспешн не имеет выходов или его выходы подключены к атрибутам, которые не используются майкой для визуализации сцены (явно или через другие ноды), то такой экспрешн скорее всего вообще никогда не будет выполняться даже если его входы будут изменены.
 

alex_alv

Активный участник
Рейтинг
11
#7
Только что попробовал экспрешн:
print(frame+"\n");

Этот экспрешн имеет один вход, подключенный к time1 и не имеет выходов вообще.

При значении Evaluation равным Always, в скрипт-эдиторе появляется номер кадра каждый раз, когда меняется время. Это говорит о том, что экспрешн выполняется.

После изменения значения Evaluation на On demand, номера кадров перестали появляться в скрипт-эдиторе.

Т. е. данный экспрешн вообще перестал выполняться, не смотря на то, что он имеет входящую связь, на которй значения все время меняются.

Т. е. происходит следующее:

Меняется время. Соответственно, вход экспрешна помечается недействительным, но сам эксрешн не запускается.
При прорисовке сцены, майка ни разу не обращается к недействительным атрибутам, зависящим от экспрешна. Т. е. экспрешн не запускается, так как его работа не требуется майке.

Т. е. все работает в соответствии с алгоритмом, который я написал вначале (только я его естественно сильно упростил, более подробное описание можно найти в документации майки).

Кстати, могу предположить, что при установке On demand, экспрешн может перестать выполняться даже если для прорисовки сцены требуется выход экспрешна, входы которого поменялись, но майке не требуется прорисовать или порендерить сцену. Например - если при запущенной анимации свернуть окно майке, вероятно, все подобные экспрешены перестанут выполняться.
Я это не проверял - просто предполагаю, что это так.
 

alex_alv

Активный участник
Рейтинг
11
#8
Провел несколько тестов и проверил свои предположения - все происходит как я и говорил.

Тест 1. В сцене есть только сфера. У ее трансформ ноды сделан кастомный атрибут asd.
Создан экспрешн следующего вида (он помечен как On demand):

print(frame+"\n");
nurbsSphere1.asd=frame;

При изменении времени экспрешн выполняется только тогда, когда на экране отображается значение атрибута asd. Стоит только закрыть Channel box и экспрешн перестает выполняться при изменении времени. Почему - см. выше.



Тест 2. Берем ту же самую сцену и модифицируем экспрешн следующим образом:

print(frame+"\n");
nurbsSphere1.asd=frame;
nurbsSphere1.scaleY=frame;

Запускаем анимацию и сворачиваем окно маи. Потом разворачиваем окно, останавливаем анимацию и лезем в скрипт-едитор.

Сразу замечаем, что пока окно майки было свернутым, экспрешн НЕ выполнялся, как я и предполагал выше (в скрипт-едиторе распечатаны только те номера кадров, которые воспроизводились при развернутом окне маи, а диапазон кадров в момент свернутого окна - пропущен).
 

Артём Ягодин

Активный участник
Рейтинг
11
#9
Просто слов нет. Замечательные ответы. Спасибо большое. Пойду переваривать все это и подгонять под свой случай.
 

alex_alv

Активный участник
Рейтинг
11
#10
Забыл написать еще об одном очень важном отличии "прямого доступа" от MEL (это отличие следует из написанного выше, но оно не так очевидно). Я написал "прямой доступ" в кавычках, потому что MEL - это более прямой доступ, чем "прямой доступ" :). Попробую пояснить.

При "прямом чтении" атрибута, реально считывается не атрибут, а его копия. Чтение производится со входа экспрешна, куда ПЕРЕД выполнением экспрешна заносится копия атрибута. Т. о., чтобы экспрешн не делал, даже если реальный атрибут, к которому обращается экспрешн, поменяется в процессе выполнения, считываться при "прямом доступе" будет одно и тоже старое значение атрибута, а не сам атрибут. Здесь есть два исключения, но о них - в конце.
При "прямой записи" в атрибут, реальной записи не происходит. Запись производится только в выход экспрешна. Если экспрешн в процессе своего выполнения несколько раз "прямо изменит" какой-нибудь атрибут, реальный атрибут не изменится ни разу. Изменения произойдут только на выходе экспрешна. Копирование результата с выхода экспрешна на реальный атрибут произойдет только ПОСЛЕ завершения работы экспрешна - в процессе дальнейшего пересчета графа (копирование будет производиться в соответстии со связями по графу).

В отличие от "прямого доступа", команда setAttr реально заносит требуемое значение прямо в атрибут назначения и прямо в момент выполнения команды. При этом, пересчета графа НЕ происходит, но все атрибуты, зависящие от измененного атрибута, помечаются недействительными.
Команда getAttr производит реальное чтение атрибута прямо в момент выполнения команды. При этом, если на момент выполнения команды атрибут окажется помеченным как недействиельный, это приведет к пересчету той части графа, которая отвечает за данный атрибут. Это произойдет посередине выполнения экспрешна и может оказаться как полезным (см. пример 2 в моем первом ответе, когда используется нода arcLengthTool), так и вредным. Самое интересное, что в процессе такого пересчета части графа, могут быть запущены другие экспрешны и только после их завершения будет продолжено выполнение первого экспрешна. Совсем самое интересное - в числе этих экспрешнов может оказаться и сам первый экспрешн. В этой ситуации, майка скорее всего зависнет, но точно сказать, что произойдет - не могу.

Теперь об исключениях.

Первое исключение - когда эксрешн "прямо читает" и "прямо записывает" один и тот же атрибут.
Например.

tx=10;
print("tx="+tx+"\n");
tx=20;
print("tx="+tx+"\n");

Такой экспрешн напечатает сначала 10, потом 20. В этой ситуации, входом и выходом экспршна является одна и та же точка (один и тот же атрибут). Вторая команда tx=20 меняет не сам tx, а выход экспрешна, который также является и его входом. Вторая команда print опрашивает этот вход, который изменен командой tx=20 и получает измененное значение tx. Т. о., получили пример, когда "прямое чтение" в процессе выполнения экспрешна дает разные результаты. Тем не менее, реального изменения атрибута tx не происходит ни при выполнении tx=10, ни при выполнении tx=20. И только после завершения работы экспрешна значение с его выхода (которое на этот момент равно 20) копируется по связи графа в реальный атрибут tx.


Второе исключение показывает некорректное обращение с MEL в экспрешне.
Рассмотрим следующий экспрешн:

print("tx1="+xxx.tx+"\n");
if(xxx.tx!=10)
{
setAttr "xxx.tx" 10;
getAttr "xxx.ty";
}
setAttr "xxx.tx" 9;
print("tx2="+xxx.tx+"\n");
xxx.ty=xxx.tx-2;

Как точно будет работать этот экспрешн - сказать трудно. Дома майки нет, проверю завтра на работе. Но возможно, что майка даже не повиснет и произойдет следующее:
(имеется в виду, что перед выполнением экспрешна значение xxx.tx НЕ равно 10).

Перед выполнением экспрешна, на его вход будет скопировано значение xxx.tx, которое не равно 10 по начальным условиям. Допустим, оно равно 9.
Далее, распечатается строка tx1=9
Затем, так как tx!=10, будет запущена ветка if, где значение атрибута tx меняется на 10 (но вход экспрешна остается равен 9).
При этом, майка значет, что от атрибута xxx.tx через экспрешн зависит атрибут xxx.ty. Соответственно, он (ty) помечается недействительным.
Далее идет команда getAttr, которая опрашивает недействительный атрибут.
Здесь майка приостанавливает работу экспрешна, так как для выполнения команды getAttr ей требуется посчитать значение атрибута ty. Для этого она двигается по связи и попадает опять в данный экспрешн.
Майка копирует значение tx на вход экспрешна, которое теперь равно 10 и снова запускает экспрешн.
Здесь появится строка
tx1=10
Затем if будет пропущен и выполнится команда setAttr, которая заносит в tx число 9
Далее будет выведена строка tx2=10 (в принте используется "прямой доступ", т. е. будет распечатано не реальное значение атрибута, а значение входа экспрешна, которое по прежнему равно 10).
Затем, в ty будет занесено значение 8 (10-2=8) и работа экспрешна завершится.
Что произойдет дальше - сложно сказать.
По идее, в этот момент успешно завершается выполнение команды getAttr и экспрешн должен перейти к выходу из конструкции if и к выполнения команды setAttr "xxx.tx" 9;
Но так как за экспрешн отвечает одна нода, внутреннее состояние которой изменилось за счет того, что экспрешн был выполнен второй раз, возможно этого не произойдет.
Но если произойдет, то дальше, будет выведено значение входа экспрешна, которое скорее всего в этом месте будет равно 10, а не 9 (из-за того, что нода только что обрабатывала второй раз экспрешн и последнее, что она занесла в свой вход - было значение 10).
Затем, в ty занесется значение 8 и работа экспрешна завершится.

Т. о., такой экспрешн может вывести следующее:
tx1=9
tx1=10
tx2=10
tx2=10

Возможно, будет выведено все тоже самое, но без последней строки.
А возможно, майка вообще уйдет или как-нибудь еще возмутится на подобное издевательство.
 

alex_alv

Активный участник
Рейтинг
11
#12
Попробовал.
Майка не зависает, но работает не так, как я думал из-за того, что срабатывает защита от бесконечного циклического просчета графа.
Соответственно, порядок выполнения несколько другой, чем я говорил, но суть, что при первом проходе экспрешна "прямое чтение" tx дает два разных результата, а при втором - два одинаковых (так реально и происходит).

Итог.
Из всего выше сказанного видно, что не рекомендуют использование в экспрешнах getAttr только из-за того, что при ее выполнении может происходить пересчет графа, что может нарушить ход выполнения экспрешна. Реально это так и есть, но иногда это - очень полезно и это можно использовать (если не пытаться заглючить майку, как это попытался сделать я в последнем примере).
 
Сверху