Batch Screenshot Generator
Generate product screenshots for all options in one or more option blocks. This example demonstrates how to combine Mimeeq's observers, actions, and utilities to automate image generation for catalogs, e-commerce listings, or marketing materials.
What You'll Build
A tool that:
- Iterates through all options in selected blocks
- Captures screenshots for each configuration
- Supports multiple camera angles per option
- Packages everything into a downloadable ZIP file
Use Cases
- Product catalogs - Generate consistent images for all product variants
- E-commerce listings - Automate thumbnail generation for online stores
- Marketing materials - Create assets showing all available options
- Documentation - Visual reference for all product configurations
Prerequisites
- A Mimeeq product with configured option blocks
- Basic understanding of Observers and Actions
- Familiarity with takeScreenshot utility
Core Concepts Used
This example brings together several Mimeeq APIs:
| API | Purpose |
|---|---|
observers.optionSets.blocks | Get available option blocks and their options |
observers.product.mainProductData | Get product metadata (name) |
observers.product.sku | Get current SKU for file naming |
observers.product.loader | Detect when scene finishes loading |
actions.markOptionByBlockNameAndOptionCode | Select options programmatically |
utils.takeScreenshot | Capture the current 3D view |
Step-by-Step Implementation
1. Basic Setup
Start with the embed component and required libraries:
<!-- Configurator -->
<mmq-embed short-code="YOUR_SHORT_CODE" template="YOUR_TEMPLATE"></mmq-embed>
<!-- Required libraries -->
<script src="https://cdn.mimeeq.com/read_models/embed/app-embed.js" async></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
JSZip packages multiple images into a single download. FileSaver triggers the browser download.
Replace YOUR_SHORT_CODE and YOUR_TEMPLATE with your product's embed code values.
2. Initialize and Track Product Data
Wait for the configurator to load, then subscribe to product data:
let blocks = [];
let productName = '';
document.addEventListener('mimeeq-app-loaded', () => {
// Track available option blocks
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
if (newValue) {
blocks = newValue;
populateBlockSelector(blocks);
}
});
// Get product name for ZIP filename
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue) {
productName = newValue.metadata.name;
}
});
});
The blocks array contains all option blocks with their options. Each block has:
blockName- Unique identifier used with actionsname- Display nameoptions- Array of available options
See Observers Reference for complete block structure.
3. Wait for Scene to Load
After selecting an option, the 3D scene needs time to update. Create a helper that waits for loading to complete:
async function waitForSceneReady() {
let sub;
await new Promise(resolve => {
sub = window.mimeeqApp.observers.product.loader.subscribe(({ newValue }) => {
const isLoading = newValue?.isLoadingScene3d ?? newValue;
if (!isLoading) {
setTimeout(resolve, 100); // Small buffer for render completion
}
});
});
sub.unsubscribe();
}
Always unsubscribe after the promise resolves. Declaring sub outside the Promise ensures it's assigned before the callback might need it.
4. Get Current SKU
Retrieve the SKU for the current configuration to use in filenames:
async function getCurrentSku() {
let sub;
const result = await new Promise(resolve => {
sub = window.mimeeqApp.observers.product.sku.subscribe(({ newValue }) => {
resolve(newValue || '');
});
});
sub.unsubscribe();
return result;
}
5. Select Options Programmatically
Use markOptionByBlockNameAndOptionCode to change the configuration:
await window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
block.blockName, // e.g., "Color"
option.code // e.g., "RED-001"
);
This method handles all standard option types. For special widgets (sliders, text inputs, color pickers), see Programmatic Configuration Control.
6. Take Screenshots
The takeScreenshot utility captures the current 3D view:
const imageData = await window.mimeeqApp.utils.takeScreenshot(
'png', // Format: 'png', 'jpeg', 'webp'
2048, // Size in pixels
'#ffffff', // Background color
{ width: 2048, height: 2048 }, // Dimensions
true, // Auto-zoom to fit product
false, // Reset camera position
'' // Custom camera position (JSON string)
);
Parameters explained:
| Parameter | Description |
|---|---|
extension | Output format. PNG for transparency, JPEG/WebP for smaller files |
size | Image width in pixels. Height calculated from aspect ratio |
backgroundColor | Hex color. Important for JPEG (no transparency) |
customDimensions | Override automatic dimension calculation |
withAutozoom | Zoom to fit entire product in frame |
withCameraReset | Return to default camera position |
customCameraPosition | JSON string with camera settings |
See Screenshot Generation for full documentation.
7. Custom Camera Angles
For multi-angle shots, pass a JSON string with camera position data:
const cameraPosition = JSON.stringify({
position: { x: -1.5, y: 1.7, z: -5.2 },
target: { x: 0, y: 0.6, z: 0 },
alpha: 4.4,
beta: 1.3,
radius: 5
});
const imageData = await window.mimeeqApp.utils.takeScreenshot(
'png',
2048,
'#ffffff',
{ width: 2048, height: 2048 },
false, // Disable auto-zoom when using custom camera
true, // Enable camera reset to apply custom position
cameraPosition
);
When using custom camera positions, set withAutozoom to false - otherwise auto-zoom overrides your camera settings.
Capturing Current Camera Position
The complete example includes a "Capture" button that reads the current camera position. This uses internal Babylon.js state which is not part of the public API:
function captureCameraPosition() {
const babylonState = window.babylonState?.current;
if (!babylonState?.camera) return null;
const camera = babylonState.camera;
return JSON.stringify({
position: { x: camera.position.x, y: camera.position.y, z: camera.position.z },
target: { x: camera.target.x, y: camera.target.y, z: camera.target.z },
alpha: camera.alpha,
beta: camera.beta,
radius: camera.radius
});
}
window.babylonState is an internal implementation detail and may change without notice. Use it for development/tooling purposes, but don't rely on it in production integrations. Consider pre-defining camera angles in your code instead.
8. Package and Download
Collect all images into a ZIP file:
const zip = new JSZip();
// Add each image
const base64Data = imageData.split('base64,')[1];
zip.file(`${filename}.png`, base64Data, { base64: true });
// Generate and download
const content = await zip.generateAsync({ type: 'blob' });
saveAs(content, `${productName}.zip`);
9. File Naming Strategy
Create unique filenames using available data:
function generateFileName(pattern, context) {
return pattern
.replace(/{sku}/g, context.sku || 'unknown')
.replace(/{blockName}/g, context.blockName || '')
.replace(/{optionCode}/g, context.optionCode || '')
.replace(/{optionName}/g, context.optionName || '')
.replace(/{index}/g, String(context.index))
.replace(/{angle}/g, context.totalAngles > 1 ? `-angle${context.angleIndex + 1}` : '');
}
// Usage
const fileName = generateFileName('{sku}-{index}{angle}', {
sku: 'CHAIR-RED-001',
blockName: 'Color',
optionCode: 'RED',
optionName: 'Cherry Red',
index: 0,
angleIndex: 0,
totalAngles: 3
});
// Result: "CHAIR-RED-001-0-angle1"
Recommended pattern: {sku}-{index}{angle} ensures unique filenames even when SKU doesn't change between options.
Main Generation Loop
Putting it all together:
async function generate(settings) {
const zip = new JSZip();
let globalIndex = 0;
for (const block of settings.blocksToProcess) {
for (const option of block.options) {
// Select the option
await window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
block.blockName,
option.code
);
// Wait for scene update
await waitForSceneReady();
// Get current SKU
const sku = await getCurrentSku();
// Take screenshot for each camera angle
for (let angleIndex = 0; angleIndex < settings.cameraAngles.length; angleIndex++) {
const cameraPosition = settings.cameraAngles[angleIndex];
const hasCustomCamera = !!cameraPosition;
const imageData = await window.mimeeqApp.utils.takeScreenshot(
settings.format,
settings.size,
settings.bgColor,
{ width: settings.size, height: settings.size },
!hasCustomCamera, // autoZoom
hasCustomCamera, // resetCamera
cameraPosition || ''
);
if (imageData) {
const fileName = generateFileName(settings.namingPattern, {
sku,
blockName: block.blockName,
optionCode: option.code,
optionName: option.name,
index: globalIndex,
angleIndex,
totalAngles: settings.cameraAngles.length,
});
const base64Data = imageData.split('base64,')[1];
zip.file(`${fileName}.${settings.format}`, base64Data, { base64: true });
}
globalIndex++;
}
}
}
// Download ZIP
const content = await zip.generateAsync({ type: 'blob' });
saveAs(content, `${productName}.zip`);
}
Best Practices
- Add progress feedback - For large option sets, show progress to the user
- Implement cancellation - Allow users to stop long-running operations
- Calculate ETA - Track time per screenshot to estimate remaining time
- Preview current shot - Show each screenshot as it's captured
- Handle errors gracefully - Wrap the loop in try/catch and continue on failures
Performance Considerations
- Size presets - 4K (4096px) images are slower to generate than 1K (1024px)
- Format choice - PNG is lossless but larger; JPEG/WebP compress better
- Scene complexity - Products with many materials take longer to render
- Camera angles - Each angle multiplies total generation time
See Also
- Programmatic Configuration Control - All methods for changing configurations
- Screenshot Generation - Full takeScreenshot documentation
- Observers Reference - Complete list of available data streams
- Headless Configurator Guide - Building custom UIs
Complete Example
Below is the full implementation with UI controls for block selection, image settings, camera angles, progress tracking, and preview.