ADN Open CIS
Сообщество программистов Autodesk в СНГ

29/11/2019

2D и 3D геометрия в Forge Viewer-е

При создании продвинутых приложений и использованием API Forge Viewer-а, часто возникает задачи обхода иерархии элементов модели, получения и изменения геометрии отдельных элементов, получения границ. Давайте разберемся, как такие задачи решаются для 2D и 3D геометрии в Forge Viewer.

Примечание: некоторые примеры кода используют непубличное API (обычно это классы и функции в пространстве имен Autodesk.Viewing.Private и методы и свойства viewer.impl). Реализация таких задач в будущем может измениться.

Перед обзором конкретных задач, нужно прояснить два важных понятия: фрагменты (fragments) и объекты (objects). Результаты преобразования для Forge Viewer-а исходных 2D и 3D моделей сервисом Model Derivative помимо всего прочего включают:

  • фрагменты (fragments), которые представляют отдельные mesh-и с такими свойствами как матрицы преобразования, bounding box, материалы
  • объекты (objects) - логические сущности со набором свойств, то, что доступно для выбора на сцене, созданной Forge Viewer-ом.

Вообще говоря, отношения между объектами и фрагментами являются "многие-ко-многим", один объект может состоять из множества фрагментов (обычно для 3D объектов с большим количеством треугольников или разными материалами), и, напротив, множество объектов могут быть объединены в единый фрагмент (обычно для 2D). При работе с API Viewer-а фрагменты доступны по их fragment ID (или fragIds), а объекты по их database ID (или dbIds)

3D

Обход иерархии модели

Иерархия 3D моделей инкапсулируется классом Autodesk.Viewing.Private.InstanceTree. Для получения экземпляра дерева модели Viewer-а используйте метод model.getInstanceTree(). С его помощью можно получить родительский или дочерние объекты, а также дочерние фрагменты заданного объекта.

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

Пример рекурсивного обхода иерархии модели, начиная с корневого объекта, по ходу которого логгируются ID и количество дочерних объектов:

Код - JavaScript: [Выделить]
  1. viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, function () {
  2.   const tree = viewer.model.getInstanceTree();
  3.   const rootId = tree.getRootId();
  4.   tree.enumNodeChildren(
  5.     rootId,
  6.     function (dbId) {
  7.       console.log('dbId:', dbId, 'childCount:', tree.getChildCount(dbId));
  8.     },
  9.     true
  10.   );
  11. });

Получение геометрии фрагмента

Фрагменты как 3D, так и 2D моделей, инкапсулируются классом Autodesk.Viewing.Private.FragmentList. Список фрагментов модели Forge Viewer можно получить, используя метод model.getFragmentList(). Экземпляр FragmentList позволяет получать и изменять различные свойства фрагментов, такие как видимость, матрицы преобразования, связанные материалы и т.д.

Примечание: к сожалению для класса FragmentList покачто отсутствует документация на портале https://forge.autodesk.com, но Вы можете изучить неминифицированный код Forge Viewer-а, например здесь: https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js.

Пример получения свойств фрагментов:

Код - JavaScript: [Выделить]
  1. const frags = viewer.model.getFragmentList();
  2. function listFragmentProperties(fragId) {
  3.   console.log('Fragment ID:', fragId);
  4.   // ID всех объектов, связанных с данным фрагментом
  5.   const objectIds = frags.getDbIds(fragId);
  6.   console.log('Linked object IDs:', objectIds);
  7.   // Матрица фрагмента в пространстве  модели
  8.   let matrix = new THREE.Matrix4();
  9.   frags.getWorldMatrix(fragId, matrix);
  10.   console.log('World matrix:', JSON.stringify(matrix));
  11.   // Границы фрагмента в пространстве модели
  12.   let bbox = new THREE.Box3();
  13.   frags.getWorldBounds(fragId, bbox);
  14.   console.log('World bounds:', JSON.stringify(bbox));
  15. }

Изменение геометрии фрагмента

Пример изменения положения, масштаба и поворота заданного фрагмента модели

Код - JavaScript: [Выделить]
  1. const frags = viewer.model.getFragmentList();
  2. function modifyFragmentTransform(fragId) {
  3.   let scale = new THREE.Vector3();
  4.   let rotation = new THREE.Quaternion();
  5.   let translation = new THREE.Vector3();
  6.   frags.getAnimTransform(fragId, scale, rotation, translation);
  7.   translation.z += 10.0;
  8.   scale.x = scale.y = scale.z = 1.0;
  9.   frags.updateAnimTransform(fragId, scale, rotation, translation);
  10. }

Примечание: существует альтернативный подход. Большая часть функционала доступна также с использованием viewer.impl. Например

  • можно получить экземпляр класса Autodesk.Viewing.Private.FragmentPointer, имеющего методы, аналогичные классу списка фрагментов: fragPointer.getWorldMatrix(matrix), fragPointer.getWorldBounds(bbox), и т.д.
  • можно получить экземпляр THREE.Mesh с помощью viewer.impl.getRenderProxy(model, fragId), являющегося аналогом fragList.getVizmesh(fragId). С объектами мешей, правда, следует быть аккуратным, поскольку в некоторых случаях, viewer может повторно использовать существующие объекты и заменить значения при следующем вызове метода fragList.getVizmesh(fragId)

2D

Как было отмечено выше, в 2D моделях часто множество объектов объединены в единый фрагмент. Это несколько усложняет такие операции, как обход геометрии и получение границ, тем не менее, viewer содержит несколько полезных служебных классов, которыми мы сможем воспользоваться для решения таких задач.

Получение геометрии

Для получения геометрических примитивов, такие как отрезки, дуги окружностей или эллипсов всех объектов в заданном фрагменте мы можем использовать метод enumGeoms(filter, callbackObject) класса Autodesk.Viewing.Private.VertexBufferReader:

Код - JavaScript: [Выделить]
  1. const frags = viewer.model.getFragmentList();
  2. function listFragmentPrimitives(fragId) {
  3.   const mesh = frags.getVizmesh(fragId);
  4.   const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
  5.   vbr.enumGeoms(null, {
  6.     onLineSegment: function (x1, y1, x2, y2, vpId) {
  7.       console.log('found line', x1, y1, x2, y2);
  8.     },
  9.     onCircularArc: function (cx, cy, start, end, radius, vpId) {
  10.       console.log('found circular arc', cx, cy, start, end, radius);
  11.     },
  12.     onEllipticalArc: function (cx, cy, start, end, major, minor, tilt, vpId) {
  13.       console.log('found elliptical arc', cx, cy, start, end, major, minor, tilt);
  14.     },
  15.     onOneTriangle: function (x1, y1, x2, y2, x3, y3, vpId) {
  16.       console.log('found triangle', x1, y1, x2, y2, x3, y3);
  17.     },
  18.     onTexQuad: function (cx, cy, width, height, rotation, vpId) {
  19.       console.log('found quad', cx, cy, width, height, rotation);
  20.     }
  21.   });
  22. }

В случае, если нужно получить только примитивы определенного объекта, слоя или viewport-а, можно передать функцию для фильтрации первым аргументом метода enumGeoms. Эта функция может принимать в качестве первых 3 аргументов id объекта, id слоя и id viewport-а, и должна возвращать true для геометрических примитивов, которые должны возвращаться enumGeoms. Также можно воспользоваться методами enumGeomsForObject(dbId, callbackObject), или enumGeomsForVisibleLayer(layerId, callbackObject) экземпляра классаVertexBufferReader.

Границы объектов

Получение границ 2D примитивов могло стать утомительной и скучной задачей. К счастью, мы можем воспользоваться вспомогательным (хоть и private-ным) классом Autodesk.Viewing.Private.BoundsCallback. Экземпляр этого класса может быть передан в методы, используемые для перечисления геометрических примитивов класса VertexBufferReader, которые мы обсудили выше. В этом случае, после выполнения методов поиска примитивов, будут заполнены свойства THREE.Box3 - bounding box-а всех найденных геометрических 2D примитивов:

Код - JavaScript: [Выделить]
  1. const frags = viewer.model.getFragmentList();
  2. function getBounds2D(dbId) {
  3.   let bounds = new THREE.Box3();
  4.   let boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bounds);
  5.   const fragIds = frags.fragments.dbId2fragId[dbId]; // находим все фрагменты, включающие примитивы объекта
  6.   if (!Array.isArray(fragIds)) {
  7.     fragIds = [fragIds];
  8.   }
  9.   for (const fragId of fragIds) {
  10.     // получаем mesh, включающий всю геометрию фрагмента
  11.     const mesh = frags.getVizmesh(fragId);
  12.     const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
  13.     vbr.enumGeomsForObject(dbId, boundsCallback); // обновляем bounds на основе геометрических примитивов объекта dbId
  14.   }
  15.   return bounds;
  16. }

Источник: https://forge.autodesk.com/blog/working-2d-and-3d-scenes-and-geometry-forge-viewer

Автор перевода: Александр Игнатович
Опубликовано 29.11.2019