Getting Started with Plugins
Overview
This guide will help you get started with the Designer Plugin system by creating a simple “Hello World” plugin. This plugin will include HTML metatags for window size and a button that sends a POST request to the execute endpoint with a simple Python command.
Step 1: Create the Plugin Directory
First, within the project {project_root}/plugins directory, create a directory for your plugin. For this example, we’ll name it hello_world_plugin.
Step 2: Create the HTML File
Inside the hello_world_plugin directory, create an index.html file. This file will define the plugin’s user interface.
Plugin HTML files are accessible in any browser (locally or on another machine), supporting modern web standards and JavaScript frameworks. When launched inside Designer, they utilise the embedded Chromium browser engine (CEF) to render.
<!DOCTYPE html>
<html>
<head>
<title>Hello World Plugin</title>
<!-- Window size metadata -->
<meta name="disguise-plugin-window-size" content="512,512">
<meta name="disguise-plugin-window-min-size" content="200,200">
<meta name="disguise-plugin-window-resizable" content="true">
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
text-align: center;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<script>
async function executeCommand() {
const urlParams = new URLSearchParams(window.location.search);
const director = urlParams.get('director') || "localhost";
const apiUrl = `http://${director}/api/session/python/execute`;
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
script: `
layer = guisystem.track.addNewLayer(TextModule, trackTime(), 60, 'Text - Hello World')
layer.findSequence('text').sequence.setString(0, "Hello world")
`
})
});
const result = await response.json();
console.log('Response:', result);
} catch (error) {
console.error('Error:', error);
}
}
</script>
<div class="container">
<h1>Hello World Plugin</h1>
<button onclick="executeCommand()">Add Hello World layer</button>
</div>
</body>
</html>
Step 3: Understanding the Code
Let’s break down the key components of this plugin:
Window Size Metadata
<meta name="disguise-plugin-window-size" content="512,512">
<meta name="disguise-plugin-window-min-size" content="200,200">
<meta name="disguise-plugin-window-resizable" content="true">
These meta tags control the plugin window’s size and behaviour in Designer:
window-size: Sets the initial window size to 512x512 pixelswindow-min-size: Sets the minimum window size to 200x200 pixelswindow-resizable: Allows the window to be resized by the user
The Execute Endpoint
The plugin communicates with Designer through the /api/session/python/execute endpoint. The request body contains:
{
"script": "Python code here"
}
The Python Command
The example uses a simple command from the useful snippets to add a text layer at the current track time, and set its first keyframe to the string Hello World:
layer = guisystem.track.addNewLayer(TextModule, trackTime(), 60, 'Text - Hello World')
layer.findSequence('text').sequence.setString(0, "Hello world")
This code demonstrates working with the Track and Layer system. For a comprehensive guide on working with tracks, layers, and sequences, see the Track & Sequencing Guide.
Step 4: Testing the Plugin
- Start Designer.
- Navigate to the Plugins menu.
- Select your
hello_world_plugindirectory - Click the Add Hello World Layer button to test the functionality.
Next Steps
This is a basic example that demonstrates the core concepts of Designer plugins. You can expand this by:
- Adding more complex UI elements.
- Implementing multiple Python commands.
- Adding error handling and user feedback.
- Creating more sophisticated interactions with Designer’s API
For more advanced examples and API documentation, check out the Python Execution API and Useful Python Snippets pages.
Using Vue.js for Better Integration
While the basic HTML/JavaScript approach works well for simple plugins, using a modern framework like Vue.js can significantly improve your development experience. Disguise provides several official libraries to help you build more sophisticated plugins:
Official Libraries
-
- A TypeScript library and Vite plugin that transforms Python code into JavaScript modules.
- Provides TypeScript definitions for better type safety and IDE support.
- Makes it easier to integrate Designer Python modules into your JavaScript/TypeScript projects.
-
- A Vue.js composable for real-time communication with Designer.
- Enables live read & write of values into a Designer session.
- Includes a helper component for easy integration.
-
- A collection of worked examples showing how to use Vue.js with Designer.
- Demonstrates integration with APIs, Python, and LiveUpdate websocket.
- Perfect for learning best practices and common patterns.
Getting Started with Vue.js
- Create a new Vue.js project using Vite:
npm create vite@latest my-designer-plugin -- --template vue
cd my-designer-plugin
npm install
- Install the required dependencies:
# Install the designer-pythonapi directly from GitHub
npm install disguise-one/designer-pythonapi
# Install the Vue.js Live Update composable
npm install disguise-one/vue-liveupdate
- Configure your
vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { designerPythonLoader } from '@disguise-one/designer-pythonapi/vite-loader'
export default defineConfig({
plugins: [
vue(),
designerPythonLoader()
],
})
- Add the required meta tags to your
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Designer Plugin meta tags -->
<meta name="disguise-plugin-window-size" content="512,512">
<meta name="disguise-plugin-window-min-size" content="200,200">
<meta name="disguise-plugin-window-resizable" content="true">
<title>Designer Plugin</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
- Create a Python file for your commands (e.g.,
src/hello_world.py):
__all__ = ["addTextLayer"]
def addTextLayer():
layer = guisystem.track.addNewLayer(TextModule, LocalState.localState().currentTransport.player.tCurrent, 60, 'Text')
layer.findSequence('text').sequence.setString(0, "Hello world")
return True
Note : This Python is slightly different to the HTML example above where the Python is run with the execute endpoint giving access to the utility method trackTime() which is not available when registering modules.
- Create
src/components/TextLayerControl.vuewhich will handle Python execution:
<template>
<div class="text-layer-section">
<h2>Text Layer Control</h2>
<button @click="handleAddTextLayer">Add Hello World</button>
</div>
</template>
<script setup>
import { hello_world } from '../hello_world.py'
// Extract the director endpoint from the URL query parameters
const urlParams = new URLSearchParams(window.location.search)
const directorEndpoint = urlParams.get('director') || 'localhost:80' // Fallback for development
// Initialize the Python bindings composable
const module = hello_world(directorEndpoint)
// Feedback about registration
module.registration.then((reg) => {
console.log('Hello World module registered', reg)
}).catch((error) => {
console.error('Error registering Hello World module:', error)
})
async function handleAddTextLayer() {
try {
await module.addTextLayer()
} catch (error) {
console.error('Error:', error)
}
}
</script>
<style scoped>
.text-layer-section {
margin: 1rem;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
}
</style>
- Create
src/components/PlayheadDisplay.vuewhich will handle real-time updates from Designer:
<template>
<div class="playhead-section">
<h2>Playhead Position</h2>
<div class="playhead-value">{{ player_tRender !== undefined ? player_tRender.toFixed(2) : '0.00' }}s</div>
</div>
</template>
<script setup>
// Define the liveUpdate prop
const props = defineProps({
liveUpdate: {
type: Object,
required: true
}
})
// Auto-subscribe to playhead position
const { player_tRender } = props.liveUpdate.autoSubscribe('transportManager:default', ['object.player.tRender'])
</script>
<style scoped>
.playhead-section {
margin: 1rem;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.playhead-value {
font-size: 1.2rem;
font-weight: bold;
}
</style>
- Finally, update your
src/App.vueto use these components:
<template>
<div class="app">
<h1>Designer Plugin</h1>
<!-- Text Layer Control Component -->
<TextLayerControl />
<!-- Playhead Display Component -->
<PlayheadDisplay :liveUpdate="liveUpdate" />
<!-- Display connection status -->
<LiveUpdateOverlay :liveUpdate="liveUpdate" />
</div>
</template>
<script setup>
import { useLiveUpdate, LiveUpdateOverlay } from '@disguise-one/vue-liveupdate'
import TextLayerControl from './components/TextLayerControl.vue'
import PlayheadDisplay from './components/PlayheadDisplay.vue'
// Extract the director endpoint from the URL query parameters
const urlParams = new URLSearchParams(window.location.search)
const directorEndpoint = urlParams.get('director') || 'localhost:80' // Fallback for development
// Initialize the live update composable for the overlay
const liveUpdate = useLiveUpdate(directorEndpoint)
</script>
<style>
.app {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}
</style>
This example demonstrates several key features of the Python Execution and Live Update system:
-
Python registration and execution: This involves registering the plugin with the Designer and executing Python scripts through the provided API endpoints. The example demonstrates how we use the composable supplied by Disguise to send a POST request to the
/api/session/python/executeendpoint with a Python script to add a text layer. -
Real-time Value Subscription: Using the
autoSubscribefunction to listen to the playhead position. TheautoSubscribefunction automatically handles property name mapping, soobject.player.tRenderbecomesplayer_tRenderin the returned object. -
Automatic Resource Management: The
autoSubscribefunction handles subscription cleanup automatically when the component is unmounted, making the code cleaner and less error-prone. -
Connection Status Display: The
LiveUpdateOverlaycomponent provides visual feedback about the WebSocket connection status and allows users to reconnect if needed. -
Director Endpoint Configuration: The example shows how to extract the director endpoint from URL parameters, which is the recommended way to configure the connection.
The Live Update system is particularly useful for:
- Real-time monitoring of Designer state.
- Creating responsive UIs that reflect current Designer values.
- Building control panels that need immediate feedback.
- Implementing visualisations that depend on the current Designer state.
For guidance while exploring Live Update, check out the Live Update plugin repository.
These tools provide a more robust foundation for building Designer plugins, with better type safety, real-time updates, and a more maintainable codebase. Check out the Useful Links page for more resources and examples.