Создание модификатора утолщения средствами ICE
Введение
Здравствуйте, те, кто использует программу Autodesk Softimage, и просто интересующиеся. Настоящий урок посвящен созданию модификатора утолщения объектов средствами ICE. Сразу же сделаем несколько пояснительных комментариев, являющиеся предпосылками к написанию этого урока. Во-первых, не очень давно (на момент публикации) вышла версия Softimage за номером 10, более широко известная, как Softimage 2012. В ней впервые появилась возможность работать с так называемой топологией объектов. Это означает, что появилась возможность создавать, удалять и иначе преобразовывать вершины, ребра и полигоны полигонального объекта (простите за каламбур). Раньше такого не было. Вот и решено было забабахать что-нибудь, что использовало бы новый для этой версии программы функционал. Отметим, что среди многочисленных видео, посвященных демонстрациям работы средств моделирования ICE-ом, встречается одно, где кое-кто из разработчиков показывает, как он сделал модификатор утолщения. Да-да, почти то же самое, о чем и пойдет речь в дальнейшем. Впрочем, его модификатор чрезвычайно плох, так как выдавливает геометрию только в направлении осей координат, написан на еще дорелизной версии программы, и вообще, там ничего не понятно.
Во-вторых, в XSI нет штатных средств утолщения объектов. Вот так вот, нет и все тут. В Максе есть Shell, а тут ничего такого нет. Конечно, написано уже несколько простых дополнений, которые решают эту досадную оплошность, а некоторые говорят, что вообще такой модификатор не нужен, так как утолщение делается несколькими движениями мыши. Но, все же, теперь и мы сделаем такой модификатор, да не просто так, а средствами ICE, потому как все, что не ICE — не айс, а ICE — это, сами понимаете, айс.
В-третьих, в настоящем уроке дается введение в программирование на ICE, потому как логика у него не очень, и есть кое-какие сложности в достижении целей. Это, конечно же, общие слова, когда перейдем к делу, видно будет, где какие сложности.
Давайте теперь оставим досужие разговоры за жизнь и начнем наш урок. Итак, первое, о чем стоит сказать — это интерфейс. На рис. 1 изображен типичный вид окна программы для случая, когда надо что-то писать на ICE: Explorer, чтобы легко было таскать атрибуты объектов, большое окно для дерева нод и маленькое окошко, где крутится объект (так называемый вид Camera).
Теперь давайте создадим какую-нибудь геометрию, не так чтобы очень уж сложную, но и не слишком простую, что-нибудь в духе того, что изображено на рисунке 2. На этой геометрии мы будем отрабатывать все то, что напрограммируем.
Убедившись, что наша геометрия зафризена и у нее сброшены все трансформации, создаем ICE Tree (рис. 3).
Рис. 3. Создаем ICE Tree
ICE Tree можно создавать в разных режимах (моделирования, анимации, вторичных деформаций и так далее), в каждом случае есть своя специфика. Для наших целей достаточно будет все время работать в Modeling Constuction Mode. Итак, корневая нода ICETree создана, и теперь самое время переходить к программированию. А тут как раз и лирическо-техническое отступление подоспело. Забудем пока о нашей главной цели и сделаем программку, которая ничего не делает. Может быть кто-нибудь спросит: дескать, а зачем нам что-то делать, чтобы ничего не делать? А ответ простой — чтобы усвоить кое-какие истины.
Все программирование зиждется на работе с переменными (и прочими свойствами классов). Программирование же на ICE зиждется на работе с атрибутами. Что такое атрибут? Ну, это вроде переменной, только значения этих переменных могут относиться не только целиком к объекту, но и к его частям. Примерами могут служить местоположения частиц (у каждой частицы свое положение в пространстве), нормаль к полигону (опять же, каждый полигон имеет свою нормаль). Бывают атрибуты, относящиеся и целиком к объекту. Как уже понял сообразительный читатель, каждый созданный объект обладает уже заранее готовым набором атрибутов. Мы, впрочем, ничем не ограничены и можем сами создавать сколько угодно атрибутов у каждого объекта, при этом записывать в них какую угодно информацию.
Переходим к конкретному примеру. Добавляем ноду Get Point Position. Где ее найти? Да в левой части окна ICE Tree. Пишем в строке поиска примерное название ноды, и все, что подходит, тут же появляется. Нам остается только найти нужную ноду и перетащить ее куда следует (рис. 4). Конечно, все ноды разбиты по категориям, и можно искать нужную ноду в соответствующей категории, но это кому как удобнее.
Рис. 4. Нашли ноду Get Point Position
Теперь время спросить себя, а что же делает нода Get Point Position. Ответ — она возвращает местоположение в пространстве каждой вершины объекта, причем приподносится это, как тройка (координаты по X, Y и Z), сопоставленная каждой вершине. Чтобы углядеть все это более явно, добавляем ноду Set Point Position. Здесь уже все сами понимают, что надо делать. Связываем выход Get Point Position со входом Set Point Position, и, наконец, все это связываем с портом Port1 корневой ноды (рис. 5).
Рис. 5. Связали Get PP и Set PP
Что делает нода Set Point Position? Сообразительный читатель уже понял, что она просто задает каждой вершине объекта ее местоположение в пространстве. Так как мы только что взяли это местоположение и тут же его обратно задали, конечно же, ничего не произошло. Но зато теперь мы можем посмотреть, а какая же информация передается от одной ноды к другой. Для этого жамкаем правой клавишей на линии, соединяющей Get PP и Set PP, и выбираем Show Values (рис. 6).
В выскочившей, как чертик из табакерки, менюшке выбираем отображение информации числовым методом (рис. 7) и жмем OK.
Рис. 7. Выбираем тип Numeric
Теперь время ахать. Что же мы видим в окне камеры, где раньше была тишь и благодать, и ничто не предвещало беды? Теперь же там неразбери-поймешь что творится (рис. 8). В каждой вершине объекта написаны ее координаты в пространстве, сиречь Point Poistion. Вот это и есть то, что называется контекст атрибута. Point Position является не массивом координат, но координатами каждой вершины в отдельности. Еще это называют per Point. Поэтому, когда хочется перекидывать информацию из одного атрибута в другой, надо следить, чтобы у них совпадал контекст, а иначе чуда не произойдет.
Рис. 8. Координаты вершин объекта
Чтобы закончить наше лирическо-техническое отступление, узнаем, а какие еще бывают атрибуты у объекта. Сделать это просто. Необходимо добавить ноду Get Data (используем поиск, рис. 9). Сразу видно, что новоявленной ноде неловко. А все потому, что не знает она, какой атрибут от нее требуют, только смотрят пристально, да языком прицыкивают.
Рис. 9. Добавляем ноду Get Data
Чтобы исправить ситуацию, жамкаем на Get Data два раза, набираем в единственной свободной строке self. (не забываем про точку), и жмем Enter. Нода стала голубенькой, потому как уже кое-что она возвращает (собственно она возвращает теперь весь объект). Нажимаем кнопочку Explorer, и вот они, родненькие, атрибуты наши (рис. 10). Не очень их много, но они есть. На этом разрешите окончить вводную часть и позвольте перейти к основному содержанию урока.
Рис. 10. Атрибуты наши родненькие
Работа с топологией
Начинаем конструировать наш модификатор утолщения. Первым делом хорошо было бы сохранить в отдельный атрибут (читай переменную) всю изначальную топологию. Что есть топология? Может быть некоторые думают, что топология — это множество подмножеств множества X, удовлетворяющее ряду аксиом? А вот и нет! В XSI (да и вообще в CG) топология — это суть совокупность вершин, ребер, полигонов и связей между ними (так называемое отношение смежности).
Так вот, сохранить топологию необходимо для того, чтобы потом, изменив геометрию, можно было как-то использовать изначальную геометрию. В общем, понадобится потом это, точно говорю. Сохранение топологии делается очень просто. Добавляем ноду Get Data, в ней прописываем, как мы учили, self., и потом из раскрывающегося списка выбираем атрибут Topology (Рис. 11). Наблюдательный читатель заметил, что на скрине написано не self.Topology, а this.Topology. В принципе это одно и то же, XSI сама заменяет одно на другое, когда ей вздумается.
Альтернативный способ получить топологию заключается в том, чтобы воспользоваться нодой Get Topology (рис. 12). Обратите внимание, что в этом случае необходимо в графе Transform Mode выставить значение No Transform.
Рис. 12. Нода Get Topology
Наконец, сохраняем топологию в отдельный атрибут. Для этого добавляем ноду Set Data (рис. 13), вписываем self.Thic_OriginalTopo (рис. 14), и связываем все между собой (рис. 15).
Рис. 14. Добавляем новый атрибут
Рис. 15. Связываем все в одну ветвь
Поясним, что же только что было сделано. Мы сохранили всю текущую топологию объекта в отдельный атрибут, который назвали Thic_OriginalTopo. Здесь каждый мог написать свое собственное имя, но лучше их все как-то выделять, чтобы потом долго не искать и не путаться (например, добавлять префикс Thic_). Убедимся, что атрибут действительно создан. Для этого добавляем ноду Get Data, пишем self., и видим, что созданный атрибут имеет место быть (рис. 16).
Рис. 16. Созданный атрибут присутствует
Поехали дальше. Теперь необходимо выдавить полигоны. Сделаем мы это с помощью ноды Extrude Polygon Island (рис. 17). Результатом работы этой ноды является новая топология, поэтому, чтобы геометрия объекта обновилась после преобразования, ее необходимо пропустить через ноду Set Topology (см. рисунок 18, на котором мы связали все три сиреневые ноды в одну ветвь. Также изменен параметр Inset, чтобы видеть, к какому полигону применяется преобразование). Кстати говоря, измененный полигон имеет индекс 0.
Рис. 17. Нода Extrude Polygon Island
Рис. 18. Нода Extrude Polygon Island в действии
Необходимо, чтобы преобразование выдавливания применялось не к одному-единственному полигону, но ко всем сразу. Сделать это так же просто, как и все то, что было до этого. Добавляем ноду Get Polygon Index и связываем ее с портом Polygon Index ноды Extrude Polygon Island (рис. 19). Смысл проделанных операций предельно ясен, тут даже и несообразительный читатель поймет, в чем дело.
Рис. 19. Теперь преобразованы все полигоны
Придаем параметру Inset прежнее значение 0, при этом все крайние полигоны становятся бесконечно тонкими, но тем не менее никуда не пропадают. На рисунке 20 мы специально отодвинули парочку вершин, чтобы убедиться, что полигоны есть и они никуда не ушли.
Рис. 20. Граничным полигонам быть
Дальше — один из ключевых моментов. Нам необходимо применить ко всем неграничным вершинам нашего объекта модификатор Push. Прямо сейчас мы его и соорудим. Итак, нам понадобится положение каждой вершины (нода Get Point Position), нормаль к каждой вершине (нода Get Point Normal), ну и, конечно же, нода Set Point Position, записывающая все изменения местоположений вершин (рис. 21).
Еще нам понадобится информация о том, является ли вершина граничной или нет. Если она граничная — то мы оставляем ее неподвижной, а если не граничная — сдвигаем в направлении нормали. Получить необходимую информацию можно из атрибута VertexIsBorder (рис. 22). Значением данного атрибута является булеановское (либо 1/0, либо истина/ложь, кому как удобнее) значение в каждой вершине.
Рис. 22. Атрибут VertexIsBorder
Мы должны сделать следующее: хватаем каждую вершину по очереди, смотрим, является ли она граничной, и, если нет, то добавляем к ее местоположению (которое является трехмерным вектором) вектор нормали. Сумма векторов — снова вектор, поэтому результат можно объявить новым местоположением вершины. Отметим, что все используемые атрибуты имеют контекст per Point, а поэтому над ними можно совершать операции, и все будет работать, причем сразу для всех вершин. Реализуем наши действия.
Полезной оказывается нода Filter, у которой есть два входных порта. В нижний порт подается любая величина, а в верхний — булеановское значение. Если оно оказывается истинным, то любая величина (из второго порта) пропускается дальше, в противном случае, величина не пропускается и, как следствие, не меняется в дальнейшей цепочке. Поэтому делаем следующее: добавляем ноду Filter, ее значение (порт Value) связываем с Get Point Position, а условие — с VertexIsBorder. Далее добавляем ноду Add, которая складывает значения, один из портов которой связываем с выходом ноды Filter, а другой — с Get Point Normal. Ну и, наконец, результат сложения связываем с Set Point Position. Сейчас все работает с точностью до наоборот: если вершина граничная, то она пропускается сквозь фильтр и следовательно сдвигается. Чтобы исправить это досадное недоразумение, просто добавляем ноду Not между фильтром и VertexIsBorder. Итоговая ветвь изображена на рисунке 23.
Рис. 23. Сдвигаем все вершины, кроме граничных
Теперь наступает очередь второго ключевого момента: необходимо добавить исходную геометрию так, чтобы получилась утолщенная поверхность. Делать это будем с помощью ноды Merge Topo. Результатом ее работы является топология, поэтому перед подключением к корневой ноде ICETree пропускаем все через Set Topology (рис. 24).
Как мы с удовольствием наблюдаем, входными параметрами ноды Merge Topo являются топологии в произвольном (в разумных рамках) количестве. Нам необходимо объединить текущую топологию и ту, что ранее была сохранена в специально отведенный для этого атрибут. Так и поступаем: добавляем ноды Get Topology и Get Data. В последней выбираем наш атрибут Thic_OriginalTopo (рис. 25).
Рис. 25. Берем ранее сохраненную топологию
Соединяем все вместе, и — вуаля! — получаем не то, что надо (рис. 26).
Рис. 26. Получили не то, что хотели
Почему это не то, что надо? — спросит несообразительный читатель. Мы так тихонько посмеемся, отечески похлопаем его по плечу, ухмыльнемся, вздохнем, но выложим всю правду, как на духу. Нормали-то у внутренней стенки перевернуты! Это надо срочно исправлять. Добавляем ноду Invert Polygon (рис. 27). Если мы просто пропустим связь от Thic_OriginalTopo к Merge Topo сквозь ноду Invert Polygon, то инвертируется только один полигон. Инвертирование необходимо применить ко всем полигонам, поэтому добавляем ноду Get Polygon Index, и связываем ее с портом Polygon Index (рис. 28). Теперь хорошо видно, что нормали у всех полигонов повернуты куда следует.
Рис. 27. Добавляем ноду Invert Polygon
Рис. 28. Инвертируем все полигоны
Сделаем небольшое отступление и добавим возможность регулировать толщину объекта. Для этого надо вспомнить, что толщина объекта определяется в тот момент, когда все неграничные вершины сдвигались в направлениях их нормалей. По умолчанию нормаль имеет единичную длину, поэтому толщина объекта получается, грубо говоря, единичная. Чтобы менять толщину, достаточно умножать вектор нормали на некоторое число. Делается это с помощью ноды Multiply by Scalar (рис. 29). Вот и все, крутим значение параметра Factor, и толщина объекта меняется. Это было первое отступление.
Второе отступление заключается в следующем: к настоящему моменту дерево с нодами у нас не сильно перегружено, но в скором времени мы добавим еще кое-чего, и будет вообще караул. Поэтому удобно, хотя бы визуально, логически законченные куски отделять друг от друга. Сделать это можно с помощью ноды Execute. Она выполняет ту же роль, что и корневая нода дерева — собирает в себе действия. С ней можно связать некоторые ветви, и утащить куда-нибудь в сторону (рис. 30). При этом сама корневая нода ICETree разгрузится, и будет логически отделен некоторый фрагмент дерева.
Объединение вершин
Этот параграф является третьим основным пунктом настоящего урока. Все, что нам осталось сделать — это попарно объединить граничные вершины. Пока ничего толкового в голову не идет, давайте посмотрим на индексы граничных вершин. Сделать это просто. Добавляем ноду Get Data и берем из нее значение атрибута VertexIndex (рис. 31).
Рис. 31. Get Data и VertexIndex
Далее добавляем ноду Set Data и записываем информацию в новый атрибут, который можно обозвать Thic_Temp (рис. 32).
Рис. 32. Создаем вспомогательный атрибут Thic_Temp
Наконец, соединяем всю ветвь и выводим на экран передаваемую информацию (рис. 33).
Рис. 33. Индексы граничных точек
Мы специально раздвинули вершины с индексами 4 и 116, чтобы можно было ясно видеть эти номера. Любознательный читатель сам быстренько потягает вершины и убедится, что разность между индексами граничных вершин, которые должны быть объединены вместе, постоянна. В нашем случае эта постоянная величина равна 112. Хорошо было бы научиться считать эту величину в независимости от конкретной геометрии. Давайте для начала посчитаем число вершин у исходного объекта, а также определим, сколько из этих вершин являются граничными.
Добавляем ноду Get Data и считываем значение атрибута VertexIndex (рис. 34). Полученное значение имеет контекст per Point, то есть является целым числом, сопоставленным каждой вершине. Нам же хочется узнать, сколько всего этих чисел. Добавляем ноду Build Array From Set (рис. 35). Эта нода возвращает одни массив, состоящий из индексов вершин, и имеющий контекст per Object. Это хорошо. Добавляем ноду Get Array Size, которая, судя по названию, возвращает число элементов в массиве. Наконец, добавляем последнюю ноду Set Data и создаем новый атрибут, который будет хранить в себе число вершин исходного объекта. Назовем этот атрибут Thic_OriginalVertexCount (рис. 36).
Рис. 35. Нода Build Array From Set
Рис. 36. Ноды Get Array Size и Set Data
Соединяем все вместе (рис. 37). Важным моментом является номер порта в корневой ноде ICETree, с которым связывается выход ноды Set Data. Этот порт должен быть выше порта, через который происходит выдавливание геометрии. Иначе мы посчитаем число вершин уже измененной геометрии, что совершенно ни к чему.
Поехали дальше. Теперь давайте считать число граничных вершин. Расчехляем атрибут VertexIsBorder и так же, как и раньше, строим из него один массив (рис. 38). Необходимо узнать, сколько в массиве значений true. Для этого используется нода Find In Array. В порт Array отправляем собственно наш массив, а для того, чтобы указать, что значение Value должно быть true, можно либо поставить галочку в свойствах ноды, либо создать константу true и связать ее с портом Value (рис. 39).
Рис. 38. Строим массив из true/false
Выходной порт Index Array ноды Find In Array содержит индексы всех вершин, которые в ранее построенном массиве имели значения true. Нам надо определить их число. Делаем это так же, как и раньше. Полученное значение записываем в атрибут Thic_BoundaryVertexCount (рис. 40).
Рис. 40. Считаем число граничных вершин
Проверяем, и действительно, наши созданные атрибуты содержат значения 80 и 32, сумма которых как раз равняется 112 (рис. 41). Вообще, результат предсказуем. Мы же объединяли два объекта в один, поэтому естественно, что сначала нумеруются вершины первого объекта, а потом второго. Следовательно, номера вершин второго объекта больше числа вершин первого объекта, что мы и наблюдаем.
Рис. 41. Проверяем сохраненные значения
Теперь вырисовывается примерная схема, как же можно попарно объединить граничные вершины. Давайте найдем сначала индексы всех граничных вершин, а потом будем циклически хватать какой-либо индекс, добавлять к нему 112 и объединять соответствующие вершины. Потом будем удалять эти два использованных индекса из массива, и заново по кругу до тех пор, пока не выйдут вон все индексы. Это лишь примерная схема, тут есть несколько нюансов, но мы их решим по мере поступления.
Итак, строим массив (в контексте per Object) всех граничных вершин. Это уже проходили, поэтому просто покажу картинку (рис. 42). Обращаем внимание, что теперь все действия снова связываем с нижними портами корневой ноды ICETree. Сам массив мы записали в атрибут с именем Thic_BoundaryVertexIndexes.
Рис. 42. Массив индексов граничных вершин
Переходим к объединению вершин. Понятно, надо добавить ноды Get Topology, Set Topology и, самое главное, Merge Vertices (рис. 43).
Внимательно смотрим на ноду Merge Vertices и видим, что объединяемые вершины должны подаваться одним массивом в порт Vertex Index Array. У нас вершины объединяются попарно, поэтому мы должны сформировать массив всего из двух индексов. Сделать это можно следующим образом. У нас же есть массив всех граничных вершин. Выбираем из него наибольший индекс, потом вычитаем 112 и формируем из этих двух чисел искомый массив. Давайте так и сделаем.
Добавляем ноду Get Data и считываем значение атрибута BoundaryVertexIndexes. С помощью ноды Get Array Maximum выбираем из этого массива наибольшее значение. Наконец, формируем новый массив с помощью ноды Build Array (рис. 44).
Рис. 44. Наибольший индекс граничной точки
Сформированный массив содержит в себе индекс одной вершины, а надо индексы двух вершин. Складываем значения атрибутов Thic_BoundaryVertexCount и Thic_OriginalVertexCount (рис. 45). Как мы раньше выяснили, сумма равна 112.
Вычитаем из ранее выбранного максимального индекса граничной вершины число 112 и добавляем полученный индекс в массив (рис. 46).
Рис. 46. Формируем второй элемент массива
Тягаем вершину с индексом 188, и — о чудо! — вершины объединились (рис. 47). Вот до чего плюсики и минусики довести могут.
Отметим, что с тем же успехом мы могли бы выбирать из массива не наибольшее значение, а наименьшее, и к нему добавлять 112. Это было бы хорошо, если бы не было плохо. При таком подходе после объединения вершин все индексы пересчитываются, и созданный ранее массив индексов граничных вершин оказывается не у дел. В подходе, который мы реализовали, перенумерация также происходит, но нам этого не видно, так как все вершины которые нам нужны будут иметь те же самые индексы, что и раньше. Это связано с логикой и последовательностью операций внутри самого ICE, поэтому нечего тут пенять и сокрушаться, можно только подстраиваться.
Продолжаем. Необходимо выкинуть использованные индексы из массива. Добавляем ноду Set Data, в которой выбираем атрибут Thic_BoundaryVertexIndexes (будем переопределять его содержимое). Потом добавляем ноду Remove From Array (рис. 48).
Рис. 48. Нода Remove From Array
С входным портом Array ноды Remove From Array связываем используемый нами массив индексов граничных вершин, а в порт Index мы должны передать индекс в массиве максимального значения. Для этого можно использовать ноду Find In Array. Итоговые связи изображены на рисунке 49.
Рис. 49. Удаляем первый элемент массива
Второй элемент массива удаляется так же легко. Сначала находим его индекс в массиве с помощью ноды Find In Array, а потом выкидываем с помощью ноды Remove From Array (рис. 50).
Рис. 50. Удаляем второй элемент массива
Мы уже почти приблизились к концу. Осталось только несколько раз повторить все вышеописанные действия. Для этого добавляем ноду Repeat, которая как раз и повторяет ту или иную ветвь дерева несколько раз. Входной порт для действий у ноды Repeat один, поэтому целесообразно операции с массивом индексов граничных вершин и с объединением вершин пропустить через ноду Execute (рис. 51).
Если два раза жамкнуть на ноду Repeat и потаскать единственный манипулятор, то можно увидеть маленькое чудо. Голубая полоска граничных ребер местами начинает заменяться на беленькие ребра. Это все потому, что вершины объединяются. Ура, товарищи! Впрочем, мы четко знаем, сколько необходимо итераций в ноде Repeat. Ровно столько, сколько граничных вершин было у исходного объекта, а это в точности значение атрибута Thic_BoundaryVertexCount (рис. 52). Вот и все, на этом основная часть создания модификатора утолщения средствами ICE закончена.
Рис. 52. Повторяем нужное число раз
Заключение
Общий вид получившегося дерева ICE изображен на рисунке 53.
Давайте создадим теперь нашу собственную ноду утолщения. Первым делом надо сделать так, чтобы с корневой нодой ICETree была связана только одна ветвь (рис. 54).
Рис. 54. Добавляем ноду Execute
Далее выделяем все ноды, кроме корневой, жмем правой клавишей и выбираем пункт Create Compound (рис. 55).
Заходим внутрь компаунда (так прямо по-русски и называется), нажав на букву e в кружочке над верхним левым углом прямоугольника. Жмем два раза на имя компаунда и вписываем туда все, что угодно, например Thickness_ICE (рис. 56).
Еще хотелось бы, чтобы у компаунда были кое-какие параметры, например, регулятор толщины. Чтобы достичь желаемого, перетаскиваем входной порт ноды Multiply by Scalar на черный кружок на левой панели (рис. 57).
Рис. 57. Выводим редактируемый параметр
Вот и все, теперь подключив наш компаунд к любому полигональному объекту, можно ему задавать произвольную толщину (рис. 58).
Следует сказать, что модификатор получился хоть и рабочим, но не очень-то и хорошим. Работает медленно. Хоть и ICE, хоть и многопоточность, но вот прямо чувствуется, как компьютер начинает работать, когда крутишь разные (пока весьма немногочисленные) параметры у компаунда. В этом отношении старые добрые дополнения, о которых упоминалось во введении, гораздо эффективнее.
Напоследок, в приложенном файле лежит немного доработанный аналогичный компаунд. В нем добавлена возможность утолщения в обе стороны (как во внешнюю часть объекта, так и во внутреннюю). Также он не портит исходного объекта, а должен подсоединяться к ICETree пустого полигонального объекта. С портом Original Mesh связывается объект, который хотелось бы утолстить. Да и вообще, теперь каждый сам с удовольствием разберет на винтики этот компаунд, так как после прохождения урока там все должно стать предельно ясным.
Засим разрешите откланяться и пожелать успеха в освоении столь важного и нужного инструмента, как ICE.