Простой способ инвертировать нормаль грани
Нередкая ситуация — грани тела ориентированы некорректно. Можно броситься искать подходящий инструмент в модуле Shape Healing (как говорилось в одном советском анекдоте, «а, кстати, где он?»), а можно реализовать простой и изящный способ обращения нормали самостоятельно. Чтобы написать правильный код надо иметь в виду следующее:
- Топология модели — это ориентированный граф, представляющий отношения вложенности граничных элементов (граней, ребер и вершин).
- В графе есть не только ГРАНИЧНЫЕ элементы, но и СТРУКТУРНЫЕ элементы (оболочки, контуры, тела, компаунды). Все вместе узлы графа называют ТОПОЛОГИЧЕСКИМИ ЭЛЕМЕНТАМИ.
- Топологический граф модели физически организован посредством вложенных коллекций объектов типа TopoDS_Shape.
- Отдельно взятый топологический элемент (TopoDS_Shape) в библиотеке OpenCascade вообще-то не редактируется. Для редактирования модели надо изъять старый топологический элемент и поместить в нужную коллекцию должным образом подготовленный новый объект.
- TopoDS_Shape — это легковесный объект. Для себя можно считать его своеобразным указателем (дополненным ориентацией и трансформацией).
- Чтобы изменить ориентацию грани нужно ЗАМЕНИТЬ соответствующий ей граничный элемент (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.
Предложенный метод не меняет топологию модели, не «расшивает» грани и не содержит абсолютно ничего лишнего.