Manifold Geometry // Многообразная Геометрия

Geometric modeling

Подписаться на эту рубрику по RSS

Заметки на геометрическую тематику.

К параметрическому моделированию на OpenCascade

"OCAF is far more that a means to supply undo/redo. OCAF is what should be used to develop a true feature based parametric modeler. Such is something truly needed by the open source community" (William Jones at OCCT forum).

Введение

Библиотека OpenCascade берет начало в системе автоматизированного проектирования Евклид (EUCLID). То была классическая САПР, реализующая привычную парадигму параметрического моделирования с историей построения конструктивных элементов. Евклид умер, а его геометрическое ядро теперь свободно дрейфует в мире инженерного софтвера, находя применение то тут, то там. Индустриальной САПР, в полной мере раскрывающей все возможности этой единственной открытой библиотеки прецизионного моделирования, так и не появилось. Такое положение дел, конечно, тормозит развитие геометрического ядра. И все-таки OpenCascade живет и взрослеет, во многом благодаря неоднородному в смысле решаемых задач сообществу пользователей. Где применяется библиотека? Немало где, всего не перечислить. Это проблемы обмена данными, инженерный анализ, измерение, редактирование немой геометрии, подготовка моделей к производству, и кто знает, что еще. Как ни странно, параметрическое моделирование, как таковое, не доминирует во многообразии приложений библиотеки. Впрочем, если разобраться, ничего странного тут нет. Вместе с Евклидом потерялась широкая клиентская база, то есть рабочие места, где инженер проектировал изделие в объектной модели CAS.CADE (тогда ядро называлось иначе). Внедрение продуктов Dassault Systemes означало переход в другую объектную модель, и абстракции фичеров Евклида оказались никому не нужны.

Надо отметить, что САПРы плохо совместимы друг с другом. Скажем, параметрическая модель SolidWorks не годится для работы в Catia, и то же самое верно для всякой другой пары систем разных вендоров. К слову, в нейтральном формате STEP есть почти никем не используемые возможности представления фичеров. Их игнорирование, по всей видимости, имеет рациональные корни, но, в конечном счете, это элементарно противоречит законам коммерции. Впрочем, стык технологий — очередная ниша, в которой кормятся компании помельче, стремясь обеспечить «бесшовную» интеграцию одной САПР с другой. Резюмируем сказанное, введя основной тезис данной заметки: в библиотеке OpenCascade фичеров нет...

// … Суета в зале, кто-то встает и, чертыхаясь, уходит.

Объектная модель OpenCascade описывает чистую, семантически «незамутненную» геометрию в граничном представлении, и конструктивные элементы в ней попросту не предусмотрены. Опираясь на сказанное выше, легко понять почему это так. Любая САПР реализует тот или иной подход к проектированию по-своему. Граничные элементы цифрового изделия компонуются в группы-фичеры, и система поддерживает целостность этих фичеров в ходе моделирования. На уровне геометрического ядра общего назначения невозможно знать какие фичеры понадобятся в той или иной САПР. Это, однако, не мешает ядру позаботиться о предоставлении разработчикам САПР набора инструментов для абстрагирования фичеров из граничной модели. Такой инструмент в OpenCascade есть. Это OCAF.

// OCAF = Open CASCADE Application Framework.

В этой заметке мы рассмотрим, как ввести в экосистеме OpenCascade абстракцию примитивного фичера и скомпонуем элементарную схему моделирования с историей. Предложенные подходы вполне годятся для создания небольших параметрических САПР, в том числе и для коммерческого применения.

Азы OCAF

С точки зрения программиста, OCAF — это набор классов и инструментов для организации данных типового инженерного приложения. Базовый механизм очень гибок. Его можно сравнить с СУБД, где в зону ответственности программиста попадает не только наполнение данными определенных структур, но и проектирование самих этих структур, задание отношений между ними. Не все, однако, знакомы с базами данных в той мере, с которой начинается интуитивное восприятие концепции. Посему не побрезгуем и примером из жизни. Вообразите гипермаркет со стеллажами, заполненными всяким добром. Стеллаж сборный, и количество полок вы регулируете сами. На одном стеллаже имеет смысл держать однотипные товары, хотя, с технической точки зрения, вы можете устроить там полный бардак. Ваш код, который оперирует с вот так организованными данными, уподобляется шустрому работнику павильона, который знает, где что лежит, как перемещать продукты, каковы законы товарного соседства и что делать со стайкой потребителей, требующих выдать именно им единственную оставшуюся банку консервов. Это не просто демагогия, а неуклюжая попытка подчеркнуть, что все проблемы совместного доступа и многие задачи поддержания согласованности данных ложатся на ваши плечи. В этом отношении СУБД гораздо мощнее, но OCAF отлично работает, если использовать его с умом и не пытаться решать несвойственные ему задачи.

Метки и Атрибуты — основные строительные блоки Документа OCAF.

Чем же привлекателен OCAF? Он прост. И при названной простоте он предоставляет готовые к использованию сервисы, такие как открытие и сохранение данных в файл, undo/redo и управление транзакциями. Короче говоря, вы получаете джентльменский набор функций по управлению данными и полную свободу в их организации.

Грамотное использование OCAF требует знакомства с его базовыми понятиями. Все начинается со структуры данных типа Документ. Он содержит такую порцию данных, которую имеет смысл держать в отдельном файле. Документ можно открыть из файла, сохранить в файл или создать новый. Документ состоит из иерархически организованных ячеек хранения данных, называемых Метками. Первое, о чем следует позаботиться, начиная работу с OCAF — это какова желаемая структура Документа, то есть иерархия Меток. Сами данные привязываются к Меткам и представляют собой Атрибуты дерева. Резюмируя сказанное, можно сравнить Документ с новогодней елкой, ветки которой являются Метками, а Атрибуты — игрушками.

Бардак или гибкость?

Представление данных как иерархии Меток и Атрибутов требует переосмысления объектной модели вашего приложения в терминах OCAF. Как переложить объект приложения на OCAF? Каковы правила игры? Уже понятно, что, вообще говоря, один объект представляет собой поддерево Меток и некоторый набор Атрибутов, причем вы сами должны спроектировать эту иерархию и осуществить декомпозицию объекта на Атрибуты. OpenCascade предоставляет набор стандартных Атрибутов, из которых можно компоновать объекты. Это примитивные типы, массивы, геометрические объекты и многое другое. Кроме того, есть возможность создать собственный тип Атрибута, унаследовав базовый класс TDF_Attribute и определив необходимые методы.

Второй способ и проще и натуральнее. На один объект выделяется одна Метка, а единственный Атрибут содержит указатель на экземпляр вашего класса. Есть и минусы. Во-первых, иерархия корневых Меток все еще подлежит обдумыванию. Во-вторых, если данные одного объекта ссылаются на данные другого, то этих взаимосвязей не будет существовать на уровне структур OCAF, и базовые механизмы поддержания целостности отношений останутся на совести приложения. В-третьих, нестандартный Атрибут не может быть сериализован стандартными средствами OCAF. Придется реализовать пару функций для записи и чтения каждого такого типа.

Стандартные Атрибуты хороши тем, что для их использования уже все подготовлено, и надо только разложить свои данные «по полочкам». С другой стороны, представление одного объекта целой иерархией Меток усложняет внутреннюю структуру документа, делает его сложным для восприятия, что может повлечь ошибки при реализации логики приложения. Кроме того, чрезмерная фрагментарность данных дает ощутимое проседание по памяти, так что Метками лучше не злоупотреблять.

Запутанная структура OCAF. Приложение ответственно за интерпретацию иерархии Меток как объектов модели данных.

Пожалуй, основная проблема OCAF состоит в том, что на программиста возлагается слишком многое. Для устранения этой проблемы были созданы библиотеки TObj и ActiveData, которые устанавливают жесткие правила игры и реализуют некоторые дополнительные механизмы поддержания целостности данных. Мы не будем сейчас углубляться ни в сам OCAF, ни в описание его надстроек, а перейдем сразу к сути.

Параметрическая модель

Настало время предъявить простой пример параметрической модели, в реализации которой нам поможет OCAF. Будем моделировать панель, представляющую собой брусок материала с отверстиями круглой формы. Вообще говоря, тип конструктивного элемента не имеет значения. Важно лишь то, что все эти фичеры задают некоторый объем материала, изымаемый из заготовки путем сверления или фрезерования. Специфика примера в том, что фичеры такой модели независимы, но для иллюстрации довольно и этого.

Объектами модели данных являются Заготовка, Фичер и Деталь (будем использовать имя собственное для объектов модели и нарицательное для терминов). В качестве параметров Заготовки примем длину, ширину и толщину бруска. Изъятием материала посредством механообработки получаются фичеры, и здесь, не нарушая общности, мы ограничимся цилиндрическими отверстиями. В результате булева вычитания Фичеров из Заготовки получается Деталь. Теперь, если пользователь изменяет параметры Фичеров или Заготовки, приложение должно автоматически пересчитать модель Изделия. Давайте разберемся, как такую логику можно реализовать.

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

Для быстрого прототипирования модели данных будем использовать библиотеку ActiveData. Это надстройка над OCAF, дающая «более объектно-ориентированный» способ мыслить, не заботясь ни о каких Метках и способах их интерпретации. По сравнению с библиотекой TObj, имеющей то же назначение, ActiveData предоставляет механизм Функций, позволяющий реализовать параметрические зависимости между элементами данных (отсюда и название).

Граф зависимостей между моделирующими Функциями. Сначала моделируется каждый Фичер отдельно, после чего все Фичеры вычитаются булевой операцией из Заготовки.

Используя механизмы OCAF через интерфейсы ActiveData, мы можем построить граф зависимостей, который, связывая Фичеры, Заготовку и Деталь, дает параметрическую модель изделия. Появление нового Фичера добавляет еще одну Функцию в граф, и булева операция выполняется снова. Тот же эффект дает редактирование Фичера, скажем, радиуса и глубины отверстия.

Ключевой вопрос в проектировании с историей построения состоит в том, а как же собственно эту историю сохранить? Ничего не стоит организовать Функции в граф зависимостей, но как узнать, какое отверстие в Детали было порождено тем или иным Фичером? Потеря этой связи автоматически делает результирующую геометрию «немой», и применить какие-либо операции к уже созданному Фичеру у нас не выйдет.

Заготовка с отмеченными на ней Фичерами и результирующая Деталь. Отверстия Детали окрашены разными цветами для иллюстрации их принадлежности разным Фичерам.

В нашем примере история организована в виде специального Атрибута-таблицы, доступного в объекте Деталь. Каждая грань твердотельной модели Фичера имеет некоторый образ в итоговой Детали. В таблицу записывается уникальный идентификатор Фичера и набор граней-образов. Все эти образы присутствуют в геометрической модели Детали, а таблица истории лишь связывает их с оригинальным объектом, который вычитался из Заготовки.

История модификаций применительно к граням Фичера. Верхняя грань уничтожается булевой операцией, нижняя используется как есть, а боковая цилиндрическая грань обрезается Заготовкой.

Эта схема возможна благодаря тому, что каждая операция моделирования геометрического ядра сохраняет внутри себя историю преобразования участвующих в этой операции топологических элементов. История извлекается следующим унифицированным образом:

TopoDS_Shape GetImage(const TopoDS_Shape&       source,
                      BRepBuilderAPI_MakeShape& API)
{
  BRep_Builder BB;
  
  // MODIFIED
  const TopTools_ListOfShape& modified = API.Modified(source);
  //
  if ( modified.Extent() == 1 )
    return modified.First();
  //
  if ( modified.Extent() )
  {
    TopoDS_Compound imageComp;
    BB.MakeCompound(imageComp);
    //
    for ( TopTools_ListIteratorOfListOfShape lit(modified); lit.More(); lit.Next() )
      BB.Add( imageComp, lit.Value() );
 
    return imageComp;
  }
  
  // GENERATED
  const TopTools_ListOfShape& generated = API.Generated(source);
  //
  if ( generated.Extent() == 1 )
    return generated.First();
  //
  if ( generated.Extent() )
  {
    TopoDS_Compound imageComp;
    BB.MakeCompound(imageComp);
    //
    for ( TopTools_ListIteratorOfListOfShape lit(generated); lit.More(); lit.Next() )
      BB.Add( imageComp, lit.Value() );
 
    return imageComp;
  }
  
  // To have this check here is useful for blended edges. We do not care of
  // edge-edge history. We only care of edge-face (GENERATED) history as
  // what we want to do is feature recognition. And feature is a set of faces J
  if ( API.IsDeleted(source) )
    return TopoDS_Shape();
 
  return source;
}

Заметим здесь, что один топологический элемент может иметь несколько образов в результате операции моделирования.

Два образа одной грани после применения булевой операции.

Есть и кое-что забавное относительно доступа к истории в OpenCascade. Допустим, вы решили сначала забрать из моделирующего инструмента все коллекции образов, чтобы «разобраться» с ними не сразу, а где-нибудь попозже. Плохая идея! Доступ к коллекции GENERATED стирает содержимое коллекции MODIFIED.

// MODIFIED
const TopTools_ListOfShape& modified = API.Modified(source);
   
// GENERATED
const TopTools_ListOfShape& generated = API.Generated(source);
   
// ‘modified’ list now contains data from ‘generated’. Yes, that’s really awkward...
if ( modified.Extent() == 1 )
  return modified.First();

Это происходит из-за веселых махинаций внутри моделирующего инструмента. Он использует ровно одну коллекцию для передачи образов в клиентский код, и всякий раз очищает ее перед повторным использованием. Выхода два. Либо «разобраться» с коллекцией сразу после ее получения, либо убрать константные ссылки в объявлении списков с историей.

// MODIFIED
const TopTools_ListOfShape& modified = API.Modified(source);
  
// Let’s process the ‘modified’ list before we ask for ‘generated’
if ( modified.Extent() == 1 )
  return modified.First();
  
// GENERATED
const TopTools_ListOfShape& generated = API.Generated(source);
  
// Now continue with ‘generated’
if ( generated.Extent() == 1 )
  return generated.First();

Повышаем эффективность

Нетрудно видеть, что предложенная схема обладает существенным недостатком. Если пользователь меняет параметр одного Фичера, то булево вычитание все равно будет выполнено на ВСЕХ операндах. Интуитивно понятно, что можно попытаться переиспользовать уже готовый результат и учесть измененный Фичер локально. Здесь начинает работать так называемое Прямое Редактирование (direct editing) модели.

Фичеры и их образы.

Допустим, что пользователь изменил параметры Фичера f, в результате чего было получено новое твердотельное представление f*. Новую твердотельную модель Фичера мы желаем вычесть из Заготовки как можно более эффективным способом. Выясняется, что в некоторых случаях, действительно, можно сэкономить на булевых. Рецепт следующий. В модели Детали берем образ im(f) Фичера f. Это набор граней в модели Детали. Если грани изолированы, их можно уничтожить, элементарно изъяв соответствующие узлы из топологического графа. Это операция подавления фичера, обозначенная ключевым словом suppress на схеме вычислений, приведенной ниже. В результате подавления образуется новая Деталь, не содержащая граней im(f). Теперь сама Деталь используется ВМЕСТО Заготовки. Из нее вычитается ТОЛЬКО новый, отредактированный фичер f*. Остальные Фичеры в данной операции не нужны, поскольку они уже присутствуют в Детали. Все довольно просто.

Концептуальная схема гибридного редактирования модели.

Однако есть пара нетривиальных моментов. Во-первых, подавлять следует только ИЗОЛИРОВАННЫЕ фичеры, поскольку подавление «трудного фичера» — это последнее, чем хотелось бы заниматься. Увы, признак изолированности фичера нигде не хранится, и эту ситуацию нужно уметь распознавать. На помощь приходит парадигма Распознавания Фичеров (feature recognition), идущая бок о бок с Прямым Редактированием, и весь комплекс ее методов. В случае же, если образ im(f) целевого Фичера не изолирован, можно пойти дальше. А именно, следует проанализировать, не участвует ли наш «трудный» фичер в группе, которая, в свою очередь, уже изолирована. Если это так, то подавляем всю группу сразу. В противном случае дело плохо — фичеры выползли на границу заготовки, и придется выполнять полное вычитание.

Итоги

Мы видим, что библиотека OpenCascade дает достаточный набор средств для реализации параметрических систем. Эффективное использование этих средств требует достаточно высокой культуры владения OpenCascade и знания классических принципов построения систем параметрического проектирования.

Смотрите демонстрацию предложенного подхода на нашем канале в youtube. Исходные коды прототипа, представленного в данной заметке, доступны в открытом репозитории.