Common Tasks
Below are some common tasks you might need to do while creating your plugins. These are not necessarily the only way to do them, but is provided to help demonstrate various plugin capabilities.
Many tasks can be accomplished with fewer lines of code using the Utils Modules introduced in Lens Studio 5.3. Check out the Utils Modules section for more information.
Importing or creating a texture of a material
You can import assets to the opened project in several ways, including from the user's file system, directly within the plugin (by using a template asset), or by utilizing assets that are built into Lens Studio itself.
Load Native Assets
There are two primary asset types: Native Assets:
- These assets are generated exclusively by Lens Studio through the createNativeAsset of the assetManager object.
- This process results in the creation of exactly one asset.
Imported Assets:
- You can import files in any format that Lens Studio supports (e.g., .png, .fbx) using the importExternalFile[Async] method.
- Depending on the file, Lens Studio may generate anywhere from zero to multiple assets upon import.
const textureType = 'ScreenTexture';
const newTextureName = 'Name For The Texture';
//parameter: asset type, new asset name, new asset directory (relative)
const screenTextureAsset = assetManager.createNativeAsset(
textureType,
newTextureName,
''
);
or you could use the Utils module (available from 5.3) to create a texture with the findOrCreateNativeAsset
function from the LensStudio:AssetUtils.js
module:
// import the AssetUtils module
import * as AssetUtils from 'LensStudio:AssetUtils.js';
const screenTextureAsset = findOrCreateNativeAsset(
/* textureType */ 'ScreenTexture',
/* assetManager */ assetManager,
/* newTextureName */ 'Screen Texture',
/* destination */ ''
);
Some Native Assets include:
- bodyDepthTexture
- bodyNormalsTexture
- depthTexture
- faceCropTexture
- faceTexture
- textTexture
- screenCropTexture
- screenTexture
- imagePickerTexture
- faceImagePickerTexture
- segmentationTexture
- objectTrackingTexture
- animationCurve
- animationLayer
- animationMixerLayer
- worldmesh
- Facemesh
- ...
Importing an image, svg or shader graph
You may place other files inside plugin directories, such as images, SVG files, ss_graphs (shader graphs) or others.
For example, here is how the gLTF example preset accesses the scene.gltf
file which is placed at the root directory of the plugin in the create(destination)
function using import.meta.resolve
function and the assetManager
component.
create(destination) {
console.log("Create Gltf preset")
// Getting the Lens Studio model component
// Assume we have saved the reference to pluginSystem in the constructor
const model = this.pluginSystem.findInterface(Editor.Model.IModel);
// Getting the Lens Studio asset manager component
const assetManager = model.project.assetManager
// Importing the file
const gltfFileName = 'scene.gltf'
const absoluteGltfPath = new Editor.Path(import.meta.url).appended(gltfFileName)
const importResult = assetManager.importExternalFile(absoluteGltfPath, destination, Editor.Model.ResultType.Packed)
// Use the imported assets
importResult.primary.sceneObjects.forEach((object) => {
const transform = object.localTransform
transform.scale = new vec3(1, 1, 1)
object.localTransform = transform
})
// Returning the asset that was imported
return importResult.primary
}
Creating a material
Another common request is to create a new material with a shader pass in scripting. This task can essentially be broken down into two steps: first, importing the shader file, and second, creating a new material (a native asset). Once these are prepared, the final step is to link them together. Here's a simplified example:
const absPath = new Editor.Path(import.meta.resolve('flat.ss_graph'));
//import the graph
const importResult = assetManager.importExternalFile(
absPath,
'',
Editor.Model.ResultType.Packed
);
//import a material which is a native asset
const material = this.context.assetManager.createNativeAsset(
'Material',
'MyNewMaterial',
'' // root of Asset Manager
);
const passInfo = material.addPass(importResult.primary);
//use the passInfo and materials to do more
Or, you could use the Utils module (available from 5.3) to create a material with a shader pass with a single line of code:
// import the AssetUtils module
import * as AssetUtils from 'LensStudio:AssetUtils.js';
// assume you already have assetManager created, and you want to create a new material with the unlit shader graph
const { material, shaderGraphAsset, passInfo } =
await AssetUtils.createMaterialFromGraph(
assetManager,
AssetUtils.ShaderGraphType.ShaderGraphUnlit, // there are other types available
'', // materialDir
'MyNewMaterial' // materialName
);
// do more with the returned material, shaderGraphAsset, and passInfo
Note the use of an empty Editor.path, signifying that the new asset will be added to the root of the Asset browser.
Creating a text or json file
Sometimes, your task might involve writing data or creating a new file. In such cases, the steps are as follows:
- Create a Temporary Working Directory: It's important to note that TempDir will automatically be deleted when no strong references are left.
- Create a File Within This Directory: This step involves actually writing or generating the data you need.
- Import the File into Studio
import * as fs from 'LensStudio:FileSystem';
function createTempFile(tempDir, fileName, fileContent) {
const targetFilePath = tempDir.path.appended(fileName);
fs.writeFile(targetFilePath, fileContent);
return targetFilePath;
}
function importFileWithContent(fileName, fileContent) {
const tempDir = fs.TempDir.create();
const targetFilePath = this.createTempFile(tempDir, fileName, fileContent);
//assume we have assetManager created
assetManager.importExternalFile(
targetFilePath,
'',
Editor.Model.ResultType.Packed
);
}
Other scripts can also be imported into a plugin script through standard ECMA import syntax. For example, you may want to have a Utils folder
in your plugin’s directory containing a Utils.js
. This script could define commonly used functions, like createScreenTransformObject()
. Then, the function could be imported into the your script as follow:
import { createScreenTransformObject } from './Utils.js';
To use the FileSystem API, just like other modules, you are required to add filesystem into your permission object array in the module.json file. Checkout Permissions section for more information.
Creating a HTTP network call
In some cases you might want to do a network call.
import * as Network from 'LensStudio:Network';
/**
* Create the Request
*/
var request = Network.RemoteServiceHttpRequest.create();
request.url = '[SOME_ENDPOINT]';
request.method = Network.RemoteServiceHttpRequest.HttpRequestMethod.Post;
request.contentType = 'application/json';
request.body = JSON.stringify({
foo: 'bar',
});
request.headers = {
hello: 'world',
};
/**
* Perform the HTTP Request
*/
Network.performHttpRequest(request, function (response) {
if (response.statusCode !== 200) {
console.log('Error: ' + response.statusCode + ' ' + response.body);
return;
}
console.log(response.statusCode);
const responseBody = JSON.parse(response.body);
console.log(responseBody);
});
Creating an Authorized Network Call
You can make authorized calls to Snap services by attaching a MyLenses token. Use performAuthorizedHttpRequest instead of performHttpRequest to include the OAuth2 token in the Authorization header.
Authorized requests are limited to Snap services and require user authorization in MyLenses.
To authorize users in MyLenses from the plugin, an authorization component was introduced. Example of how to implement login button:
import * as Ui from 'LensStudio:Ui';
// Assume we have saved the reference to pluginSystem in the constructor
const authorization = this.pluginSystem.findInterface(Editor.IAuthorization);
console.log("Current auth status: " + authComponent.isAuthorized);
const loginButton = Ui.PushButton.create(parent);
loginButton.text = "Login";
loginButton.primary = true;
this.connections.push(loginButton.onClick.connect(() => {
authComponent.authorize();
}));
this.connections.push(authComponent.onAuthorizationChange.connect((status) => {
// handle authorization status update here
});
Create a TCP server and connection
You can create a localhost TCP server for tasks like OAuth or inter-plugin communication. Check out the TcpServerManager example script as the starting point.
Asset and Object Picker
You can request specific objects from the user's Scene Hierarchy or Asset Browser.
// Get access to the project's model component
// Assume we have saved the reference to pluginSystem in the constructor
const model = this.pluginSystem.findInterface(Editor.Model.IModel);
const project = model.project;
// Get access to the picker component
const refPicker = this.pluginSystem.findInterface(Editor.IEntityPicker);
// Open a picker
const type = 'SceneObject'; // or Asset, etc.
refPicker.requestPicker(type, (pickedEntity) => {
console.log(pickedEntity);
});
// List assets in project
const assetManager = project.assetManager;
assetManager.assets.forEach((asset) => {
console.log(asset.id);
});
// List objects in project's scene
const scene = project.scene;
scene.sceneObjects.forEach((obj) => {
console.log(obj.id);
});
For more information, refer to the AssetMenuItem documentation on GitHub.
refPicker.requestPicker(type, async (entity) => {
// List assets in project
const assetManager = project.assetManager;
assetManager.assets.forEach((asset) => {
if (asset.id.toString() === entity.id.toString()) {
console.log('Found Match: ' + asset.name);
}
});
});