Forge Viewer: инструмент Snapper
Возможно, Вы уже задумывались о том, как бы хорошо было использовать привязки, предлагаемые такими инструмента Forge Viewer-а, как измерения (measure tool) и markups, в своих собственных проектах. Тогда у нас для Вас хорошие новости. В этой статье мы покажем, как Вы можете использовать их в своих расширениях Forge Viewer-а.
Алгоритмы привязок (snapper) инкапсулированы в классе Autodesk.Viewing.Extensions.Snapping.Snapper расширения Autodesk.Snapping. Они реализованы как инструмент Forge Viewer-а (tool).
Мы ранее описывали для Вас основные концепции того, что мы называем инструментом (tool) Forge Viewer в статье: https://forge.autodesk.com/blog/custom-tools-forge-viewer. (см. также перевод на нашем сайте)
После добавления snapper-а в стек инструментов и его активации Forge Viewer начинает автоматически реагировать на действия пользователя. Вам, как разработчику собственного расширения остается добавить только обработку результатов, возвращаемых этим инструментом:
- // ...
- viewer.loadExtension('Autodesk.Snapping');
- // ...
- let snapper = new Autodesk.Viewing.Extensions.Snapping.Snapper(viewer);
- viewer.toolController.registerTool(snapper);
- viewer.toolController.activateTool(snapper.getName());
- // ...
- if (snapper.isSnapped()) {
- const result = snapper.getSnapResult();
- const { SnapType } = Autodesk.Viewing.MeasureCommon;
- switch (result.geomType) {
- case SnapType.SNAP_VERTEX:
- console.log('Snapped to vertex', result.geomVertex);
- break;
- case SnapType.SNAP_MIDPOINT:
- console.log('Snapped to midpoint', result.geomVertex);
- break;
- case SnapType.SNAP_INTERSECTION:
- console.log('Snapped to intersection', result.geomVertex);
- break;
- case SnapType.SNAP_CIRCLE_CENTER:
- console.log('Snapped to circle center', result.geomVertex);
- break;
- case SnapType.RASTER_PIXEL:
- console.log('Snapped to pixel', result.geomVertex);
- break;
- case SnapType.SNAP_EDGE:
- console.log('Snapped to edge', result.geomEdge);
- break;
- case SnapType.SNAP_CIRCULARARC:
- console.log('Snapped to circular arc', result.geomEdge);
- break;
- case SnapType.SNAP_CURVEDEDGE:
- console.log('Snapped to curved edge', result.geomEdge);
- break;
- case SnapType.SNAP_FACE:
- console.log('Snapped to face', result.geomFace);
- break;
- case SnapType.SNAP_CURVEDFACE:
- console.log('Snapped to curved face', result.geomFace);
- break;
- }
- }
- // ...
Объект, который возвращает метод snapper.getSnapResult() всегда будет содержать свойство geomType, по которому становится понятно, какой тип привязки был использован (см. Autodesk.Viewing.MeasureCommon.SnapType, где описаны все возможные значения). В зависимости от значения этого свойства, Вы можете в дальнейшем получить дополнительную информацию о привязке, такую как вершину (geomVertex), ребро (geomEdge) или поверхность (geomFace).
Ещё в этом объекте есть свойство "indicator". С его помощью Вы сможете контролировать визуальное представление, отображаемое в Forge Viewer:
- snapper.indicator.render(); // отобразить результаты привязки (mesh)
- snapper.indicator.clearOverlays(); // очистить привязку
Чтобы показать этот функционал на практическом примере, давайте создадим инструмент для рисования "3D областей" с использованием THREE.ExtrudeGeometry и этого замечательного инструмента привязки к вершинам, срединным точкам и пересечениям. Наш инструмент будет сохранять точки, выбранные пользователем и будет постоянно обновлять геометрию на сцене (overlay scene), включая текущую выбранную вершину (если такая существует). Контур выдавленной геометрии будет рассчитан на основе координат X, Y каждой точки, а высота будет определяться на основе минимального и максимального значений Z точек. После нажатия клавиши esc выдавленная геометрия будет «сохранена» на overlay сцене Forge Viewer-а, и будет запущено создание новой геометрии.
Обратите внимание, что в этой статье мы рассматриваем применение инструмента привязок для 3D моделей, но данный функционал также доступен и для 2D чертежей.
Реализация нашего инструмента может выглядеть следующим образом:
- const DrawBoundsToolName = 'draw-bounds-tool';
- const DrawBoundsOverlayName = 'draw-bounds-overlay';
- class DrawBoundsTool extends Autodesk.Viewing.ToolInterface {
- constructor(viewer) {
- super();
- this.viewer = viewer;
- this.names = [DrawBoundsToolName];
- this.active = false;
- this.snapper = null;
- this.points = []; // Нарисованные точки контура
- this.mesh = null; // Геометрия (Mesh) нарисованной 3D-области
- // Hack: удаляем методы, определенные в ToolInterface delete functions defined on the *instance* of a ToolInterface (мы хотим, чтобы взамен вызывались наши собственные методы)
- delete this.register;
- delete this.deregister;
- delete this.activate;
- delete this.deactivate;
- delete this.getPriority;
- delete this.handleMouseMove;
- delete this.handleSingleClick;
- delete this.handleKeyUp;
- }
- register() {
- this.snapper = new Autodesk.Viewing.Extensions.Snapping.Snapper(this.viewer, { renderSnappedGeometry: true, renderSnappedTopology: true });
- this.viewer.toolController.registerTool(this.snapper);
- this.viewer.toolController.activateTool(this.snapper.getName());
- console.log('DrawBoundsTool registered.');
- }
- deregister() {
- this.viewer.toolController.deactivateTool(this.snapper.getName());
- this.viewer.toolController.deregisterTool(this.snapper);
- this.snapper = null;
- console.log('DrawBoundsTool unregistered.');
- }
- activate(name, viewer) {
- if (!this.active) {
- this.viewer.overlays.addScene(DrawBoundsOverlayName);
- console.log('DrawBoundsTool activated.');
- this.active = true;
- }
- }
- deactivate(name) {
- if (this.active) {
- this.viewer.overlays.removeScene(DrawBoundsOverlayName);
- console.log('DrawBoundsTool deactivated.');
- this.active = false;
- }
- }
- getPriority() {
- return 42; // Вы можете использовать любое число (больше 0). 0 - приоритет tools-ов Viewer-а по умолчанию
- }
- handleMouseMove(event) {
- if (!this.active) {
- return false;
- }
- this.snapper.indicator.clearOverlays();
- if (this.snapper.isSnapped()) {
- const result = this.snapper.getSnapResult();
- const { SnapType } = Autodesk.Viewing.MeasureCommon;
- switch (result.geomType) {
- case SnapType.SNAP_VERTEX:
- case SnapType.SNAP_MIDPOINT:
- case SnapType.SNAP_INTERSECTION:
- case SnapType.SNAP_CIRCLE_CENTER:
- case SnapType.RASTER_PIXEL:
- // console.log('Snapped to vertex', result.geomVertex);
- this.snapper.indicator.render();
- this._update(result.geomVertex);
- break;
- case SnapType.SNAP_EDGE:
- case SnapType.SNAP_CIRCULARARC:
- case SnapType.SNAP_CURVEDEDGE:
- // console.log('Snapped to edge', result.geomEdge);
- break;
- case SnapType.SNAP_FACE:
- case SnapType.SNAP_CURVEDFACE:
- // console.log('Snapped to face', result.geomFace);
- break;
- }
- }
- return false;
- }
- handleSingleClick(event, button) {
- if (!this.active) {
- return false;
- }
- if (button === 0 && this.snapper.isSnapped()) {
- const result = this.snapper.getSnapResult();
- const { SnapType } = Autodesk.Viewing.MeasureCommon;
- switch (result.geomType) {
- case SnapType.SNAP_VERTEX:
- case SnapType.SNAP_MIDPOINT:
- case SnapType.SNAP_INTERSECTION:
- case SnapType.SNAP_CIRCLE_CENTER:
- case SnapType.RASTER_PIXEL:
- this.points.push(result.geomVertex.clone());
- this._update();
- break;
- default:
- // Не используем другие типы привязки
- break;
- }
- return true; // Событие не отправляется другим инструментам в стеке
- }
- return false;
- }
- handleKeyUp(event, keyCode) {
- if (this.active) {
- if (keyCode === 27) {
- // Теперь будет создаваться новая геометрия 3D области
- this.points = [];
- this.mesh = null;
- return true;
- }
- }
- return false;
- }
- _update(intermediatePoint = null) {
- if ((this.points.length + (intermediatePoint ? 1 : 0)) > 2) {
- if (this.mesh) {
- this.viewer.overlays.removeMesh(this.mesh, DrawBoundsOverlayName);
- }
- let minZ = this.points[0].z, maxZ = this.points[0].z;
- let shape = new THREE.Shape();
- shape.moveTo(this.points[0].x, this.points[0].y);
- for (let i = 1; i < this.points.length; i++) {
- shape.lineTo(this.points[i].x, this.points[i].y);
- minZ = Math.min(minZ, this.points[i].z);
- maxZ = Math.max(maxZ, this.points[i].z);
- }
- if (intermediatePoint) {
- shape.lineTo(intermediatePoint.x, intermediatePoint.y);
- minZ = Math.min(minZ, intermediatePoint.z);
- maxZ = Math.max(maxZ, intermediatePoint.z);
- }
- let geometry = new THREE.BufferGeometry().fromGeometry(new THREE.ExtrudeGeometry(shape, { steps: 1, amount: maxZ - minZ, bevelEnabled: false }));
- let material = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true });
- this.mesh = new THREE.Mesh(geometry, material);
- this.mesh.position.z = minZ;
- this.viewer.overlays.addMesh(this.mesh, DrawBoundsOverlayName);
- this.viewer.impl.sceneUpdated(true);
- }
- }
- }
Теперь создадим extension для Forge Viewer-а, чтобы наш код было проще использовать в других проектах. Для корректной работы нам необходимо, чтобы было загружено расширение Autodesk.Snapping, содержащее класс Autodesk.Viewing.Extensions.Snapping.Snapper:
- class DrawBoundsToolExtension extends Autodesk.Viewing.Extension {
- constructor(viewer, options) {
- super(viewer, options);
- this.tool = new DrawBoundsTool(viewer);
- this.button = null;
- }
- async load() {
- await this.viewer.loadExtension('Autodesk.Snapping');
- this.viewer.toolController.registerTool(this.tool);
- console.log('DrawBoundsToolExtension has been loaded.');
- return true;
- }
- async unload() {
- this.viewer.toolController.deregisterTool(this.tool);
- console.log('DrawBoundsToolExtension has been unloaded.');
- return true;
- }
- onToolbarCreated(toolbar) {
- const controller = this.viewer.toolController;
- this.button = new Autodesk.Viewing.UI.Button('draw-bounds-tool-button');
- this.button.onClick = (ev) => {
- if (controller.isToolActivated(DrawBoundsToolName)) {
- controller.deactivateTool(DrawBoundsToolName);
- this.button.setState(Autodesk.Viewing.UI.Button.State.INACTIVE);
- } else {
- controller.activateTool(DrawBoundsToolName);
- this.button.setState(Autodesk.Viewing.UI.Button.State.ACTIVE);
- }
- };
- this.button.setToolTip('Draw Bounds Tool');
- this.group = new Autodesk.Viewing.UI.ControlGroup('draw-tool-group');
- this.group.addControl(this.button);
- toolbar.addControl(this.group);
- }
- }
- Autodesk.Viewing.theExtensionManager.registerExtension('DrawBoundsToolExtension', DrawBoundsToolExtension);
Вы можете найти полный код на GitHub, в особенности обратите внимание на DrawBoundsToolExtension.js
Источник: https://forge.autodesk.com/blog/snappy-viewer-tools
Опубликовано 16.09.2020