diff --git a/docs/MPQEditor.md b/docs/MPQEditor.md
new file mode 100644
index 00000000..eba70cb7
--- /dev/null
+++ b/docs/MPQEditor.md
@@ -0,0 +1,65 @@
+# MixedPrecisionQantizationEditor (MPQEditor for short)
+
+## About
+
+MPQEditor povides easy way of creation/editing MPQ json files, suitable for
+Quantization step in CfgEditor. Also it gives the ability to select the most
+sensitive to quantization layers using `visq` data.
+
+## How to use
+
+Currently MPQEditor is integrated into EXPLORER view by context menu item
+"Create MPQ json".
+
+In order to use it:
+1) call context menu for a `.circle` model in EXPLORER view
+2) call "Create MPQ json"
+3) give a name to MPQ json
+4) edit MPQ json using MPQEditor
+5) all layers from 'layers' section in json are shown in grid-table, so
+that their options for quantization can be edited using their respective
+dropdowns. Layers which don't belong to 'layers' section will be quantized
+using default parameters.
+6) to make 'model-graph-view' more informative the user may use 'visq'
+information, it shows which layers are more sensitive to quantization than
+others
+
+## Screen layout
+Layout is divided into three main parts:
+1) Default Quantization Parameters
+
+
+
+2) 'Layers' section
+
+
+
+3) Model graph view
+
+
+
+### Layers section
+
+
+1. `Add more layers`
+ - use it to add more layers for editing their quantization properties
+2. `VISQ control`
+ - info
+ - actual path
+ - 'load visq' button
+ - 'clear visq' button
+3. `Model graph is shown/hidden`
+ - use this to enable selection on `Model graph view`
+4. `Individual layer`
+ - it consists of quantization properties set for each layer
+5. `Quantization properties of layer`
+ - quantization and granularity fields for each layer
+6. `Restore layer`
+ - use it to restore the layer to default state
+
+
+For the whole workflow see:
+
+
+
+
diff --git a/docs/images/MpqDefaultSection.png b/docs/images/MpqDefaultSection.png
new file mode 100644
index 00000000..7c75fbd7
Binary files /dev/null and b/docs/images/MpqDefaultSection.png differ
diff --git a/docs/images/MpqLayersSection.png b/docs/images/MpqLayersSection.png
new file mode 100644
index 00000000..8eea2331
Binary files /dev/null and b/docs/images/MpqLayersSection.png differ
diff --git a/docs/images/MpqLayersSectionGen.png b/docs/images/MpqLayersSectionGen.png
new file mode 100644
index 00000000..7125ed0d
Binary files /dev/null and b/docs/images/MpqLayersSectionGen.png differ
diff --git a/docs/images/MpqModelGraphView.png b/docs/images/MpqModelGraphView.png
new file mode 100644
index 00000000..6e1a3a5e
Binary files /dev/null and b/docs/images/MpqModelGraphView.png differ
diff --git a/docs/images/MpqWorkflow.gif b/docs/images/MpqWorkflow.gif
new file mode 100644
index 00000000..91a096fc
Binary files /dev/null and b/docs/images/MpqWorkflow.gif differ
diff --git a/media/CircleGraph/index.js b/media/CircleGraph/index.js
index 96a6bb21..7c77e62f 100644
--- a/media/CircleGraph/index.js
+++ b/media/CircleGraph/index.js
@@ -52,6 +52,7 @@ const viewMode = {
viewer: 0, // default circle viewer
selector: 1, // circle partition editor node selector
visq: 2, // quantization error viewer
+ visqselector: 3, //quantization error viewer which is able to select nodes
// refer https://github.com/Samsung/ONE-vscode/issues/1350
};
@@ -97,6 +98,8 @@ host.BrowserHost = class {
this._mode = viewMode.selector;
} else if (__viewMode === "visq") {
this._mode = viewMode.visq;
+ } else if (__viewMode === "visqselector") {
+ this._mode = viewMode.visqselector;
}
}
@@ -167,6 +170,9 @@ host.BrowserHost = class {
case "visq":
this._msgVisq(message);
break;
+ case "scrollToSelected":
+ this._view.setScrollToSelected(message.value);
+ break;
}
});
@@ -263,7 +269,7 @@ host.BrowserHost = class {
});
this._view.show("welcome spinner");
- if (this._mode === viewMode.visq) {
+ if (this._mode === viewMode.visq || this._mode === viewMode.visqselector) {
// request visq data prior to model
// model is inside visq data
vscode.postMessage({ command: "visq" });
diff --git a/media/CircleGraph/view.js b/media/CircleGraph/view.js
index 58f50ab8..ed837306 100644
--- a/media/CircleGraph/view.js
+++ b/media/CircleGraph/view.js
@@ -542,13 +542,21 @@ view.View = class {
}
}
+ setScrollToSelected(value) {
+ this._scrollToSelected = value;
+ }
+
/**
* @brief toggleSelect will select or toggle select with CtrlKey down
* @param viewNode view.Node instance
* @note works on host mode is viewMode.selector
*/
toggleSelect(viewNode) {
- if (viewNode && this._host._mode === viewMode.selector) {
+ if (
+ viewNode &&
+ (this._host._mode === viewMode.selector ||
+ this._host._mode === viewMode.visqselector)
+ ) {
if (this._keyCtrl) {
// toggle selection
let index = this._selectionNodes.indexOf(viewNode);
@@ -585,7 +593,10 @@ view.View = class {
clearSelection() {
this._clearSelection();
- if (host._mode === viewMode.selector) {
+ if (
+ host._mode === viewMode.selector ||
+ this._host._mode === viewMode.visqselector
+ ) {
this._host.onView("selection");
}
}
@@ -1098,7 +1109,10 @@ view.View = class {
}
applyStyleSheetVisq(element) {
- if (this._host._mode === viewMode.visq) {
+ if (
+ this._host._mode === viewMode.visq ||
+ this._host._mode === viewMode.visqselector
+ ) {
let rules = [];
for (const styleSheet of this._host.document.styleSheets) {
if (styleSheet.title === "visq_style") {
@@ -1503,7 +1517,7 @@ view.Node = class extends grapher.Node {
_visq(node) {
const host = this.context.view._host;
- if (host._mode !== viewMode.visq) {
+ if (host._mode !== viewMode.visq && host._mode !== viewMode.visqselector) {
return;
}
@@ -1535,7 +1549,7 @@ view.Node = class extends grapher.Node {
}
}
let visqSuffix = undefined;
- if (host._mode === viewMode.visq) {
+ if (host._mode === viewMode.visq || host._mode === viewMode.visqselector) {
if (node.visq_index) {
let qstyle = `node-item-type-visq-${node.visq_index}`;
styles.push(qstyle);
@@ -1571,7 +1585,10 @@ view.Node = class extends grapher.Node {
if (host._mode === viewMode.viewer || host._mode === viewMode.visq) {
title.on("click", () => this.context.view.showNodeProperties(node, null));
- } else if (host._mode === viewMode.selector) {
+ } else if (
+ host._mode === viewMode.selector ||
+ host._mode === viewMode.visqselector
+ ) {
// toggle select with click
title.on("click", () => {
this.context.view.toggleSelect(this);
diff --git a/media/MPQEditor/index.html b/media/MPQEditor/index.html
new file mode 100644
index 00000000..4b68f953
--- /dev/null
+++ b/media/MPQEditor/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+ Mixed Precision Quantization editor
+
+
+
+
+
+
+
+ Edit mixed precision quantization json file
+
+
+
+
+
+ Quantized Data Type
+ uint8
+ int16
+
+
+
+
+ Granularity
+ layer
+ channel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ visq.json file with layer-wise quantization errors
+
+
+
+
+
+ Model Graph
+
+
+
+
+
+ Name
+ Quantization
+ Granularity
+
+
+
+
+
+
diff --git a/media/MPQEditor/index.js b/media/MPQEditor/index.js
new file mode 100644
index 00000000..efefd2a1
--- /dev/null
+++ b/media/MPQEditor/index.js
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const vscode = acquireVsCodeApi();
+
+// Just like a regular webpage we need to wait for the webview
+// DOM to load before we can reference any of the HTML elements
+// or toolkit components
+window.addEventListener("load", main);
+
+function main() {
+ register();
+
+ window.addEventListener("message", (event) => {
+ const message = event.data;
+ switch (message.type) {
+ case "displayMPQ":
+ displayMPQToEditor(message.content);
+ break;
+ //case "selectionChanged":
+ // handleSelectionChanged(message.names);
+ // break;
+ case "modelNodesChanged":
+ handleModelNodesChanged(message.names);
+ break;
+ case "VisqFileLoaded":
+ handleVisqFileLoaded(message.visqFile);
+ break;
+ case "modelGraphIsShown":
+ handleModelGraphIsShown(message.shown);
+ break;
+ default:
+ break;
+ }
+ });
+
+ vscode.postMessage({ type: "requestModelNodes" });
+ vscode.postMessage({ type: "requestDisplayMPQ" });
+ vscode.postMessage({ type: "showModelNodes" });
+}
+
+function register() {
+ registerMainControls();
+}
+
+function handleModelGraphIsShown(shown) {
+ document.getElementById("circle-graph").checked = shown;
+}
+
+function handleVisqFileLoaded(visqFile) {
+ document.getElementById("VisqInputPath").value = visqFile;
+}
+
+//function handleSelectionChanged(names) {
+// document.getElementById("RemoveSelected").disabled = names.length < 1;
+//}
+
+function handleModelNodesChanged(names) {
+ document.getElementById("AddSpecificLayer").disabled = names.length < 1;
+}
+
+function registerMainControls() {
+ document
+ .getElementById("DefaultDtype")
+ .addEventListener("click", function () {
+ updateDefaultQuantization();
+ applyUpdates();
+ });
+
+ document
+ .getElementById("DefaultGranularity")
+ .addEventListener("click", function () {
+ updateGranularity();
+ applyUpdates();
+ });
+
+ document
+ .getElementById("AddSpecificLayer")
+ .addEventListener("click", function () {
+ vscode.postMessage({
+ type: "addSpecificLayerFromDialog",
+ });
+ });
+
+ //document
+ // .getElementById("RemoveSelected")
+ // .addEventListener("click", function () {
+ // vscode.postMessage({
+ // type: "removeSelectedFromLayers",
+ // });
+ // });
+
+ // show model graph on openening by default
+ document.getElementById("circle-graph").checked = true;
+ document
+ .getElementById("circle-graph")
+ .addEventListener("click", function () {
+ vscode.postMessage({
+ type: "toggleCircleGraphIsShown",
+ show: document.getElementById("circle-graph").checked,
+ });
+ });
+
+ document
+ .getElementById("VisqInputPath")
+ .addEventListener("input", function () {
+ vscode.postMessage({
+ type: "VisqInputPathChanged",
+ path: document.getElementById("VisqInputPath").value,
+ });
+ });
+
+ document.getElementById("visq-file").addEventListener("click", function () {
+ vscode.postMessage({
+ type: "loadVisqFile",
+ });
+ });
+
+ document.getElementById("visq-delete").addEventListener("click", function () {
+ document.getElementById("VisqInputPath").value = "";
+ vscode.postMessage({
+ type: "removeVisqFile",
+ });
+ });
+
+ //document.getElementById("RemoveSelected").disabled = true;
+ document.getElementById("AddSpecificLayer").disabled = true;
+}
+
+function displayMPQToEditor(mpqCfg) {
+ document.getElementById("DefaultDtype").value =
+ mpqCfg?.["default_quantization_dtype"];
+ document.getElementById("DefaultGranularity").value =
+ mpqCfg?.["default_granularity"];
+
+ const length = mpqCfg && mpqCfg["layers"] ? mpqCfg["layers"].length : 0;
+
+ let names = Array(length);
+ let quantization = Array(length);
+ let granularity = Array(length);
+ for (let i = 0; i < length; i++) {
+ names[i] = mpqCfg["layers"][i]["name"];
+ quantization[i] = mpqCfg["layers"][i]["dtype"];
+ granularity[i] = mpqCfg["layers"][i]["granularity"];
+ }
+
+ const layersTable = document.getElementById("LayersTable");
+ layersTable.replaceChildren();
+ addQuantizedNodes(names, quantization, granularity, false);
+}
+
+function addQuantizedNodes(names, quantization, granularity, update) {
+ const layersTable = document.getElementById("LayersTable");
+ // for (let idx = 0; idx < names.length; idx++) {
+ // if (names[idx].length < 1) {
+ // continue;
+ // }
+ //
+ // let row = document.createElement("vscode-data-grid-row");
+ //
+ // const name = names[idx];
+ // // selection check
+ // let cellSwitch = document.createElement("vscode-data-grid-cell");
+ // let checkbox = document.createElement("vscode-checkbox");
+ // checkbox.setAttribute("id", "checkboxSelect" + name);
+ // cellSwitch.appendChild(checkbox);
+ // cellSwitch.setAttribute("grid-column", "1");
+ // row.appendChild(cellSwitch);
+ //
+ // // name
+ // let cellName = document.createElement("vscode-data-grid-cell");
+ // cellName.textContent = name;
+ // cellName.setAttribute("grid-column", "2");
+ // row.appendChild(cellName);
+ //
+ // // quantization
+ // let cellQuantization = document.createElement("vscode-data-grid-cell");
+ // let quantDropdown = document.createElement("vscode-dropdown");
+ // {
+ // let uint8Opt = document.createElement("vscode-option");
+ // uint8Opt.innerText = "uint8";
+ // uint8Opt.value = 0;
+ // quantDropdown.appendChild(uint8Opt);
+ //
+ // let int16Opt = document.createElement("vscode-option");
+ // int16Opt.innerText = "int16";
+ // int16Opt.value = 1;
+ // quantDropdown.appendChild(int16Opt);
+ // }
+ // quantDropdown.setAttribute("id", "dropdownQuantization" + name);
+ // cellQuantization.appendChild(quantDropdown);
+ // cellQuantization.setAttribute("grid-column", "3");
+ // row.appendChild(cellQuantization);
+ //
+ // // granularity
+ // let cellGranularity = document.createElement("vscode-data-grid-cell");
+ // let granularityDropdown = document.createElement("vscode-dropdown");
+ // {
+ // let layerOpt = document.createElement("vscode-option");
+ // layerOpt.innerText = "layer";
+ // layerOpt.value = 0;
+ // granularityDropdown.appendChild(layerOpt);
+ //
+ // let channelOpt = document.createElement("vscode-option");
+ // channelOpt.innerText = "channel";
+ // channelOpt.value = 1;
+ // granularityDropdown.appendChild(channelOpt);
+ // }
+ // granularityDropdown.setAttribute("id", "dropdownGranularity" + name);
+ // cellGranularity.appendChild(granularityDropdown);
+ // cellGranularity.setAttribute("grid-column", "4");
+ // row.appendChild(cellGranularity);
+ //
+ // layersTable.appendChild(row);
+ // }
+
+ for (let idx = 0; idx < names.length; idx++) {
+ if (names[idx].length < 1) {
+ continue;
+ }
+
+ let row = document.createElement("vscode-data-grid-row");
+ const name = names[idx];
+
+ // name
+ let cellName = document.createElement("vscode-data-grid-cell");
+ cellName.textContent = name;
+ cellName.setAttribute("grid-column", "1");
+ row.appendChild(cellName);
+ // quantization
+ let cellQuantization = document.createElement("vscode-data-grid-cell");
+ let quantDropdown = document.createElement("vscode-dropdown");
+ {
+ let uint8Opt = document.createElement("vscode-option");
+ uint8Opt.innerText = "uint8";
+ uint8Opt.value = 0;
+ quantDropdown.appendChild(uint8Opt);
+ let int16Opt = document.createElement("vscode-option");
+ int16Opt.innerText = "int16";
+ int16Opt.value = 1;
+ quantDropdown.appendChild(int16Opt);
+ }
+ quantDropdown.setAttribute("id", "dropdownQuantization" + name);
+ cellQuantization.appendChild(quantDropdown);
+ cellQuantization.setAttribute("grid-column", "2");
+ row.appendChild(cellQuantization);
+ // granularity
+ let cellGranularity = document.createElement("vscode-data-grid-cell");
+ let granularityDropdown = document.createElement("vscode-dropdown");
+ {
+ let layerOpt = document.createElement("vscode-option");
+ layerOpt.innerText = "layer";
+ layerOpt.value = 0;
+ granularityDropdown.appendChild(layerOpt);
+ let channelOpt = document.createElement("vscode-option");
+ channelOpt.innerText = "channel";
+ channelOpt.value = 1;
+ granularityDropdown.appendChild(channelOpt);
+ }
+ granularityDropdown.setAttribute("id", "dropdownGranularity" + name);
+ cellGranularity.appendChild(granularityDropdown);
+ cellGranularity.setAttribute("grid-column", "3");
+ row.appendChild(cellGranularity);
+
+ // remove button
+ let cellRemoveBtn = document.createElement("vscode-data-grid-cell");
+ let remBtn = document.createElement("vscode-button");
+ remBtn.setAttribute("id", "removeButton" + name);
+ remBtn.appearance = "icon";
+ {
+ let iconSpan = document.createElement("span");
+ iconSpan.className = "codicon codicon-chrome-close";
+ iconSpan.slot = "start";
+ remBtn.appendChild(iconSpan);
+ }
+ cellRemoveBtn.appendChild(remBtn);
+ cellRemoveBtn.setAttribute("grid-column", "4");
+ row.appendChild(cellRemoveBtn);
+
+ layersTable.appendChild(row);
+ }
+
+ // set quantization and granularity attributes
+ for (let idx = 0; idx < names.length; idx++) {
+ if (names[idx].length < 1) {
+ continue;
+ }
+
+ const name = names[idx];
+ const qValue = quantization[idx] === "uint8" ? 0 : 1;
+ document.getElementById("dropdownQuantization" + name).value = qValue;
+
+ const gValue = granularity[idx] === "layer" ? 0 : 1;
+ document.getElementById("dropdownGranularity" + name).value = gValue;
+ }
+
+ // set change values
+ for (let idx = 0; idx < names.length; idx++) {
+ if (names[idx].length < 1) {
+ continue;
+ }
+
+ const name = names[idx];
+ document
+ .getElementById("dropdownQuantization" + name)
+ .addEventListener("change", function () {
+ updateSpecificQuantization(name);
+ applyUpdates();
+ });
+
+ document
+ .getElementById("dropdownGranularity" + name)
+ .addEventListener("change", function () {
+ updateSpecificGranularity(name);
+ applyUpdates();
+ });
+
+ // document
+ // .getElementById("checkboxSelect" + name)
+ // .addEventListener("click", function () {
+ // updateLayersSelection(name);
+ // });
+ document
+ .getElementById("removeButton" + name)
+ .addEventListener("click", function () {
+ removeLayer(name);
+ });
+ }
+
+ if (update) {
+ updateLayers(names, quantization, granularity);
+ applyUpdates();
+ }
+}
+
+//function updateLayersSelection(name) {
+// vscode.postMessage({
+// type: "toggleSelectedNode",
+// name: name,
+// });
+//}
+
+function updateLayers(names, quantization, granularity) {
+ vscode.postMessage({
+ type: "updateLayers",
+ names: names,
+ quantization: quantization,
+ granularity: granularity,
+ });
+}
+
+function removeLayer(name) {
+ vscode.postMessage({
+ type: "removeLayer",
+ name: name,
+ });
+}
+
+function updateSpecificQuantization(name) {
+ const value =
+ document.getElementById("dropdownQuantization" + name).value === 0
+ ? "uint8"
+ : "int16";
+ vscode.postMessage({
+ type: "updateSpecificQuantization",
+ name: name,
+ value: value,
+ });
+}
+
+function updateSpecificGranularity(name) {
+ const value =
+ document.getElementById("dropdownGranularity" + name).value === 0
+ ? "layer"
+ : "channel";
+ vscode.postMessage({
+ type: "updateSpecificGranularity",
+ name: name,
+ value: value,
+ });
+}
+
+function updateDefaultQuantization() {
+ let value = document.getElementById("DefaultDtype").value;
+ vscode.postMessage({
+ type: "updateSection",
+ section: "default_quantization_dtype",
+ value: value,
+ });
+}
+
+function updateGranularity() {
+ let value = document.getElementById("DefaultGranularity").value;
+ vscode.postMessage({
+ type: "updateSection",
+ section: "default_granularity",
+ value: value,
+ });
+}
+
+function applyUpdates() {
+ vscode.postMessage({ type: "updateDocument" });
+}
diff --git a/media/MPQEditor/style.css b/media/MPQEditor/style.css
new file mode 100644
index 00000000..285b093f
--- /dev/null
+++ b/media/MPQEditor/style.css
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+label {
+white-space: pre-wrap;
+}
+
+body {
+min-width: 500px;
+width: auto !important;
+}
+
+.maintitle {
+width: 100%;
+display: flex;
+font-size: medium;
+}
+
+.maintitle span {
+float: left;
+display: block;
+padding-left: 5px;
+}
+
+.leftbtns {
+float: left;
+}
+
+.rightbtns {
+float: right;
+}
+
+.codicon.codicon-question .help {
+visibility: hidden;
+width: auto;
+background-color: var(--vscode-editorSuggestWidget-background);
+color: var(--vscode-editorSuggestWidget-foreground);
+border: 2px solid var(--vscode-editorSuggestWidget-border);
+font-size: small;
+text-align: left;
+position: absolute;
+z-index: 1;
+margin-left: 10px;
+padding: 6px 6px 10px 8px;
+font-family: var(--vscode-font-family);
+}
+
+.codicon.codicon-question:hover .help {
+visibility: visible;
+}
diff --git a/package.json b/package.json
index ff229e69..71fc4191 100644
--- a/package.json
+++ b/package.json
@@ -128,6 +128,16 @@
}
],
"priority": "default"
+ },
+ {
+ "viewType": "one.editor.mpq",
+ "displayName": "MPQ Editor",
+ "selector": [
+ {
+ "filenamePattern": "*.mpq.json"
+ }
+ ],
+ "priority": "default"
}
],
"commands": [
@@ -256,6 +266,16 @@
"command": "one.editor.cfg.setDefaultValues",
"title": "ONE: Set Default Values",
"category": "ONE"
+ },
+ {
+ "command": "one.editor.mpq.showFromDefaultExplorer",
+ "title": "Create MPQ json",
+ "category": "ONE"
+ },
+ {
+ "command": "one.editor.mpq.showFromOneExplorer",
+ "title": "Create MPQ json",
+ "category": "ONE"
}
],
"keybindings": [
@@ -363,6 +383,11 @@
"command": "one.viewer.metadata.showFromOneExplorer",
"when": "view == OneExplorerView && viewItem =~ /baseModel|product/",
"group": "7_metadata@1"
+ },
+ {
+ "command": "one.editor.mpq.showFromOneExplorer",
+ "when": "view == OneExplorerView && viewItem == product && !one.job:running",
+ "group": "6_tools"
}
],
"explorer/context": [
@@ -370,6 +395,11 @@
"command": "one.viewer.metadata.showFromDefaultExplorer",
"when": "resourceExtname in one.metadata.supportedFiles && !explorerResourceIsFolder",
"group": "7_metadata@1"
+ },
+ {
+ "command": "one.editor.mpq.showFromDefaultExplorer",
+ "when": "resourceExtname == .circle",
+ "group": "6_tools"
}
],
"commandPalette": [
@@ -452,6 +482,10 @@
{
"command": "one.viewer.metadata.showFromOneExplorer",
"when": "false"
+ },
+ {
+ "command": "one.editor.mpq.showFromDefaultExplorer",
+ "when": "false"
}
]
},
diff --git a/src/CircleGraph/CircleGraphCtrl.ts b/src/CircleGraph/CircleGraphCtrl.ts
index c8152c05..556aedd0 100644
--- a/src/CircleGraph/CircleGraphCtrl.ts
+++ b/src/CircleGraph/CircleGraphCtrl.ts
@@ -55,6 +55,7 @@ export class MessageDefs {
public static readonly backendColor = "backendColor";
public static readonly error = "error";
public static readonly colorTheme = "colorTheme";
+ public static readonly scrollToSelected = "scrollToSelected";
// loadmodel type
public static readonly modelpath = "modelpath";
public static readonly uint8array = "uint8array";
diff --git a/src/MPQEditor/MPQCircleSelector.ts b/src/MPQEditor/MPQCircleSelector.ts
new file mode 100644
index 00000000..7901a494
--- /dev/null
+++ b/src/MPQEditor/MPQCircleSelector.ts
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as path from "path";
+import * as vscode from "vscode";
+
+import {
+ CircleGraphCtrl,
+ CircleGraphEvent,
+ MessageDefs,
+} from "../CircleGraph/CircleGraphCtrl";
+
+export interface MPQSelectionEvent {
+ onSelection(names: string[], document: vscode.TextDocument): void;
+ onClosed(panel: vscode.WebviewPanel): void;
+ onOpened(panel: vscode.WebviewPanel): void;
+ onFailedToLoadVISQFile(
+ visqPath: string,
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ): void;
+}
+
+export type MPQSelectionCmdOpenArgs = {
+ modelPath: string;
+ document: vscode.TextDocument;
+ names: any;
+ panel: vscode.WebviewPanel;
+};
+
+export type MPQVisqData = {
+ visqPath: string;
+};
+
+export type MPQSelectionCmdCloseArgs = {
+ modelPath: string;
+ document: vscode.TextDocument;
+};
+
+export type MPQSelectionCmdLayersChangedArgs = {
+ modelPath: string;
+ document: vscode.TextDocument;
+ names: any;
+};
+
+export class MPQSelectionPanel
+ extends CircleGraphCtrl
+ implements CircleGraphEvent
+{
+ public static readonly viewType = "one.viewer.mpq";
+ public static readonly cmdOpen = "one.viewer.mpq.openGraphSelector";
+ public static readonly cmdClose = "one.viewer.mpq.closeGraphSelector";
+ public static readonly cmdChanged = "one.viewer.mpq.layersChangedByOwner";
+ public static readonly cmdOpenVisq = "one.viewer.mpq.loadVisq";
+
+ public static panels: MPQSelectionPanel[] = [];
+
+ private _panel: vscode.WebviewPanel;
+ private _disposables: vscode.Disposable[] = [];
+ private _document: vscode.TextDocument;
+ private _ownerPanel: vscode.WebviewPanel;
+ private _ownerId: string; // stringified uri of the owner document
+ private _modelPath: string; // circle file path
+ private _mpqEventHandler?: MPQSelectionEvent;
+ private _lastSelected: string[];
+ private _visqData: string[];
+ private _closedByOwner: boolean = false;
+
+ public static register(context: vscode.ExtensionContext): void {
+ const registrations = [
+ vscode.commands.registerCommand(
+ MPQSelectionPanel.cmdOpen,
+ (args: MPQSelectionCmdOpenArgs, handler: MPQSelectionEvent) => {
+ MPQSelectionPanel.createOrShow(
+ context.extensionUri,
+ args,
+ "",
+ handler
+ );
+ }
+ ),
+ vscode.commands.registerCommand(
+ MPQSelectionPanel.cmdClose,
+ (args: MPQSelectionCmdCloseArgs) => {
+ MPQSelectionPanel.closeByOwner(context.extensionUri, args);
+ }
+ ),
+ vscode.commands.registerCommand(
+ MPQSelectionPanel.cmdChanged,
+ (args: MPQSelectionCmdLayersChangedArgs) => {
+ MPQSelectionPanel.forwardSelectionByOwner(context.extensionUri, args);
+ }
+ ),
+ vscode.commands.registerCommand(
+ MPQSelectionPanel.cmdOpenVisq,
+ (
+ args: MPQSelectionCmdOpenArgs,
+ visqData: MPQVisqData,
+ handler: MPQSelectionEvent
+ ) => {
+ MPQSelectionPanel.createOrShow(
+ context.extensionUri,
+ args,
+ visqData.visqPath,
+ handler
+ );
+ }
+ ),
+ // TODO add more commands
+ ];
+
+ registrations.forEach((disposable) =>
+ context.subscriptions.push(disposable)
+ );
+ }
+
+ public static createOrShow(
+ extensionUri: vscode.Uri,
+ args: MPQSelectionCmdOpenArgs,
+ visqPath: string,
+ handler: MPQSelectionEvent | undefined
+ ) {
+ let column = args.panel.viewColumn;
+ if (column) {
+ if (column >= vscode.ViewColumn.One) {
+ column = column + 1;
+ }
+ }
+
+ // search for existing panel
+ const oldPanel = MPQSelectionPanel.findSelPanel(
+ args.modelPath,
+ args.document.uri.toString()
+ );
+ if (oldPanel) {
+ oldPanel._panel.reveal(column);
+ return;
+ }
+
+ // Otherwise, create a new panel.
+ const lastSlash = args.modelPath.lastIndexOf(path.sep) + 1;
+ const fileNameExt = args.modelPath.substring(lastSlash);
+ const panel = vscode.window.createWebviewPanel(
+ MPQSelectionPanel.viewType,
+ fileNameExt,
+ column || vscode.ViewColumn.Two,
+ { retainContextWhenHidden: true }
+ );
+
+ const graphSelPanel = new MPQSelectionPanel(
+ panel,
+ extensionUri,
+ args,
+ visqPath,
+ handler
+ );
+
+ MPQSelectionPanel.panels.push(graphSelPanel);
+ graphSelPanel.loadContent();
+ graphSelPanel.onForwardSelection(args.names);
+ }
+
+ private static findSelPanel(
+ docPath: string,
+ id: string
+ ): MPQSelectionPanel | undefined {
+ let result = undefined;
+ MPQSelectionPanel.panels.forEach((selpan) => {
+ if (docPath === selpan._modelPath && id === selpan._ownerId) {
+ result = selpan;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * @brief called when owner is closing
+ */
+ public static closeByOwner(
+ extensionUri: vscode.Uri,
+ args: MPQSelectionCmdCloseArgs
+ ) {
+ let selPanel = MPQSelectionPanel.findSelPanel(
+ args.modelPath,
+ args.document.uri.toString()
+ );
+ if (selPanel) {
+ selPanel._closedByOwner = true;
+ selPanel.dispose();
+ }
+ }
+
+ /**
+ * @brief called when owner selection state of nodes has changed
+ */
+ public static forwardSelectionByOwner(
+ extensionUri: vscode.Uri,
+ args: MPQSelectionCmdLayersChangedArgs
+ ) {
+ let selPanel = MPQSelectionPanel.findSelPanel(
+ args.modelPath,
+ args.document.uri.toString()
+ );
+ if (selPanel) {
+ selPanel.onForwardSelection(args.names);
+ }
+ }
+
+ private constructor(
+ panel: vscode.WebviewPanel,
+ extensionUri: vscode.Uri,
+ args: MPQSelectionCmdOpenArgs,
+ visqPath: string,
+ handler: MPQSelectionEvent | undefined
+ ) {
+ super(extensionUri, panel.webview);
+
+ this._panel = panel;
+ this._ownerId = args.document.uri.toString();
+ this._document = args.document;
+ this._ownerPanel = args.panel;
+ this._modelPath = args.modelPath;
+ this._mpqEventHandler = handler;
+ this._lastSelected = args.names;
+ this._visqData = [];
+
+ // Listen for when the panel is disposed
+ // This happens when the user closes the panel or when the panel is closed programmatically
+ this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
+
+ this.initGraphCtrl(this._modelPath, this);
+ if (visqPath.length < 1) {
+ this.setMode("selector");
+ } else {
+ this.setMode("visqselector");
+ const visqUri = vscode.Uri.file(visqPath);
+ vscode.workspace.fs.readFile(visqUri).then((visqData) => {
+ try {
+ this._visqData = JSON.parse(visqData.toString());
+ } catch (error) {
+ this.onInvalidVISQData(visqPath);
+ }
+
+ // check whether _visqData pretend to be valid
+ if (!("error" in this._visqData) || !("meta" in this._visqData)) {
+ this.onInvalidVISQData(visqPath);
+ }
+ });
+ }
+
+ if (this._mpqEventHandler) {
+ this._mpqEventHandler.onOpened(this._ownerPanel);
+ }
+
+ this.sendScrollToSelected();
+ }
+
+ private onInvalidVISQData(visqPath: string) {
+ this._visqData = [];
+ if (this._mpqEventHandler) {
+ this._mpqEventHandler.onFailedToLoadVISQFile(
+ visqPath,
+ this._document,
+ this._ownerPanel
+ );
+ }
+ }
+
+ public dispose() {
+ if (!this._closedByOwner && this._mpqEventHandler) {
+ this._mpqEventHandler.onClosed(this._ownerPanel);
+ }
+
+ this.disposeGraphCtrl();
+
+ MPQSelectionPanel.panels.forEach((selPan, index) => {
+ if (
+ this._ownerId === selPan._ownerId &&
+ this._modelPath === selPan._modelPath
+ ) {
+ MPQSelectionPanel.panels.splice(index, 1);
+ }
+ });
+
+ // Clean up our resources
+ this._panel.dispose();
+
+ while (this._disposables.length) {
+ const x = this._disposables.pop();
+ if (x) {
+ x.dispose();
+ }
+ }
+ }
+
+ /**
+ * CircleGraphEvent interface implementations
+ */
+ public onViewMessage(message: any) {
+ switch (message.command) {
+ case MessageDefs.selection:
+ this._lastSelected = message.names;
+ // we need to update the document, but not save to file.
+ // pass to owner to handle this.
+ if (this._mpqEventHandler) {
+ this._mpqEventHandler.onSelection(message.names, this._document);
+ }
+ break;
+ case MessageDefs.finishload:
+ this.onForwardSelection(this._lastSelected);
+ break;
+ case MessageDefs.visq:
+ this.sendVisq(this._visqData);
+ break;
+ }
+ }
+
+ public onForwardSelection(selection: any) {
+ let selections: string[] = [];
+ let items = selection as Array;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].length > 0) {
+ selections.push(items[i]);
+ }
+ }
+ this.setSelection(selections);
+ }
+
+ public setTitle(title: string) {
+ this._panel.title = title;
+ }
+
+ public sendScrollToSelected() {
+ this._webview.postMessage({
+ command: MessageDefs.scrollToSelected,
+ value: false,
+ });
+ }
+}
diff --git a/src/MPQEditor/MPQEditor.ts b/src/MPQEditor/MPQEditor.ts
new file mode 100644
index 00000000..9e209680
--- /dev/null
+++ b/src/MPQEditor/MPQEditor.ts
@@ -0,0 +1,865 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as cp from "child_process";
+import * as fs from "fs";
+import * as path from "path";
+import * as vscode from "vscode";
+
+import { Balloon } from "../Utils/Balloon";
+import { Logger } from "../Utils/Logger";
+import { getNonce } from "../Utils/external/Nonce";
+import { getUri } from "../Utils/external/Uri";
+
+import { MPQSelectionEvent, MPQSelectionPanel } from "./MPQCircleSelector";
+import {
+ MPQSelectionCmdCloseArgs,
+ MPQSelectionCmdOpenArgs,
+ MPQSelectionCmdLayersChangedArgs,
+ MPQVisqData,
+} from "./MPQCircleSelector";
+
+class MPQData {
+ private _content: any;
+ private _allModelNodes: string[] | undefined;
+ private _modelNodes?: string[];
+ private _selectedNodes: Set;
+ private _visqPath: string = ""; // empty means no visqData is provided
+
+ static _layersKey: string = "layers";
+ static _nameKey: string = "name";
+ static _defQuantizationKey: string = "default_quantization_dtype";
+ static _defGranularityKey: string = "default_granularity";
+
+ constructor() {
+ this._selectedNodes = new Set();
+ }
+
+ getAsString(): string {
+ return JSON.stringify(this._content, null, " ");
+ }
+
+ getValue(key: string): string {
+ return this._content[key].toString();
+ }
+
+ setWithString(text: string) {
+ this._content = JSON.parse(text);
+ }
+
+ updateSection(section: string, value: string) {
+ this._content[section] = value;
+ }
+
+ setAllModelNodes(modelNodes: string[]) {
+ this._allModelNodes = modelNodes.filter((name) => name.length > 0);
+ //filter by content
+ this.filterModelNodesbyContent();
+ }
+
+ filterModelNodesbyContent() {
+ let curLayers = this.getLayers();
+ this.filterModelNodesBy(curLayers);
+ }
+
+ getLayers() {
+ return this._content[MPQData._layersKey].map(
+ (item: any) => item[MPQData._nameKey]
+ );
+ }
+
+ setLayers(names: string[]): string[] {
+ let layersToAdd = Array();
+ let layersToDelete = Array();
+ this._content[MPQData._layersKey].forEach((layer: any) => {
+ let foundIndex = names.findIndex(
+ (name: string) => name === layer[MPQData._nameKey]
+ );
+ if (foundIndex < 0) {
+ // name to delete
+ layersToDelete.push(layer["name"]);
+ }
+ });
+ names.forEach((name: any) => {
+ let foundIndex = this._content[MPQData._layersKey].findIndex(
+ (x: any) => x[MPQData._nameKey] === name
+ );
+ if (foundIndex < 0) {
+ // name to add
+ layersToAdd.push(name);
+ }
+ });
+ this.removeModelNodesFromLayers(layersToDelete);
+ this.addLayers(layersToAdd);
+
+ return layersToAdd;
+ }
+
+ addLayers(names: string[]): void {
+ if (names.length < 1) {
+ return;
+ }
+
+ const otherQuantization =
+ this._content[MPQData._defQuantizationKey] === "uint8"
+ ? "int16"
+ : "uint8";
+ let quantization = Array(names.length);
+ quantization.fill(otherQuantization);
+ let granularity = Array(names.length);
+ granularity.fill(this._content[MPQData._defGranularityKey]);
+ this.updateLayers(names, quantization, granularity);
+ }
+
+ filterModelNodesBy(filter: string[]) {
+ this._modelNodes = this._allModelNodes?.filter(
+ (name) => filter.find((filterName) => name === filterName) === undefined
+ );
+ }
+
+ getAllModelNodes(): string[] | undefined {
+ return this._modelNodes;
+ }
+
+ updateLayers(names: string[], quantization: string[], granularity: string[]) {
+ if (!(MPQData._layersKey in this._content)) {
+ this._content[MPQData._layersKey] = [];
+ }
+ for (let i = 0; i < names.length; i++) {
+ let layer = {
+ name: names[i],
+ dtype: quantization[i],
+ granularity: granularity[i],
+ };
+ this._content[MPQData._layersKey].push(layer);
+ }
+ this.filterModelNodesbyContent();
+ }
+
+ updateSectionOfLayer(name: string, section: string, value: string) {
+ let layer = this._content[MPQData._layersKey].find(
+ (x: any) => x["name"] === name
+ );
+ if (layer) {
+ layer[section] = value;
+ }
+ }
+
+ toggleSelectedNode(name: string) {
+ if (this._selectedNodes.has(name)) {
+ this._selectedNodes.delete(name);
+ } else {
+ this._selectedNodes.add(name);
+ }
+ }
+
+ getSelected(): Set {
+ return this._selectedNodes;
+ }
+
+ clearSelection() {
+ this._selectedNodes.clear();
+ }
+
+ removeModelNodesFromLayers(names: any) {
+ names.forEach((name: any) => {
+ let foundIndex = this._content[MPQData._layersKey].findIndex(
+ (x: any) => x["name"] === name
+ );
+ if (foundIndex > -1) {
+ this._content[MPQData._layersKey].splice(foundIndex, 1);
+ }
+ });
+ this.filterModelNodesbyContent();
+ }
+
+ getVisqPath(): string {
+ return this._visqPath;
+ }
+
+ setVisqPath(path: string): void {
+ this._visqPath = path;
+ }
+}
+
+export class MPQEditorProvider
+ implements vscode.CustomTextEditorProvider, MPQSelectionEvent
+{
+ public static readonly viewType = "one.editor.mpq";
+ public static readonly fileExtension = ".mpq.json";
+
+ private _disposables: vscode.Disposable[] = [];
+ private _mpqDataMap: any = {};
+
+ public static register(context: vscode.ExtensionContext): void {
+ const provider = new MPQEditorProvider(context);
+ const registrations = [
+ vscode.window.registerCustomEditorProvider(
+ MPQEditorProvider.viewType,
+ provider,
+ {
+ webviewOptions: { retainContextWhenHidden: true },
+ }
+ ),
+ // Add command registration here
+ vscode.commands.registerCommand(
+ "one.editor.mpq.showFromDefaultExplorer",
+ (uri) => {
+ MPQEditorProvider.createMPQConfig(uri);
+ }
+ ),
+ vscode.commands.registerCommand(
+ "one.editor.mpq.showFromOneExplorer",
+ async (uri) => {
+ MPQEditorProvider.createMPQConfig(uri);
+ }),
+ ];
+
+ registrations.forEach((disposable) =>
+ context.subscriptions.push(disposable)
+ );
+ }
+
+ public static createMPQConfig(uri: vscode.Uri) {
+ const extName = path.parse(uri.path).ext.slice(1);
+ if (extName !== 'circle') {
+ return;
+ }
+
+ const dirPath = path.parse(uri.path).dir;
+ const modelName = path.parse(uri.path).name;
+ const circleName = path.parse(uri.path).base;
+
+ const content =
+ '{"default_quantization_dtype": "uint8",' +
+ '"default_granularity": "channel",' +
+ '"layers": [],' +
+ '"model_path": ' +
+ '"' +
+ circleName +
+ '"' +
+ "}";
+
+ const findInputPath = (mpqName: string): string => {
+ const maxIters = 5;
+ for (let i = 0; i < maxIters; i++) {
+ const mpqPath: string = path.join(
+ dirPath,
+ mpqName + MPQEditorProvider.fileExtension
+ );
+ if (!fs.existsSync(mpqPath)) {
+ return mpqName + MPQEditorProvider.fileExtension;
+ }
+ mpqName = mpqName + "(1)";
+ }
+ return "";
+ };
+
+ let mpqName = findInputPath(modelName);
+ if (mpqName.length < 1) {
+ // failed to find valid name, just revert to initial version
+ mpqName = modelName + MPQEditorProvider.fileExtension;
+ }
+
+ const validateInputPath = (mpqName: string): string | undefined => {
+ const mpqPath: string = path.join(dirPath, mpqName);
+
+ if (!mpqPath.endsWith(MPQEditorProvider.fileExtension)) {
+ return (
+ "A file extension must be " + MPQEditorProvider.fileExtension
+ );
+ }
+
+ if (fs.existsSync(mpqPath)) {
+ return `A file or folder ${mpqPath} already exists at this location. Please choose a different name.`;
+ }
+ };
+
+ vscode.window
+ .showInputBox({
+ title: `Create mixed precision quantization configuration for '${modelName}.${extName}' :`,
+ placeHolder: `Enter a file name`,
+ value: mpqName,
+ valueSelection: [
+ 0,
+ mpqName.length - `${MPQEditorProvider.fileExtension}`.length,
+ ],
+ validateInput: validateInputPath,
+ })
+ .then((value) => {
+ if (!value) {
+ Logger.debug("MPQEditor", "User hit the escape key!");
+ return;
+ }
+
+ // 'uri' path is not occupied, assured by validateInputPath
+ const uri = vscode.Uri.file(`${dirPath}/${value}`);
+
+ const edit = new vscode.WorkspaceEdit();
+ edit.createFile(uri);
+ edit.insert(uri, new vscode.Position(0, 0), content);
+
+ vscode.workspace.applyEdit(edit).then((isSuccess) => {
+ if (isSuccess) {
+ vscode.workspace.openTextDocument(uri).then((document) => {
+ document.save();
+ vscode.commands.executeCommand(
+ "vscode.openWith",
+ uri,
+ MPQEditorProvider.viewType
+ );
+ });
+ } else {
+ Logger.error(
+ "MPQEditor",
+ "CreateMPQ",
+ `Failed to create the file ${uri}`
+ );
+ }
+ });
+ });
+ }
+
+ constructor(private readonly context: vscode.ExtensionContext) {}
+
+ public async resolveCustomTextEditor(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel,
+ _token: vscode.CancellationToken
+ ): Promise {
+ this._mpqDataMap[document.uri.toString()] = new MPQData();
+ await this.initWebview(document, webviewPanel);
+ this.initWebviewPanel(document, webviewPanel);
+ this.updateWebview(document, webviewPanel.webview);
+ }
+
+ private async initWebview(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ): Promise {
+ let webview: vscode.Webview = webviewPanel.webview;
+
+ webview.options = {
+ enableScripts: true,
+ };
+
+ const nonce = getNonce();
+ const scriptUri = getUri(webview, this.context.extensionUri, [
+ "media",
+ "MPQEditor",
+ "index.js",
+ ]);
+ const styleUri = getUri(webview, this.context.extensionUri, [
+ "media",
+ "MPQEditor",
+ "style.css",
+ ]);
+ const codiconUri = getUri(webview, this.context.extensionUri, [
+ "node_modules",
+ "@vscode",
+ "codicons",
+ "dist",
+ "codicon.css",
+ ]);
+ const toolkitUri = getUri(webview, this.context.extensionUri, [
+ "node_modules",
+ "@vscode",
+ "webview-ui-toolkit",
+ "dist",
+ "toolkit.js",
+ ]);
+ const htmlUri = vscode.Uri.joinPath(
+ this.context.extensionUri,
+ "media/MPQEditor/index.html"
+ );
+ let html = Buffer.from(
+ await vscode.workspace.fs.readFile(htmlUri)
+ ).toString();
+ html = html.replace(/\${nonce}/g, `${nonce}`);
+ html = html.replace(/\${webview.cspSource}/g, `${webview.cspSource}`);
+ html = html.replace(/\${scriptUri}/g, `${scriptUri}`);
+ html = html.replace(/\${toolkitUri}/g, `${toolkitUri}`);
+ html = html.replace(/\${cssUri}/g, `${styleUri}`);
+ html = html.replace(/\${codiconUri}/g, `${codiconUri}`);
+ webview.html = html;
+
+ // Receive message from the webview.
+ webview.onDidReceiveMessage((e) => {
+ switch (e.type) {
+ case "requestDisplayMPQ":
+ this.updateWebview(document, webview);
+ break;
+ case "addSpecificLayerFromDialog":
+ this.hadleAddSpecificLayerFromDialog(document);
+ break;
+ case "setModelNodesToDefault":
+ this._mpqDataMap[document.uri.toString()].removeModelNodesFromLayers(
+ e.names
+ );
+ break;
+ case "toggleSelectedNode":
+ this._mpqDataMap[document.uri.toString()].toggleSelectedNode(e.name);
+ webview.postMessage({
+ type: "selectionChanged",
+ names: Array.from(
+ this._mpqDataMap[document.uri.toString()].getSelected()
+ ),
+ });
+ break;
+ case "removeSelectedFromLayers":
+ this.handleRemoveSelectedFromLayers(document, webview);
+ break;
+ case "updateLayers":
+ this._mpqDataMap[document.uri.toString()].updateLayers(
+ e.names,
+ e.quantization,
+ e.granularity
+ );
+ break;
+ case "updateSpecificQuantization":
+ this._mpqDataMap[document.uri.toString()].updateSectionOfLayer(
+ e.name,
+ "dtype",
+ e.value
+ );
+ break;
+ case "updateSpecificGranularity":
+ this._mpqDataMap[document.uri.toString()].updateSectionOfLayer(
+ e.name,
+ "granularity",
+ e.value
+ );
+ break;
+ case "requestModelNodes":
+ this.handleRequestModelNodes(document, webview);
+ break;
+ case "updateSection":
+ this._mpqDataMap[document.uri.toString()].updateSection(
+ e.section,
+ e.value
+ );
+ break;
+ case "updateDocument":
+ this.updateDocument(document);
+ break;
+ case "toggleCircleGraphIsShown":
+ this.toggleCircleGraphIsShown(e.show, document, webviewPanel);
+ break;
+ case "loadVisqFile":
+ this.loadVisqFile(document, webviewPanel);
+ break;
+ case "removeVisqFile":
+ this.removeVisqFile(document, webviewPanel);
+ break;
+ case "VisqInputPathChanged":
+ this.hadleVisqInputPathChanged(e.path, document, webviewPanel);
+ break;
+ case "showModelNodes":
+ this.handleShowModelNodes(document, webviewPanel);
+ break;
+ case "removeLayer":
+ this.handleRemoveLayerFromLayers(e.name, document, webview);
+ break;
+ default:
+ break;
+ }
+ });
+ }
+
+ private updateDocument(document: vscode.TextDocument) {
+ if (
+ this._mpqDataMap[document.uri.toString()].getAsString() !==
+ document.getText()
+ ) {
+ // TODO Optimize this to modify only changed lines
+ const edit = new vscode.WorkspaceEdit();
+ edit.replace(
+ document.uri,
+ new vscode.Range(0, 0, document.lineCount, 0),
+ this._mpqDataMap[document.uri.toString()].getAsString()
+ );
+ vscode.workspace.applyEdit(edit);
+ }
+ }
+
+ private initWebviewPanel(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ): void {
+ vscode.commands.executeCommand(
+ "setContext",
+ MPQEditorProvider.viewType,
+ true
+ );
+
+ const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(
+ (e) => {
+ if (
+ e.contentChanges.length > 0 &&
+ e.document.uri.toString() === document.uri.toString()
+ ) {
+ this.updateWebview(document, webviewPanel.webview);
+
+ {
+ // synchronize circle view
+ const args: MPQSelectionCmdLayersChangedArgs = {
+ modelPath: this.getModelFilePath(document),
+ document: document,
+ names: this._mpqDataMap[document.uri.toString()].getLayers(),
+ };
+ vscode.commands.executeCommand(
+ MPQSelectionPanel.cmdChanged,
+ args,
+ this
+ );
+ }
+ }
+ }
+ );
+
+ webviewPanel.onDidChangeViewState(
+ () => {
+ vscode.commands.executeCommand(
+ "setContext",
+ MPQEditorProvider.viewType,
+ webviewPanel.visible
+ );
+ },
+ null,
+ this._disposables
+ );
+
+ webviewPanel.onDidDispose(() => {
+ this.closeModelGraphView(document);
+
+ changeDocumentSubscription.dispose();
+ while (this._disposables.length) {
+ const x = this._disposables.pop();
+ if (x) {
+ x.dispose();
+ }
+ }
+ vscode.commands.executeCommand(
+ "setContext",
+ MPQEditorProvider.viewType,
+ false
+ );
+ });
+ }
+
+ private closeModelGraphView(document: vscode.TextDocument): void {
+ const args: MPQSelectionCmdCloseArgs = {
+ modelPath: this.getModelFilePath(document),
+ document: document,
+ };
+ vscode.commands.executeCommand(MPQSelectionPanel.cmdClose, args);
+ }
+
+ private updateWebview(
+ document: vscode.TextDocument,
+ webview: vscode.Webview
+ ): void {
+ this._mpqDataMap[document.uri.toString()].setWithString(document.getText());
+ const content = JSON.parse(document.getText());
+ if (content !== undefined) {
+ webview.postMessage({
+ type: "displayMPQ",
+ content: content,
+ });
+ }
+ }
+
+ private hadleAddSpecificLayerFromDialog(document: vscode.TextDocument) {
+ const nodes = this._mpqDataMap[document.uri.toString()].getAllModelNodes();
+ const pickOptions = {
+ canPickMany: true,
+ };
+
+ vscode.window
+ .showQuickPick(nodes, pickOptions)
+ .then((values: string | undefined) => {
+ if (!values) {
+ return;
+ }
+
+ this._mpqDataMap[document.uri.toString()].addLayers(values);
+ this.updateDocument(document);
+ });
+ }
+
+ private handleRemoveSelectedFromLayers(
+ document: vscode.TextDocument,
+ webview: vscode.Webview
+ ) {
+ let curConf = this._mpqDataMap[document.uri.toString()];
+ let selection = curConf.getSelected();
+
+ curConf.removeModelNodesFromLayers(selection);
+ curConf.clearSelection();
+
+ this.updateDocument(document);
+ webview.postMessage({
+ type: "selectionChanged",
+ names: Array(),
+ });
+ }
+
+ private handleRemoveLayerFromLayers(
+ name: string,
+ document: vscode.TextDocument,
+ webview: vscode.Webview
+ ) {
+ let curConf = this._mpqDataMap[document.uri.toString()];
+
+ curConf.removeModelNodesFromLayers([name]);
+ curConf.clearSelection();
+
+ this.updateDocument(document);
+ webview.postMessage({
+ type: "selectionChanged",
+ names: Array(),
+ });
+ }
+
+ private getModelFilePath(document: vscode.TextDocument): string {
+ const dirPath = path.parse(document.uri.path).dir;
+ let fileName =
+ this._mpqDataMap[document.uri.toString()].getValue("model_path");
+ return path.join(dirPath, fileName);
+ }
+
+ private handleRequestModelNodes(
+ document: vscode.TextDocument,
+ webview: vscode.Webview
+ ): void {
+ const K_DATA: string = "data";
+ const K_EXIT: string = "exit";
+ const K_ERROR: string = "error";
+ let modelFilePath = this.getModelFilePath(document);
+
+ // TODO integrate with Toolchain
+ const tool = "/usr/share/one/bin/circle-operator";
+ if (!fs.existsSync(tool)) {
+ // check whether it is installed
+ Balloon.info(
+ "To add more layers for editing please install ONE-toolchain"
+ );
+ return;
+ }
+
+ const toolargs = ["--name", modelFilePath];
+ let result: string = "";
+ let error: string = "";
+
+ let runPromise = new Promise((resolve, reject) => {
+ let cmd = cp.spawn(tool, toolargs, { shell: false });
+
+ cmd.stdout.on(K_DATA, (data: any) => {
+ let str = data.toString();
+ if (str.length > 0) {
+ result = result + str;
+ }
+ });
+
+ cmd.stderr.on(K_DATA, (data: any) => {
+ error = result + data.toString();
+ Logger.error("MPQEditor", error);
+ });
+
+ cmd.on(K_EXIT, (code: any) => {
+ let codestr = code.toString();
+ if (codestr === "0") {
+ resolve(result);
+ } else {
+ let msg = "Failed to load model: " + modelFilePath;
+ Balloon.error(msg);
+ reject(msg);
+ }
+ });
+
+ cmd.on(K_ERROR, () => {
+ let msg = "Failed to run circle-operator: " + modelFilePath;
+ Balloon.error(msg);
+ reject(msg);
+ });
+ });
+
+ runPromise
+ .then((names) => {
+ const nodesNames = names.split(/\r?\n/);
+ this._mpqDataMap[document.uri.toString()].setAllModelNodes(nodesNames);
+ webview.postMessage({
+ type: "modelNodesChanged",
+ names: nodesNames,
+ });
+ })
+ .catch((error) => {
+ Logger.error("MPQEditor", error);
+ });
+ }
+
+ private showCircleModelGraph(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ const args: MPQSelectionCmdOpenArgs = {
+ modelPath: this.getModelFilePath(document),
+ document: document,
+ names: this._mpqDataMap[document.uri.toString()].getLayers(),
+ panel: webviewPanel,
+ };
+ vscode.commands.executeCommand(MPQSelectionPanel.cmdOpen, args, this);
+ }
+
+ private showVisqCircleModelGraph(
+ visqPath: string,
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ const args: MPQSelectionCmdOpenArgs = {
+ modelPath: this.getModelFilePath(document),
+ document: document,
+ names: this._mpqDataMap[document.uri.toString()].getLayers(),
+ panel: webviewPanel,
+ };
+
+ const visqData: MPQVisqData = {
+ visqPath: visqPath,
+ };
+ vscode.commands.executeCommand(
+ MPQSelectionPanel.cmdOpenVisq,
+ args,
+ visqData,
+ this
+ );
+
+ webviewPanel.webview.postMessage({
+ type: "VisqFileLoaded",
+ visqFile: visqPath,
+ });
+ }
+
+ private toggleCircleGraphIsShown(
+ show: boolean,
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ if (show) {
+ const docUri = document.uri.toString();
+ const visqPath = this._mpqDataMap[docUri].getVisqPath();
+ if (visqPath.length < 1) {
+ this.showCircleModelGraph(document, webviewPanel);
+ } else {
+ this.showVisqCircleModelGraph(visqPath, document, webviewPanel);
+ }
+ } else {
+ this.closeModelGraphView(document);
+ }
+ }
+
+ private handleShowModelNodes(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ this.showCircleModelGraph(document, webviewPanel);
+ }
+
+ private hadleVisqInputPathChanged(
+ path: string,
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ): void {
+ if (
+ (path === "" || !path.endsWith(".visq.json")) &&
+ this._mpqDataMap[document.uri.toString()].getVisqPath().length > 0
+ ) {
+ // remove invalid path
+ this.removeVisqFile(document, webviewPanel);
+ } else if (path.endsWith(".visq.json")) {
+ // reload visq
+ this._mpqDataMap[document.uri.toString()].setVisqPath(path);
+ this.closeModelGraphView(document);
+ this.showVisqCircleModelGraph(path, document, webviewPanel);
+ }
+ }
+
+ private removeVisqFile(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ this._mpqDataMap[document.uri.toString()].setVisqPath(""); // clear visqPath
+
+ this.closeModelGraphView(document);
+ this.showCircleModelGraph(document, webviewPanel);
+ }
+
+ private loadVisqFile(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ) {
+ const dialogOptions = {
+ canSelectMany: false,
+ canSelectFolders: false,
+ openLabel: "Open",
+ filters: { "target files": ["visq.json"], "all files": ["*"] },
+ };
+
+ vscode.window.showOpenDialog(dialogOptions).then((fileUri) => {
+ if (fileUri && fileUri[0]) {
+ const visqPath = fileUri[0].fsPath.toString();
+
+ let docUri = document.uri.toString();
+ this._mpqDataMap[docUri].setVisqPath(visqPath);
+ // close previous view if any
+ this.closeModelGraphView(document);
+ // open new view
+ this.showVisqCircleModelGraph(visqPath, document, webviewPanel);
+ }
+ });
+ }
+
+ onSelection(names: string[], document: vscode.TextDocument): void {
+ let docUri = document.uri.toString();
+ this._mpqDataMap[docUri].setLayers(names);
+ this.updateDocument(document);
+ }
+
+ onClosed(panel: vscode.WebviewPanel): void {
+ panel.webview.postMessage({
+ type: "modelGraphIsShown",
+ shown: false,
+ });
+ }
+
+ onOpened(panel: vscode.WebviewPanel): void {
+ panel.webview.postMessage({
+ type: "modelGraphIsShown",
+ shown: true,
+ });
+ }
+
+ onFailedToLoadVISQFile(
+ visqPath: string,
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel
+ ): void {
+ Balloon.error("Invalid visq file " + visqPath);
+ this._mpqDataMap[document.uri.toString()].setVisqPath("");
+ this.closeModelGraphView(document);
+ // revert to slector mode
+ this.showCircleModelGraph(document, webviewPanel);
+ }
+}
diff --git a/src/extension.ts b/src/extension.ts
index ad6c476e..9614db93 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -30,6 +30,8 @@ import { PartGraphSelPanel } from "./PartEditor/PartGraphSelector";
import { ToolchainProvider } from "./Toolchain/ToolchainProvider";
import { Logger } from "./Utils/Logger";
import { VisqViewerProvider } from "./Visquv/VisqViewer";
+import { MPQEditorProvider } from "./MPQEditor/MPQEditor";
+import { MPQSelectionPanel } from "./MPQEditor/MPQCircleSelector";
/* istanbul ignore next */
export function activate(context: vscode.ExtensionContext) {
@@ -77,6 +79,9 @@ export function activate(context: vscode.ExtensionContext) {
MetadataViewerProvider.register(context);
+ MPQEditorProvider.register(context);
+ MPQSelectionPanel.register(context);
+
// returning backend registration function that will be called by backend extensions
return backendRegistrationApi();
}