Skip to content

Commit

Permalink
Switch to webview for gltf preview
Browse files Browse the repository at this point in the history
  • Loading branch information
bghgary committed Nov 5, 2018
1 parent d0fc9cd commit e9ddd03
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 94 deletions.
16 changes: 16 additions & 0 deletions src/contextBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as vscode from "vscode";
import { toResourceUrl } from "./utilities";

export abstract class ContextBase {
protected readonly _context: vscode.ExtensionContext;
private readonly _extensionRootPath: string;

constructor(context: vscode.ExtensionContext) {
this._context = context;
this._extensionRootPath = `${toResourceUrl(this._context.extensionPath.replace(/\\$/, ''))}/`;
}

protected get extensionRootPath(): string {
return this._extensionRootPath;
}
}
37 changes: 9 additions & 28 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as vscode from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
import { DataUriTextDocumentContentProvider } from './dataUriTextDocumentContentProvider';
import { GltfPreviewDocumentContentProvider } from './gltfPreviewDocumentContentProvider';
import { GltfOutlineTreeDataProvider } from './gltfOutlineTreeDataProvider';
import { GltfPreview } from './gltfPreview';
import { ConvertGLBtoGltfLoadFirst, ConvertToGLB, getBuffer } from 'gltf-import-export';
import * as GltfValidate from './validationProvider';
import * as jsonMap from 'json-source-map';
import * as path from 'path';
import * as Url from 'url';
import * as fs from 'fs';
import { getFromJsonPointer, guessMimeType, btoa, guessFileExtension, getAccessorData, AccessorTypeToNumComponents } from './utilities';
import { getFromJsonPointer, guessMimeType, btoa, guessFileExtension, getAccessorData, AccessorTypeToNumComponents, parseJsonMap } from './utilities';
import { GLTF2 } from './GLTF2';

function checkValidEditor(): boolean {
Expand All @@ -35,7 +34,7 @@ function pointerContains(pointer: any, selection: vscode.Selection): boolean {

function tryGetJsonMap() {
try {
return jsonMap.parse(vscode.window.activeTextEditor.document.getText()) as { data: GLTF2.GLTF, pointers: Array<string> };
return parseJsonMap(vscode.window.activeTextEditor.document.getText());
} catch (ex) {
vscode.window.showErrorMessage('Error parsing this document. Please make sure it is valid JSON.');
}
Expand Down Expand Up @@ -354,32 +353,17 @@ export function activate(context: vscode.ExtensionContext) {
}
}));

const gltfPreview = new GltfPreview(context);

//
// Register a preview of the whole glTF file.
// Preview a glTF model.
//
const gltfPreviewProvider = new GltfPreviewDocumentContentProvider(context);
const gltfPreviewRegistration = vscode.workspace.registerTextDocumentContentProvider('gltf-preview', gltfPreviewProvider);
context.subscriptions.push(gltfPreviewRegistration);

context.subscriptions.push(vscode.commands.registerCommand('gltf.previewModel', () => {
if (!checkValidEditor()) {
return;
}

const fileName = vscode.window.activeTextEditor.document.fileName;
const baseName = path.basename(fileName);
const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(fileName));

vscode.commands.executeCommand('vscode.previewHtml', gltfPreviewUri, vscode.ViewColumn.Two, `glTF Preview [${baseName}]`)
.then((success) => { }, (reason) => { vscode.window.showErrorMessage(reason); });

// This can be used to debug the preview HTML.
//vscode.workspace.openTextDocument(gltfPreviewUri).then((doc: vscode.TextDocument) => {
// vscode.window.showTextDocument(doc, ViewColumn.Three, false).then(e => {
// });
//}, (reason) => { vscode.window.showErrorMessage(reason); });

gltfPreviewProvider.update(gltfPreviewUri);
gltfPreview.showPanel(vscode.window.activeTextEditor.document);
}));

//
Expand Down Expand Up @@ -704,11 +688,8 @@ export function activate(context: vscode.ExtensionContext) {
//
// Update all preview windows when the glTF file is saved.
//
vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => {
if (document === vscode.window.activeTextEditor.document) {
const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(document.fileName));
gltfPreviewProvider.update(gltfPreviewUri);
}
vscode.workspace.onDidSaveTextDocument((document) => {
gltfPreview.updatePanel(document);
});
}

Expand Down
135 changes: 69 additions & 66 deletions src/gltfPreviewDocumentContentProvider.ts → src/gltfPreview.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { ContextBase } from './contextBase';
import { toResourceUrl, parseJsonMap } from './utilities';

export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentContentProvider {
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
private _context: vscode.ExtensionContext;
private _mainHtml: string;
private _babylonHtml: string;
private _cesiumHtml: string;
private _threeHtml: string;
export class GltfPreview extends ContextBase {
private readonly _mainHtml: string;
private readonly _babylonHtml: string;
private readonly _cesiumHtml: string;
private readonly _threeHtml: string;

public UriPrefix = 'gltf-preview://';
private _panels: { [fileName: string]: vscode.WebviewPanel } = {};

constructor(context: vscode.ExtensionContext) {
this._context = context;
this._mainHtml = fs.readFileSync(this._context.asAbsolutePath('pages/previewModel.html'), 'UTF-8')
super(context);

this._mainHtml = fs.readFileSync(this._context.asAbsolutePath('pages/previewModel.html'), 'UTF-8');
this._babylonHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/babylonView.html'), 'UTF-8'));
this._cesiumHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/cesiumView.html'), 'UTF-8'));
this._threeHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/threeView.html'), 'UTF-8'));
}

private addFilePrefix(file: string): string {
return ((file[0] === '/') ? 'file://' : 'file:///') + file;
}

private getFilePath(file: string): string {
return this.addFilePrefix(this._context.asAbsolutePath(file));
}

private toUrl(file: string): string {
return this.addFilePrefix(file.replace(/\\/g, '/'));
}

// Instructions to open Chrome DevTools on the HTML preview window:
//
// 1. With the HTML preview window open, click Help->Toggle Developer Tools.
Expand All @@ -47,53 +36,76 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo
// click the pull-down and change "top" to "active-frame (webview.html)".
// Now you can debug the HTML preview in the sandboxed iframe.

public provideTextDocumentContent(uri: vscode.Uri): string {
let filePath = decodeURIComponent(uri.authority);
const document = vscode.workspace.textDocuments.find(e => e.fileName.toLowerCase() === filePath.toLowerCase());
if (!document) {
return 'ERROR: Can no longer find document in editor: ' + filePath;
public showPanel(gltfDocument: vscode.TextDocument): void {
const gltfFilePath = gltfDocument.fileName;

let panel = this._panels[gltfFilePath];
if (!panel) {
panel = vscode.window.createWebviewPanel('gltf.preview', 'glTF Preview', vscode.ViewColumn.Two, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.file(this._context.extensionPath),
vscode.Uri.file(path.dirname(gltfFilePath)),
],
});

panel.onDidDispose(() => {
delete this._panels[gltfFilePath];
});

this._panels[gltfFilePath] = panel;
}
// Update case of `fileName` to match actual document filename.
filePath = document.fileName;

const gltfContent = document.getText();
const gltfFileName = path.basename(filePath);
let gltfRootPath: string = this.toUrl(path.dirname(filePath));
if (!gltfRootPath.endsWith("/")) {
gltfRootPath += "/";

const gltfContent = gltfDocument.getText();
this.updatePanelInternal(panel, gltfFilePath, gltfContent);
panel.reveal(vscode.ViewColumn.Two);
}

public updatePanel(gltfDocument: vscode.TextDocument): void {
const gltfFileName = gltfDocument.fileName;
let panel = this._panels[gltfFileName];
if (panel) {
const gltfContent = gltfDocument.getText();
this.updatePanelInternal(panel, gltfFileName, gltfContent);
}
}

private updatePanelInternal(panel: vscode.WebviewPanel, gltfFilePath: string, gltfContent: string): void {
const map = parseJsonMap(gltfContent);

const gltfRootPath = toResourceUrl(`${path.dirname(gltfFilePath)}/`);
const gltfFileName = path.basename(gltfFilePath);

var gltfMajorVersion = 1;
try {
const gltf = JSON.parse(gltfContent);
if (gltf && gltf.asset && gltf.asset.version && gltf.asset.version[0] === '2') {
gltfMajorVersion = 2;
}
} catch (ex) { }

let extensionRootPath: string = this._context.asAbsolutePath('').replace(/\\/g, '/');
if (!extensionRootPath.endsWith("/")) {
extensionRootPath += "/";
const gltf = map.data;
let gltfMajorVersion = 1;
if (gltf && gltf.asset && gltf.asset.version && gltf.asset.version[0] === '2') {
gltfMajorVersion = 2;
}

panel.title = `glTF Preview [${gltfFileName}]`;
panel.webview.html = this.formatHtml(gltfMajorVersion, gltfContent, gltfRootPath, gltfFileName);
}

private formatHtml(gltfMajorVersion: number, gltfContent: string, gltfRootPath: string, gltfFileName: string): string {
const defaultEngine = vscode.workspace.getConfiguration('glTF').get('defaultV' + gltfMajorVersion + 'Engine');

const defaultBabylonReflection = String(vscode.workspace.getConfiguration('glTF.Babylon')
.get('environment')).replace('{extensionRootPath}', extensionRootPath.replace(/\/$/, ''));
.get('environment')).replace('{extensionRootPath}', this.extensionRootPath);
const defaultThreeReflection = String(vscode.workspace.getConfiguration('glTF.Three')
.get('environment')).replace('{extensionRootPath}', extensionRootPath.replace(/\/$/, ''));
const dracoLoaderPath = extensionRootPath + 'engines/Draco/draco_decoder.js';
.get('environment')).replace('{extensionRootPath}', this.extensionRootPath);
const dracoLoaderPath = this.extensionRootPath + 'engines/Draco/draco_decoder.js';

// These strings are available in JavaScript by looking up the ID. They provide the extension's root
// path (needed for locating additional assets), various settings, and the glTF name and contents.
// Some engines can display "live" glTF contents, others must load from the glTF path and filename.
// The path name is needed for glTF files that include external resources.
const strings = [
{ id: 'extensionRootPath', text: this.toUrl(extensionRootPath) },
{ id: 'extensionRootPath', text: this.extensionRootPath },
{ id: 'defaultEngine', text: defaultEngine },
{ id: 'defaultBabylonReflection', text: this.toUrl(defaultBabylonReflection) },
{ id: 'defaultThreeReflection', text: this.toUrl(defaultThreeReflection) },
{ id: 'dracoLoaderPath', text: this.toUrl(dracoLoaderPath) },
{ id: 'defaultBabylonReflection', text: defaultBabylonReflection },
{ id: 'defaultThreeReflection', text: defaultThreeReflection },
{ id: 'dracoLoaderPath', text: dracoLoaderPath },
{ id: 'babylonHtml', text: this._babylonHtml },
{ id: 'cesiumHtml', text: this._cesiumHtml },
{ id: 'threeHtml', text: this._threeHtml },
Expand All @@ -112,6 +124,7 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo
const scripts = [
'engines/Cesium/Cesium.js',
'node_modules/babylonjs/babylon.max.js',
'node_modules/babylonjs/babylon.inspector.min.js',
'node_modules/babylonjs-loaders/babylonjs.loaders.js',
'engines/Three/three.min.js',
'engines/Three/DDSLoader.js',
Expand All @@ -125,19 +138,9 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo
];

// Note that with the file: protocol, we must manually specify the UTF-8 charset.
const content = this._mainHtml.replace('{assets}',
styles.map(s => `<link rel="stylesheet" href="${this.getFilePath(s)}"></link>\n`).join('') +
return this._mainHtml.replace('{assets}',
styles.map(s => `<link rel="stylesheet" href="${this.extensionRootPath + s}"></link>\n`).join('') +
strings.map(s => `<script id="${s.id}" type="text/plain">${s.text}</script>\n`).join('') +
scripts.map(s => `<script type="text/javascript" charset="UTF-8" src="${this.getFilePath(s)}"></script>\n`).join(''));

return content;
}

get onDidChange(): vscode.Event<vscode.Uri> {
return this._onDidChange.event;
}

public update(uri: vscode.Uri) {
this._onDidChange.fire(uri);
scripts.map(s => `<script type="text/javascript" charset="UTF-8" src="${this.extensionRootPath + s}"></script>\n`).join(''));
}
}
10 changes: 10 additions & 0 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GLTF2 } from "./glTF2";
import * as jsonMap from 'json-source-map';

export const ComponentTypeToBytesPerElement = {
5120: Int8Array.BYTES_PER_ELEMENT,
Expand Down Expand Up @@ -124,3 +125,12 @@ export function guessMimeType(filename: string): string {
}
return 'application/octet-stream';
}

export function toResourceUrl(path: string): string {
return `vscode-resource:${path.replace(/\\/g, '/')}`;
}

export function parseJsonMap(content: string) {
return jsonMap.parse(content) as { data: GLTF2.GLTF, pointers: Array<string> };
}

0 comments on commit e9ddd03

Please sign in to comment.