眺めるViewer3D.js Part 1: コンテキストメニュー

最近、 Viewer3D.js のソースコードをよく読みます。

このJavaScriptを使うとウェブブラウザで3Dモデルを表示できます、といっても今どきそのぐらい珍しいことではありませんがソースコードを眺めたことはなかったのでした。

かなり説明を省きますが、ビューアのインスタンスは次のように作ります。

1
2
3
var htmlElement = document.getElementById('ViewerDiv');
var viewer = new Autodesk.Viewing.GuiViewer3D(htmlElement);
viewer.start();

Viewer3D.js という名前ですが Autodesk.Viewing.GuiViewer3D でインスタンスを作るところが肝です。 config が渡せるのが良いですね。具体的に何が良いのかは別に書くことにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
export function GuiViewer3D(container, config) {
    if (!config) config = {};

    // Explicitly set startOnInitialize = false, as we want to finish some initialization
    // before starting the main loop.
    //
    config.startOnInitialize = false;

    Viewer3D.call(this, container, config);

    this.toolbar = null;

    // Container for the UI docking panels
    this.dockingPanels = [];

    this.onFullScreenModeEvent = this.onFullScreenModeEvent.bind(this);
    this.onProgressBarUpdate = this.onProgressBarUpdate.bind(this);

}

exportについてはMDN web docsに良さげな記事が上がっていたのでチラ見しておくと吉。

viewer.start(); で初期化処理の Viewer3D.prototype.start() を呼びます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# From: https://autodeskviewer.com/viewers/latest/docs/src_application_Viewer3D.js.html#line-344
Viewer3D.prototype.start = function (url, options, onSuccessCallback, onErrorCallback) {
    if (this.started) {
        return 0;
    }
    this.started = true;

    var viewer = this;

    // Initialize the renderer and related stuff
    var result = viewer.initialize();
    if (result !== 0) {
        if (onErrorCallback) {
            setTimeout(function(){ onErrorCallback(result); }, 1);
        }
        return result;
    }

    //load extensions and set navigation overrides, etc.
    //Delayed so that it runs a frame after the long initialize() call.
    setTimeout(function() {viewer.setUp(viewer.config);}, 1);

    //If a model URL was given, kick off loading first, then initialize, otherwise just continue
    //with initialization immediately.
    if (url)
        this.loadModel(url, options, onSuccessCallback, onErrorCallback);

    return 0;
};

そうすると内部で var result = viewer.initialize(); を実行してレンダラー周りの初期化をする GuiViewer3D.prototype.initialize() を呼びます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
GuiViewer3D.prototype.initialize = function (initOptions) {
    var viewerErrorCode = Viewer3D.prototype.initialize.call(this, initOptions);

    if (viewerErrorCode > 0)    // ErrorCode was returned.
    {
        ErrorHandler.reportError(this.container, viewerErrorCode); // Show UI dialog
        return viewerErrorCode;
    }

    var viewer = this;

    // Add padding to bottom to account for toolbar, when calling fitToView()
    // TODO: Use pixel size for setting these.
    //---this.navigation.FIT_TO_VIEW_VERTICAL_OFFSET = 0.03;
    //---this.navigation.FIT_TO_VIEW_VERTICAL_MARGIN = 0.0;

    if (this.toolController) {
        var hottouch = new HotGestureTool(this);

        this.toolController.registerTool(hottouch);

        this.toolController.activateTool(hottouch.getName());
    }

    this.addEventListener(et.FULLSCREEN_MODE_EVENT, this.onFullScreenModeEvent);

    // Context menu
    if (!this.contextMenu) {
        this.setDefaultContextMenu();
    }

    // Create a progress bar. Shows streaming.
    //
    this.progressbar = new ProgressBar(this.container);
    this.addEventListener(et.PROGRESS_UPDATE_EVENT, this.onProgressBarUpdate);

    this.addEventListener(et.VIEWER_RESIZE_EVENT, function (event) {
        viewer.resizePanels();
        viewer.updateToolbarButtons(event.width, event.height);
    });

    this.addEventListener(et.NAVIGATION_MODE_CHANGED_EVENT, function (event) {
        viewer.updateToolbarButtons(viewer.container.clientWidth, viewer.container.clientHeight);
    });

    this.initEscapeHandlers();

    // Now that all the ui is created, localize it.
    this.localize();

    
    this.addEventListener( et.WEBGL_CONTEXT_LOST_EVENT, function(event) {
        this.impl.stop();
        // Hide all divs
        var div = this.container;
        var divCount = div.childElementCount;
        for (var i=0; i<divCount; ++i) {
            div.children[i].style.display = 'none';
        }
        ErrorHandler.reportError(this.container, ErrorCodes.WEBGL_LOST_CONTEXT);
    }.bind(this));

    // Now that all of our initialization is done, start the main loop.
    //
    this.run();

    return 0;   // No errors initializing.
};

上記コードの先頭行でViewer3Dの初期化をしています。

1
var viewerErrorCode = Viewer3D.prototype.initialize.call(this, initOptions);

その初期化処理を見ると以下の通りです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
Viewer3D.prototype.initialize = function(initOptions)
{

    //Set up the private viewer implementation
    this.setScreenModeDelegate(this.config ? this.config.screenModeDelegate : undefined);

    var dimensions = this.getDimensions();
    this.canvas.width = dimensions.width;
    this.canvas.height = dimensions.height;

    // For Safari and WKWebView and UIWebView on ios device with retina display,
    // needs to manually rescale our canvas to get the right scaling. viewport metatag
    // alone would not work.
    if (isIOSDevice() && this.getWindow().devicePixelRatio) {
        this.canvas.width /= this.getWindow().devicePixelRatio;
        this.canvas.height /= this.getWindow().devicePixelRatio;
    }

    //Call this after setting canvas size above...
    this.impl.initialize(initOptions);

    //Only run the WebGL failure logic if the renderer failed to initialize (otherwise
    //we don't have to spend time creating a GL context here, since we know it worked already
    if (!this.impl.glrenderer()) {
        var webGL = detectWebGL();
        if (webGL <= 0) {  // WebGL error.
            return webGL === -1 ? ErrorCodes.BROWSER_WEBGL_NOT_SUPPORTED : ErrorCodes.BROWSER_WEBGL_DISABLED;
        }
    }

    var self = this;

    // Add a callback for the panels to resize when the viewer resizes.
    // For some reason, Safari iOS updates the DOM dimensions *after* the resize event,
    // so in that case we handle the resizing asynchronously.
    if (isIOSDevice()) {
        var _resizeTimer;
        this.onResizeCallback = function(e) {
            clearTimeout(_resizeTimer);
            _resizeTimer = setTimeout(self.resize.bind(self), 500);
        };
    } else {
        this.onResizeCallback = function(e) {
            var oldWidth = self.impl.camera.clientWidth;
            var oldHeight = self.impl.camera.clientHeight;
            var newWidth = self.container.clientWidth;
            var newHeight =  self.container.clientHeight;

            if (oldWidth !== newWidth ||
                oldHeight !== newHeight) {
                self.resize();
            }
        };
    }
    this.addWindowEventListener('resize', this.onResizeCallback, false);

    this.onScrollCallback = function(e) {
        self.impl.canvasBoundingclientRectDirty = true;
    };
    this.addWindowEventListener('scroll', this.onScrollCallback);

    this.initContextMenu();

    // Localize the viewer.
    this.localize();


    this.impl.controls = this.createControls();

    // Initialize the preference callbacks
    this.initializePrefListeners();

    this.setDefaultNavigationTool( "orbit" );

    if( this.impl.controls )
        this.impl.controls.setAutocam(this.autocam);

    var canvasConfig = (this.config && this.config.canvasConfig) ? this.config.canvasConfig : Viewer3D.kDefaultCanvasConfig;
    this.setCanvasClickBehavior(canvasConfig);


    // Allow clients not load the spinner. This is needed for embedding viewer in a WebView on mobile,
    // where the spinner makes the UI looks less 'native'.
    if (!canvasConfig.disableSpinner) {

        // Create a div containing an image: this will be a
        // spinner (aka activity indicator) that tells the user
        // that the file is loading.
        //
        // Keep reference for backwards compatibility.
        this.loadSpinner = this._loadingSpinner.createDom(this.container);
    }

    // Auxiliary class to get / restore the viewer state.
    this.viewerState = new ViewerState( this );

    // The default behavior is to run the main loop immediately, unless startOnInitialize
    // is provided and is false.
    //
    if (!this.config || !this.config.hasOwnProperty("startOnInitialize") || this.config.startOnInitialize)
    {
        this.run();
    }

    this.getWindow().NOP_VIEWER = this;

    this.addEventListener(et.MODEL_ADDED_EVENT, function(e) {
        self.onModelAdded(e.model, e.preserveTools);
    });

    this.addEventListener(et.GEOMETRY_LOADED_EVENT, function(e) {
        if (e.model.is2d() && !e.model.isLeaflet()) {
            self.navigation.setMinimumLineWidth(e.model.loader.svf.minLineWidth);
        }
    });

    this.dispatchEvent(et.VIEWER_INITIALIZED);

    this.trackADPSettingsOptions();
    this.trackADPExtensionsLoaded();

    Viewer3D.ViewerCount++;

    // These calls are useful for Internet Explorer's use of spector.
    // Uncomment this code, and add the https://spectorcdn.babylonjs.com/spector.bundle.js script
    // in index.html, and Spector's menu shows up in the application itself.
    /*
    var spector = new SPECTOR.Spector();
    window.spector = spector;
    spector.displayUI();    // comment this line out if you instead want to use _spectorDump and the "u" key
    spector.spyCanvases();
    */

    return 0;   // No Error initializing.
};

this.initContextMenu(); で以下のコードに移ります。話が逸れますが disableBrowserContextMenu というconfigの値があるようですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Viewer3D.prototype.initContextMenu = function() {

    // Disable the browser's default context menu by default, or if explicitly specified.
    //
    var disableBrowserContextMenu = !this.config || (this.config.hasOwnProperty("disableBrowserContextMenu") ? this.config.disableBrowserContextMenu : true);
    if (disableBrowserContextMenu) {
        this.onDefaultContextMenu = function (e) {
            e.preventDefault();
        };
        this.container.addEventListener('contextmenu', this.onDefaultContextMenu, false);
    }

    var self = this;

    var canvas = this.canvas || this.container;

    this.onMouseDown = function(event) {
        if (EventUtils.isRightClick(event)) {
            self.startX = event.clientX;
            self.startY = event.clientY;
        }
    }

    canvas.addEventListener( 'mousedown', this.onMouseDown);

    this.onMouseUp = function(event) {
        if (EventUtils.isRightClick(event) && event.clientX === self.startX && event.clientY === self.startY) {
            self.triggerContextMenu(event);
        }
        return true;
    }

    canvas.addEventListener( 'mouseup', this.onMouseUp, false);
};

その次は GuiViewer3D.prototype.initialize に戻り、デフォルトコンテキストメニューが設定されます。

1
2
3
4
// Context menu
if (!this.contextMenu) {
    this.setDefaultContextMenu();
}

中身を見るとこのようになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * Activates the default context menu.<br>
 * Contains options Isolate, Hide selected, Show all objects, Focus and Clear selection.
 *
 * @returns {boolean} Whether the default context menu was successfully set (true) or not (false)
 */
Viewer3D.prototype.setDefaultContextMenu = function() {

    var ave = Autodesk.Viewing.Extensions;
    if (ave && ave.ViewerObjectContextMenu) {
        this.setContextMenu(new ave.ViewerObjectContextMenu(this));
        return true;
    }
    return false;
};

setContextMenu ではすでに this.contextMenu がある場合は hide() しているのですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * Sets the object context menu.
 * @param {?ObjectContextMenu=} [contextMenu]
 */
Viewer3D.prototype.setContextMenu = function (contextMenu) {

    if (this.contextMenu) {

        // Hide the current context menu, just in case it's open right now.
        // This does nothing if the context menu is not open.
        //
        this.contextMenu.hide();
    }

    this.contextMenu = contextMenu || null; // to avoid undefined
};

コンテキストメニューは ObjectContextMenu.js で形成されています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/***/ "./src/gui/ObjectContextMenu.js":
/*!**************************************!*\
  !*** ./src/gui/ObjectContextMenu.js ***!
  \**************************************/
/*! exports provided: ObjectContextMenu */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectContextMenu", function() { return ObjectContextMenu; });
/* harmony import */ var _ContextMenu__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ContextMenu */ "./src/gui/ContextMenu.js");
/* harmony import */ var _application_GlobalManagerMixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../application/GlobalManagerMixin */ "./src/application/GlobalManagerMixin.js");




/**
                                                                         * Context Menu object is the base class for the viewer's context menus.
                                                                         *
                                                                         * @alias Autodesk.Viewing.UI.ObjectContextMenu
                                                                         * @param {Autodesk.Viewing.Viewer3D} viewer - Viewer instance.
                                                                         * @constructor
                                                                         */
function ObjectContextMenu(viewer) {
  this.viewer = viewer;
  this.setGlobalManager(viewer.globalManager);
  this.contextMenu = new _ContextMenu__WEBPACK_IMPORTED_MODULE_0__["ContextMenu"](viewer);
}

ObjectContextMenu.prototype.constructor = ObjectContextMenu;
_application_GlobalManagerMixin__WEBPACK_IMPORTED_MODULE_1__["GlobalManagerMixin"].call(ObjectContextMenu.prototype);

/**
                                                       * Shows the context menu.
                                                       * @param {Event} event - Browser event that requested the context menu.
                                                       */
ObjectContextMenu.prototype.show = function (event) {
  var numSelected = this.viewer.getSelectionCount(),
  visibility = this.viewer.getSelectionVisibility(),
  rect = this.viewer.impl.getCanvasBoundingClientRect(),
  status = {
    event: event,
    numSelected: numSelected,
    hasSelected: 0 < numSelected,
    hasVisible: visibility.hasVisible,
    hasHidden: visibility.hasHidden,
    canvasX: event.clientX - rect.left,
    canvasY: event.clientY - rect.top },

  menu = this.buildMenu(event, status);

  this.viewer.runContextMenuCallbacks(menu, status);

  if (menu && 0 < menu.length) {
    this.contextMenu.show(event, menu);
  }
};

/**
    * Hides the context menu.
    * @returns {boolean} True if the context menu was open, false otherwise.
    */
ObjectContextMenu.prototype.hide = function () {
  return this.contextMenu.hide();
};

/**
    * Builds the context menu to be displayed.
    * Override this method to change the context menu.
    *
    * Sample menu item:
    * `{title: 'This is a menu item', target: function () {alert('Menu item clicked');}}`.
    * A submenu can be specified by providing an array of submenu items as the target.
    * @param {Event} event - Browser event that requested the context menu.
    * @param {object} status - Information about nodes.
    * @param {number} status.numSelected - The number of selected objects.
    * @param {boolean} status.hasSelected - True if there is at least one selected object.
    * @param {boolean} status.hasVisible - True if at least one selected object is visible.
    * @param {boolean} status.hasHidden - True if at least one selected object is hidden.
    * @returns {array} An array of menu items.
    */
ObjectContextMenu.prototype.buildMenu = function (event, status) {
  return null;
};

/***/ }),

ObjectContextMenu.prototype.buildMenu のドキュメントコメントに "Override this method to change the context menu." とあります。 探したところコンテキストメニューの実装は ViewerObjectContextMenu.js にありました。この中で buildMenu メソッドをみると Show all objects をメニューとして生成しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/***/ "./src/gui/ViewerObjectContextMenu.js":
/*!********************************************!*\
  !*** ./src/gui/ViewerObjectContextMenu.js ***!
  \********************************************/
/*! exports provided: ViewerObjectContextMenu */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ViewerObjectContextMenu", function() { return ViewerObjectContextMenu; });
/* harmony import */ var _ObjectContextMenu__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ObjectContextMenu */ "./src/gui/ObjectContextMenu.js");
/* harmony import */ var _logger_Logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../logger/Logger */ "./src/logger/Logger.js");





/**
                                            * Constructs a ViewerObjectContextMenu object.
                                            * @param {Viewer} viewer
                                            * @constructor
                                            */
function ViewerObjectContextMenu(viewer) {
  _ObjectContextMenu__WEBPACK_IMPORTED_MODULE_0__["ObjectContextMenu"].call(this, viewer);
}

ViewerObjectContextMenu.prototype = Object.create(_ObjectContextMenu__WEBPACK_IMPORTED_MODULE_0__["ObjectContextMenu"].prototype);
ViewerObjectContextMenu.prototype.constructor = ViewerObjectContextMenu;

/**
                                                                          * Builds the context menu to be displayed.
                                                                          * @override
                                                                          * @param {Event} event - Browser event that requested the context menu
                                                                          * @param {Object} status - Information about nodes: numSelected, hasSelected, hasVisible, hasHidden.
                                                                          * @returns {?Array} An array of menu items.
                                                                          */
ViewerObjectContextMenu.prototype.buildMenu = function (event, status) {var _this = this;

  // Context menu varies depending on whether we show 2D or 3D models. If we have neither 2d nor 3d, just don't create it.
  if (!this.viewer.model) {
    return;
  }

  var that = this,
  menu = [],
  nav = this.viewer.navigation,
  is2d = this.viewer.model.is2d();

  // the title strings here are added to the viewer.loc.json for localization
  if (status.hasSelected) {
    menu.push({
      title: "Isolate",
      target: function target() {
        var selection = that.viewer.getAggregateSelection();
        that.viewer.impl.visibilityManager.aggregateIsolate(selection);
        that.viewer.clearSelection();
        _logger_Logger__WEBPACK_IMPORTED_MODULE_1__["logger"].track({ name: 'isolate_count', aggregate: 'count' });
      } });

    if (status.hasVisible) {
      menu.push({
        title: "Hide Selected",
        target: function target() {
          var visMan = that.viewer.impl.visibilityManager;
          that.viewer.getAggregateSelection(function (model, dbId) {
            visMan.hide(dbId, model);
          });
          that.viewer.clearSelection();
        } });

    }
    if (status.hasHidden) {
      menu.push({
        title: "Show Selected",
        target: function target() {
          // This is such a weird use case. Users can't select hidden nodes.
          // For this to work, selection must have been done through code.
          var selected = that.viewer.getSelection();
          that.viewer.clearSelection();
          that.viewer.show(selected);
        } });

    }
  }

  if (is2d) {
    menu.push({
      title: "Show All Layers",
      target: function target() {
        that.viewer.setLayerVisible(null, true);
      } });

  }

  menu.push({
    title: "Show All Objects",
    target: function target() {
      that.viewer.showAll();
      _logger_Logger__WEBPACK_IMPORTED_MODULE_1__["logger"].track({ name: 'showall', aggregate: 'count' });
    } });



  // Fit-to-view only work with selections from one model.
  var aggregateSelection = that.viewer.getAggregateSelection();
  if (!is2d && aggregateSelection.length === 1 && nav.isActionEnabled('gotoview')) {
    menu.push({
      title: "Focus",
      target: function target() {
        aggregateSelection = that.viewer.getAggregateSelection(); // Get the aggregate selection again
        if (aggregateSelection.length > 0) {
          var singleRes = aggregateSelection[0];
          that.viewer.fitToView(singleRes.selection, singleRes.model);
        } else if (aggregateSelection.length === 0) {
          that.viewer.fitToView(); // Fit to whole model, the first one loaded.
        }
        _logger_Logger__WEBPACK_IMPORTED_MODULE_1__["logger"].track({ name: 'fittoview', aggregate: 'count' });
      } });

  }

  // Pivot point
  if (!is2d) {
    var rect = this.viewer.impl.getCanvasBoundingClientRect();
    var canvasX = event.clientX - rect.left;
    var canvasY = event.clientY - rect.top;
    var res = this.viewer.clientToWorld(canvasX, canvasY, false);
    if (res) {
      menu.push({
        title: "Pivot",
        target: function target() {
          _this.viewer.navigation.setPivotPoint(res.point);
        } });

    }
  }

  if (status.hasSelected) {
    menu.push({
      title: "Clear Selection",
      target: function target() {
        that.viewer.clearSelection();
        _logger_Logger__WEBPACK_IMPORTED_MODULE_1__["logger"].track({ name: 'clearselection', aggregate: 'count' });
      } });

  }

  return menu;
};

Viewer3D.prototype.initContextMenumouseup のイベントハンドリングをしています。

1
2
3
4
5
6
7
8
this.onMouseUp = function(event) {
    if (EventUtils.isRightClick(event) && event.clientX === self.startX && event.clientY === self.startY) {
        self.triggerContextMenu(event);
    }
    return true;
}

canvas.addEventListener( 'mouseup', this.onMouseUp, false);

ですのでビューア上で右クリックをすると Viewer3D.prototype.triggerContextMenu が動きます。 そうなると this.contextMenu.show()ViewerObjectContextMenubuildMenu を呼びコンテキストメニューが画面に出ます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Viewer3D.prototype.triggerContextMenu = function (event) {
    if (this.config && this.config.onTriggerContextMenuCallback) {
        this.config.onTriggerContextMenuCallback(event);
    }

    if (this.contextMenu) {
        this.contextMenu.show(event);
        return true;
    }
    return false;
};