Skip to main content

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

Core Concepts Used

This example brings together several Mimeeq APIs:

APIPurpose
observers.optionSets.blocksGet available option blocks and their options
observers.product.mainProductDataGet product metadata (name)
observers.product.skuGet current SKU for file naming
observers.product.loaderDetect when scene finishes loading
actions.markOptionByBlockNameAndOptionCodeSelect options programmatically
utils.takeScreenshotCapture 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 actions
  • name - Display name
  • options - 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();
}
tip

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:

ParameterDescription
extensionOutput format. PNG for transparency, JPEG/WebP for smaller files
sizeImage width in pixels. Height calculated from aspect ratio
backgroundColorHex color. Important for JPEG (no transparency)
customDimensionsOverride automatic dimension calculation
withAutozoomZoom to fit entire product in frame
withCameraResetReturn to default camera position
customCameraPositionJSON 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
);
warning

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
});
}
Internal API

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

  1. Add progress feedback - For large option sets, show progress to the user
  2. Implement cancellation - Allow users to stop long-running operations
  3. Calculate ETA - Track time per screenshot to estimate remaining time
  4. Preview current shot - Show each screenshot as it's captured
  5. 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


Complete Example

Below is the full implementation with UI controls for block selection, image settings, camera angles, progress tracking, and preview.