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

OpenCascade

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

Open CASCADE Technology (OpenCascade, OCCT).

Простой способ инвертировать нормаль грани

Нередкая ситуация — грани тела ориентированы некорректно. Можно броситься искать подходящий инструмент в модуле Shape Healing (как говорилось в одном советском анекдоте, «а, кстати, где он?»), а можно реализовать простой и изящный способ обращения нормали самостоятельно. Чтобы написать правильный код надо иметь в виду следующее:

  1. Топология модели — это ориентированный граф, представляющий отношения вложенности граничных элементов (граней, ребер и вершин).

  2. В графе есть не только ГРАНИЧНЫЕ элементы, но и СТРУКТУРНЫЕ элементы (оболочки, контуры, тела, компаунды). Все вместе узлы графа называют ТОПОЛОГИЧЕСКИМИ ЭЛЕМЕНТАМИ.

  3. Топологический граф модели физически организован посредством вложенных коллекций объектов типа TopoDS_Shape.

  4. Отдельно взятый топологический элемент (TopoDS_Shape) в библиотеке OpenCascade вообще-то не редактируется. Для редактирования модели надо изъять старый топологический элемент и поместить в нужную коллекцию должным образом подготовленный новый объект.

  5. TopoDS_Shape — это легковесный объект. Для себя можно считать его своеобразным указателем (дополненным ориентацией и трансформацией).

  6. Чтобы изменить ориентацию грани нужно ЗАМЕНИТЬ соответствующий ей граничный элемент (TopoDS_Face, являющийся наследником TopoDS_Shape) на другой — с инвертированным флагом ориентации.

Поле нормалей на плоской грани твердотельной модели ANC101. Красный цвет означает ориентацию FORWARD.

Идея метода, который обращает ориентацию грани, состоит в следующем. Рекурсивно итерируемся по вложенным спискам TopoDS_Shape, переходя в топологическом графе от яруса к ярусу. Причем итерируемся не просто так, а на ходу подготавливаем НОВЫЙ топологический граф, помня о том, что объекты TopoDS_Shape не редактируются. Итерируясь сверху-вниз, мы проходим сначала структурные топологические элементы (например, TopoDS_Compound -> TopoDS_Solid -> TopoDS_Shell), а потом достигаем граничных (начиная с TopoDS_Face). Достигнув яруса граней, создаем ИНВЕРТИРОВАННУЮ КОПИЮ грани и помещаем ее в текущий список. Ниже яруса граней спускаться не нужно.

void buildTopoGraphLevel(const TopoDS_Shape& root,
                         const TopoDS_Shape& face2Invert,
                         TopoDS_Shape&       result) const
{
  BRep_Builder BB;
  
  // NOTICE: we enable accumulation of locations because TopoDS_Builder::Add()
  //         recomputes the relative transformations. So if we do not accumulate
  //         transformations here, we will have an improperly placed result.
  //         E.g., imagine that your root shape is transformed.
  for ( TopoDS_Iterator it(root, false, true); it.More(); it.Next() )
  {
    const TopoDS_Shape& currentShape = it.Value();
    TopoDS_Shape newResult;
    
    if ( currentShape.ShapeType() < TopAbs_FACE )
    {
      newResult = makeShape( currentShape.ShapeType() );
      this->buildTopoGraphLevel( currentShape, face2Invert, newResult );
    }
    else
    {
      if ( currentShape.ShapeType() == TopAbs_FACE && currentShape == face2Invert )
        newResult = currentShape.Reversed();
      else
        newResult = currentShape;
    }
    BB.Add(result, newResult);
  }
}

Функция makeShape — это удобная обвязка над BRep_Builder.

TopoDS_Shape makeShape(const TopAbs_ShapeEnum type)
{
  TopoDS_Shape result;
  BRep_Builder BB;
  switch ( type )
  {
    case TopAbs_COMPOUND:
    {
      TopoDS_Compound compound;
      BB.MakeCompound(compound);
      result = compound;
      break;
    }
    case TopAbs_COMPSOLID:
    {
      TopoDS_CompSolid compSolid;
      BB.MakeCompSolid(compSolid);
      result = compSolid;
      break;
    }
    case TopAbs_SOLID:
    {
      TopoDS_Solid solid;
      BB.MakeSolid(solid);
      result = solid;
      break;
    }
    case TopAbs_SHELL:
    {
      TopoDS_Shell shell;
      BB.MakeShell(shell);
      result = shell;
      break;
    }
    case TopAbs_FACE:
    {
      TopoDS_Face face;
      BB.MakeFace(face);
      result = face;
      break;
    }
    case TopAbs_WIRE:
    {
      TopoDS_Wire wire;
      BB.MakeWire(wire);
      result = wire;
      break;
    }
    case TopAbs_EDGE:
    {
      TopoDS_Edge edge;
      BB.MakeEdge(edge);
      result = edge;
      break;
    }
    case TopAbs_VERTEX:
    {
      TopoDS_Vertex vertex;
      BB.MakeVertex(vertex);
      result = vertex;
      break;
    }
    default: return TopoDS_Shape();
  }
  return result;
}

Код, представленный выше, пересобирает верхние (структурные) ярусы топологического графа, а нижние ярусы, начиная с уровня граней, просто копирует из оригинального графа модели. Копирование легковесно, поэтому новая геометрия не создается, что делает указанный метод весьма производительным (на модели из 6700 граней — а это довольно сложная деталь — метод сработал за 0.005 сек. у меня на ноутбуке). Следует заметить, что новый граф конструируется снизу-вверх (таков порядок рекурсии). Если пытаться строить граф сверху-вниз, добавляя в объекты верхнего яруса нижестоящие объекты несколько раз, то OpenCascade генерирует исключение TopoDS_FrozenShape. Происходит это в силу уже названной причины: редактировать топологические элементы нельзя.

Результат инверсии. Синий цвет означает ориентацию REVERSED.

Исходный код решения доступен в исследовательской программе Analysis Situs, начиная с версии 0.1.5. Для инвертирования грани ее нужно выбрать во вьювере и запустить команду «Invert faces» из контекстного меню.

Инвертирование грани в Analysis Situs.

Предложенный метод не меняет топологию модели, не «расшивает» грани и не содержит абсолютно ничего лишнего.