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

31/05/2020

Forge Viewer: расширение SceneBuilder в боевых условиях на практическом примере

Пошаговое руководство от простого примера использования SceneBuilder-а до создания параметрического углового шкафа.

Как заметил один из наших мудрых инженеров, каждый раз, когда кто-то пытается создать просмотрщик моделей, то он всегда принимает попытки расширить его до редактора. Forge Viewer не является исключением, нам часто задавали подобные вопросы, в особенности о возможности добавления пользовательской геометрии.

Мы уже рассказывали о возможностях добавления геометрии в сцену Forge Viewer-а в некоторых статьях, и даже добавили пример в пользовательскую документацию, показывающий рекомендуемый способ решения подобной проблемы.

Конечно, то, что Forge Viewer основан на three.js не является секретом, все способы добавления пользовательской геометрии во Viewer осуществляются с помощью низкоуровневого API этой библиотеки. Это, фактически, является ответом на вопрос, почему Viewer не знает о существовании такой добавленной геометрии и не позволяет взаимодействовать с ней. Для того, чтобы обеспечить возможность выбора таких объектов, Вам необходимо работать напрямую на уровне three.js и самим реализовать возможность выбора этих объектов. В качестве примера, посмотрите, как мы продемонстрировали такую возможность с использованием технологии ray casting.

Тем не менее, при таком подходе пользовательская геометрии всё равно выглядит чужеродной, например, инструменты измерения или создания секущих плоскостей её игнорируют.

По этой причине наши инженеры разработали расширение SceneBuilder, которое интегрирует пользовательскую геометрию в Forge Viewer и позволяют другим инструментам viewer-а работать с ней.

Мы представили инструкцию по добавлению пользовательской геометрии с помощью расширения SceneBuilder-а, а также статью Custom models in Forge Viewer (примечание переводчика: статья в русском переводе) моего коллеги Petr Broz, которые могут быть полезны в качестве отправной точки по работе с новым расширением.

В этой статье мне хотелось создать какой-то практический пример работы с расширением SceneBuilder, акцентировать Ваше внимание на тонкостях работы со SceneBuilder-ом (и не только с ним!).

Мы создадим параметрическую модель углового шкафа на основе простой модели полки:

Я покажу вам как:

  • создать пустую пользовательскую модель и добавить в неё геометрию;
  • изменять геометрию и её расположение в пользовательской модели;
  • получать геометрию из существующей/импортированной модели и добавлять её в пользовательскую модель.

Мы начнем с простой модели Fusion360: CornerBoard.

Здесь я не буду показывать создание расширения с нуля. Как создать расширение и как работать с docking panel Вы можете посмотреть в Viewer extension tutorial.

Шаг I. Добавляем заднюю стенку шкафа как пользовательскую модель

Самый простой способ добавления геометрии:

Код - JavaScript: [Выделить]
  1. viewer.loadExtension("Autodesk.Viewing.SceneBuilder").then(() => {
  2.     sceneBuilder = viewer.getExtension("Autodesk.Viewing.SceneBuilder");
  3.  
  4.     sceneBuilder.addNewModel({})
  5.         .then((modelBuilder) => {
  6.             let geom = new THREE.BufferGeometry().fromGeometry(
  7.                                         new THREE.BoxGeometry(210,160,10));
  8.             let phongMaterial = new THREE.MeshPhongMaterial({
  9.                 color: new THREE.Color(1, 0, 0)
  10.             });
  11.             mesh = new THREE.Mesh(geom, phongMaterial);
  12.             modelBuilder.addMesh(mesh);
  13.  
  14.         });

Если мы добавим этот код в наше расширение SimpleCustomGeometry extension, мы увидим пользовательскую модель в точке (0, 0, 0):

Мы можем уже взаимодействовать с созданной геометрией, мы можем выделить объект, используя инструмент измерения мы можем проверить отступы пользовательской геометрии от изначальной модели для того, чтобы настроить их в дальнейшем:

Здесь хорошо видно, что нам следует переместить создаваемый объект на 100 мм по оси X.

Есть по крайней мере три способа исправить положение созданной геометрии:

1. Настроить смещение перед добавлением mesh-а в модель:

Код - JavaScript: [Выделить]
  1. ...
  2. mesh = new THREE.Mesh(geom, phongMaterial);
  3. mesh.matrix = new THREE.Matrix4().compose(
  4.                 new THREE.Vector3(0, 0, -100),
  5.                 new THREE.Quaternion(0, 0, 0, 1),
  6.                 new THREE.Vector3(1, 1, 1)
  7.     );
  8. modelBuilder.addMesh(mesh);
  9. ...

Применив указанный способ, мы получим следующий результат:

Нам всё ещё нужно подвинуть созданный объект на 85мм по оси Z. Но давайте посмотрим сначала другой способ добиться нужного нам результата.

2. Обновить позицию mesh-а и затем сам mesh

Этот подход не подойдёт для создания анимации, но вполне неплох для того, чтобы переместить созданные объекты. Идея заключается в использовании встроенный методов ModelBuilder для замены mesh-а и работает как в случае изменения геометрии самого mesh-а, так и для изменения позиции или поворота mesh-а:

Код - JavaScript: [Выделить]
  1. ...
  2. mesh = new THREE.Mesh(geom, phongMaterial);
  3. modelBuilder.addMesh(mesh);
  4.  
  5. mesh.matrix.setPosition(new THREE.Vector3(0,85,-100));
  6. modelBuilder.updateMesh(mesh);
  7. ...

С новым смещением (0,85,-100) смотрится гораздо лучше:

Как отмечено выше, метод полезен при изменении геометрии, нам следует только позаботиться о настройке аффинных преобразований для новой геометрии:

Код - JavaScript: [Выделить]
  1. ...
  2. mesh = new THREE.Mesh(geom, phongMaterial);
  3. modelBuilder.addMesh(mesh);
  4.  
  5. mesh.geometry = new THREE.BufferGeometry().fromGeometry(
  6.                                 new THREE.CylinderGeometry( 105, 105, 10, 128 ));
  7. mesh.geometry.computeBoundingBox();
  8. mesh.matrix.makeRotationFromEuler(new THREE.Euler(Math.PI/2,0,0));
  9. mesh.matrix.setPosition(new THREE.Vector3(0,0,-100));              
  10. modelBuilder.updateMesh(mesh);
  11. ...

Результат:

3. Настроить позицию после добавления mesh-а, трансформируя фрагменты

Этот способ выглядит более сложно, но в дальнейшем именно он оказывается для нас гораздо полезнее, поскольку он предоставляет многие дополнительные возможности, а его сложность можно достаточно просто абстрагировать. Чтобы этот подход стал для Вас более понятным, давайте разберемся, какие именно данные хранятся в экземпляре ModelBuilder-а после того, как мы добавим в него mesh:

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

Обратите внимание, что в geomList.geoms экземпляра ModelBuider-а мы можем найти всю геометрию, которую мы добавляли, вне зависимости от способа её создания (был ли это mesh или список фрагментов):

Мы воспользуемся именно этим способом получения id фрагментов компонента пользовательской модели. Поскольку в нашем случае мы добавили только один геометрический компонент, всё выглядит очень просто:

Код - JavaScript: [Выделить]
  1. let new_matrix = new THREE.Matrix4().compose(
  2.                     new THREE.Vector3(0, 85, -100),
  3.                     new THREE.Quaternion(0, 0, 0, 1),
  4.                     new THREE.Vector3(1, 1, 1)
  5.                     );
  6.  
  7. modelBuilder.changeFragmentTransform(1, new_matrix);

Здесь единица — это id фрагмента в моём случае.

Моя маленькая подсказка для Вас — это именно тот подход, который пригодится Вам для создания анимации объектов.

Последний финальный штрих — хотелось бы поменять материал пользовательской геометрии на один из существующих на сцене. В нашем случае, было бы неплохо заменить его на Mahogany – материал полки изначальной модели.

Для этого сначала нам нужно найти, где он сохранён во Viewer-е. Наверняка, многие из Вас знакомы с matman:

Здесь уже доступны материалы из обеих моделей (изначальной и нашей пользовательской), но нас интересуют именно материал из исходной модели, в нашем случае это model:1|mat:0, который мы можем применить к пользовательской модели, опять же используя фрагменты:

Результат:

Уже неплохо, но всё ещё недостаточно хорошо, поскольку геометрия компонента не содержит необходимые данные UV. Данный вопрос мы оставим для отдельной статьи, посещенной способам изменению и замене материалов в ForgeViewer-е. Несмотря на это, уже сейчас у нас есть неплохая смесь «системной» и «пользовательской» моделей на сцене Viewer-а.

Здесь Вы можете посмотреть на то, как выглядит наш проект на первом этапе.

Шаг II. Добавляем промежуточных полок шкафа пользовательскими моделями из геометрии оригинальной полки

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

В нашем случае, было бы неплохо «скопировать» оригинальную полку и разместить её сверху. Это станет первым шагом для создания параметрического шкафа, где потенциальный клиент сможет указать необходимое количество полок.

Для того, чтобы это сделать, следует разобраться с renderProxy:

Как показано выше, последовательность действий получения render proxy фрагментов:

  • получить id нужного объекта сцены, 
  • получить id фрагментов геометрии объекта (их может быть более одного), 
  • получить render proxy каждого фрагмента. 

RenderProxy фактически представляет собой mesh, сейчас нас интересует только геометрия, которая хранится в нем:

Несколько необычная структура... Нам следует преобразовать данные в формат THREE.BufferGeometry, который мы сможем использовать для создания пользовательской модели с помощью расширения ModelBuilder.

Всё, что нам нужно, содержится в свойстве vb: позиция, нормаль, uv, индекс. В attributes есть itemOffset,  itemSize, свойство vbstride содержит «repeating chunk size».

Все, что нам нужно на данном этапе — извлечь эти данные (или, как минимум позиции вершин и индексы для преобразования поверхностей) и преобразовать их в структуру  THREE.BufferGeometry:

Это достаточно пугающая задача, в основном, потому что исходные данные чередуются в одном массиве, и Вам нужно чётко понимать, как извлекать необходимые данные.

К счастью, в API Viewer-а есть класс VertexEnumerator в пространстве имён Autodesk.Viewing.Private.

Вот как извлечь необходимые данные:

Код - JavaScript: [Выделить]
  1. let geom = new THREE.Geometry();
  2. let renderProxy = this.viewer.impl.getRenderProxy(this.viewer.model, fragmentId);
  3.  
  4. let VE = Autodesk.Viewing.Private.VertexEnumerator;
  5.  
  6. VE.enumMeshVertices(renderProxy.geometry, (v, i) => {
  7.             geom.vertices.push(new THREE.Vector3(v.x, v.y, v.z));
  8.         });
  9.  
  10. VE.enumMeshIndices(renderProxy.geometry, (a, b, c) => {
  11.                     geom.faces.push(new THREE.Face3(a, b, c))
  12.             });
  13.  
  14. geom.computeFaceNormals();

Получив THREE.Geometry, мы теперь можем легко создать из неё BufferGeometry:

Код - JavaScript: [Выделить]
  1. let mesh = new THREE.Mesh(
  2.         new THREE.BufferGeometry().fromGeometry(geom),
  3.         new THREE.MeshPhongMaterial({
  4.             color: new THREE.Color(1, 0, 0)
  5.         }));
  6.        
  7. modelBuilder.addMesh(mesh);

И мы успешно создали копию исходной геометрии... ну почти...

Скопированный mesh успешно создался (я скрыл оригинальный объект), но масштаб неверный, поэтому нам необходимо масштабировать созданную пользовательскую модель, увеличив её. RenderProxyсодержит нужную нам матрицу преобразования:

Самым простым способом исправить масштаб, возможно, расположение и поворот (зависит от исходной модели) геометрии будет присвоить создаваемому meshэту матрицу, при необходимости поправить положение:

Код - JavaScript: [Выделить]
  1. mesh.matrix = renderProxy.matrixWorld.clone();
  2. mesh.matrix.setPosition(new THREE.Vector3(0,140,0));

Заменив материал, как мы сделали ранее, получим:

Теперь у нас есть абсолютно всё необходимое для того, чтобы собрать расширение для ForgeViewer, которое будет на основе введенных пользователем данных добавлять, удалять и изменять геометрию параметрического углового шкафа:

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

 

Источник: https://forge.autodesk.com/blog/scenebuilder-extension-forge-viewer-action

 

 

 

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