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

28/02/2021

Forge Viewer: динамическое размещение моделей

В Forge Viewer нет встроенного функционала динамического размещения моделей, тем не менее, его, возможно, реализовать самостоятельно за пару шагов: (1) загрузить модель, (2) изменять позицию размещения модели placementTransform (начальное значение можно задать при загрузке модели). В этом примере мы будем отслеживать положение курсора мыши для управления позицией загруженной модели.

В большинстве примеров реализуется перемещение фрагментов (например, TransformationExtension), но, если требуется переместить модель целиком, то более логичным выглядит изменение placementTransform. Я также использовал этот подход в статье Поворот листов (см. перевод на нашем сайте)

Для примера, приведенного в этой статье, нам понадобится вызывать метод viewer.impl.invalidate(true, true, true) поскольку мы будем постоянно изменять положение по событию изменения положения курсора мыши, чтобы Forge Viewer смог успеть обновить сцену.

Есть два метода определения положения дополнительно загруженной модели: размещать её непосредственно на "земле" (viewer.impl.intersectGround() - как показано в этой статье (см. перевод на нашем сайте)). Альтернативный метод - размещать на объекте под курсором мыши (viewer.impl.hitTest() - также можно посмотреть пример использования в статье)

Мы будем использовать оба подхода. Сначала пытаемся найти объект под курсором мыши с помощью hitTest(), если объект не найден - разместим загруженную модель на "земле".

В процессе разработки, когда я попытался использовать hitTest() я столкнулся с проблемами производительности, полагаю, что дело было в том, что hitTest() пыталась найти также объекты модели, которую я перемещал. К счастью, мы можем указать, в каких моделях нам нужно искать объекты 5-ым параметром этого метода: hitTest(clientX, clientY, ignoreTransparent, dbIds, modelIds).

В примере модели машинки, который я использовал, центр координат находится в её центре по всем направлениям. Поэтому, если я просто перемещал модель в координаты, найденные hitTest, то половина машинки оказывалась под поверхностью найденного объекта, поэтому я сохранил половину высоты модели машинки и добавляю значение extraZ к координатам Z при размещении.

В статье "React+TypeScript: Показываем общедоступные модели Autodesk A360 в Forge Viewer" () (см. перевод на нашем сайте), я показал, как можно работать с моделями, которые размещены в Autodesk A360 в общем доступе. В этой статье я буду использовать тот же самый подход. Это позволяет мне не писать серверный код и разместить всё в одном html файле:

Код - HTML: [Выделить]
  1.     <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
  2.     <meta charset="utf-8">
  3.  
  4.     <!-- The Viewer CSS -->
  5.     <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css" type="text/css">
  6.  
  7.     <!-- Developer CSS -->
  8.     <style>
  9.         body {
  10.             margin: 0;
  11.         }
  12.        
  13.         #MyConytainerDiv {
  14.             width: 100%;
  15.             height:100%;
  16.             position: relative;
  17.         }
  18.  
  19.         #MyViewerDiv {
  20.             width: 100%;
  21.             height: 100%;
  22.             margin: 0;
  23.             background-color: #F0F8FF;
  24.         }
  25.     </style>
  26.    
  27.     <title>Showing A360 Shared files</title>
  28. </head>
  29.  
  30.  
  31.     <!-- The Viewer will be instantiated here -->
  32.     <div id="MyConytainerDiv">
  33.         <div id="MyViewerDiv"></div>
  34.     </div>
  35.    
  36.     <!-- The Viewer JS -->
  37.     <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
  38.  
  39.     <!-- jQuery -->
  40.     <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
  41.  
  42.     <!-- Developer JS -->
  43.     <script>
  44.     (function test() {
  45.         // this is the iframe URL that shows up when sharing a model embed on a page
  46.         var myRevitFile = 'https://myhub.autodesk360.com/ue29c89b7/shares/public/SH7f1edQT22b515c761e81af7c91890bcea5?mode=embed'; // Revit file
  47.         var myDwfxFile = 'https://autodesk3743.autodesk360.com/shares/public/SH919a0QTf3c32634dcf03b8a55be243c021?mode=embed'; // Sports Car.dwfx
  48.  
  49.         var viewer;
  50.  
  51.         function getURN(embedURLfromA360, onURNCallback) {
  52.             $.get({
  53.                 url: embedURLfromA360.replace('public', 'metadata').replace('mode=embed', ''),
  54.                 dataType: 'json',
  55.                 success: function (metadata) {
  56.                     if (onURNCallback) {
  57.                         let urn = btoa(metadata.success.body.urn).replace("/", "_").replace("=", "");
  58.                         onURNCallback(urn);
  59.                     }
  60.                 }
  61.             })
  62.         }
  63.  
  64.         function getForgeToken(onTokenCallback) {
  65.             $.post({
  66.                 url: myRevitFile.replace('public', 'sign').replace('mode=embed', 'oauth2=true'),
  67.                 data: '{}',
  68.                 success: function (oauth) {
  69.                     if (onTokenCallback)
  70.                         onTokenCallback(oauth.accessToken, oauth.validitySeconds);
  71.                 }
  72.             });
  73.         }
  74.  
  75.         let options = {
  76.             env: 'AutodeskProduction',
  77.             getAccessToken: getForgeToken
  78.         };
  79.        
  80.         Autodesk.Viewing.Initializer(options, function onInitialized() {
  81.             var viewerDiv = document.getElementById('MyViewerDiv');
  82.             viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
  83.             viewer.start();
  84.  
  85.             let mainModel = null;
  86.             let secondModel = null;
  87.             let extraZ = 0;
  88.  
  89.             document.onkeydown = event => {
  90.                 if (!event.shiftKey)
  91.                     return;
  92.  
  93.                 if (event.code === "ArrowRight") {
  94.                     let tr = secondModel.getPlacementTransform();
  95.                     tr.elements[12] += 1;
  96.                     secondModel.setPlacementTransform(tr);
  97.                 }
  98.  
  99.                 if (event.code === "ArrowLeft") {
  100.                     let tr = secondModel.getPlacementTransform();
  101.                     tr.elements[12] -= 1;
  102.                     secondModel.setPlacementTransform(tr);
  103.                 }
  104.             };
  105.  
  106.             document.onmousemove = event => {
  107.                 if (!event.ctrlKey)
  108.                     return;
  109.  
  110.                 let res = viewer.impl.hitTest(event.clientX, event.clientY, true, null, [mainModel.getModelId()]);
  111.                 let pt = null;
  112.                
  113.                 if (res) {
  114.                     pt = res.intersectPoint;
  115.                 } else {
  116.                     pt = viewer.impl.intersectGround(event.clientX, event.clientY);
  117.                 }
  118.                
  119.                 let tr = secondModel.getPlacementTransform();
  120.                 tr.elements[12] = pt.x;
  121.                 tr.elements[13] = pt.y;
  122.                 tr.elements[14] = pt.z + extraZ;
  123.                 secondModel.setPlacementTransform(tr);
  124.                 viewer.impl.invalidate(true, true, true);
  125.             }
  126.  
  127.             getURN(myRevitFile, function (urn) {
  128.                 let options = {
  129.                     env: 'AutodeskProduction',
  130.                     getAccessToken: getForgeToken
  131.                 };
  132.                 let documentId = 'urn:' + urn;
  133.                
  134.                 Autodesk.Viewing.Document.load(documentId, (doc) => {
  135.                     let items = doc.getRoot().search({
  136.                         'type': 'geometry',
  137.                         'role': '3d'
  138.                     }, true);
  139.                     if (items.length === 0) {
  140.                         console.error('Document contains no viewables.');
  141.                         return;
  142.                     }
  143.  
  144.                     viewer.loadDocumentNode(doc, items[0], {}).then(function (model1) {
  145.                         mainModel = model1;
  146.                         getURN(myDwfxFile, function (urn) {
  147.                             let documentId = 'urn:' + urn;
  148.                             
  149.                             Autodesk.Viewing.Document.load(documentId, (doc) => {
  150.                                 let items = doc.getRoot().search({
  151.                                     'type': 'geometry',
  152.                                     'role': '3d'
  153.                                 }, true);
  154.                                 if (items.length === 0) {
  155.                                     console.error('Document contains no viewables.');
  156.                                     return;
  157.                                 }
  158.  
  159.                                 let tr = new THREE.Matrix4();
  160.                                 tr.set(
  161.                                     0, 0, .005, 0,
  162.                                     .005, 0, 0, 0,
  163.                                     0, .005, 0, 0, 
  164.                                     0, 0, 0, 1
  165.                                 );
  166.                                 viewer.loadDocumentNode(doc, items[0], {keepCurrentModels: true, placementTransform: tr}).then(function (model2) {
  167.                                     secondModel = model2;
  168.                                     let bb = secondModel.getBoundingBox();
  169.                                     extraZ = bb.max.z;
  170.                                 });
  171.                             });
  172.                         });
  173.                     });
  174.                 });
  175.             });
  176.         });
  177.     })();
  178.     </script>
  179. </body>
  180. </html>

Просто разместите эту страницу на любом HTTP сервере, откройте её в браузере и перетаскивайте машинку, зажав CTRL и перемещая мышь. Также Вы можете перемещать её по оси X, нажимая SHIFT+стрелка влево или вправо.

 

Источник: https://forge.autodesk.com/blog/dynamic-model-placement

 

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