JavaScript Documentation

Complete setup, data, customization and API reference for Iza 3D Product Designer & Product Builder Customizer.

Getting Started

Iza 3D Product Designer & Product Builder Customizer combines a Fabric.js design canvas with a Three.js model viewport. Customers can edit text, images, shapes and artwork in 2D or directly on a bound 3D surface. Both views share the same design layers, permissions, colors and history. The editor also includes product libraries, rulers, saved designs, preview downloads and print-ready 2D production exports.

The editor is configured from JavaScript while products and reusable content are described with JSON. Product assets are loaded relative to the product JSON file, allowing every product to live in its own portable folder.

Watch how to create a product from a GLB model, configure its views and adjust the printing boxes in Iza 3D Product Designer & Product Builder Customizer.

Important: run the project through an HTTP or HTTPS server. Loading the page with file:// will prevent JSON, fonts and image assets from loading because browsers restrict local fetch requests.

Requirements

The 3D viewport requires a modern browser with WebGL2 available and a self-contained, uncompressed GLB model with usable UV coordinates. Separate mesh parts are recommended for independently editable surfaces. Use model and image assets you have permission to distribute.

  • A modern browser with Canvas, Promises, Fetch and the FontFace API.
  • An HTTP/HTTPS web server. For development, run npm run dev.
  • Correct CORS headers when JSON, fonts or images are hosted on another domain.
  • Optimized PNG, JPG and SVG assets. Use JPG for photos, PNG for transparency and SVG for scalable artwork. Keep files compressed but large enough for sharp production output, oversized images and complex SVGs increase memory usage and slow loading and exports.

Choosing the right image format

The best format depends on whether the asset needs editable colors, transparent areas, photographic detail or unlimited scaling. Selecting the appropriate format keeps the editor responsive and produces cleaner preview and production files.

Format Recommended use Color customization
SVG Logos, icons, shapes, line art and product sections that must remain sharp at every size. Best option. The designer can expose a single fill or multiple detected fill groups for independent editing.
PNG Transparent photographs, overlays, shadows, textures and raster artwork that cannot be represented accurately as vectors. Supported through image tinting. Existing dark details and shading can be preserved, but results depend on the source pixels. A fully white PNG behaves like a clean color mask, so the selected color is reproduced accurately, similarly to recoloring a single-fill SVG.
JPG Photographs and full rectangular background images that do not require transparency. Not recommended. Because JPG has no transparent regions, applying a fill cannot isolate the visible subject reliably.

SVG recommendations

  • Use actual vector paths with explicit fill colors. Avoid embedding PNG or JPG files inside the SVG.
  • Use fill for a single-color SVG and svgFill when individual SVG fill groups need separate controls.
  • Simplify excessive path points and remove unused metadata before publishing the asset.

PNG and JPG recommendations

  • Use PNG when transparent pixels or soft alpha edges are required, use JPG for opaque photographic content.
  • Size raster files for the largest expected production output. Enlarging a small source cannot recreate missing image detail.
  • Avoid unnecessarily large pixel dimensions. Large textures consume more browser memory and can slow loading, editing and export.
Practical rule: choose SVG for artwork whose colors customers can change, PNG for transparent raster content, and JPG for ordinary photographs. Always test the final asset with the intended color and production-export settings.

3D Product Workflow

Use Create Product to turn your GLB model into a ready-to-configure product. It generates the views, mesh bindings, UV silhouettes, Base layers, fitted cameras and transparent preview thumbnails together. You do not need to export and connect a silhouette manually for every mesh.

1. Create a product from GLB

Prepare your model: Create your model at its real-world size, use meters as the unit (70 cm = 0.7 m), and apply its scale before exporting the GLB.

Wait for the designer to finish loading, then open File > Create Product and choose your model. This generates a downloadable ZIP without replacing the open product or adding sample artwork.

The front-facing starting part stays first. Related names such as body_front and body_back, or sleeve_left_outside and sleeve_right_outside, are placed next to each other. Name families recognize left/right, front/back/rear and top/bottom markers. Build views are grouped by group, then option, with parts in natural name order inside each option. The starting part stays first even if its name would sort later. Ordering does not change the selected Build option. Mesh names and geometry stay unchanged. The original GLB, materials, colors and textures are preserved, although scene lighting can affect their appearance.

Create Product uses the same name families for ordering, base naming and color synchronization. Ordinary pairs such as body_front and body_back share one Base title and an Active Build color and material link. Left/right, front/back/rear and top/bottom markers are ignored when identifying a family. Build families use the group and part role, keeping distinct roles such as stones and setting separate. Matching roles across options share a Base title, but only parts in the active option synchronize. Inactive options retain their own appearance. The UV surface binding is updated to match each Base title. Unrelated names are not guessed.

Base layers use simple names such as Base, Base1 and Base2, assigned by family in generated view order. Matching parts share a name, while unrelated parts receive different names. These names do not rename the GLB meshes. The numbering is not tied to a product type, and existing artwork names are skipped to avoid collisions. Each view's model3D.mapping.surface is updated to its base layer title.

Families containing multiple parts receive syncColorType: 'activeBuild'. Independent parts use none. Initial colors are preserved. Inactive Build options keep their own colors and materials, and switching options changes which parts participate. Artwork and geometry are not copied. Set synchronization to None to keep a part independent. Existing products and saved designs are not changed.

  1. Export a self-contained, uncompressed .glb 2.0 file with static triangle meshes. Give the parts unique, readable names and usable UV coordinates wherever customers will add artwork.
  2. Wait for the designer to be ready, open Create Product and choose the file. Ordinary meshes become views. Meshes named group__option__part become interchangeable Build options, with a default-choice review before generation and one view per part. A suitable front-facing part comes first using the model's positive Z direction.
  3. Download the generated ZIP. It contains product.json, the original model.glb, mesh-named SVG silhouettes and transparent PNG view thumbnails. Model colors and textures are retained, including white and near-white parts. No starter color is forced onto the model.

The generated JSON includes material presetSettings, view materials, texture settings and optional-feature defaults. Cloth physics, adaptive texture refinement, the floor shadow and the generated reflection environment start disabled. The scene background, lighting and selection styling follow the current editor. UI labels and action/module layouts still belong to the embedding page.

Each generated view starts with material: { "preset": "original", "adjustments": {} }, without a per-view enable switch. Both root selectionOutline groups start with thickness 1. Texture resolution starts at 2048, interactive resolution at 1024 and anisotropy at 8. Adjustable presets include their explicit starting values, original has no presetSettings entry.

Product export preserves both root selectionOutline groups, including fractional thickness, and the configured texture resolution, anisotropy, refinement delay and adaptive quality settings. It omits the unused empty presetSettings.original entry while keeping Original available in the material list. Export does not replace an existing product's settings with Create Product defaults.

UVs must already exist in the model. Create Product generates silhouettes from those UVs, it does not unwrap the model or repair overlapping or distorted UVs. Ordinary parts without usable UV silhouettes start with options.disableContentEditing: true. Named builder parts require usable UVs and report an error if their silhouette cannot be generated.

2. Load the generated product

Extract the ZIP into a product folder and keep its files together. For example, place them in public/products/my-product/ when using the source project's Vite server. Add the generated JSON URL to your instance's product list:

// Inside your designer constructor settings.
product: [
    './products/my-product/product.json'
]

Serve the folder over HTTP/HTTPS. In Vite, files inside public/ are served without public/ in the URL. Model and image sources inside the JSON are relative to that JSON file. Create Product does not install the ZIP or add its URL to the Products module automatically.

3. Customize and export

  1. Open the generated product and review its views. The mesh bindings, Base shapes and initial cameras are already configured. Rename the views and choose which ones customers can select or edit using the view visibility and editing settings.
  2. Set the product colors and materials, then add any starting text, images or shapes. Check their placement in both 2D and 3D.
  3. Configure the real print dimensions and print areas using Production Export. Automatic fitting cannot infer the manufacturer's physical print specifications.
  4. Adjust a view's camera only if you want different framing. The camera-copy tool is optional, the generated cameras already provide starting positions.
  5. Use the product export to download the configured product and its assets. Extract that ZIP into your served product folder and test it again, including all views, previews and production exports.

That is the normal product-creation workflow. The manual silhouette tools below are for replacing an existing silhouette or setting up a view by hand, not additional steps required after Create Product.

Create Product button and validation settings

Default model limits: Create Product accepts a GLB up to 100 MB, with up to 128 mesh parts and 2,000,000 vertices. An object with multiple material primitives can count as more than one mesh part. These are validation limits, not a guarantee of smooth performance. They reject oversized models rather than simplifying them.

These limits apply to browser-based product creation. When uploading the GLB to WordPress, PHP and server upload limits may be lower. Raising createProduct.maxFileSizeMB does not raise the server's upload limit.

Add { label: 'Create Product', button: 'createProduct' } to one of the three actionBar.nav arrays. Configure validation separately at the constructor root:

// Constructor settings, separate from modelView.
createProduct: {
    maxFileSizeMB: 100,
    maxMeshes: 128,
    maxVertices: 2000000
}

Remove the action entry to hide the button. There is no createProduct.enabled flag, and hiding the button does not disable the API. The limits above are validation limits, not a promise that every model of that size will run smoothly on a phone.

Create Product optionDefaultMeaning
maxFileSizeMB100Maximum GLB size, calculated as this value multiplied by 1024 squared bytes.
maxMeshes128Limit on model primitives and loaded mesh parts. One authored object with several material primitives can produce multiple parts.
maxVertices2000000Vertex budget, with additional limits on accessor, index and node counts to reject excessively complex files.

Use positive finite values for the three limits. Invalid values fall back to their defaults. These validation settings belong to constructor createProduct, not the generated product's scene configuration.

Dialog labels can be overridden in createProduct: title, text, choose, working, complete, error, invalid, tooLarge, external, empty, compressed, unsupportedExtension, staticModel, invalidGeometry, meshNames, tooComplex, unnamed, builderReview, builderCreate and builderUVError. The completion message accepts {count} and {restrictedViews}. The builder UV error accepts {mesh}. Naming-validation errors identify the invalid group, option or part directly.

Model requirements: Draco, Meshopt and KTX2 compression are not supported. External buffers/textures, skinning, GPU instances, active morph deformation, invalid geometry and unsupported required extensions are rejected. Bake the model to static geometry and embed its resources before importing. PNG/JPEG textures are the safest portable choice. Silhouette generation reads existing UVs, it does not repair seams, overlaps or distorted UV layouts.

Dense and fur silhouettes: Create Product and Export UV Silhouette use the same automatic optimization. Ordinary UV boundaries stay exact. Complex overlapping UVs are merged into filled coverage and traced back into a compact SVG, instead of retaining a path for every strand. The coverage grid uses up to four samples per SVG unit, capped at 4096 on its longest side. Contours are simplified within that sampling precision. Extreme disconnected detail may use a coarser grid to keep the SVG manageable, so features below that resolution can merge. This does not change the model or its UVs, and it cannot repair an unsuitable UV unwrap. Use exportCurrentViewUVSilhouette({ optimize: false }) when exact, potentially very large boundaries are required.

Optional: set up a view manually

Skip this section when using the views generated by Create Product. Use it if you are building a product JSON by hand or need to reconnect a view after changing the model. For a replacement silhouette in an existing view, go directly to replace a silhouette.

How the generated silhouettes work: each SVG represents the UV layout of its model part and acts as the Base layer of the matching 2D view. Create Product configures this connection automatically. The examples below show the shirt's flat UV shapes. The model's materials and scene lights provide its 3D shading, so a separate shadow overlay is not required.

Fur interaction surfaces: The supplied heart keeps its full fur for rendering but uses the underlying UV-mapped skin for selection, dragging and control visibility. This is opt-in GLB node metadata: extras: { "fwdInteractionSurface": "uv-surface" }. Use it only for prepared meshes whose decorative fur triangles each have three identical root UV coordinates and whose remaining triangles form the complete editable surface. Other models are unchanged. Animated meshes, custom raycasting, material groups and partial draw ranges retain their original interaction geometry. This reduces interaction work, not the cost of drawing the visible fur.

Actual UV silhouette exported from the shirt front_outside mesh
Front: front_outside
Actual UV silhouette exported from the shirt back_outside mesh
Back: back_outside

Manual model and view binding

Use the included public/products/shirt/ folder as the working example. Keep model.glb, product.json, the SVG silhouettes and thumbnails together. In the Vite development server, public/products/shirt/product.json is requested as ./products/shirt/product.json. Model and image sources inside that JSON are relative to the JSON file, not to the documentation page.

  1. Prepare an optimized GLB with named, UV-mapped parts. For the supplied shirt, the Front view targets front_outside and the Back view targets back_outside. Get mesh names from the model author or inspect the GLB in a 3D tool.
  2. Add the root model3D block below. This loads one model for the product.
  3. Create one view per editable surface and add its own model3D.targets entry. This tells the exporter which mesh to use even before the Base SVG exists.
{
    "title": "T-Shirt",
    "thumbnail": "preview-front.png",
    "startAtView": "Front",
    "model3D": {
        "source": "model.glb",
        "backgroundColor": "#ffffff",
        "showSmallViewport": true,
        "smallViewportWidth": 224,
        "smallViewportHeight": 162,
        "previewResolution": 1600,
        "castShadow": false,
        "receiveShadow": false,
        "showFloor": true
    },
    "views": [
        {
            "title": "Front",
            "thumbnail": "preview-front.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "front_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "texture": {
                    "flipY": false,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                }
            },
            "elements": [],
            "options": {
                "canvas_width_mm": 784,
                "canvas_height_mm": 749,
                "view_width_mm": 784,
                "view_height_mm": 749
            }
        }
    ]
}

This is an incomplete manual setup for exporting a silhouette, not a replacement for the generated JSON. An empty elements list has no 2D product shape yet. Add the Base element shown below to complete this manual example. Create Product already includes that element and its placement, plus whole-millimeter view_width_mm and view_height_mm immediately after the canvas dimensions. For manual setup, add both physical dimensions once the mapped Base is in place. See physical view sizing.

Optional: export or replace a silhouette

Use this tool when a model's UV layout has changed or you are configuring a view manually. There is no need to repeat it for the silhouettes already created by Create Product. Enable the preparation buttons in your instance's constructor settings, such as the project's index.html:

modelView: {
    viewLayout: 'model', // 'model', 'canvas', 'model-canvas' or 'canvas-model'
    showDownloadPreviewButton: true,
    downloadPreviewText: 'Download preview',
    previewDownloadedText: 'Preview downloaded',
    previewDownloadErrorText: 'Unable to download preview',
    showExportUVSilhouetteButton: true,
    exportUVSilhouetteText: 'Export current UV silhouette',
    uvSilhouetteExportedText: 'UV silhouette exported',
    uvSilhouetteExportErrorText: 'Unable to export UV silhouette',
    uvSilhouetteFill: '#000000',
    uvSilhouetteSize: 1000,
    showCopyCameraButton: true
}
  1. Open the product and choose its Front view. Wait for the model and product assets to finish loading.
  2. Click Export current UV silhouette beside the viewport controls. The browser downloads base-front.svg. The exporter reads all targets sharing the current view's mapping surface. Camera rotation does not affect the result. Use separate views for independently mapped surfaces.
  3. Save the downloaded SVG in the product folder. The button does not write to the project folder or update product.json automatically.
  4. For an existing generated view, update its Base element's source instead of adding a second Base. For a manual view without a Base, add the example below to its elements array. Keep mapping.surface equal to the Base element's title.
  5. Position and uniformly scale the Base to fit the canvas. The mapping follows the Base transform automatically. Keep the exported SVG's viewBox and internal proportions intact.
  6. Repeat only for other views that need manual changes. Add a text or image and check its placement in both viewports.
{
    "title": "Base",
    "source": "base-front.svg",
    "type": "image",
    "globalParameters": {
        "fill": "#FFFFFF",
        "colors": ["#FFFFFF", "#CF4035", "#111111"],
        "originX": "center",
        "originY": "center",
        "showInLayers": true,
        "colorPicker": true,
        "locked": false,
        "visible": true,
        "opacity": 1,
        "canMove": false,
        "canRotate": false,
        "canResize": false,
        "canDuplicate": false,
        "canDelete": false,
        "canReorder": false,
        "canReset": true,
        "excludeFromExport": false,        "excludeFromProductionExport": true,
        "boundingBoxMode": "none"
    },
    "parameters2D": {
        "left": 392,
        "top": 371,
        "scaleX": 0.84,
        "scaleY": 0.84
    },
    "parameters3D": {}
}

The example placement values are from the included shirt, adjust them for your own canvas. The Base remains selectable for color editing, but customers cannot move or resize it. Its fill supplies the model's base color. The layer referenced by model3D.mapping.surface is automatically excluded from the painted texture, so no excludeFrom3DTexture flag is needed on it. This follows the mapping even when the layer is renamed. Other artwork is included unless explicitly excluded.

No manual contentBounds or camera aspect is needed for this UV workflow. The SVG comes from the model's authored UVs, and the Base supplies its canvas placement. Replacing it with an unrelated circle or changing its internal shape does not create a new UV unwrap. If the GLB's UV layout changes, regenerate the silhouette.

Optional: adjust a view camera

Create Product already fits and saves a camera for each generated view. To change its framing, enable modelView.showCopyCameraButton, rotate, pan and zoom the model to the desired view, then click Copy 3D camera position. Paste the copied position, target, up and fov into that view's model3D.camera block. This copies values to the clipboard, it does not edit the JSON file. Clipboard access may require HTTPS or localhost and a browser permission.

"camera": {
    "position": [0, 0.03, 1.45],
    "target": [0, 0.02, 0.009],
    "up": [0, 1, 0],
    "fov": 34,
    "cameraTransitionDuration": 0.65
}

These coordinates are the supplied shirt's Front example, not universal product settings. Copy the camera again for a different model. View changes use an orbital quaternion transition around the target instead of moving straight through the product. Direct orbit interaction interrupts the transition. The view camera only changes the presentation when using the default UV mapping, it does not change the design's UV placement.

3D window and preparation controls

modelView in the JavaScript constructor contains interface settings only: layout, buttons, colors for those buttons and UI labels. Put product scene settings in the JSON's root model3D, including texture quality, adaptive refinement, lights, navigation controls, materials, cloth physics, shadows and preview dimensions. Selection styling belongs in root selectionOutline, alongside model3D, with separate 2D and 3D groups. View-specific view.model3D defines target meshes, texture mapping, materials and cameras.

One place for each setting: scene values in constructor modelView are no longer read. Edit the product JSON instead. Missing JSON values use internal defaults, not values from the embedding page. Create Product writes the scene settings into its generated JSON, and product export preserves them.
Constructor modelView optionPurpose
viewLayoutPage fallback when the product does not specify model3D.viewLayout. 'model' starts with the 3D model and a small 2D preview. 'canvas' starts with the 2D canvas and a small 3D preview. Click the small window to swap them. 'canvas-model' shows 2D on the left and 3D on the right. 'model-canvas' reverses that order, with 3D on the left and 2D on the right. Both use equal-width editable panes. The aliases '2d-3d' and '3d-2d' are also supported. Existing 'split' settings keep 2D on the left. The chosen order is restored after returning from the compact layout. Opening or closing a module panel refits the model from its geometry and saved view camera, and balances the flat canvas between its natural fit and the model's fitted height. Your orbit angle, independent zoom levels and authored close-up framing are preserved. When the instance becomes narrower than 670 pixels and the modules move to the bottom, split mode switches to the main 3D model with a small 2D preview. Widening the instance restores split mode. Split panes always stay side by side, even when an open module panel narrows the editing area. Selection tools follow the window you click, while both windows share the design but keep independent zoom levels. The zoom slider follows the active window. Product preview dimensions and showSmallViewport apply to the single-window layouts, including the compact fallback from split mode. The previous modelView.startView setting remains a fallback when viewLayout is omitted.
buttonBg, buttonBgOver, buttonIcon, buttonIconOverNormal and hover colors shared by the viewport preparation buttons. They include the existing hover transition and tooltips.
maximizeModelText, maximizeCanvasText, viewAriaLabelTooltips for switching viewports and the accessible 3D view label.
showCopyCameraButtonSet true to show the camera-copy tool. Its messages are copyCameraText, cameraCopiedText and cameraCopyErrorText.
showFreeCameraButtonDefaults to false. Shows the optional Free Camera setup toggle, initially off. Configure its label and tooltip with freeCameraText and freeCameraHelpText. Copy Camera accepts the new framing as 100%, but you must paste the copied values into the view JSON to persist them.
showExportUVSilhouetteButtonSet true to show SVG silhouette export. Hide both preparation buttons on a customer-facing instance if they are not needed.
showDownloadPreviewButtonSet true to show Download preview beside silhouette export. Defaults to false. Downloads the current 3D camera view as a transparent PNG, retaining its orbit, pan, zoom and viewport proportions. Like the other viewport tools, it is hidden while loading and in the compact mobile layout.
downloadPreviewText, previewDownloadedText, previewDownloadErrorTextTooltip, success and error messages for the current-view PNG button. It shares the viewport button colors and fullscreen tooltip behavior.
exportUVSilhouetteText, uvSilhouetteExportedText, uvSilhouetteExportErrorTextTooltip, success and error messages for the optional silhouette-export button.
materialLabels, printingBoxControlLabelsMaterial-panel text and printing-box resize/copy feedback. These are interface labels, not product materials or print dimensions.
uvSilhouetteSize, uvSilhouetteFillNominal longest SVG dimension and its fill, for example 1000 and '#000000'. The other dimension follows the UV bounds ratio. This does not set physical size or print DPI.
elementControlLabelsLabels for remove, rotate, move and resize. These describe the 3D controls, element permissions still decide which actions are available.
loadingText, loadingErrorText, unavailableText, resolveErrorText, initializationErrorText3D loading/error messages. Keep {source} in the resolve-error message to include the model URL. Model loading and initial textures are included in startup, the instance is not ready before that work completes.

Product JSON scene settings

Root model3D optionPurpose
textureResolutionFinal artwork texture size, default 2048 pixels on the longest side. A view can override it with view.model3D.texture.resolution. Higher values use more GPU memory and do not change print DPI.
interactiveTextureResolutionTexture size during edits, default 1024, limited by the final size. A view can override it with view.model3D.texture.interactiveResolution.
textureRefineDelayIdle time before restoring final texture quality, default 160 milliseconds.
adaptiveTextureQualityClose-up refinement settings: enabled, maxResolution, zoomThreshold and settleDelay. Create Product starts this feature disabled.
selectionDragThresholdClick-versus-drag threshold, default 6 CSS pixels. Clicking a selectable model part selects its view, camera drags and the small preview do not.
previewResolutionPreview image resolution, default 1600 pixels. Also sets the longest edge of 2D PNG, JPG and PDF preview downloads, capped at 4096 pixels. Workspace proportions are preserved. Separate from the small viewport dimensions and production DPI.

For example, add this group inside the product JSON's model3D object, not inside constructor modelView:

"adaptiveTextureQuality": {
    "enabled": false,
    "maxResolution": 4096,
    "zoomThreshold": 1.25,
    "settleDelay": 180
}

Set model3D.viewLayout in each product JSON to choose its opening layout: model, canvas, model-canvas or canvas-model. This overrides the page's modelView.viewLayout whenever a product or saved design loads or resets. Missing or invalid values use the page's original setting, never the previous product's layout. Switching between ordinary views keeps the current layout. Create Product writes model, and export preserves the configured opening choice rather than a temporary user swap. Split layouts retain the same compact-screen fallback.

"model3D": {
    "viewLayout": "model",
    "showSmallViewport": true
}

Product-level smallViewportWidth and smallViewportHeight set the small viewport size. Set model3D.showSmallViewport to false to hide the small viewport. Click the small viewport to switch between 2D and 3D. A valid GLB model is required in this edition, use Iza Product Designer for 2D-only products. A loading error is not a successful ready state.

The small preview uses a fitted view rather than copying the main window's zoom. Minimizing the model immediately restores the active view's saved camera. At the compact module breakpoint, preview dimensions are reduced by 50% and the camera-copy/UV-export preparation buttons are hidden. showSmallViewport does not remove either full pane in desktop split mode.

For camera setup, enable modelView.showFreeCameraButton: true in the constructor settings. The optional Free Camera toggle starts off. Turn it on to rotate, pan and zoom beyond the normal customer limits. Only a tiny safety distance prevents a zero-length orbit. Click Copy Camera to copy the framing, exit Free Camera and make that position the current 100% reference without a jump. Paste the copied values into the view's model3D.camera to keep them after reloading. Turning the toggle off without copying restores the previous position. Changing views or minimizing the model also cancels the mode. The saved starting position is 100% and is the smallest allowed model size. Scroll up to enlarge the model. Scroll down to return to 100%, where outward movement stops. The zoom slider follows the same range. Free Camera remains unrestricted for setup. The 2D canvas is unchanged.

The button label and tooltip are configurable with modelView.freeCameraText and modelView.freeCameraHelpText. The API provides setFreeCameraEnabled(true) and isFreeCameraEnabled(). Call setFreeCameraEnabled(false) to cancel, or setFreeCameraEnabled(false, { commit: true }) to adopt the current framing as 100%. Committing alone does not write product JSON.

Set model3D.camera.maxZoomIn separately for each view. It is a magnification multiplier, not a distance: 1 means 100% only, 2 allows up to 200%, and 8 allows up to 800%. Decimals such as 1.5 are supported. The default is 2. Scroll up to enlarge the model and down to return to the saved 100% position. Normal zoom never makes the model smaller than this starting view. Wheel, pinch and slider controls share this limit. Free Camera and the 2D canvas are unaffected.

"maxZoomIn": 2
// Constructor settings: choose the pane order and module position.
modelView: {
    viewLayout: 'canvas-model',
    showCopyCameraButton: false,
    showExportUVSilhouetteButton: false
},
modules: {
    pos: 'left',
    toggled: true
}

For products with an explicit opening layout, set model3D.viewLayout to the same split value in their JSON. The dedicated demo pages apply their example layout to each loaded product. Use viewLayout: 'model-canvas' for 3D on the left. Set modules.pos: 'bottom' for bottom modules, independently of the pane order. Compare the side-by-side examples and the bottom-module example.

Product model3D settingMeaning
sourceRequired GLB URL relative to product.json. The model always loads, there is no model enable/disable switch.
backgroundColor, toneMapping, exposureScene background and rendered color response. Use neutral, aces or none for tone mapping.
modelScale, rotationX, rotationY, rotationZProduct placement and rotation, with rotation in radians. Finalize these before authoring view cameras.
viewLayoutProduct opening layout. Accepts model, canvas, model-canvas or canvas-model. Overrides the page default on product load. Create Product uses model.
showSmallViewport, smallViewportWidth, smallViewportHeightSmall viewport size in CSS pixels and visibility. Defaults used by the layout are 224 by 162 before responsive constraints.
previewResolutionPreview image resolution in pixels for 3D captures and 2D raster downloads, separate from the small viewport size and production DPI.

Loading covers the complete instance, including any toggled-open module panel. The GLB download contributes byte progress, followed by parsing, asset preparation and texture uploads. Seeing the model build up during loading does not mean it is ready for API calls. On a product change the new model uses that product's configured starting view and camera, not the previous product's orbit. Listen for initial READY and await product-changing methods.

Preview dialog labels are constructor modelView options: previewChoiceText, previewTitleText, previewSVGDescriptionText, downloadChoiceText, printTitleText, printChoiceText, preview2DText, preview3DText and previewErrorText. requiredModelText supplies the missing-model message.

Settings location and migration

SettingWhere it belongs
Layout, preparation buttons, button colors and UI textConstructor modelView.
Texture quality, lights, controls, cloth physics, material presets, scene colors, shadows and preview sizesProduct JSON's root model3D.
Selected-object outlines in 2D and 3DProduct JSON's root selectionOutline, with separate 2D and 3D groups.
Mesh targets, mapping, texture transforms, per-view material and cameraEach view's model3D.
Shared layer permissions, colors, export exclusions and clipping modeEach element's existing globalParameters.
Create Product file limits and dialog messagesConstructor createProduct, separate from modelView.

When updating an older integration, move any scene values from constructor modelView into each product's root model3D. Keep values already defined by that product, including explicit false and 0. Constructor scene values are ignored. The included products and saved designs have already been migrated.

Remove the retired model3D.enabled field. A model source is required. Click selection is configured independently in each view using views[].model3D.selectViewOnClick, not at the product root. Older product-wide choices are moved into individual views when loaded or exported, preserving their behavior. The global materials.enabled switch and nested enabled fields for the reflection environment, lights, cloth physics, adaptive quality and selection outlines remain supported. Per-view material.enabled is no longer used.

Reload the page or recreate the instance after editing JSON files on the server. The running instance keeps initial product snapshots, loadProductByIndex(index, true) resets from that snapshot rather than forcing a fresh network request. Editing the copy returned by getProductData() does not update the live designer.

Lighting, shadows and navigation

Keep lighting and product-specific scene settings in the product JSON's root model3D block. The reference below uses JavaScript notation for readability, use quoted keys and numeric radians when copying it into JSON. These are example values, not required values for every product. Do not put these settings in constructor modelView.

The key, fill and rim lights are all THREE.DirectionalLight lights. They shine parallel rays, like sunlight. Their names describe their jobs, not different light types:

  • Key: the main light. In this example it lights the model from above and the front-right.
  • Fill: a weaker light from the front-left that brightens areas left dark by the key light.
  • Rim: a light behind the model that helps separate its edges from the background.

position is [x, y, z] in scene coordinates. Positive X is right, positive Y is up and positive Z faces the usual front camera. Each directional light points from its position toward the scene origin, [0, 0, 0]. The light target is not a JSON option and is independent of the view camera's target. Moving a light farther along the same direction does not dim it, change intensity to adjust brightness. These lights stay in the scene when the camera orbits, they do not follow the camera.

The hemisphere light is THREE.HemisphereLight. It supplies broad illumination that blends skyColor and groundColor according to the surface direction. It is not a spotlight, does not produce cast shadows and has no configurable position in this product JSON.

controls: {
    enableDamping: true,
    dampingFactor: 0.075,
    enablePan: true,
    enableRotate: true,
    enableZoom: true,
    rotateSpeed: 0.7,
    zoomSpeed: 0.8,
    minPolarAngle: 0.15,
    maxPolarAngle: Math.PI - 0.15,
    autoRotate: false,
    autoRotateSpeed: 0.8
},
lights: {
    hemisphere: {
        enabled: true,
        skyColor: '#FFFFFF',
        groundColor: '#444444',
        intensity: 2.25
    },
    key: {
        enabled: true,
        color: '#FFFFFF',
        intensity: 2,
        position: [3.5, 5, 4.5],
        castShadow: false,
        shadowMapSize: 1024
    },
    fill: {
        enabled: true,
        color: '#FFFFFF',
        intensity: 1,
        position: [-4, 2.5, 3]
    },
    rim: {
        enabled: true,
        color: '#FFFFFF',
        intensity: 1.5,
        position: [0, 3, -4]
    }
},
toneMapping: 'neutral',
exposure: 1,
castShadow: false,
receiveShadow: false,
showFloor: true,
floorShadowColor: '#000000',
floorShadowOpacity: 0.18,
floorShadowWidth: 1.15,
floorShadowDepth: 0.45,
floorShadowSoftness: 0.72
Root model3D groupOptions and behavior
controlsenableRotate, enableZoom and enablePan independently allow navigation. Rotation uses standard upright orbit controls with world Y as up. Loading a view preserves its saved camera target, including framing created by panning before copying the camera. The resting centre of the whole model is used only when the view has no target. This also supports models authored away from zero without moving their geometry. Manual panning remains available when enabled. enableDamping and dampingFactor control inertia. rotateSpeed and zoomSpeed control sensitivity. minPolarAngle and maxPolarAngle are radians measured from world up. autoRotate and autoRotateSpeed control automatic orbiting. These do not change layer move/rotate/resize permissions.
lights.hemisphereenabled, skyColor, groundColor and nonnegative intensity control broad ambient illumination.
lights.key, lights.fill, lights.rimEach supports enabled, color, nonnegative intensity and a three-number position. The key light also supports castShadow and shadowMapSize. Fill and rim lights do not expose active shadow-map controls.
enabled, intensity, colorSet a light's enabled to false to hide it. Intensity 0 also gives no light, larger values make it brighter. Color tints the illumination without changing saved product colors. Hemisphere uses separate sky and ground colors.
lights.key.shadowMapSizeResolution of the key light's square shadow texture. For example, 1024 means 1024 by 1024 pixels. Larger maps can sharpen shadows but use more GPU memory and processing time. The setting has no visible effect while the key light's castShadow is false. Fill and rim shadow fields may appear in exported data but are not applied by the current light configuration code.
toneMappingneutral uses neutral tone mapping, aces uses ACES filmic tone mapping and none disables tone mapping. These convert rendered lighting values into the displayed image, they do not rewrite texture files or saved colors.
exposureOverall brightness multiplier for tone-mapped rendering. 1 is normal, 0.8 is darker and 1.2 is brighter. It applies across the model, not to one view. With toneMapping: 'none' it has no effect. The current implementation requires a positive finite number, zero, negative and invalid values fall back to 1.
castShadow, receiveShadowWhether the model meshes cast and receive scene shadows. The key light must also cast shadows for those shadows to appear. Keep both false if self-shadowing obscures artwork or product colors.
showFloorShow a separate soft contact-shadow plane below the model. This is a generated gradient, not a physically simulated shadow, and does not require mesh shadow casting.
floorShadowColor, floorShadowOpacityContact-shadow color and opacity from 0 to 1.
floorShadowWidth, floorShadowDepthPositive proportions based on model width. Geometry-based minimum sizes also account for model depth and overall bounds.
floorShadowSoftnessGradient softness, clamped to 0.05-1. Higher values spread the fade across more of the contact shadow.

Include complete light settings in each product for repeatable results. When a light's color, intensity or position is omitted, the current code keeps that light's existing value, which can come from the previously loaded product. Reload after editing the JSON to test the new configuration. Enabling shadows also requires a mesh that casts them and a surface that receives them. The separate floor-shadow graphic does not depend on these shadow settings.

  • Navigation: drag empty model space to orbit, use the wheel to zoom and right-drag to pan when enabled. Touch navigation uses OrbitControls gestures. Dragging a selected design object or its handle edits the design instead of orbiting.
  • Limits: polar angles are radians and restrict vertical orbit. dampingFactor controls smoothing, rotate/zoom speed control navigation sensitivity. In JSON, use numbers instead of JavaScript expressions such as Math.PI.
  • Lights: hemisphere supplies sky/ground ambient lighting, key is the main directional light, fill softens dark areas, rim lights the opposite side. Each can be disabled or recolored independently.
  • Color: toneMapping accepts 'neutral', 'aces' or 'none'. Neutral is the current color-focused choice. Materials, lighting and exposure still affect appearance, a lit 3D surface will not match every flat 2D pixel exactly.
  • Floor: the floor shadow is a soft contact-shadow graphic, not a photographic overlay on the shirt. Width/depth are model-relative multipliers, opacity and softness control its appearance. Keep model cast/receive shadows off if they introduce unwanted self-shadowing. A key-light shadow also needs lights.key.castShadow.

Materials and adjustments

Available preset names are original, fabric, matte, glossy, metal, glass, diamond, plastic, rubber, leather and ceramic. These configure surface shading, not new geometry. Their appearance depends on lighting, the model's original detail maps and any saved adjustments.

Configure available finishes in root model3D.materials. Put a part's initial finish on its view at views[].model3D.material. Materials change the 3D surface appearance, not the artwork's position, its print size or the flat 2D design.

materials.enabled controls whether users can edit materials and their adjustments through the toolbar. Setting it to false does not remove configured finishes or reset them to original. Each view still uses its own material preset and any saved material edits.

"materials": {
    "enabled": true,
    "presets": ["original", "fabric", "matte", "glossy", "metal", "glass", "diamond", "plastic", "rubber", "leather", "ceramic"],
    "presetSettings": {
        "fabric": { "roughness": 0.9, "sheen": 0.35, "sheenRoughness": 0.8 },
        "matte": { "roughness": 0.85, "metalness": 0 },
        "glossy": { "roughness": 0.18, "metalness": 0, "clearcoat": 1, "clearcoatRoughness": 0.12 },
        "metal": { "roughness": 0.22, "metalness": 1 },
        "glass": { "roughness": 0.06, "transmission": 0.98, "thickness": 0, "ior": 1.5 },
        "diamond": { "roughness": 0.015, "ior": 2.417, "fire": 0.25, "rayBounces": 5, "brilliance": 1 },
        "plastic": { "roughness": 0.32, "clearcoat": 0.25, "clearcoatRoughness": 0.2, "ior": 1.46 },
        "rubber": { "roughness": 0.72 },
        "leather": { "roughness": 0.58, "sheen": 0.1, "sheenRoughness": 0.7, "clearcoat": 0.08, "clearcoatRoughness": 0.5 },
        "ceramic": { "roughness": 0.48, "clearcoat": 0.18, "clearcoatRoughness": 0.38 }
    },
    "environment": { "enabled": true, "intensity": 1, "applyToOriginal": false }
}

original retains the imported material instead of applying a preset. Imported surface-detail maps are retained where supported. The generated studio environment supplies reflections for metal, glass, plastic, leather and ceramic, or another non-original preset with positive metalness. It is only a fallback when neither the material nor the scene already supplies an environment. original skips this feature unless environment.applyToOriginal is true. Ordinary fabric, matte and glossy presets do not use it unless their metalness is increased.

environment.enabled: false removes the generated reflection map, not the scene lights or an imported environment. Its nonnegative intensity controls only those generated reflections: 0 removes their contribution, 1 is normal and larger values strengthen it. It does not change the background or act as global exposure. The effect depends on roughness, metalness, geometry and the chosen material, so it can be subtle on rough surfaces.

Set environment.applyToOriginal: true to light imported metallic or transmissive materials with the same generated studio reflections while retaining their original colors, roughness and maps. Existing products keep their saved setting. Imported environment maps are never replaced.

Create Product checks the GLB's material properties, including inactive Build options. When a material uses metalness or transmission, it enables the neutral reflection environment with applyToOriginal: true and uses it for generated thumbnails too. It does not replace the original materials. Transparent PNG captures preserve the visible surface of refractive objects while leaving pixels outside the model transparent.

For an individual view, add this inside its model3D block:

"material": {
    "preset": "leather",
    "presets": ["original", "fabric", "leather"],
    "adjustments": {
        "roughness": 0.65,
        "sheen": 0.12
    }
}

A string such as "material": "leather" is also accepted. Allowed preset lists are intersected with the product list, a view cannot re-enable a finish excluded globally. Material editing uses the global materials.enabled switch only. The retired per-view and per-layer material.enabled fields are ignored on import and omitted from exports. Each view chooses its own material.preset, there is no global material default. Saved surface edits take priority over view settings, then legacy materials.parts settings. Without a preset, the finish falls back to original, or the first allowed preset if original is excluded. Create Product gives each generated view an explicit original preset. Global settings still control available presets, their adjustment values and the reflection environment.

AdjustmentRangeEffect
roughness, metalness0-1Surface roughness and metallic response.
clearcoat, clearcoatRoughness0-1Strength and roughness of the outer coating.
sheen, sheenRoughness0-1Soft surface sheen and its roughness.
transmission0-1Glass-like light transmission, different from layer opacity.
thickness0-1, model unitsOptical thickness used by the glass finish.
ior1-2.33, Diamond 1.01-3Index of refraction.

Only adjustments supported by the selected preset are available. Add { label: 'Material', button: 'material' } to toolbar.buttons to expose the material panel for a mapped structural surface. Selected part edits that part and its linked surfaces, Entire product applies to eligible parts. Locked or material-disabled parts keep their restrictions. Use Adjust material for sliders and Reset adjustments to return to the preset's configured values. Labels are configurable through modelView.materialLabels.

Surface layers sharing the same title and globalParameters.syncColorType: "allViews" synchronize color and material changes. Both ends must use the same globalParameters.syncColorType, either allViews or activeBuild. Each view's mapping.surface must reference its own matching element title. Export and saved designs preserve material edits in that surface's parameters3D.material, including its preset and adjustments. Do not remove these overrides if you want to preserve the customer's edited finish.

Leather currently exposes roughness, sheen, sheen roughness, clearcoat and clearcoat roughness. Its procedural grain is part of the preset, there is no separate grain-size or pattern-size JSON setting. Use the model's authored normal map with original when a specific grain pattern must be retained.

Diamond material

Add diamond to model3D.materials.presets, then set the gemstone view's material.preset to diamond. This finish traces internal reflections through the actual triangles and separates red, green and blue refraction for subtle rainbow fire. Glass and other finishes are unchanged. A dedicated HDR studio is used when the reflection environment is enabled and no imported or scene environment is supplied.

"material": {
    "preset": "diamond",
    "adjustments": {
        "roughness": 0.015,
        "ior": 2.417,
        "fire": 0.25,
        "rayBounces": 5,
        "brilliance": 1
    }
}

Diamond accepts roughness 0-1, IOR 1.01-3, fire 0-1, one to eight internal reflections and brilliance 0-2. Lower reflection counts are faster but approximate more of the remaining light with the blurred environment. Setting fire to zero uses one ray path instead of three. Product defaults belong in materials.presetSettings.diamond, while edits use the existing per-layer material adjustments and survive undo, saved designs and product export.

Use closed, rigid, faceted gemstone meshes. The ray tree is cached per geometry, so rotating the camera and changing colors do not rebuild it. Animated, cloth-deformed, empty or oversized meshes above 65,536 triangles use a simpler physical fallback with a console warning. Open surfaces do not provide a reliable gemstone volume. This is an environment-based gemstone effect, not full-scene path tracing or caustic lighting. The custom effect is restored by the designer from product JSON, it is not embedded as a portable shader in the GLB.

Cloth physics

Cloth inertia is optional and belongs only in the product JSON's root model3D.clothPhysics. It applies a shared deformation to eligible static parts so seams can move together. It responds to model navigation, it is not a garment-fitting or collision simulation and does not alter exported model geometry.

Create Product leaves cloth disabled for new models while keeping its tuning values available. Enable it deliberately for flexible products. Rigid parts should not run the cloth solver. Existing products keep their saved setting.

"clothPhysics": {
    "enabled": true,
    "horizontal": true,
    "vertical": false,
    "strength": 0.65,
    "stiffness": 0.45,
    "damping": 0.5,
    "pinTop": 0.18,
    "maxDisplacement": 0.12
}
OptionValuesPurpose
enabledBoolean, default falseEnable the simulation. Changing strength cannot enable a disabled simulation.
horizontal, verticalBooleans, both default trueEnable horizontal and vertical navigation response independently.
strength0-2Response strength. Zero produces no new movement.
stiffness, damping0-1Resistance to bending and how quickly motion settles.
pinTop0.01-0.5Pinned upper fraction of model-local height.
maxDisplacement0-0.25Movement limit relative to the longest garment dimension.

There is no per-mesh list. Ruler calibration stays based on the rest shape, not temporary deformation. Models driven by an embedded animation mixer do not run this additional simulation. Leave it disabled for rigid products such as mugs and phone cases, and test the strength on the intended garment.

Texture quality and previews

Texture resolution controls the detail of artwork painted onto the model. Preview resolution controls the size of a captured model image. Print DPI controls flattened production output. They are separate settings.

"textureResolution": 2048,
"interactiveTextureResolution": 1024,
"textureRefineDelay": 160,
"textureAnisotropy": 8,
"adaptiveTextureQuality": {
    "enabled": true,
    "maxResolution": 4096,
    "zoomThreshold": 1.25,
    "settleDelay": 180
},
"previewResolution": 1600

Put these settings in the product JSON's root model3D only. While editing, the lower interactive resolution keeps texture updates lighter. After input settles, the final resolution is restored. Adaptive quality refines the active view after zooming, not on every orbit frame. Other views release their temporary refinement when the active view changes. Generated starter products explicitly disable adaptive quality until you enable it.

textureAnisotropy requests filtering for model and artwork textures on angled surfaces and is limited by the GPU. Create Product writes 8 for new products, while normal product export preserves the configured value. Generated design textures use minimal filtering during edits and the configured value after settling. Older products without this setting retain maximum supported filtering for their artwork. Higher resolutions use more memory and cannot recover detail absent from an uploaded image. Test 4096 before choosing 8192. A view can override the base sizes with model3D.texture.resolution and interactiveResolution.

3D previews use each view's saved model3D.camera, including position, target, up and fov. They use equal square frames at previewResolution, capped at 4096 and the GPU limit. Supply a camera for every view, otherwise capture falls back to the current camera. Rulers, selection outlines and editing controls are not included. PNG downloads support transparentBackground: true. A 3D SVG download contains a rendered PNG inside SVG, not vector geometry.

Create appearance-only views

Use appearance__ for a model part that customers can recolor or change the material of, but cannot add text, images or shapes to. It works with a single mesh or a parent containing many meshes. Create Product turns the named part into one view.

1. Name the model object

In Blender, rename the mesh object or its parent Empty using this pattern:

appearance__part_name

For example, use appearance__diamond for one gemstone or appearance__side_stones for a parent containing several stones. Use the object name in the Outliner, not the material name, mesh data name or collection name. The prefix must be lowercase and end with exactly two underscores. Start the part identifier with a letter and use letters, numbers, single underscores or hyphens. Keep each named part unique.

2. Put related meshes under one parent

If several meshes should share one view, parent them to the named Empty while keeping their positions. Name only the owning parent with appearance__. The children can remain nested, and they can keep separate materials.

ProductRoot
    Band
    appearance__side_stones
        left_cluster
            stone_01
            stone_02
        right_cluster
            stone_03
            stone_04

This example creates two views: an ordinary editable Band view and one appearance-only Side Stones view. The clusters and individual stones do not create extra views. A single mesh named appearance__side_stones works too, including a mesh containing several disconnected stones. You do not have to join the meshes to use this feature.

Blender visibility is separate from grouping in the designer. Hiding an Empty with its eye icon does not hide its children. If you want one eye icon to hide a whole appearance part, join its geometry into one mesh object while retaining its material slots, UVs and textures. Keep the owning name on that object. Otherwise, hide the child meshes or use Blender collection visibility. Build alternatives overlap in the GLB, so hide the other alternatives when inspecting one in Blender. Create Product keeps the supplied GLB, it does not join or simplify its meshes.

3. Export the GLB and create the product

  1. Export a self-contained, uncompressed GLB with the named object and all its mesh children included.
  2. Open Create Product, choose the GLB and download the generated ZIP.
  3. Load its product.json and open the generated view. Select its Base layer or the model part to change its color or material.
  4. Review the generated camera and preview. Export the product after making your changes.

Create Product adds the binding and editing restriction automatically. These are the relevant fields from the generated view, not a complete product JSON:

{
    "model3D": {
        "targets": [
            {
                "mesh": "appearance__side_stones"
            }
        ],
        "material": {
            "preset": "original",
            "adjustments": {}
        }
    },
    "options": {
        "disableContentEditing": true
    }
}

The view gets one transparent PNG reference instead of a UV silhouette. That image is only a reference, it is not painted onto the model. The original geometry, hierarchy, colors and material slots are preserved. Color and material edits affect the whole bound part. Visibility, opacity, locking and allowed reset actions still work.

Ruler modes and spacing

Set rulerMode inside each view's options, not in the product's model3D settings or in a layer. It controls what that view measures. The action-bar ruler button remains the global on/off switch, so a view cannot show its rulers while that switch is off.

ValueWhen to use itWhat appears
print-areaArtwork views where you need to check the printable area.The configured print box is projected onto the 3D surface using its UV mapping. The 2D ruler stays around the canvas or print box according to rulerPosition. Both use the same physical calibration.
model-dimensionsAppearance parts, assemblies and geometry that cannot be represented by one reliable UV plane.A 3D ruler measures the bound part in a plane oriented to its saved camera. The 2D reference image has no ruler in this mode. These are projected straight-line dimensions, not circumference, ring size or distance along a curved surface.
hiddenViews where measurements are not useful.No ruler in either window for this view. The model part remains visible and selectable. This is different from options.hidden, which removes the view from navigation.

This fragment belongs inside a view. Keep that view's existing workspace, print-area and other options:

"options": {
    "rulerMode": "print-area",
    "rulerSpacing": {
        "top": 0,
        "left": 0,
        "right": 0
    }
}

rulerPosition controls the 2D ruler placement: canvas measures the full workspace, while printingBox measures the print box. The 3D print-area mode continues to measure the print box. A smaller 3D endpoint is expected when the print box is smaller than the workspace. The physical scale is unchanged. A missing or invalid print box hides its rulers. If the box is valid but the model has no reliable UV plane, only the 3D ruler is hidden, it does not silently change to model measurements. Minimized preview windows do not show rulers.

Extra space around the 3D ruler

rulerSpacing adds an offset to the existing clearance, in screen pixels. Zero leaves the current spacing unchanged. top moves the top ruler upward, left moves a left-side ruler outward, and right does the same when the vertical ruler is on the right. Only the currently used side applies.

For example, the ring uses six extra pixels on each side:

"options": {
    "rulerMode": "model-dimensions",
    "rulerSpacing": {
        "top": 6,
        "left": 6,
        "right": 6
    }
}

Use non-negative JSON numbers. Missing values, negative values and invalid values use zero. Each offset stays the same screen size while zooming. It does not change the 2D ruler, model, camera, print area or measurement values. If there is not enough room in the viewport, unreadable axes or labels can still be hidden.

Create Product, export and older files

Create Product suggests print-area for usable printable UV planes, model-dimensions for appearance assemblies, closed surfaces and unreliable UV planes, or hidden when geometry has fewer than two measurable dimensions. It writes all three spacing offsets as zero. Check the generated choice before publishing, it is not a guarantee that the model's units or print calibration are correct.

Product export and saved designs preserve the mode and spacing. Files without a supported rulerMode keep the older automatic behavior, including the constructor's rulerPosition where applicable. Missing rulerSpacing leaves the original gaps unchanged.

Change the mode through the API

Call these methods after the designer is ready. View indexes are zero-based, and omitting the index uses the current view:

// Measure the first view's model part.
designer0.setViewRulerMode('model-dimensions', 0);

// Read the current view's mode.
const mode = designer0.getViewRulerMode();

// Hide the current view's rulers.
designer0.setViewRulerMode('hidden');

An invalid view index or unsupported mode passed to the setter throws a RangeError. An invalid index passed to the getter also throws a RangeError. Changing the mode does not change artwork, camera positions, the global ruler switch or production-export eligibility. Configure spacing in the view JSON, there is no separate spacing setter.

Geometry-based rulers

Appearance-only views use the same top and side rulers as editable views. Their measuring plane comes from the actual geometry of every visible mesh in the bound part, oriented to the saved view camera. It does not depend on UVs or the reference PNG. Parts whose packed or wrapped UVs cannot form a reliable plane also use this geometry fallback, even without an appearance prefix. The plane stays fixed to the product as you orbit. Its rulers stay horizontal and vertical on screen while preserving each axis's projected tick positions. Labels remain a fixed screen size, the rulers clear the model and small preview, and unreadable edge-on axes or clipped labels are hidden.

Dimensions are projected straight-line extents in that plane, not distances around a curved surface, ring sizes or circumferences. Author the GLB at its real size in meters. Measurements convert to the product's millimeters, centimeters or inches, with millimeters used in pixel mode. Zoom does not change the measured values. Both ruler modes display whole-number labels, just like the 2D ruler. Only the displayed labels are rounded, geometry, tick positions and export measurements retain their precision. Ordinary editable views keep their existing calibrated print rulers.

UVs and editing limits

An appearance-only part does not need UVs to generate its reference image. Existing model textures still need their original UVs. Parts that accept artwork need usable UVs and ordinary mesh names without this prefix. Setting disableContentEditing to false does not turn an appearance__ part into a printable view. To make it editable, rename the model object and create the product again.

Use options.disableContentEditing: true on an ordinary view when you only want to disable artwork editing. The appearance__ naming convention additionally tells Create Product to treat a complete parent hierarchy as one part and generate a PNG reference without requiring a UV silhouette.

Appearance-only parts in the WordPress backend

In the WordPress view editor, select the base and open Visibility and Editing. For an appearance__ binding, Disable Content Editing stays checked and locked, with reduced opacity and an explanation beside it. The view uses a rendered reference image, not an editable UV silhouette. Color and material changes remain available if their separate permissions allow them, and the view's visibility can still be changed.

For example, appearance__strap__blue_leather__details is the watch strap's appearance-only Details view. Edit strap artwork in the ordinary UV-mapped strap__blue_leather__bracelet view instead. Changing the checkbox, JSON flag or layer title cannot convert Details into an editable surface. That requires a suitable UV-mapped model part without the prefix and creating the product again. Ordinary editable views keep their normal editing toggle.

Appearance parts with Build options

For interchangeable parts, add the prefix before the normal Build name:

appearance__head__round__setting
appearance__head__round__stones
appearance__head__emerald__setting
appearance__head__emerald__stones

This creates a Head group with Round and Emerald options. Each option contains matching Setting and Stones parts, and selecting an option switches both parts together. Each line can name a mesh or a parent with mesh children. See Nested appearance assemblies for the complete hierarchy and Build naming rules for matching options and parts.

Name only the owning root: its children must not contain another appearance__ declaration or a Build name with double underscores. Empty parents with no mesh descendants, duplicate names and conflicting nested declarations stop product creation. Grouping meshes into one view does not reduce their geometry or bypass the file, mesh or vertex limits.

Hidden and appearance-only views

Use options.hidden when a part should stay in the model and synchronize with another surface, but should not have its own selectable view. Use options.disableContentEditing when a visible view should allow only color, material and permitted reset actions.

"options": {
    "hidden": true,
    "disableContentEditing": true
}

This is an options fragment, retain the view's canvas and print settings. Hidden views remain in product data with their bindings and assets, but are omitted from view navigation, direct mesh selection and separate preview pages. Hidden does not mean the model part is invisible and is not an export-exclusion flag. To synchronize a hidden back panel with the front, give both mapped surface elements the same title and set globalParameters.syncColorType: "allViews" on both.

disableContentEditing disables the Text, Images, Shapes and Artwork modules for that view, hides its printing box and restricts the toolbar to color, material and reset when allowed. It does not remove the view or its model part. The older appearanceOnly option is migrated on load, use disableContentEditing in new JSON.

Editing the model and design layers

There is one editable design per product view, not a separate design stored in each viewport. The 2D objects are rendered together into the bound view texture. Text and images are not independent meshes or separate textures per object.

Action or layer typeCurrent behavior
Base / structural surfaceThe layer referenced by the view's mapping surface represents that model part. Its fill, opacity and layer visibility synchronize with the model. It uses the structural-surface toolbar, without the text/image shadow control. Its title can be different from Base as long as the mapping refers to it.
Text, images, shapes and artworkUse the same editable canvas objects and available toolbar actions in the primary 3D view. Colors and sliders update the texture, Oblique and Bridge text are retained during full texture refreshes as well as live edits.
Layer order, visibility and lockingOrdering changes texture stacking. A mapped Base covers artwork below it, even when its image is excluded from the 3D texture. Move artwork above the Base to reveal it. The Base's silhouette and opacity determine the covered area. Visibility hides design content, hiding a mapped Base hides its bound surface. Locked layers cannot be selected for editing. showInLayers only controls the layer-list entry.
SelectionClick a selectable bound surface to select its view. Switching to a different view also moves to its saved camera. Selection shows an outline and the appropriate toolbar. Clicking empty space or pressing Escape clears selection. The small, secondary viewport does not allow object selection/editing, maximize it first.
Move / rotate / scale / delete3D handles use the 2D icons and permission settings. Outside-mesh corners use a stable screen-space extension. Rotation uses a stable mapping for the duration of the drag. Scaling consumes movement continuously and keeps the existing flip state. Reaching the minimum size does not end the gesture: reverse direction to grow the object without releasing. Uniform scaling follows the projected handle direction. Free scaling limits each local axis independently. Shift toggles proportional scaling relative to the layer's lockUniScaling setting. Use the Flip controls for intentional mirroring.
Occluded handlesVisible product meshes are tested, with extra tolerance for shallow bumps on the selected surface. Partly exposed buttons stay usable. Buttons clearly hidden behind geometry or facing the back of the selected surface fade and cannot be clicked. A handle already being dragged continues working until release, even if it becomes occluded. When all available handles are occluded, the corresponding toolbar is dimmed and noninteractive.

Artwork outside the mapped surface has no model pixels on which to appear. Stable handles help move or resize it back, they do not extend the printable mesh. Layer shadows on ordinary artwork are different from the model's scene lighting and floor shadow.

Mapping and texture reference

View model3D propertyMeaning
selectViewOnClickBoolean, default true. Place it first in this view's model3D block. False prevents selecting this part by clicking the 3D model. Other views keep their own choices. Artwork remains selectable in the current editable view, and camera controls, Build and the Views selector remain available.
targetsList of mesh bindings, for example [{ "mesh": "front_outside" }]. Use a distinct view/target for each independently designed part. Target entries may override mapping and texture.
targets[].meshExact model node name. A named group includes its mesh descendants. If no node matches, the loader can match a material name, so unique mesh names are recommended. Multiple targets in one view receive the shared design, use separate views for independent designs. Legacy mesh/meshes aliases are accepted, prefer targets for new products.
cameraposition, target and optional up are three-number arrays in the rendered scene's coordinates. fov is degrees and defaults to 34 if omitted. Adjust the saved position and target to change framing, there is no global fitting-margin setting. Set camera.cameraTransitionDuration in seconds for the movement into this view. It defaults to 0.65, and 0 switches instantly. There is no global duration setting. Initial loading and minimizing the model apply the saved camera immediately, without this transition. Orbiting manually interrupts an active transition. Reload the page after editing the product JSON. Use the camera-copy tool after setting model scale and rotation. This view camera is separate from the projection camera used by projected mapping.
mapping (UV by default)Omit mode for the normal silhouette workflow. Explicit "mode": "uv" remains supported for older products. Remaps original model UV bounds into the Base layer's transformed canvas rectangle. It does not unwrap a model or infer UVs from arbitrary artwork.
mapping.surfaceThe Base element's title. Advanced configurations can supply a surface object, including element and normalized contentBounds: [left, top, right, bottom]. Generated silhouettes normally need only the title.
mapping.uvBounds, mapping.flipX, mapping.flipYOptional authored-UV bounds and orientation overrides. Automatic UV bounds and no flips are used by the supplied shirt. If changing a mapping flip, export the matching silhouette again.
mapping.mode: 'projected'Advanced alternative using a mapping camera, not the normal silhouette workflow. Its camera needs a position and may use target, fov, aspect and up. Mapping camera coordinates are relative to the model. A projected shape does not replace a proper unwrap for curved or wrapping parts.
texture.source (optional)Omit this property to render the complete canvas view. Create Product uses this default without writing a source field. Explicit 'canvas' remains supported. Set 'printingbox' to render the production crop and apply its exclusion flags. A cropped texture is a different coordinate domain: configure and validate that mapping instead of changing this option alone.
texture.preserveOriginalWhen true, retain the GLB's original color, color map and UVs while the view has no visible artwork and its Base color still matches the imported material. Adding visible artwork or changing that color activates the generated canvas texture. Removing the artwork and restoring the original color restores the original appearance without resetting the selected material finish. Create Product sets this to true. False or omitted means always use the generated canvas texture.
texture.flipY, offset, repeat, center, rotationTexture orientation/transform. Offset, repeat and center are two-number arrays, rotation is in radians. Start with the values in the example and keep them consistent across the view binding and silhouette setup.
texture.backgroundColorOptional texture background, otherwise the view's Base fill supplies its base color. Scene backgroundColor is the space behind the model, not this texture.

Keep surface detail under artwork

Artwork updates the surface color texture. The model's original normal, roughness and other material maps can still provide grain and shading, using their original UV coordinates. Keep these maps and UVs when exporting or joining parts. A leather or rubber pattern should remain visible through the lighting on printed areas, but a flat color image alone does not provide raised detail. Changing a material preset can replace its finish, use material.preset: "original" when the authored finish should stay intact.

Position artwork on curved parts

The 2D shape follows the model's UV layout, not a photograph of its front. Its center may not be the visual center of a curved strap, cap or shoe. Check the artwork in 3D from the saved camera and from nearby angles. Keep it within the intended UV island and away from seams, holes and hardware. Move related artwork layers together in 2D to adjust placement. Do not change the camera or Base mapping to compensate for misplaced artwork. Distorted or overlapping UVs need correction in the model.

Checks, limitations and export expectations

  • No model: check model3D.source, HTTP/CORS access and WebGL2 support. Use a self-contained, uncompressed GLB with its textures and geometry embedded. A model is required, there is no model enable/disable switch. Separate model files and decoder-dependent compression are not supported.
  • No silhouette: check every target name and its non-degenerate authored UVs. Targets must use UV mapping and share one surface. The exporter supports indexed and non-indexed geometry, UV islands, holes and mapping flips, but cannot turn several independent surfaces into one template or repair an invalid unwrap.
  • Wrong shape or repeated artwork: use the SVG exported from that exact mesh. Overlapping/mirrored UV islands repeat the same design region, and seams follow the model's unwrap. Separate the editable parts and correct the model's UVs when independent placement is required.
  • A smaller shirt painted onto the shirt: check that model3D.mapping.surface points to the correct surface layer. That layer is excluded automatically. Use globalParameters.excludeFrom3DTexture: true only for other 2D-only layers, such as a shading overlay.
  • Placement mismatch: check the surface title, silhouette proportions, Base position/scale, mapping flips and texture transforms. Do not substitute a camera screenshot for the exported UV shape.
  • Another product: the included shirt, cap, mug, bag, shoe and phone case are configured 3D examples. Create Product generates a starter ZIP from a self-contained, uncompressed GLB. Mesh names identify the views and their assets. Parts without usable UVs allow appearance changes only. Review the generated cameras and print measurements before publishing.
  • Printing: configure physical dimensions and the printing box separately. The UV silhouette size and model scene units do not automatically determine print dimensions. The ruler uses the existing physical-size conversion, not a measurement of the 3D garment.
  • Exports: Preview, Print and Download Preview offer a choice between the 2D design and a rendered 3D model image. Production export uses the printable 2D artwork and respects element export exclusions. Export packages the editable product JSON, GLB and referenced assets. UV silhouette export downloads an SVG outline, not a manufactured sewing pattern.

3D API

Call these after the instance is ready. The preparation buttons are optional UI for the same workflow.

// Read camera values for the current view, then paste them into its model3D.camera.
const camera = fwdi3dpd0.getCurrentModelCameraState();

// Enable camera setup independently of the optional button.
fwdi3dpd0.setFreeCameraEnabled(true);
const freeCameraActive = fwdi3dpd0.isFreeCameraEnabled();
// Accept the current position as 100%, without writing product JSON.
fwdi3dpd0.setFreeCameraEnabled(false, { commit: true });

// Download the UV silhouette for the currently active view's shared surface.
const exported = fwdi3dpd0.exportCurrentViewUVSilhouette({ size: 1000, fill: '#000000' });

// Download the current 3D camera view as a transparent PNG.
const downloaded = await fwdi3dpd0.downloadCurrent3DPreview();
// Capture the same preview without downloading it.
const preview = await fwdi3dpd0.getCurrent3DPreview();
// preview contains dataURL, width, height, title and index, or is false when unavailable.

// Capture a compact transparent thumbnail synchronously, without starting a download.
const thumbnail = fwdi3dpd0.getCurrent3DThumbnail(512);
// Capture selected view thumbnails without changing the live model or saving product data.
const viewThumbnails = await fwdi3dpd0.getView3DThumbnails([0, 1]);
// Each item contains index and dataURL. The longest image edge is 512 pixels.
// Returns a PNG data URL, or an empty string if unavailable. Size is the longest edge.

// Swap the main model/canvas viewport.
fwdi3dpd0.togglePrimaryProductViewport();

// Apply an existing view binding to the model. This is not the UI's canvas-view selector.
await fwdi3dpd0.applyProductViewToModel(0, { animateCamera: true, refreshTexture: true });

getCurrentModelCameraState() returns { position, target, up, fov } or null. Preserve up as well as position and target when copying a tilted camera. exportCurrentViewUVSilhouette() starts a download and returns a boolean, it does not return the SVG string or save configuration. Size is the longest SVG dimension, clamped to 1-16384, not print DPI. applyProductViewToModel() returns a promise resolving to a boolean and only updates the model binding. Use await loadProductView(index) to switch the editable canvas and model together.

downloadCurrent3DPreview() downloads one PNG and resolves to true, or false if the instance is not ready or its product changes. Capture errors reject the promise. This method and its viewport button use the live camera at the time of the request, unlike the preview dialog's saved view cameras. The longest PNG edge uses product model3D.previewResolution, capped at 4096 and the GPU limit. The other dimension follows the current viewport aspect ratio. Visible design layers, materials and the floor shadow are included, but rulers, editing controls and selection outlines are not. The background is transparent, independent of the configured editor background. This is a visual preview, not a production export.

getCurrent3DPreview() returns the same capture without downloading it. Await its result before using dataURL, width, height, title and index. It refreshes pending artwork first and returns false if unavailable. For a quick thumbnail, getCurrent3DThumbnail(resolution = 512) returns a PNG data URL synchronously, or an empty string while unavailable. Its longest edge is clamped to 64-1024 pixels. It flushes the active design but does not wait for other views, use the asynchronous preview for a full-quality capture.

getView3DThumbnails(viewIndexes, options = {}) returns transparent PNG thumbnails for the requested zero-based view indices. It uses the current camera for the active view and saved cameras for the others. An isolated renderer assembles each Build alternative without changing the live selection, artwork or camera. Hidden and export-excluded views can also be captured. Pass a complete product snapshot as options.product to render its saved colors, artwork and default Build choices instead of the live editor's temporary choices. The snapshot is cloned, not modified. Await the result and handle capture errors. The method returns an empty array when no valid indices were supplied. It does not save the returned images into the product.

Build: model setup and interchangeable parts

Build lets customers change physical parts of a product, such as sleeve length, collar style or a bag handle. Every alternative must already exist in the GLB. The designer swaps those meshes, it does not generate new geometry or stretch one part into another.

Start by preparing the model, name its alternatives, then run Create Product. It generates the views and their group settings automatically. You do not need to write the group JSON by hand.

1. Prepare the model

  • Model at real-world size using meters, apply the object scale, and export a self-contained, uncompressed GLB 2.0 with static triangle meshes.
  • Include every alternative in the same file, already positioned where it belongs on the assembled product. For example, regular and long sleeves should meet the body at the same shoulder seam. Alternatives may overlap in the source file because only one option is shown at a time.
  • Give every editable interchangeable mesh usable UVs, including inner surfaces. Create Product uses those UVs to generate each part's 2D silhouette. It does not unwrap meshes or repair distorted UVs.
  • Use one material primitive per editable Build mesh. If a part has several materials, split it into separate mesh objects and give them matching part names across the options. Ordinary multi-material meshes outside Build remain supported.
  • Keep editable interchangeable meshes under a parent that does not swap, such as an empty object named ProductRoot. An editable Build mesh must not contain another mesh, even through an empty transform. Use the appearance assembly setup below for nested parts without artwork. Reparent children while preserving their world positions.

A body mesh that never changes can remain an ordinary view. A badge or lining that should change with a sleeve needs its own matching Build part. If it should stay visible regardless of the selected sleeve, leave it outside the interchangeable meshes.

2. Name the interchangeable meshes

Use this pattern on the actual mesh object names:

group__option__part
  • group is the Build section, for example sleeves.
  • option is the choice shown to the customer, for example regular, cropped or long.
  • part matches the pieces that replace one another, for example left and right. A long left sleeve replaces the regular left sleeve, not the right one.

Separate these identifiers with exactly two underscores. Inside an identifier, use letters, numbers, single underscores or hyphens, starting with a letter. Use flat_top, not flat top or flat.001. Names are checked before the loader can change them. Duplicate names and invalid characters produce an error, they are not automatically repaired.

Here is a complete shirt example. The body stays the same, the sleeves switch as a pair, and the collar is a separate choice:

body_front
body_back
sleeves__regular__left
sleeves__regular__right
sleeves__cropped__left
sleeves__cropped__right
sleeves__long__left
sleeves__long__right
collar__crew
collar__raised

Omit part when each option contains only one mesh, as in the collar example. Create Product gives these single-part views the internal part identifier main. Names without double underscores remain ordinary views.

Each group needs at least two options. Every option in that group must contain exactly the same part identifiers, once each. If Regular has left and right, Long needs both too. The number of matching parts is not limited to two, a furniture leg option could contain four parts. Group, option and part identifiers are matched without case sensitivity.

3. Set up inside and outside surfaces

An inner sleeve and an outer sleeve can be separate meshes. Keep them beside each other under the non-swapping parent, not with the inner mesh parented to the outer mesh. Use distinct part identifiers so both surfaces switch together:

ProductRoot
    body_front
    body_back
    sleeves__short__left_outer
    sleeves__short__left_inner
    sleeves__short__right_outer
    sleeves__short__right_inner
    sleeves__long__left_outer
    sleeves__long__left_inner
    sleeves__long__right_outer
    sleeves__long__right_inner

ProductRoot is an ordinary parent object. All indented entries are its separate mesh children. Choosing Long replaces all four Short meshes together. Each inner and outer mesh gets its own normal view, UV silhouette, material and camera. The same arrangement works for a lid with inner and outer surfaces or a handle with several separately modeled pieces.

If customers should not select an inner surface directly, set options.hidden: true on its generated views for every option. The mesh still belongs to the Build choice and still switches. Hiding a view does not remove its mesh or its part price.

Nested appearance assemblies

For the basic setup in Blender and the generated view fields, start with Create appearance-only views. This section shows how to use the same parts in Build options.

Use one appearance assembly when a part contains many meshes but only needs color and material editing, such as a ring's diamonds. Name its owning parent appearance__diamonds. All descendant meshes belong to one view, even through several nested parents. A mesh with several materials can also be the owner.

ProductRoot
    Band
    appearance__side_stones
        left_cluster
            stone_01
            stone_02
        right_cluster
            stone_03
            stone_04
    appearance__head__round__setting
        metal_mount
    appearance__head__round__stones
        center_stone
    appearance__head__emerald__setting
        metal_mount_emerald
    appearance__head__emerald__stones
        center_stone_emerald

The side stones form one ordinary appearance view. The Head group has Round and Emerald choices, each containing matching Setting and Stones parts. Choosing a head switches both complete assemblies together. The parent names are the bindings, the children do not become separate views.

Create Product sets options.disableContentEditing: true and creates one transparent PNG reference for each appearance assembly. It does not generate UV silhouettes for its children or project that reference back onto the model. Text, images and shapes are unavailable, but color, material, visibility, opacity and locking still work. Original child colors and textures are retained until changed, and material edits apply to every bound child. These views are not printable artwork.

Appearance-only assemblies do not need UVs for the generated reference. Their original texture maps still need the UVs authored for those maps. Parts that accept artwork need usable UVs and the normal editable-mesh setup. Do not use an appearance assembly for a surface that needs printing or engraving artwork.

Use the exact lowercase prefix appearance__. After it, use a simple unique identifier for an ordinary assembly, or group__option__part for a Build assembly. Only name the owning root this way. Nested children must not declare another appearance assembly or Build option, which would give them conflicting owners. Duplicate roots, empty assemblies and conflicting declarations produce an error.

The original hierarchy, transforms, geometry and material slots stay intact. Inactive roots are detached and restored together with their children. Exports and saved designs keep the parent binding, reference image, all alternatives and material settings. File, mesh and vertex limits still count the actual model geometry, grouping does not bypass those limits.

4. Create and load the product

  1. Export the complete GLB with all alternatives included. Use actual mesh names for editable parts, or the owning appearance parent names for complete assemblies. Material names alone do not define Build choices.
  2. Open Create Product and choose the GLB. Review the detected Build groups and choose one starting option for each group.
  3. Download the generated ZIP and extract it into a served product folder. It contains the original GLB, product.json, SVG silhouettes for editable parts, PNG references for appearance assemblies and transparent preview images, including inactive alternatives.
  4. Add the JSON URL to the designer's product list. Add {label: 'Build', button: 'build'} to modules.buttons if it is not already there. The module appears when the product has grouped views.
  5. Review the generated cameras, real print dimensions, print areas, materials and thumbnails before publishing the product.

The API can choose the starting options without opening the review dialog. Call it after the designer is ready, with a GLB File:

const result = await designer0.createProduct(file, {
    builderDefaults: {
        sleeves: 'long',
        collar: 'crew'
    },
    download: true
});

Use group and option identifiers from your model. Omit builderDefaults to use the first detected option in each group. An unknown group or option produces an error.

5. Understand the generated views and prices

Every alternative is a view with its own binding, material, saved camera and thumbnail. Editable parts also have an SVG Base, artwork and print area. Appearance assemblies use a single PNG reference instead. Create Product adds group beside model3D, options and elements in each interchangeable view. There is no separate builder.groups definition. For the long left sleeve, the entry looks like this:

"group": {
    "id": "sleeves",
    "option": "long",
    "part": "left",
    "selected": false,
    "price": 3,
    "preserveDesign": false
}

The long right sleeve has the same id and option, with part: "right". Only the starting option has selected: true on all its parts. If none is selected, the first option is used. Labels are derived from the identifiers, and each option card uses its first view's thumbnail.

group.price is the additional price of that individual part. Create Product starts it at 0. Use non-negative numbers in the product currency, including decimals. Missing or invalid prices count as zero. Only selected parts are charged:

Total = basePrice + selected part prices + artwork charges in active views

For example, a base price of 20, two selected sleeves priced at 3 each and a selected collar priced at 2 total 28 before artwork charges. If an inner sleeve has no separate charge, leave its price at 0. Switching options replaces their charges, it does not accumulate them. Product exports, saved designs and undo/redo preserve the prices and selected options.

Build cards show the combined part and visible layer prices for each option. Free options have no badge. The cards share the image library's size, padding, rounded corners, shadow, itemBg and itemBgOver colors. Hover fades in the overlay, and the selected option keeps it visible. Badges use the product currency and the Products window's priceColor and priceBg settings.

Each layer thumbnail shows that element's own price, including Base layers. A view thumbnail shows its additional view price plus all visible, undeleted layer prices. For example, an additional view price of 10 and a Base layer priced at 100 produce a view price of 110. The product base price is added once to the total, not to each thumbnail. Inactive Build alternatives are not charged.

Additional View Price is an optional surcharge on top of the view's element prices, not the price of the Base element or the whole Build option. Set it to 0 if element prices already cover the full charge. It is stored as group.price, so existing product files remain compatible.

globalParameters.syncColorType controls linked color, shadow and material changes with one choice. "none" keeps the layer independent in both directions: editing it does not recolor other views, and linked edits elsewhere do not recolor it. Build switching and backend saving also retain each independent base's own color and material. Choosing None does not undo changes already saved. "allViews" links matching layer names across every view, including inactive Build alternatives. "activeBuild" links only the selected Build options and ordinary views, preserving the other options' colors and finishes. Both layers must use the same title and synchronization type. This setting controls automatic linking. Explicitly choosing All Parts in the material toolbar is a separate whole-product action. It does not remove a shared physical mesh from other views or reverse colors previously copied into saved data. For example, use Active Build on Gemstones and Side Stones so Round and Emerald retain different colors. Create Product writes "none" initially. Export Product and saved designs retain the choice. Older products are converted automatically when loaded, preserving their previous behavior.

Builder and designer examples

Ring: Build changes the setting and stone option while linked surfaces coordinate the stone colors. Appearance assemblies keep the small gemstone meshes together as one view. Use geometric rulers for these parts rather than treating a packed UV layout as a printable plane.

FWD Watch: Build changes the case, dial and strap. The leather and rubber strap views also accept artwork. Alternatives can keep different designs with group.preserveDesign: true, while appearance-only components retain their imported finish. This combines a product builder and a designer in one product.

For a builder-only interface, use startModule: "build" and model3D.showSmallViewport: false. To keep the authored appearance, use each view's material.preset: "original" and disable model3D.materials.enabled. Prevent color edits with an empty surface colors array and colorPicker: false. Set canReset: false separately if needed. These are product choices, not rules tied to a ring or watch model.

Set views[].model3D.selectViewOnClick: false only on parts that should not be selected by a model click. Build and Views navigation still work. Turning off that switch does not stop selecting existing artwork in the active editable view.

6. Check the finished product

Preview generation during a Build switch. Build prepares the replacement parts and their textures before completing the change. Background live-texture updates and quality refinement wait until the switch finishes, so they cannot interrupt the preview being prepared. They resume afterward. Additional Build selections are ignored while the current switch is running.

If a required texture cannot be generated, the switch reports an error and restores the previous Build option. You can try again after it finishes restoring. A slower texture update alone should not produce an "Unable to generate the 3D preview" error. No extra JSON setting is needed for this behavior.

Switch through every option and check the seams, visible parts, artwork placement, prices and cameras. Inactive meshes are detached from the scene, not destroyed. They are excluded from view navigation, picking, previews, printing and pricing, but their data and assets stay in product exports and saved designs. Each group is independent, so check combinations such as long sleeves with a raised collar too.

Artwork transfers between matching parts unless Preserve Design is enabled. Base colors and materials follow Color Synchronization: matching layers set to All Views share their appearance, while None and Active Build retain each alternative's own base appearance. Artwork keeps its printed size and position relative to the print-area center where possible. On a smaller part, the arrangement moves inward or shrinks uniformly to fit with a small margin. Text and shapes stay aligned. It is not automatically enlarged again when returning to a larger part, Undo restores the previous layout. Parts without configured print areas use surface-centered placement.

Set group.preserveDesign: true on an alternative that needs its own design. When either the outgoing or incoming part has this flag, switching keeps each option's layers, positions, colors and material instead of transferring them. Your edits remain when you return. This is useful for leather and rubber straps with different artwork layouts. Omit the property or use false for the normal transfer behavior. Create Product starts it at false. Export, saved designs and undo preserve it.

The shirt's alternative silhouettes follow their authored UV layouts and surface proportions. This does not repair distorted UVs. Check real print measurements before production. Clicking a Build card opens its corresponding view and animates to that view's saved camera. It keeps the same logical part when switching options, such as the right sleeve. From another group, it opens the first visible part. Clicking an already selected option also opens its view. Hidden views are never opened by a Build card.

Invalid models stop before a ZIP is created. Errors identify duplicate or invalid names, incomplete options, unusable editable-part UVs, multi-material editable Build parts and undeclared mesh children inside an editable Build part. Explicit appearance assemblies support nested meshes and multiple materials, but reject empty roots and conflicting child declarations. The open product is not changed. Loading also checks grouped targets before detaching meshes, including manually written JSON. Ordinary transformed parent groups, distinct mesh objects sharing geometry, and ordinary multi-material meshes outside Build remain supported.

Installation

Current source project

The working 3D instance is the project-root index.html, importing ./js/FWDI3DPD.js as an ES module with a default export. From the project root, run npm install once, then npm run dev and open the URL reported by Vite. Editable classes are in js/, assets under public/ are served from the web root. Source imports require Vite or another bundler, the compiled demo script can be served directly.

<link rel="stylesheet" href="./content/global.css">
<div id="designer"></div>
<script type="module">
    import FWDI3DPD from './js/FWDI3DPD.js';
    new FWDI3DPD({
        id: 'fwdi3dpd0', parent: 'designer',
        product: ['./products/shirt/product.json'],
        display: 'responsive', maxW: 1420, maxH: 800,
        modelView: { viewLayout: 'model', showExportUVSilhouetteButton: true, showCopyCameraButton: true }
    });
</script>

Packaged compiled script

The following setup is for a distribution that supplies the compiled global script. Do not mix it with the source-module import above. Changing source code does not update older compiled copies automatically, generate and verify your distribution separately before deployment.

Use the distribution's start.html as the integration reference when supplied. For the existing demo folder, the compiled script is js/FWDI3DPD.js, with content and products beside it. A Vite build writes the compiled script to build/src/FWDI3DPD.js. Match the script URL to the package you deploy, and keep its associated assets together.

Include the global stylesheet first, followed by the compiled product designer script:

<head>
    <link rel="stylesheet" href="content/global.css?v=20261018-release" type="text/css">
    <script type="text/javascript" src="src/FWDI3DPD.js"></script>
</head>

Add the holder element where the designer should appear. Its ID must match the parent setting:

<body>
    <div id="myDiv"></div>
</body>
Keep the folder structure intact: product JSON, thumbnails, product assets, library JSON and fonts are loaded through relative URLs. If a folder is moved, update the corresponding paths in the settings and JSON files.

Initialization

This is the complete constructor configuration from start.html, including the full 3D interface, labels, preparation tools, Create Product settings, lightbox, modules and object toolbar. Initialize after the document is ready. The compiled script exposes the global FWDI3DPD class, so no module import is required.

Product-specific cameras, lighting, materials, texture quality, physics and selection outlines belong in the product JSON, not in this constructor. Their complete reference is in Product settings and interface settings. Change product URLs and the holder ID to match your installation.

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', fwdi3dpdInit, { once: true });
} else {
    fwdi3dpdInit();
}

function fwdi3dpdInit() {
    new FWDI3DPD({

        // Core: instance, product data, parent, main sizing.
        id: 'fwdi3dpd0',
        product: ['./products/shirt/product.json', './products/ring/product.json', './products/cap/product.json', './products/mug/product.json', './products/bag/product.json', './products/watch/product.json', './products/shoe/product.json', './products/phone/product.json', './products/heart/product.json', './products/business-card/product.json'],
        prodcutIndex: 0,
        display: 'responsive',
        parent: 'myDiv',
        maxW: 1420,
        resizeW: 1000,
        maxH: 800,
        minH: 500,
        canvasW: 800,
        canvasH: 650,
        initVisible: false,
        unit: 'mm', // px, mm, cm, inch. Fallback when the product JSON does not define its own unit.
        bleed: 3, // Default bleed in millimeters. A view JSON bleed value overrides it.
        rulerPosition: 'canvas', // 2D placement: canvas or printingBox. The 3D ruler follows the view's rulerMode.
        rulerFixed: false, // If true, ruler starts visible and can still be hidden with the Ruler action.
        autoScale: true,
        currency: '$',
        bg: '#f6f6f6',
        toggleSnapToCenter: true,
        snapColor: '#CF4035',
        snapThreshold: 8,

        // View layout: one main window with a small preview, or two editable windows.
        modelView: {
            viewLayout: 'model', // 'model', 'canvas', 'model-canvas' (3D left) or 'canvas-model' (2D left)

            materialLabels: {
                title: 'Material', original: 'Original', fabric: 'Fabric', matte: 'Matte', glossy: 'Glossy',
                metal: 'Metal', glass: 'Glass', diamond: 'Diamond',
                plastic: 'Plastic', rubber: 'Rubber', leather: 'Leather', ceramic: 'Ceramic',
                scope: 'Apply to', thisPart: 'Selected part', allParts: 'Entire product',
                unavailable: 'This material is not available for every editable part.',
                adjustMaterial: 'Adjust material', resetAdjustments: 'Reset adjustments',
                mixedMaterials: 'Choose a single preset for these parts to adjust their material.',
                originalAdjustments: 'Choose a material preset to adjust its properties.', mixedValue: 'Mixed',
                roughness: 'Roughness', metalness: 'Metalness', clearcoat: 'Clearcoat',
                clearcoatRoughness: 'Clearcoat roughness', sheen: 'Sheen', sheenRoughness: 'Sheen roughness',
                transmission: 'Transmission', thickness: 'Thickness (model units)', ior: 'Index of refraction',
                fire: 'Rainbow dispersion', rayBounces: 'Internal reflections', brilliance: 'Brilliance'
            },
            elementControlLabels: {
                remove: 'Delete',
                rotate: 'Rotate',
                move: 'Move',
                resize: 'Scale'
            },
            printingBoxControlLabels: {
                resize: 'Resize printing box',
                copy: 'Copy printing box',
                copied: 'Printing box copied',
                copyError: 'Unable to copy printing box'
            },
            loadingText: 'Loading 3D model',
            loadingErrorText: 'Loading 3D model failed.',
            previewChoiceText: 'Choose what to preview.',
            previewTitleText: 'Preview',
            previewSVGDescriptionText: 'SVG containing the rendered 3D image',
            downloadChoiceText: 'Choose which preview to download.',
            printTitleText: 'Print',
            printChoiceText: 'Choose what to print.',
            preview2DText: '2D design',
            preview3DText: '3D model',
            previewErrorText: 'Unable to generate the 3D preview.',
            requiredModelText: 'A 3D model is required. Set model3D.source to your GLB model. For 2D-only use, choose Iza Product Designer.',
            unavailableText: '3D preview is unavailable',
            resolveErrorText: 'Unable to resolve 3D model: {source}',
            initializationErrorText: 'Unable to initialize the 3D viewport.',
            viewAriaLabel: 'Interactive 3D product view',
            maximizeModelText: 'Maximize 3D View',
            maximizeCanvasText: 'Maximize Design Canvas',
            showCopyCameraButton: true,
            showFreeCameraButton: false,
            freeCameraText: 'Free Camera',
            freeCameraHelpText: 'Set the starting camera without the normal zoom limits',
            copyCameraText: 'Copy 3D Camera Position',
            cameraCopiedText: 'Camera position copied',
            cameraCopyErrorText: 'Unable to copy camera position',
            showExportUVSilhouetteButton: true,
            exportUVSilhouetteText: 'Export Current UV Silhouette',
            uvSilhouetteExportedText: 'UV silhouette exported',
            uvSilhouetteExportErrorText: 'Unable to export UV silhouette',
            showDownloadPreviewButton: true,
            downloadPreviewText: 'Download preview',
            previewDownloadedText: 'Preview downloaded',
            previewDownloadErrorText: 'Unable to download preview',
            uvSilhouetteFill: '#000000',
            uvSilhouetteSize: 1000,
            buttonBg: 'rgba(255, 255, 255, .94)',
            buttonBgOver: '#CF4035',
            buttonIcon: '#111111',
            buttonIconOver: '#FFFFFF'
        },

        // Lightbox mode and Customize button.
        lightbox: {
            label: 'Customize',
            bg: 'rgba(0, 0, 0, .7)',
            btnBg: '#CF4035',
            btnBgOver: 'rgba(184, 50, 41, 1)',
            btnText: '#FFFFFF',
            btnTextOver: '#FFFFFF',
            closeW: 34,
            closeH: 34,
            closeBg: '#CF4035',
            closeBgOver: '#FFFFFF',
            closeIcon: '#FFFFFF',
            closeIconOver: '#CF4035'
        },

        // Printing box and clipping controls.
        boundingBoxMode: ['clipping'],
        boundingBoxColor: '#4B5563',
        showPrintingBox: true,
        boundingBoxEditable: false,
        boundingBoxInputs: true,
        boundingBoxInputsBg: 'rgba(255, 255, 255, .94)',
        boundingBoxInputsText: '#000000',
        boundingBoxInputsFieldBg: '#f6f6f6',
        boundingBoxInputsBorder: '#e2e2e2',
        boundingBoxInputsBorderFocus: '#CF4035',

        // Keyboard shortcuts.
        addKeyboardSupport: true, // Enables keyboard shortcuts. Shift + drag moves artwork along one axis. Space + drag pans over artwork. Mouse-wheel zoom needs no modifier or visible ruler.
        keuboardMoveNudgeAmmount: 1, // Arrow keys: move the selected object by this amount in pixels.
        keyboardMoveNudgeLargeAmmount: 10, // Shift + Arrow keys: move the selected object by this larger amount in pixels.
        keyboardRotateAmmount: 1, // Alt + Left / Right: rotate the selected object by this amount in degrees.
        keyboardRotateLargeAmmount: 15, // Shift + Alt + Left / Right: rotate the selected object by this larger amount in degrees.
        keyboardScaleAmmount: 0.001, // Ctrl/Cmd + Arrow keys: scale the selected object by this amount.
        keyboardScaleLargeAmmount: 0.1, // Ctrl/Cmd + Shift + Arrow keys: scale the selected object by this larger amount. Ctrl/Cmd + Z: undo. Ctrl/Cmd + Y or Ctrl/Cmd + Shift + Z: redo.

        // Full-screen loader shown while product assets are prepared.
        loader: {
            text: 'Loading ',
            bg: 'rgba(255, 255, 255, 0.6)',
            iconBg: 'rgba(207, 64, 53, 0.2)',
            icon: '#CF4035'
        },

        // Fonts available to text objects and text templates.
        font: {
            def: 'Roboto',
            list: [
                {label: 'Arial', family: 'Arial'},
                {label: 'Times New Roman', family: 'Times New Roman'},
                {label: 'Roboto', family: 'Roboto', url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'},
                {label: 'Montserrat', family: 'Montserrat', url: 'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;700&display=swap'},
                {
                    label: 'MD Nichrome',
                    family: 'MDNichrome',
                    files: [
                        {url: './content/fonts/MD_Nichrome/MDNichrome-Regular.otf', weight: 400, style: 'normal'},
                        {url: './content/fonts/MD_Nichrome/MDNichrome-Bold.otf', weight: 700, style: 'normal'},
                        {url: './content/fonts/MD_Nichrome/MDNichrome-Black.otf', weight: 900, style: 'normal'}
                    ]
                }
            ]
        },

        // Shared modal window used for delete, reset, errors and info.
        dialog: {
            ok: 'Ok',
            overlay: 'rgba(0, 0, 0, 0.6)',
            bg: 'rgba(255, 255, 255, 1)',
            text: 'rgba(0, 0, 0, .7)',
            btn: {
                bg: 'rgba(207, 64, 53, 1)',
                bgOver: 'rgba(184, 50, 41, 1)',
                text: '#FFFFFF',
                textOver: '#FFFFFF'
            },
            close: {
                icon: '#444444',
                iconOver: '#CF4035'
            },
            del: {
                show: true,
                text: 'Are you sure you want to delete this element?',
                btn: 'Delete'
            },
            reset: {
                show: true,
                text: 'Are you sure you want to reset this element?',
                btn: 'Reset'
            },
            info: {
                maxW: 840,
                html: `
                    <div class="fwdi3dpd-info-window-content">
                        <h3>Editor help</h3>
                        <p>Edit the same design in 2D or 3D. Select an element to see its available controls. Locked elements and restricted product parts may have fewer editing options.</p>
                        <section>
                            <p><strong>Views and navigation</strong></p>
                            <dl>
                                <dt>Split view</dt><dd>Edit side by side. Click a window to use its selection tools. Both windows share the design, but zoom independently. The zoom slider controls the last window you clicked or zoomed.</dd>
                                <dt>Small preview</dt><dd>Click it to switch between 2D and 3D. Editing is disabled inside the small preview.</dd>
                                <dt>Small screens</dt><dd>Split mode switches to the main 3D view with a small 2D preview. Widen the designer to restore split mode.</dd>
                                <dt>Product views</dt><dd>Choose a view or click an editable model part. Changing views moves the camera and resets zoom.</dd>
                                <dt>3D rotation</dt><dd>Drag the model away from its editing handles. Rotating or panning clears the selection.</dd>
                                <dt>3D pan</dt><dd>Right-drag to pan when enabled. Shift + left-drag also pans outside selected artwork. On selected artwork, Shift locks movement to one design axis.</dd>
                                <dt>Mouse wheel</dt><dd>Zoom over the 2D or 3D window. No modifier key or visible ruler is required.</dd>
                                <dt>2D pan</dt><dd>Zoom in, then drag empty canvas or a fixed product surface. Hold Space while dragging to pan over artwork. Hold Shift while dragging artwork to lock its first horizontal or vertical direction.</dd>
                            </dl>
                        </section>
                        <section style="margin-top: 18px">
                            <p><strong>Editing and output</strong></p>
                            <dl>
                                <dt>Edit text</dt><dd>Double-click text in the editable 2D window, then type. Click outside the text to finish.</dd>
                                <dt>Element controls</dt><dd>Use the selected element's handles to move, rotate or resize it. Use its toolbar for colors and other available settings.</dd>
                                <dt>Materials</dt><dd>Select a product part and open Materials, when available. Choose a preset and use Adjust to fine-tune it.</dd>
                                <dt>Layers</dt><dd>Select, reorder, hide or lock elements. Changes apply to the shared 2D and 3D design.</dd>
                                <dt>Preview and print</dt><dd>Choose the 2D design or 3D model when opening Preview, Print or Download Preview.</dd>
                            </dl>
                        </section>
                        <section style="margin-top: 18px">
                            <p><strong>Keyboard shortcuts</strong></p>
                            <p>Select an editable element first. Shortcuts require keyboard support and do not replace normal typing while editing text.</p>
                            <dl>
                                <dt>Arrow keys</dt><dd>Move by the configured small step.</dd>
                                <dt>Shift + Arrow keys</dt><dd>Move by the configured larger step.</dd>
                                <dt>Alt + Left / Right</dt><dd>Rotate left / right.</dd>
                                <dt>Shift + Alt + Left / Right</dt><dd>Rotate in larger steps.</dd>
                                <dt>Ctrl / Cmd + Up / Down</dt><dd>Scale up / down uniformly.</dd>
                                <dt>Ctrl / Cmd + Left / Right</dt><dd>Scale width down / up.</dd>
                                <dt>Ctrl / Cmd + Shift + Up / Down</dt><dd>Scale height up / down.</dd>
                                <dt>Delete / Backspace</dt><dd>Delete the selected element, when allowed.</dd>
                                <dt>Ctrl / Cmd + D</dt><dd>Duplicate the selected element, when allowed.</dd>
                                <dt>Ctrl / Cmd + Z</dt><dd>Undo.</dd>
                                <dt>Ctrl / Cmd + Y</dt><dd>Redo.</dd>
                                <dt>Ctrl / Cmd + Shift + Z</dt><dd>Redo.</dd>
                                <dt>Escape</dt><dd>Finish the current edit or clear the selection.</dd>
                            </dl>
                        </section>
                    </div>
                `
            }
        },

        // Top action bar.
        actionBar: {
            leftlistLabel: 'File',
            leftList: true,
            leftListBg: '#ffffff',
            leftListIcon: '#000000',
            leftListIconOver: '#CF4035',
            leftListBorder: '#e2e2e2',

            rightList: false,
            rightlistLabel: 'More',
            rightListBg: '#ffffff',
            rightListIcon: '#000000',
            rightListIconOver: '#CF4035',
            rightListBorder: '#e2e2e2',

            bg: '#CF4035',
            nav: [
                [
                    {label: 'Create Product', button: 'createProduct'},
                    {label: 'Save Design', button: 'savedDesign'},
                    {label: 'Saved Designs', button: 'savedDesigns'},
                    {label: 'Preview', button: 'preview'},
                    {label: 'Print', button: 'print'},
                    {label: 'Download Preview', button: 'download'},
                    {label: 'Doownload Production', button: 'productionExport'},
                ],
                [
                    {label: 'Undo', button: 'undo'},
                    {label: 'Redo', button: 'redo'},
                    {label: 'Ruler', button: 'ruler'},
                    {label: 'Zoom', button: 'zoom'},
                    {label: 'Fullscreen', button: 'fullscreen'},
                    {label: 'Snap To Center', button: 'snapToCenter'},
                    // {label: 'Prev View', button: 'prevView'},
                    // {label: 'Next View', button: 'nextView'},
                    {label: 'Reset', button: 'reset'}

                ],
                [
                    {label: 'Info', button: 'info'},
                    {label: 'Export', button: 'export'},
                    {label: 'Total', button: 'done'},
                ]
            ],
            icon: '#ffffff',
            iconOver: '#FFE1DC',
            iconDisabled: 'rgba(255, 255, 255, 0.3)',
            toolTip: {
                bg: '#000000',
                text: '#ffffff'
            }
        },

        // Create a starter product from a self-contained GLB. Remove createProduct from
        // actionBar.nav above to hide the button without changing the other actions.
        createProduct: {
            title: 'Create Product',
            text: 'Choose a self-contained GLB model. Use group__option__part for interchangeable parts. Editable parts need usable UVs. Prefix a parent with appearance__ to keep its nested meshes in one color/material-only view.',
            builderReview: 'Choose the starting option for each detected group. All matching parts switch together.',
            builderCreate: 'Create product',
            builderUVError: 'Builder part "{mesh}" needs usable UVs before its editing surface can be created.',
            choose: 'Choose GLB file',
            working: 'Creating product. Please wait.',
            complete: 'Product ZIP downloaded. {count} views created. {restrictedViews} views have content editing disabled. Review the cameras and print measurements before publishing.',
            error: 'Unable to create the product.',
            invalid: 'Choose a valid GLB 2.0 file.',
            tooLarge: 'The GLB file exceeds the configured size limit.',
            external: 'This model references external files. Export it as a self-contained GLB and try again.',
            empty: 'The model has no usable mesh geometry.',
            compressed: 'Compressed geometry or KTX2 textures are not supported. Export an uncompressed GLB with PNG or JPEG textures.',
            unsupportedExtension: 'This GLB requires an unsupported extension. Export a GLB using standard materials and geometry.',
            staticModel: 'Export a static triangle mesh. Bake skinning, active morphs and instances before creating the product.',
            invalidGeometry: 'The model contains invalid or collapsed geometry. Repair it and export the GLB again.',
            meshNames: 'Mesh names must be unique. Rename the parts and export the GLB again.',
            tooComplex: 'The model exceeds the configured mesh or vertex limit. Simplify it before creating the product.',
            unnamed: 'Part',
            maxFileSizeMB: 100,
            maxMeshes: 128,
            maxVertices: 2000000
        },

        // Download.
        download: {
            title: 'Download',
            text: 'Choose what you want to export.',
            scope: 'all', // 'all' or 'current'
            all: 'All views',
            current: 'Current view',
            maxW: 390,
            bg: 'rgba(0, 0, 0, 0.02)',
            bgOver: 'rgba(207, 64, 53, 1)',
            formats: [
                {label: 'PNG', button: 'png', description: 'Best for transparent images'},
                {label: 'JPG', button: 'jpg', description: 'Flattened image file'},
                {label: 'SVG', button: 'svg', description: 'Editable vector file'},
                {label: 'PDF', button: 'pdf', description: 'One PDF with selected views'}
            ]
        },

        // Full-screen preview window.
        preview: {
            overlay: 'rgba(0, 0, 0, .8)',
            title: '#ffffff',
            itemBg: 'rgba(255, 255, 255, .96)',
            itemText: '#000000',
            close: '#ffffff',
            closeOver: '#CF4035',
            scrollbar: '#CF4035'
        },

        // View switcher and its thumbnail grid.
        views: {
            pos: 'bottomRight', // 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
            bg: '#ffffff',
            border: '#e2e2e2',
            icon: '#000000',
            iconOver: '#CF4035',
            nav: {
                maxW: 210,
                h: 42,
                bottomGap: 12
            },
                grid: {
                    bg: '#FFFFFF',
                    itemBg: 'rgba(255, 255, 255, 1)',
                    itemBgOver: 'rgba(184, 50, 41, 1)',
                    itemText: '#000000',
                    itemTextOver: '#FFFFFF'
                }
        },

        // Left modules bar and the panel behind Layers/Text/Images.
        modules: {
            buttons: [
                {label: 'Products', button: 'products'},
                {label: 'Build', button: 'build'},
                {label: 'Saved Designs', button: 'savedDesigns'},
                {label: 'Layers', button: 'layers'},
                {label: 'Text', button: 'text'},
                {label: 'Images', button: 'images'},
                {label: 'Shapes', button: 'shapes'},
                {label: 'Artwork', button: 'artworkLibrary'}
            ],
            pos: 'left', // 'left' or 'bottom'
            allowToggle: true,
            toggled: true,
            bg: '#ffffff',
            border: '#e2e2e2',
            panelBg: '#ffffff',
            icon: '#000000',
            iconOver: '#CF4035',
            btnBg: 'rgba(255, 255, 255, 0.1)',
            btnBgOver: 'rgba(207, 64, 53, 0.1)'
        },

        // Products module previews.
        products: {
            priceColor: '#FFFFFF',
            priceBg: 'rgba(0,0,0,.7)',
            bg: '#ffffff',
            btnBg: 'rgba(207, 64, 53, 1)',
            btnBgOver: 'rgba(184, 50, 41, 1)',
            btnText: '#FFFFFF',
            btnTextOver: '#FFFFFF',
            close: '#111111',
            closeOver: '#CF4035',
            itemBg: '#ffffff',
            itemBgOver: 'rgba(207, 64, 53, 0.2)',
            titleColor: '#FFFFFF',
            titleBg: 'rgba(0,0,0,.7)',
            titleSize: 14
        },

        // Saved Designs module.
        savedDesigns: {
            json: './content/saved-designs.json',
            inputPlaceholder: 'Optional title',
            btn: 'Save design',
            bg: '#ffffff',
            btnBg: 'rgba(207, 64, 53, 1)',
            btnBgOver: 'rgba(184, 50, 41, 1)',
            btnText: '#FFFFFF',
            btnTextOver: '#FFFFFF',
            removable: true,
            close: '#111111',
            closeOver: '#CF4035',
            itemBg: '#ffffff',
            itemBgOver: 'rgba(207, 64, 53, 0.2)',
            titleColor: '#FFFFFF',
            titleBg: 'rgba(0,0,0,.7)',
            titleSize: 12,
            inputBg: '#ffffff',
            inputText: '#000000',
            inputBorder: '#e2e2e2',
            inputBorderFocus: '#CF4035',
            canSave: true,
            cooldownSeconds: 10,
            loginText: 'Log in to save',
            disabledText: 'Disabled for {seconds}'
        },

        // Layers list colors.
        layers: {
            bg: '#ffffff',
            canEdit: true,
            itemBg: 'rgba(207, 64, 53, 0)',
            itemBgOver: 'rgba(207, 64, 53, 0.1)',
            icon: '#333333',
            iconOver: '#CF4035'
        },

        // Add Text module defaults and colors.
        addText: {
            json: './content/text.json',
            btn: 'Add text',
            price: 0,
            removable: true,
            boundingBoxMode: 'clipping',
            canDuplicate: true,
            colorPicker: true,
            colors: ['#3858e9', '#B65A3C', '#D8A24A', '#C2413A', '#111111'],
            canReset: true,
            canMove: true,
            canRotate: true,
            canResize: true,
            canDelete: true,
            canReorder: true,
            font: 'Roboto',
            size: 30,
            lineH: 1,
            letter: 0,
            curveType: 'normal',
            radius: 0,
            curve: 0,
            offset: 0,
            fill: '#ffffff',
            stroke: null,
            strokeW: 0,
            bg: '#ffffff',
            priceColor: '#FFFFFF',
            priceBg: 'rgba(0, 0, 0, .7)',
            btnBg: 'rgba(207, 64, 53, 1)',
            btnBgOver: 'rgba(184, 50, 41, 1)',
            btnText: '#FFFFFF',
            btnTextOver: '#FFFFFF',
            inputBg: '#ffffff',
            inputText: '#000000',
            inputBorder: '#d2d2d2'
        },

        // Add Images module defaults and colors.
        addImg: {
            json: './content/images.json',
            btn: 'Add images',
            price: 0,
            removable: true,
            boundingBoxMode: 'clipping',
            colorPicker: true,
            colors: ['#CF4035', '#287D8E', '#F0E9DD', '#343638', '#FFFFFF'],
            canDuplicate: true,
            canReset: true,
            canMove: true,
            canRotate: true,
            canResize: true,
            canDelete: true,
            canReorder: true,
            priceColor: '#FFFFFF',
            priceBg: 'rgba(0,0,0,.7)',
            bg: '#ffffff',
            dropBg: '#f6f6f6',
            dropBorder: '#d2d2d2',
            dropText: 'Drop your images here',
            dropColor: '#000000',
            btnBg: 'rgba(207, 64, 53, 1)',
            btnBgOver: 'rgba(184, 50, 41, 1)',
            btnText: '#FFFFFF',
            btnTextOver: '#FFFFFF',
            close: '#111111',
            closeOver: '#CF4035',
            itemBg: '#ffffff',
            itemBgOver: 'rgba(207, 64, 53, 0.2)'
        },

        // Add Shapes module. Uses built-in SVG shapes.
        addShapes: {
            json: './content/shapes.json',
            price: 4,
            priceColor: '#FFFFFF',
            priceBg: 'rgba(0,0,0,.7)',
            fill: '#000000',
            colors: ['#3858e9', '#B65A3C', '#D8A24A', '#C2413A', '#000000'],
            colorPicker: true,
            removable: true,
            boundingBoxMode: 'clipping',
            canDuplicate: true,
            canReset: true,
            canMove: true,
            canRotate: true,
            canResize: true,
            canDelete: true,
            canReorder: true,
            bg: '#ffffff',
            close: '#111111',
            closeOver: '#CF4035',
            itemFill: '#000000',
            itemBg: '#ffffff',
            itemBgOver: 'rgba(207, 64, 53, 0.2)'
        },

        // Artwork Library module. Supports categories and search.
        artworkLibrary: {
            json: './content/artwork_library.json',
            searchPlaceholder: 'Search artwork',
            allCategoriesLabel: 'All',
            price: 4,
            removable: false,
            boundingBoxMode: 'clipping',
            colorPicker: true,
            colors: ['#3858e9', '#B65A3C', '#D8A24A', '#C2413A', '#111111'],
            canDuplicate: true,
            canReset: true,
            canMove: true,
            canRotate: true,
            canResize: true,
            canDelete: true,
            canReorder: true,
            notFoundText: 'No artwork found',
            bg: '#ffffff',
            priceColor: '#FFFFFF',
            priceBg: 'rgba(0,0,0,.7)',
            close: '#111111',
            closeOver: '#CF4035',
            itemBg: '#ffffff',
            itemBgOver: 'rgba(207, 64, 53, 0.2)',
            searchBg: '#ffffff',
            searchText: '#000000',
            searchBorder: '#e2e2e2',
            searchBorderFocus: '#CF4035',
            catBg: 'rgba(0, 0, 0, 0.02)',
            catBgOver: 'rgba(207, 64, 53, 0.1)',
            catText: '#000000',
            catTextOver: '#CF4035'
        },

        // Canvas background and object-control buttons.
        canvas: {
            showDimensions: true,
            dimenstionsBg: 'rgba(0,0,0,.9)',
            dimenstionsText: '#FFFFFF',
            bg: '#FFFFFF',
            icon: '#000000',
            iconOver: '#ffffff',
            iconBg: 'rgba(255, 255, 255, 1)',
            iconBgOver: 'rgba(207, 64, 53, 1)',
            zoomLevels: [1, 1.25, 1.5, 2],
            zoomStep: 0.01,
            rulerBg: 'rgba(255, 255, 255, 0.72)',
            rulerText: 'rgba(0, 0, 0, 0.68)',
            rulerTick: 'rgba(207, 64, 53, 1)',
            rulerTickMajor: 'rgba(0, 0, 0, 0.65)',
            rulerBorder: 'rgba(0, 0, 0, 0.2)',
            toggleRuler: true,
        },

        // Floating object toolbar.
        toolbar: {
            buttons: [
                {label: 'Color', button: 'color'},
                {label: 'Material', button: 'material'},
                {label: 'Transform', button: 'transform'},
                {label: 'Text', button: 'textFormat'},
                {label: 'Curved Text', button: 'curvedText'},
                {label: 'Align', button: 'position'},
                {label: 'Reset', button: 'reset'},
                {label: 'Duplicate', button: 'duplicate'},
                {label: 'Remove', button: 'delete'}
            ],
            align: [
                {label: 'Top', button: 'top'},
                {label: 'Middle', button: 'middle'},
                {label: 'Bottom', button: 'bottom'},
                {label: 'Left', button: 'left'},
                {label: 'Center', button: 'center'},
                {label: 'Right', button: 'right'}
            ],
            curve: [
                {label: 'Normal Text', button: 'normal'},
                {label: 'Curved Up', button: 'curvedUp'},
                {label: 'Curved Down', button: 'curvedDown'},
                {label: 'Oblique', button: 'oblique'},
                {label: 'Bridge', button: 'bridge'},
                {label: 'Radius', button: 'radius'},
                {label: 'Font Size', button: 'fontSize'},
                {label: 'Letter Spacing', button: 'letterSpacing'},
                {label: 'Curve', button: 'curve'},
                {label: 'Offset', button: 'offset'}
            ],
            color: [
                {label: 'Fill Color', button: 'fill'},
                {label: 'Transparency', button: 'transparency'},
                {label: 'Shadow', button: 'shadow'},
                {label: 'Blur', button: 'blur'},
                {label: 'Stroke Color', button: 'strokeColor'},
                {label: 'Stroke Width', button: 'strokeWidth'},
                {label: 'Font Family', button: 'fontFamily'},
                {label: 'Font Size', button: 'fontSize'},
                {label: 'Line Height', button: 'lineHeight'},
                {label: 'Letter Spacing', button: 'letterSpacing'},
                {label: 'Offset X', button: 'offsetX'},
                {label: 'Offset Y', button: 'offsetY'}
            ],
            transform: [
                {label: 'Scale X', button: 'scaleX'},
                {label: 'Scale Y', button: 'scaleY'},
                {label: 'Rotate', button: 'rotate'},
                {label: 'Skew X', button: 'skewX'},
                {label: 'Skew Y', button: 'skewY'},
                {label: 'Flip horizontal', button: 'flipHorizontal'},
                {label: 'Flip vertical', button: 'flipVertical'}
            ],
            objectToolbarMode: 'floating', // 'floating' or 'fixed'
            toggled: false, // Opens the first available fixed-toolbar panel when an object is selected.
            maxW: 393,
            textMaxW: 460,
            maxScale: 6,
            maxSkew: 20,
            defRadius: 40,
            bg: 'rgba(255, 255, 255, 1)',
            icon: '#000000',
            iconOver: '#CF4035',
            sliderBg: '#f6f6f6',
            sliderBorder: '#e2e2e2',
            sliderFill: '#CF4035',
            sliderHandle: '#f6f6f6',
            sliderInput: '#f6f6f6',
            sliderHandleBorder: '#CF4035',
            back: 'Back',
            secBg: 'rgba(0, 0, 0, 0.02)',
            secBgOver: 'rgba(207, 64, 53, 0.1)',
            secIcon: '#000000',
            secIconOver: '#CF4035',
            secBorder: '#e2e2e2'
        }

    });
}

The id value also becomes the global API instance name. With id: 'fwdi3dpd0', methods are available through window.fwdi3dpd0 or directly as fwdi3dpd0.

<button type="button" onclick="fwdi3dpd0.export()">Export</button>
<button type="button" onclick="fwdi3dpd0.destroy()">Destroy</button>

Multiple products

The product setting accepts one JSON path, one literal product object, or an array containing any combination of paths and objects. Only the selected product is rendered, the remaining entries are kept as product references for the Products module.

product: [
    './products/shirt/product.json',
    './products/cap/product.json',
    literalProductObject
],
productIndex: 0 // Also accepts a product title.

Each product keeps its own canvas size, views, printing boxes, base price and initial state. Switching products reloads that product from its original data and resets zoom, view controls, selection, history and synchronized-view state.

Responsive layout

The editor measures its real parent container, not only the browser viewport. The module bar can be placed on the left or bottom. At compact widths the module content fills the available editor area and the object toolbar is attached to the bottom of the browser for reliable touch access.

maxW, resizeW, maxH and minH control the outer editor. Per-view canvas_width_mm and canvas_height_mm control the physical workspace in millimeters. Set the mapped surface's physical size separately with view_width_mm and view_height_mm. See physical view sizing.


Settings

Settings use short grouped names to keep initialization readable. Library JSON and product JSON can override the corresponding defaults at item or element level.

Value notation: alternatives separated with | are the accepted values or types. A CSS color can be any browser-supported color such as #CF4035, rgb(...), rgba(...), hsl(...), a named color or transparent.

Core settings

Configure product sources, editor sizing, startup behavior, working units, pricing and the main appearance.

OptionType / valuesDescription
idNon-empty string (required)Unique global instance name. instance is also accepted.
productJSON URL string | literal product object | array containing eitherOne product source or an ordered list of product sources.
productIndexInteger >= 0 | exact product-title stringInitial product index or title. The legacy alias prodcutIndex is accepted.
basePriceNumber >= 0 | numeric string such as '10.00'Fallback base price. Do not include a currency symbol, display currency comes from currency. A product JSON basePrice takes precedence.
startAtViewInteger >= 0 | exact view-title stringFallback initial view. A product JSON startAtView takes precedence.
display'responsive' | 'afterparent' | 'lightbox'Controls how the editor is displayed. Lightbox mode uses the dedicated lightbox settings below. displayType is an alias.
parentNon-empty element-ID string, without #ID of the required editor holder. In lightbox mode it holds only the generated launch button. parentId is an alias.
maxW, resizeWFinite number > 0 (CSS pixels) | percentage string in lightbox modeMaximum editor width and width at which automatic scaling starts. Lightbox values such as '90%' are relative to browser width.
maxH, minHFinite number > 0 (CSS pixels) | percentage string in lightbox modeMaximum and minimum editor heights. Lightbox values such as '90%' are relative to browser height.
canvasW, canvasHFinite number > 0 (canvas pixels)Fallback internal pixel size for legacy views without per-view dimensions. New products use canvas_width_mm/canvas_height_mm and internal rendering metadata.
initVisibletrue | falseWhen true, initialization waits until the editor enters the viewport.
autoScaletrue | falseScales the editor height with its responsive width.
unit'mm' | 'cm' | 'inch' | 'px'Fallback working unit for measurements and production output metadata. A root unit in the active product JSON overrides it. The ruler temporarily uses pixels while boundingBoxEditable is enabled.
bleedFinite number >= 0, in millimetersGlobal physical bleed per edge. A view JSON options.bleed overrides it, an explicit value of 0 disables bleed.
rulerPosition'canvas' | 'printingBox'2D ruler placement around the full workspace or the print box. A view's rulerMode does not override this position. See Ruler modes and spacing.
rulerFixedtrue | falseStarts the ruler visible. The Ruler action can still hide it.
currencyString, for example '$', '€' or 'USD'Currency symbol or code used for base, layer and library prices.
bg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentMain editor background.
dropShadowValid CSS box-shadow string | 'none'Outer editor shadow.

Configure the full-browser overlay, generated Customize button and close button used by display: 'lightbox'. Closing only hides the viewer, so reopening it preserves the current product, edits and history. When both maxW and maxH are '100%', the close button becomes the final button in the action bar's right section instead of overlapping it.

lightbox: {
    label: 'Customize',
    bg: 'rgba(0, 0, 0, .7)',
    btnBg: '#CF4035',
    btnBgOver: 'rgba(184, 50, 41, 1)',
    btnText: '#FFFFFF',
    btnTextOver: '#FFFFFF',
    closeW: 34,
    closeH: 34,
    closeBg: '#CF4035',
    closeBgOver: '#FFFFFF',
    closeIcon: '#FFFFFF',
    closeIconOver: '#CF4035'
}
OptionType / valuesDescription
labelNon-empty stringText displayed in the generated launch button. Defaults to 'Customize'.
bgCSS colorBackground covering the browser while the lightbox is open.
btnBg, btnBgOverCSS colorCustomize button background in its normal and hover states.
btnText, btnTextOverCSS colorCustomize button text in its normal and hover states.
closeW, closeHFinite number >= 24 (CSS pixels)Close-button width and height.
closeBg, closeBgOverCSS colorClose-button background in its normal and hover states.
closeIcon, closeIconOverCSS colorClose icon in its normal and hover states.

Bounds and snapping

Control object boundaries, clipping, editable printing boxes and center-snapping behavior.

OptionType / valuesDescription
boundingBoxMode'none', 'inside', 'clipping', an object title such as 'Bounding Box', or an array of these valuesFallback bounds used when an item or module does not define its own mode. Use one value for all products, or an array in the same order as product, one value per product. An object title uses that view object as the mask.
boundingBoxColor#hex | rgb() | rgba() | hsl() | hsla() | named | transparentPrinting-box guide and editable bounding-box color.
showPrintingBoxtrue | falseShows the dashed printing-box guide. Hiding it does not change clipping, stored geometry or production export.
boundingBoxEditabletrue | falseControls whether the current view printing box can be selected, moved and resized. With physical view dimensions, the ruler and X, Y, W and H inputs use the selected product unit, and crop width and height are independent. Legacy print-only views retain pixel inputs and print-ratio normalization.
boundingBoxInputstrue | falseShows editable X, Y, width and height fields in both viewports. Values are whole numbers in the selected ruler unit for physically calibrated views, or canvas pixels for legacy print-only views. Press Enter or leave the field to apply an edit. Escape cancels it. Decimal entries are rounded on commit, and width and height must be positive. Typing is not overwritten by rendering updates. Internal canvas coordinates retain their precision, and changing one field does not round the others.
boundingBoxInputsBg, boundingBoxInputsText#hex | rgb() | rgba() | hsl() | hsla() | named | transparentBounding-box input panel background and text.
boundingBoxInputsFieldBg, boundingBoxInputsBorder, boundingBoxInputsBorderFocus#hex | rgb() | rgba() | hsl() | hsla() | named | transparentBounding-box field background, normal border and focused border.
toggleSnapToCentertrue | falseInitial global center-snapping state. The action bar toggle can change it at runtime.
snapColor#hex | rgb() | rgba() | hsl() | hsla() | named | transparentVertical and horizontal snap-guide color.
snapThresholdFinite number >= 0 (canvas pixels)Distance from center at which objects and editable bounds snap.

Supported boundingBoxMode values

ValueBehavior
noneNo boundary restriction or clipping is applied. The object can be moved and rendered anywhere on the canvas.
insideUses the current view's rectangular printing_box as a movement boundary and keeps the object's transformed bounds inside it. Content is not masked.
clippingUses the current view's rectangular printing_box as a clipping area. Parts of the object extending outside the box are hidden.
An element title, such as Bounding Box or OverlayFinds that element in the active view and uses its visible SVG shape or PNG alpha as the clipping mask. Title matching is case-insensitive. If no matching element exists in a view, no element-based mask can be applied there.
Guide precedence: when the active view defines printing_box, the red dotted guide always represents that rectangle. An object-title mode can still use an SVG shape or PNG alpha as the actual clipping mask, but it does not replace the visual printing-box guide. Without a printing box, the guide falls back to the selected object's applicable bounds.

A single value applies to every configured product:

boundingBoxMode: 'clipping'

For multiple products, array positions follow the order of the product setting. In this example the first product uses its printing box, while the second uses the visible shape of an element titled Bounding Box:

product: [
    './products/shirt/product.json',
    './products/cap/product.json'
],
boundingBoxMode: ['clipping', 'Bounding Box']
Fallback: if the current product has no corresponding non-empty array entry, the first non-empty mode in the array is used. If no usable mode is supplied, the default is clipping.

Keyboard settings

Enable keyboard controls and configure movement, rotation and scaling increments.

OptionType / valuesDescription
addKeyboardSupporttrue | falseEnables editor shortcuts. Transform shortcuts are ignored when no editable object is selected.
keuboardMoveNudgeAmmountFinite number >= 0 (pixels)Arrow-key movement. The misspelled key is retained for compatibility.
keyboardMoveNudgeLargeAmmountFinite number >= 0 (pixels)Shift + Arrow movement.
keyboardRotateAmmountFinite number >= 0 (degrees)Alt + Left/Right rotation step.
keyboardRotateLargeAmmountFinite number >= 0 (degrees)Shift + Alt + Left/Right rotation step.
keyboardScaleAmmountFinite number > 0, typically 0.001 to 0.1Normal keyboard scale increment.
keyboardScaleLargeAmmountFinite number > 0, typically 0.01 to 1Large keyboard scale increment.

Loader and fonts

Customize the loading screen and define the system, web and custom fonts available for text.

Group / optionType / valuesDescription
loader.textString, including an empty stringText shown while product assets and fonts load.
loader.bg, loader.iconBg, loader.icon#hex | rgb() | rgba() | hsl() | hsla() | named | transparentLoader overlay, icon background and icon color.
font.defFont-family string matching a configured or installed fontDefault family for text when the JSON does not specify one.
font.list{label, family}[] | {label, family, url}[] | {label, family, files: [{url, weight, style}]}[]Fonts shown in the text toolbar. Supports system fonts, CSS URLs and custom font files with multiple weights/styles.
font: {
    def: 'Roboto',
    list: [
        {label: 'Arial', family: 'Arial'},
        {
            label: 'Roboto',
            family: 'Roboto',
            url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'
        },
        {
            label: 'MD Nichrome',
            family: 'MDNichrome',
            files: [
                {url: './content/fonts/MDNichrome-Regular.otf', weight: 400, style: 'normal'},
                {url: './content/fonts/MDNichrome-Bold.otf', weight: 700, style: 'normal'}
            ]
        }
    ]
}
Font loading: configured web and custom fonts are prepared before editing so each dropdown item can be previewed in its actual family. Keep the list intentional, hundreds of remote fonts will increase startup time.

Dialog settings

Customize confirmation, error and information dialogs, including their content, colors and buttons.

OptionType / valuesDescription
dialog.okNon-empty stringDefault acknowledgement button label.
dialog.overlay, dialog.bg, dialog.text#hex | rgb() | rgba() | hsl() | hsla() | named | transparentFull overlay, dialog panel and content text colors.
dialog.btn{bg, bgOver, text, textOver}, each: #hex | rgb(a) | hsl(a) | named | transparentPrimary dialog button states.
dialog.close{icon, iconOver}, each: #hex | rgb(a) | hsl(a) | named | transparentClose-icon states.
dialog.del{show: true | false, text: string, btn: string}Delete confirmation visibility, message and button label.
dialog.reset{show: true | false, text: string, btn: string}Object/product reset confirmation visibility, message and button label.
dialog.info{maxW: number > 0, html: HTML string}Custom help/info window. HTML, links, images and structured shortcut lists are accepted.

Action bar

The action bar is divided into equal left, center and right arrays. Button order is exactly the order in actionBar.nav. Remove an entry to hide that action.

This video uses Iza Product Designer (the 2D edition), but the features shown also apply to Iza 3D Product Designer & Product Builder Customizer. See the 2D documentation for reference.

OptionType / valuesDescription
actionBar.nav[leftButtons[], centerButtons[], rightButtons[]], each item {label: string, button: actionName}Exactly three arrays. Supported action names are listed below this table.
actionBar.bg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentAction bar background.
actionBar.icon, actionBar.iconOver, actionBar.iconDisabled#hex | rgb() | rgba() | hsl() | hsla() | named | transparentNormal, hover and disabled button colors.
actionBar.toolTip.bg, actionBar.toolTip.text#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDocument-level tooltip colors.
actionBar.leftList, actionBar.rightListtrue | falseForces a File/More dropdown. Even when false, a side automatically becomes a list if its buttons do not fit.
actionBar.leftlistLabel, actionBar.rightlistLabelString, including an empty stringLabels next to the left menu and right more icons.
actionBar.leftListBg, actionBar.rightListBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDropdown window backgrounds.
actionBar.leftListIcon, actionBar.leftListIconOver, actionBar.rightListIcon, actionBar.rightListIconOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDropdown trigger and item colors.
actionBar.leftListBorder, actionBar.rightListBorder#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDropdown borders.

Supported actionBar.nav[].button values: 'savedDesign', 'savedDesigns', 'preview', 'print', 'download', 'productionExport', 'createProduct', 'undo', 'redo', 'ruler', 'zoom', 'fullscreen', 'snapToCenter', 'prevView', 'nextView', 'reset', 'info', 'export' and 'done'. Fullscreen expands the complete designer to the browser viewport and automatically returns to its configured size when the browser exits fullscreen.

Download and preview

Configure how customers preview the product and download the current view or all available views.

Group / optionType / valuesDescription
download.title, download.textStrings, including an empty stringDownload dialog title and introduction.
download.scope'all' | 'current'Initially selected view scope.
download.all, download.currentNon-empty stringsScope button labels.
download.maxWFinite number > 0 (CSS pixels)Maximum download dialog width.
download.bg, download.bgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentFormat/scope button normal and hover backgrounds.
download.formats{label: string, button: 'png' | 'jpg' | 'svg' | 'pdf', description: string}[]Ordered download-format definitions. Remove an entry to hide that format.
preview.overlay#hex | rgb() | rgba() | hsl() | hsla() | named | transparentFull-browser preview overlay.
preview.title, preview.itemText#hex | rgb() | rgba() | hsl() | hsla() | named | transparentPreview title and view label colors.
preview.itemBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentView preview card background.
preview.close, preview.closeOver, preview.scrollbar#hex | rgb() | rgba() | hsl() | hsla() | named | transparentClose icon and responsive scrollbar colors.

Views

In the 3D edition, a product view connects an editable 2D design to a part of the GLB model, such as the front, back or sleeve. It stores the mesh binding, UV surface, material and saved camera as well as the design layers and print settings. Switching views changes the active design in both the 2D and 3D windows, including in split layouts.

Create Product generates the initial views from the model's meshes. Each part needs usable UVs to generate an accurate 2D silhouette and accept artwork. Parts without usable UV silhouettes start with content editing disabled, allowing color and material changes only.

The constructor's views settings below control navigation appearance. The product JSON's views array defines the actual product parts. See Product JSON for a complete example and Mapping and texture reference for mesh and camera settings.

OptionType / valuesDescription
views.pos'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight'View navigation position.
views.bg, views.border#hex | rgb() | rgba() | hsl() | hsla() | named | transparentNavigation background and button border.
views.icon, views.iconOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentNormal and hover icon colors.
views.nav.maxW, views.nav.hFinite number > 0 (CSS pixels)Navigation maximum width and height.
views.nav.bottomGap, topGap, sideGapFinite number >= 0 (CSS pixels)Navigation offsets.
views.grid.bg, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentFull view-grid window and thumbnail colors.

Select a thumbnail, use Previous/Next or click a selectable bound part of the model to change the active view. Switching to a different view moves the 3D camera to its saved position, target, up and fov, resetting its zoom in the same transition. Set the duration in seconds with that view's model3D.camera.cameraTransitionDuration. Clicking a part or design element in the already active view keeps the current camera position. The small secondary viewport must be enlarged before selecting or editing objects.

The view navigation is automatically hidden when the selected product contains fewer than two visible views. Previous and next wrap at either end and skip options.hidden views. The current-view label uses the visible order, while API indexes refer to the original views array.

Set options.hidden: true to keep a bound part in the model without a selectable view or thumbnail entry. Set options.disableContentEditing: true for a visible view that permits only color, material and allowed reset actions. Neither setting removes the model geometry. See Hidden and appearance-only views for synchronization and editing restrictions.

views.grid.itemText and views.grid.itemTextOver control normal and selected/hover thumbnail labels. The current demo uses white selected text. View thumbnail files come from each view's thumbnail URL, while generated 3D preview/download images use its saved camera.

Modules

Configure the module bar, its position and the tools customers can open while customizing a product.

For model preparation, naming and grouped view settings, see Build: interchangeable parts.

This video uses Iza Product Designer (the 2D edition), but the features shown also apply to Iza 3D Product Designer & Product Builder Customizer. See the 2D documentation for reference.

OptionType / valuesDescription
modules.buttons{label: string, button: 'products' | 'build' | 'savedDesigns' | 'layers' | 'text' | 'images' | 'shapes' | 'artworkLibrary'}[]Ordered module definitions. Remove an entry to hide that module. Build also requires a configured builder product.
modules.pos'left' | 'bottom'Desktop module-bar position.
modules.allowToggletrue | falseAllows the active module button to show/hide its content window.
modules.toggledtrue | falseWhether the module panel opens automatically. False keeps it closed even when a product specifies startModule. Module buttons can still open it manually. Compact layouts start closed.
modules.bg, modules.border, modules.panelBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentBar, shared border and content panel backgrounds.
modules.icon, modules.iconOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentModule icon and label normal/hover colors.
modules.btnBg, modules.btnBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentModule button normal/hover backgrounds.

Products and saved designs

Configure product selection and saved-design management, including thumbnails, titles and removal controls.

Saved designs can be loaded from savedDesigns.json: './content/saved-designs.json'.

Group / optionType / valuesDescription
products.bg, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentProducts window and preview-card colors.
products.titleColor, titleBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentProduct title text and strip background.
products.titleSizeFinite number > 0 (CSS pixels)Product-title font size.
products.priceColor, priceBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentOptional price badge colors.
products.btnBg, btnBgOver, btnText, btnTextOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentShared product action button states.
products.close, closeOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentOptional item close button states.
savedDesigns.jsonJSON URL string | JSON source string | literal saved-designs objectInitial saved-design collection. Runtime saves stay in memory and are dispatched for server persistence.
savedDesigns.inputPlaceholder, btnStrings, including an empty placeholderOptional title placeholder and Save Design label.
savedDesigns.canSavetrue | falseSet this from your authentication state. When false, saving is disabled and loginText is shown.
savedDesigns.cooldownSecondsInteger >= 0Seconds before another design can be saved. The default is 10.
savedDesigns.loginText, disabledTextStringsDisabled button labels. Use {seconds} in the cooldown label.
savedDesigns.removabletrue | falseGlobal fallback for close/delete buttons, individual JSON entries can override it.
savedDesigns.bg, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentWindow and thumbnail states.
savedDesigns.btnBg, btnBgOver, btnText, btnTextOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentSave button states.
savedDesigns.close, closeOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDelete icon states.
savedDesigns.titleColor, titleBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentMultiline title/date overlay colors.
savedDesigns.titleSizeFinite number > 0 (CSS pixels)Saved-design title/date font size.
savedDesigns.inputBg, inputText, inputBorder, inputBorderFocus#hex | rgb() | rgba() | hsl() | hsla() | named | transparentSaved-design title input states.

Layers

Configure the layers panel, layer editing and the visual states of layer items and controls.

OptionType / valuesDescription
layers.bg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentLayers window and text-editor background.
layers.canEdittrue | falseAllows layer names to be edited on double click. Text-layer content remains synchronized with canvas text.
layers.itemBg, layers.itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentLayer normal and selected/hover backgrounds.
layers.icon, layers.iconOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentLayer action normal and hover colors.

Add Text

Configure text templates, default typography, object permissions, pricing and module appearance.

The demo text-template library is loaded from addText.json: './content/text.json'.

This video uses Iza Product Designer (the 2D edition), but the features shown also apply to Iza 3D Product Designer & Product Builder Customizer. See the 2D documentation for reference.

OptionType / valuesDescription
addText.jsonJSON URL string | JSON source string | literal text-library objectText-template library.
btnNon-empty stringAdd Text button label.
priceNumber >= 0 | currency-free numeric stringDefault price assigned to newly added text. When provided, the text template JSON price overrides this value.
removabletrue | falseDefault removal permission for text templates.
colorPickertrue | falseAllows the custom color picker. When false, preset swatches remain available.
colorsArray of #hex, rgb(a), hsl(a), named or transparent values | empty arrayDefault fill swatches for newly added text. A template-level colors array overrides this palette.
canDuplicate, canReset, canMove, canRotate, canResize, canDelete, canReorderEach value: true | falseDefault permissions for text created from the module.
fontFont-family string matching font.list or an installed system fontDefault text family.
fontWeight'normal' | 'bold' | numeric weight such as 400, 700, 900Text template font weight. For custom fonts, the value should match one of the weights registered in font.list.
sizeFinite number > 0Default font size.
lineHFinite number > 0Default line-height multiplier, for example 1.2.
letterFinite numberDefault Fabric character spacing, negative, zero and positive values are accepted.
curveType'normal' | 'curvedUp' | 'curvedDown' | 'oblique' | 'bridge'Default text geometry.
radiusNumber from 0 to 300Default curved-up/down radius.
curveNumber from -100 to 100Default oblique/bridge deformation.
offsetNumber from 0 to 100Default internal oblique/bridge offset.
fill#hex | rgb(a) | hsl(a) | named | transparent | empty stringDefault text fill, an empty string requests no fill.
stroke#hex | rgb(a) | hsl(a) | named | transparent | null | empty stringDefault text stroke.
strokeWFinite number >= 0Default stroke width, 0 disables the stroke.
bg, priceColor, priceBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentWindow and price badge.
btnBg, btnBgOver, btnText, btnTextOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentAdd Text button states.
inputBg, inputText, inputBorder#hex | rgb() | rgba() | hsl() | hsla() | named | transparentLive text-area styling.
close, closeOver, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentTemplate close icon and thumbnail states.

Add Images

Configure the image library and upload area, including defaults, permissions, pricing and visual states.

The demo image library is loaded from addImg.json: './content/images.json'.

OptionType / valuesDescription
addImg.jsonJSON URL string | JSON source string | literal image-library objectInitial image/SVG library.
btnNon-empty stringBrowse button label.
priceNumber >= 0 | currency-free numeric stringDefault price assigned to newly added media. When provided, the image library JSON price overrides this value.
removabletrue | falseDefault removal permission for image-library entries.
colorPickertrue | falseAllows the custom color picker. When false, preset swatches remain available.
colorsArray of #hex, rgb(a), hsl(a), named or transparent values | empty arrayDefault tint/fill swatches for added PNG, JPG and SVG media. An image-level colors array overrides this palette.
canDuplicate, canReset, canMove, canRotate, canResize, canDelete, canReorderEach value: true | falseDefault permissions for added media.
bg, dropBg, dropBorder, dropColor#hex | rgb() | rgba() | hsl() | hsla() | named | transparentWindow, drop-area background, border and text colors.
dropTextString, including an empty stringText displayed inside the fixed drag/drop area.
priceColor, priceBg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentPrice badge.
btnBg, btnBgOver, btnText, btnTextOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentBrowse button states.
close, closeOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentRemovable library-item close icon.
itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentThumbnail holder and animated overlay.

Add Shapes

Configure the shape library, default colors, permissions, pricing and thumbnail appearance.

The demo shapes library is loaded from addShapes.json: './content/shapes.json'.

OptionType / valuesDescription
addShapes.jsonJSON URL string | JSON source string | literal shape-library objectShape library. Item order in JSON is display order.
priceNumber >= 0 | currency-free numeric stringGlobal fallback price for shapes. A positive per-shape price overrides it, 0 or an omitted value inherits it.
removabletrue | falseDefault removal permission for shape-library entries.
colorPickertrue | falseAllows the custom color picker. When false, preset swatches remain available.
fill#hex | rgb(a) | hsl(a) | named | transparent | empty stringDefault canvas shape fill.
colorsArray of #hex, rgb(a), hsl(a), named or transparent values | empty arrayDefault fill swatches for added shapes. A shape-level colors array overrides this palette.
canDuplicate, canReset, canMove, canRotate, canResize, canDelete, canReorderEach value: true | falseDefault shape permissions.
bg, itemFill, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentWindow, preview-shape fill and thumbnail states.
priceColor, priceBg, close, closeOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentPrice and removable button states.

Artwork Library

Configure categorized artwork, search behavior, object defaults, pricing and library appearance.

The demo artwork library is loaded from artworkLibrary.json: './content/artwork_library.json'.

OptionType / valuesDescription
artworkLibrary.jsonJSON URL string | JSON source string | literal artwork-library objectCategories and artwork library.
searchPlaceholder, allCategoriesLabel, notFoundTextStrings, including empty stringsSearch and category UI labels.
priceNumber >= 0 | currency-free numeric stringDefault price assigned to newly added artwork. When provided, the artwork JSON price overrides this value.
removabletrue | falseDefault removal permission for artwork entries.
colorPickertrue | falseAllows the custom color picker. When false, preset swatches remain available.
colorsArray of #hex, rgb(a), hsl(a), named or transparent values | empty arrayDefault tint/fill swatches for added artwork. An artwork-level colors array overrides this palette.
canDuplicate, canReset, canMove, canRotate, canResize, canDelete, canReorderEach value: true | falseDefault artwork permissions.
bg, itemBg, itemBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentWindow and artwork-card colors.
priceColor, priceBg, close, closeOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentPrice and removal button states.
searchBg, searchText, searchBorder, searchBorderFocus#hex | rgb() | rgba() | hsl() | hsla() | named | transparentCategory search field.
catBg, catBgOver, catText, catTextOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentCategory buttons.

Canvas

Configure the canvas appearance, object controls, dimensions, zoom behavior and ruler styling.

OptionType / valuesDescription
canvas.showDimensionstrue | falseShows rounded selected-object dimensions without covering canvas controls.
dimenstionsBg, dimenstionsText#hex | rgb() | rgba() | hsl() | hsla() | named | transparentDimension badge colors. The historical spelling is supported.
bg#hex | rgb() | rgba() | hsl() | hsla() | named | transparentCanvas background. Selection outlines are configured in the product JSON, not the page settings.
icon, iconOver, iconBg, iconBgOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentCanvas move/rotate/resize/delete control states.
zoomLevelsArray of finite numbers > 0, for example [1, 1.25, 1.5, 2]Reference zoom levels, 1 is automatically included.
zoomStepFinite number > 0, typically 0.01 to 0.25Zoom slider and Space + wheel increment.
toggleRulertrue | falseShows the rulers when the designer starts. The Ruler action can still toggle them.
rulerBg, rulerText, rulerTick, rulerTickMajor, rulerBorder#hex | rgb() | rgba() | hsl() | hsla() | named | transparentRuler panel, labels, minor/major ticks and border colors.

Object toolbar

Configure the actions and editing panels shown for selected objects, including colors, transforms and text tools.

OptionType / valuesDescription
toolbar.buttons{label: string, button: 'color' | 'material' | 'transform' | 'textFormat' | 'curvedText' | 'position' | 'reset' | 'duplicate' | 'delete'}[]Main object actions and display order. Material appears only for eligible mapped model surfaces.
toolbar.align{label: string, button: 'top' | 'middle' | 'bottom' | 'left' | 'center' | 'right'}[]Alignment actions and display order.
toolbar.curve{label: string, button: 'normal' | 'curvedUp' | 'curvedDown' | 'oblique' | 'bridge' | 'radius' | 'fontSize' | 'letterSpacing' | 'curve' | 'offset'}[]Curved-text modes and slider labels.
toolbar.color{label: string, button: 'fill' | 'transparency' | 'shadow' | 'blur' | 'strokeColor' | 'strokeWidth' | 'fontFamily' | 'fontSize' | 'lineHeight' | 'letterSpacing' | 'offsetX' | 'offsetY'}[]Color, shadow, text typography and stroke controls.
toolbar.transform{label: string, button: 'scaleX' | 'scaleY' | 'rotate' | 'skewX' | 'skewY' | 'flipHorizontal' | 'flipVertical'}[]Transform controls and display order.
objectToolbarMode'floating' | 'fixed'Floating toolbar near the selected object or fixed toolbar integrated into the modules panel. toolbar.mode is also accepted.
toggledtrue | falseIn fixed mode, automatically opens the first available editing panel when an object is selected. The order in toolbar.buttons determines which compatible panel opens first.
maxW, textMaxWFinite number > 0 (CSS pixels)Normal and text-object toolbar maximum widths.
maxScaleFinite number > 0Maximum scale value available in the toolbar.
maxSkewFinite number from 0 to 89 (degrees)Positive and negative skew limit.
defRadiusNumber from 0 to 300Default curved-text radius.
bg, icon, iconOver#hex | rgb() | rgba() | hsl() | hsla() | named | transparentToolbar window and primary icon colors.
sliderBg, sliderBorder, sliderFill, sliderHandle, sliderInput, sliderHandleBorder#hex | rgb() | rgba() | hsl() | hsla() | named | transparentAll object-toolbar sliders and inputs.
backString, including an empty stringBack button label in floating subwindows.
secBg, secBgOver, secIcon, secIconOver, secBorder#hex | rgb() | rgba() | hsl() | hsla() | named | transparentAlign, format, flip and other secondary button states.
Permission-driven UI: toolbar buttons and canvas controls are automatically hidden when the selected element does not allow the related action. For example, canResize: false hides scale controls, and canRotate: false hides rotation.

Working with Colors

Colors are configured independently for the interface, product layers and module content. Every color option accepts browser-supported CSS colors such as hex, RGB, RGBA, HSL, HSLA, named colors and transparent. Use RGBA or transparent when opacity is required.

Interface theme

Theme each interface area through its own settings group. Update normal, hover and selected states together so the accent remains consistent across the action bar, modules, object toolbar, canvas controls, rulers and dialogs.

const themeSettings = {
    actionBar: {
        bg: '#CF4035',
        icon: '#ffffff',
        iconOver: '#ffe0e0'
    },
    modules: {
        iconOver: '#CF4035',
        btnBgOver: 'rgba(207, 64, 53, .1)'
    },
    toolbar: {
        iconOver: '#CF4035',
        sliderFill: '#CF4035',
        sliderHandleBorder: '#CF4035'
    },
    canvas: {
        rulerTick: '#CF4035'
    },
    modelView: {
        buttonBg: 'rgba(255, 255, 255, .94)',
        buttonBgOver: '#CF4035',
        buttonIcon: '#111111',
        buttonIconOver: '#FFFFFF'
    }
};

Merge these interface groups into your constructor settings before creating the instance, preserving other settings in the same groups. This is not a runtime theme setter. Unspecified interface colors keep their defaults. Set the model background, lighting and selection outline separately in the product JSON's model3D object.

Content palettes

Text, Images, Shapes and Artwork can expose preset swatches through colors. Enable colorPicker when customers may also choose colors outside the preset palette.

addShapes: {
    fill: '#000000',
    colorPicker: true,
    colors: ['#CF4035', '#2f5f8f', '#111111']
}

A palette provides selectable choices. Its first color is not applied automatically. Individual templates and library items can define their own palette, which takes precedence over the module defaults for that item.

SVG and image colors

  • SVG: editable fills remain sharp at every size. Multi-path SVG files can preserve their original colors while exposing configured choices.
  • White PNG: works as an accurate tint mask and displays the selected color cleanly.
  • Shaded PNG: tinting can preserve dark shading and texture, but the result depends on the source pixels.
  • JPG: use mainly for photographs because recoloring affects the complete rectangular image.

Color changes are stored with the design element and reproduced in previews, saved designs and exports. The original source asset is not modified.

Color precedence

  1. The color saved on the current view element.
  2. The individual template or library-item color.
  3. The module default fill, tint or palette.
  4. The product or constructor fallback.

When color synchronization is enabled, matching product elements can share a customer color choice across views. Unrelated elements retain their own saved colors.


Editable Printing Box

The editable printing box is a product-setup tool for positioning and sizing the real customizable area directly on the canvas. Enable it when preparing a product, then export the product JSON after the box is correct.

Required view data: every editable view needs an options.printing_box. Its position and size use canvas pixels, including fractions. Set view_width_mm and view_height_mm to calibrate the mapped surface. The printing box then determines the physical print area.

1. Define the view and print area

This example assumes a named mapped base with displayed bounds of 600 × 400 px. Its physical size is 300 × 200 mm, so the 400 × 200 px crop represents 200 × 100 mm.

"options": {
    "canvas_width_mm": 450,
    "canvas_height_mm": 350,
    "view_width_mm": 300,
    "view_height_mm": 200,
    "print_width_mm": 200,
    "print_height_mm": 100,
    "dpi": 300,
    "bleed": 3,
    "printing_box": {
        "left": 250,
        "top": 250,
        "width": 400,
        "height": 200
    }
}

Workspace dimensions are saved in millimeters. The printing_box geometry remains internal editor coordinates, converted by the runtime. The physical view dimensions define the surface scale. print_width_mm and print_height_mm are synchronized from the crop. Changing the crop does not change the surface size or ruler calibration.

2. Enable visual editing

Add these options to the main FWDI3DPD configuration:

boundingBoxEditable: true,
boundingBoxInputs: true,
boundingBoxColor: '#FF0000'

boundingBoxEditable enables selection and transform handles in both editable viewports. boundingBoxInputs adds the floating X, Y, W and H fields, including in 3D. For physically calibrated views, the ruler and input fields use the selected product unit. Legacy print-only views use pixels in this setup mode. Views with disableContentEditing: true do not expose the box.

The fields display whole numbers. Click a field and type its value, then press Enter or leave the field to apply it. Escape restores the current value without applying the draft. Decimal entries are rounded on commit, width and height must be positive, and X and Y may be negative. Rendering updates do not interrupt typing. Dragging updates the displayed measurements, while the underlying canvas coordinates keep their precision. Editing one field leaves the others unchanged, except when a legacy print ratio links width and height.

3. Adjust and export

  1. Select: click the red dashed printing box. Its translucent fill and resize controls appear only while selected.
  2. Move: drag the selected box, use its X/Y inputs, or press an Arrow key. Shift + Arrow uses the configured large movement step.
  3. Resize: drag a resize control, change W/H, or use Ctrl/Cmd + Arrow. Add Shift for the large resize step.
  4. Center: when Snap To Center is active, the box snaps to the horizontal and vertical canvas center and displays the configured guides.
  5. Finish: click outside the canvas to deselect it, then use Product Export to save the updated printing-box values.
Crop sizing: physically calibrated views allow independent width and height, preserving fractional coordinates. Legacy files with only print dimensions retain their print-ratio lock and normalize the box height from its width. This is separate from the WordPress View Surface Size aspect-ratio lock.

boundingBoxMode and boundingBoxEditable have different jobs. The mode controls how added objects are constrained or clipped, the editable option controls whether the product administrator can modify the view's rectangular printing box.

The 3D printing-box tool includes a copy button. It copies the current rectangle for pasting into options.printing_box, it does not save the JSON automatically. Labels are configured in modelView.printingBoxControlLabels with resize, copy, copied and copyError. The 2D and 3D tools edit the same box. Use getCurrentPrintingBoxInfo() for its pixel geometry and physical-size information.


Production Export and Unit Conversion

Production Export creates a print-ready file from the customizable area of a product. It crops the design to the printing box, adds bleed when required, and generates the final file at the configured physical size and DPI.

View, workspace and print area: view_width_mm and view_height_mm define the mapped surface in whole millimeters. The canvas dimensions define the workspace in millimeters. printing_box selects the crop, whose physical print size is calculated from the surface scale.

Set the physical view size

Set views[i].options.view_width_mm and view_height_mm to the physical width and height represented by the mapped base surface. These are whole millimeters regardless of the ruler display unit. Creation, saving and product export write these values as JSON numbers, rounded to the nearest integer (minimum 1), in the order canvas_width_mm, canvas_height_mm, view_width_mm, view_height_mm. All four workspace and surface size fields use positive integer millimeters, rounded to the nearest whole number (minimum 1). The surface is the product part, not the surrounding workspace and not necessarily the printable area.

For example, 412.95841584158404 × 605.4486486486485 mm saves as 413 × 605 mm. This rounding applies to all four workspace and surface size fields. It does not round artwork transforms, printing-box coordinates or calculated millimeters-per-pixel values.

  1. Set the view size: enter the real dimensions of the mapped product surface. GLB-created values are starter estimates. Check them against your production specification.
  2. Choose the workspace: canvas_width_mm and canvas_height_mm define the workspace in millimeters. Extra workspace around a named base does not change its physical size.
  3. Place the print area: printing_box contains pixel coordinates relative to the workspace. Its physical dimensions are calculated from the view size and the mapped base bounds.
  4. Choose export resolution: DPI changes output pixel resolution, not the view size or artwork placement. Bleed is stored in millimeters per edge.

The root unit selects ruler display units: mm, cm, inch or px. Physical dimensions are used directly, without an additional measurement multiplier.

Size the printing box

For a base measuring 600 × 400 internal pixels and a view measuring 300 × 200 mm, one pixel represents 0.5 mm on both axes. A 400 × 200 px printing box therefore represents 200 × 100 mm. Its size can change independently of the base and workspace.

With physical view dimensions, fractional printing-box coordinates are preserved. Editing its width or height does not recalibrate the ruler. In WordPress, the print-area controls accept the selected ruler unit and convert to internal coordinates automatically.

View and production options

This example assumes model3D.mapping.surface identifies a base whose displayed bounds are 600 × 400 px. The larger workspace provides room around that base.

"options": {
    "canvas_width_mm": 450,
    "canvas_height_mm": 350,
    "view_width_mm": 300,
    "view_height_mm": 200,
    "printing_box": {
        "left": 250,
        "top": 250,
        "width": 400,
        "height": 200
    },
    "print_width_mm": 200,
    "print_height_mm": 100,
    "dpi": 300,
    "bleed": 0
}

print_width_mm and print_height_mm describe the print crop, not the entire view. When both physical view dimensions are supplied, the runtime synchronizes print dimensions from the crop and the surface scale. To change the physical product part, edit the view dimensions. To change the printable area in standalone JSON, edit printing_box.

Workspace, surface and output units

ValueUnitPurpose
view_width_mm, view_height_mmMillimetersPhysical size of the mapped base surface, before the root unit multiplier. Supply both positive integer values.
canvas_width_mm, canvas_height_mmWhole millimetersPhysical workspace dimensions, matching the backend controls. Padding does not change the physical size of a named base.
printing_boxInternal pixelsPosition and size of the printable crop. Fractions are allowed.
print_width_mm, print_height_mmMillimetersPrint-area dimensions synchronized from its crop when physical view dimensions exist and saved as positive integer JSON numbers, not strings. Internal crop coordinates retain their precision.
unitmm, cm, inch or pxDisplay unit for rulers and authoring controls.
dpiPixels per inchRaster export detail. Zero uses the crop's internal pixel resolution.
bleedMillimeters per edgeExtra physical area around the print crop.
mmPerPixelX = view_width_mm / loadedSurfaceWidthPx;
mmPerPixelY = view_height_mm / loadedSurfaceHeightPx;
canvasWidthPx = canvas_width_mm * loadedSurfaceWidthPx / view_width_mm;
canvasHeightPx = canvas_height_mm * loadedSurfaceHeightPx / view_height_mm;
workspaceWidthMm = canvas_width_mm;
workspaceHeightMm = canvas_height_mm;

The runtime measures the loaded base bounds once, including scale and rotation. Multiple mapped bases use their combined bounds. These measurements stay in memory, not in product JSON. A mapping without a named base uses its canvas rectangle, or the workspace when no rectangle is set. Resizing a base or artwork does not change the ruler calibration during editing. Saved physical dimensions and loaded surface geometry determine the scale after reopening.

Existing files: files without the new dimensions still use their previous millimetersPerPixel setting, or their print-size-to-printing-box conversion. Legacy ratio normalization is retained for files using only print dimensions. Saving or exporting a loaded view derives view_width_mm and view_height_mm from its existing scale, rounds them to whole millimeters, and removes the old authored conversion entry. Existing positive decimal view dimensions can still load, but saving or exporting rounds them to whole millimeters. Whole-millimeter rounding may slightly change physical measurements after reloading. Artwork coordinates retain their precision. Invalid or incomplete physical view dimensions fall back to legacy calibration.

Workspace migration: older canvas_width/canvas_height (or camelCase aliases) are interpreted as pixels. Saving a loaded view converts them to canvas_width_mm/canvas_height_mm, rounds to whole millimeters, and removes the legacy fields. Artwork transforms and printing-box coordinates retain their precision. Rounding workspace size may slightly change its outer edge. Surface pixel bounds are calculated from the loaded artwork and kept in memory. They are not authored or exported JSON fields. Workspace sizing uses those measured bounds and the physical surface dimensions.

millimetersPerPixel may still appear in measurement API results as calculated information. It is no longer a setting authors need to write into new product JSON.

Physical-size conversion formulas

There are exactly 25.4 millimeters in one inch. The renderer first converts all production values to millimeters and then calculates the final raster dimensions:

trimWidthPx = Math.round(outputWidthMm * dpi / 25.4);
trimHeightPx = Math.round(outputHeightMm * dpi / 25.4);

finalWidthPx = Math.round((outputWidthMm + bleedMm * 2) * dpi / 25.4);
finalHeightPx = Math.round((outputHeightMm + bleedMm * 2) * dpi / 25.4);

The selected display unit does not change the resulting pixel count. It only changes how the same physical measurement is presented:

centimeters = millimeters / 10;
inches = millimeters / 25.4;

// All three values describe the same physical width.
// 240 mm = 24 cm = approximately 9.449 in.

PDF page dimensions are calculated in points after rasterization:

pdfWidthPoints = finalWidthPx / dpi * 72;
pdfHeightPoints = finalHeightPx / dpi * 72;

Printing-box crop and bleed

Bleed uses the same physical scale as the configured print output. Because this project stores production sizes in print_width_mm and print_height_mm, bleed is also measured in millimeters. Resolution order is runtime API, view JSON, global constructor, then 0. The renderer maps the physical bleed proportionally into each printing-box axis:

bleedCanvasX = bleedMm / print_width_mm * printing_box.width;
bleedCanvasY = bleedMm / print_height_mm * printing_box.height;

cropLeft = printing_box.left - bleedCanvasX;
cropTop = printing_box.top - bleedCanvasY;
cropWidth = printing_box.width + bleedCanvasX * 2;
cropHeight = printing_box.height + bleedCanvasY * 2;

Every exported object is cloned, moved relative to the expanded crop origin, and scaled into the final raster canvas. Object clip paths are transformed by the same values. excludeFromExport: true removes a layer from every download, while excludeFromProductionExport: true removes it only from print-ready production files.

Every layer stores excludeFromExport explicitly in globalParameters. Its default is false, so the layer remains included. Loading an older product supplies the missing default without changing existing exclusion choices. New layers and product exports retain this setting. In WordPress, select the layer and open Layer Options > Exclude From Export to change it.

Product silhouette and background: the printing box defines a rectangular crop, but it does not paint that rectangle. Production PNG keeps transparent pixels outside rounded or irregular product shapes, preventing the editor background from appearing around products such as phone cases. JPG and PDF use white because they do not preserve the same transparent-canvas workflow. To print a solid color, add an explicit rectangle, SVG or PNG layer and leave both export-exclusion options set to false.

For objects using rectangular clipping, production export expands the clip to the bleed crop so available artwork can reach the exported image edges. Normal editor and preview rendering continue to use the printing-box trim boundary. Image-based bounding masks retain their original shape and include every source pixel whose alpha is greater than zero.

Output proportions: physical view dimensions calibrate the crop independently on each axis. Runtime outputWidth/outputHeight overrides should keep the calibrated physical crop ratio to avoid stretching. Automatic pixel-ratio correction applies only to legacy print-only files.

Worked 300-DPI example

This standalone example uses a 95 × 117 mm view with a full-workspace crop of 518 × 638 px, with no bleed. No named base is configured, so the workspace supplies the reference bounds:

{
    "unit": "cm",
    "views": [{
      "options": {
          "canvas_width_mm": 95,
          "canvas_height_mm": 117,
          "view_width_mm": 95,
          "view_height_mm": 117,
          "print_width_mm": 95,
          "print_height_mm": 117,
          "dpi": 300,
          "bleed": 0,
          "printing_box": {
              "left": 0,
              "top": 0,
              "width": 518,
              "height": 638
          }
      }
    }]
}

The physical output is 95 × 117 mm. At 300 DPI, the generated raster dimensions are:

bleedCanvasX = 0 / 95 * 518;  // 0 canvas px per edge
bleedCanvasY = 0 / 117 * 638; // 0 canvas px per edge

width  = Math.round(95 * 300 / 25.4);  // 1122 px
height = Math.round(117 * 300 / 25.4); // 1382 px

With zero bleed, the source crop remains exactly left: 0, top: 0, 518 × 638 px. The root display unit is cm, but the authoritative production values remain in millimeters.

API, overrides, and generated files

The production API accepts PNG, JPG, or PDF. By default it exports every view. Use currentViewOnly, viewIndex, or viewIndexes to select specific views.

// Export every view using its product JSON production settings.
await fwdi3dpd0.productionExport('png');

// Export only the active view.
await fwdi3dpd0.productionExport('jpg', {
    currentViewOnly: true,
    quality: 0.92,
    fileName: 'shirt-front-production.jpg'
});

// Deliberately override the configured physical size for this export only.
// Keep the override ratio aligned with the printing box.
await fwdi3dpd0.productionExport('pdf', {
    viewIndex: 0,
    outputWidth: 240,  // millimeters
    outputHeight: 400, // millimeters
    bleed: 3,          // millimeters per edge
    dpi: 300,
    outputUnit: 'cm'
});
  • A single PNG or JPG view downloads as one image.
  • Multiple PNG or JPG views are packaged into one ZIP file.
  • PDF combines the rendered production views into one PDF document.
  • jpeg is normalized to jpg.
  • Production SVG is not supported because production output is rasterized at the configured DPI.
  • Without API overrides, production uses the printing box calibrated from view_width_mm and view_height_mm. Legacy files fall back to their existing print-size calibration.
  • Runtime outputWidth, outputHeight, and bleed are interpreted as canonical millimeters. Runtime bleed overrides view JSON, which overrides the global fallback. unit/outputUnit controls converted presentation metadata.

Production validation checklist

  • Define canvas_width_mm and canvas_height_mm in millimeters for every view.
  • Define an accurate printing_box inside the canvas.
  • Set the real mapped-surface size as positive integer millimeters in view_width_mm and view_height_mm, immediately after canvas_height_mm, then position and size the printable crop.
  • Keep the output aspect ratio equal to the printing-box aspect ratio.
  • Product export preserves the configured physical output values instead of replacing them with printing-box pixels.
  • If API size overrides are used, keep their aspect ratio equal to the printing-box ratio.
  • Use a suitable DPI, typically 300 for high-quality print output.
  • Confirm with the print provider whether bleed is required and how many millimeters to use.
  • Use sufficiently large source images, increasing DPI cannot restore detail missing from a low-resolution asset.
  • Run a production export and inspect its pixel dimensions before delivering the product configuration.
Production export is different from product export: productionExport() renders print-ready PNG, JPG, or PDF files. export() creates an editable ZIP containing product.json and referenced product assets.

Product JSON

A product is portable data: product metadata, one or more views, elements and per-view production geometry. Asset paths are resolved relative to the JSON file that declares the product.

Product root

This complete shirt example comes from Create Product. It includes the product settings, both selection outlines, scene lighting, controls, texture quality, material presets, optional features and all five generated views. Each view includes its mesh binding, material, camera, Base layer and print settings. The outline colors below use the interface accent color.

Create Product packages this JSON with model.glb, the generated SVG silhouettes and the PNG thumbnails referenced below. For another model, generate its own product instead of copying these mesh names, camera positions or dimensions. Check the physical print measurements before production.

A newly created product contains Base layers only. See Element schema for added text, images, shapes and artwork. Export Product also includes the font manifest and font files when the design uses them.

{
    "title": "T-Shirt",
    "thumbnail": "preview-front_outside.png",
    "basePrice": 0,
    "startAtView": 0,
    "unit": "mm",
    "startModule": "products",
    "selectionOutline": {
        "2D": {
            "enabled": true,
            "color": "#CF4035",
            "thickness": 1,
            "opacity": 1
        },
        "3D": {
            "enabled": true,
            "color": "#CF4035",
            "thickness": 1,
            "opacity": 1
        }
    },
    "model3D": {
        "backgroundColor": "#ffffff",
        "toneMapping": "neutral",
        "exposure": 1,
        "lights": {
            "hemisphere": {
                "enabled": true,
                "skyColor": "#ffffff",
                "groundColor": "#444444",
                "intensity": 2.25
            },
            "key": {
                "enabled": true,
                "color": "#ffffff",
                "intensity": 2,
                "position": [3.5, 5, 4.5],
                "castShadow": false,
                "shadowMapSize": 1024
            },
            "fill": {
                "enabled": true,
                "color": "#ffffff",
                "intensity": 1,
                "position": [-4, 2.5, 3],
                "castShadow": false,
                "shadowMapSize": 512
            },
            "rim": {
                "enabled": true,
                "color": "#ffffff",
                "intensity": 1.5,
                "position": [0, 3, -4],
                "castShadow": false,
                "shadowMapSize": 512
            }
        },
        "source": "model.glb",
        "modelScale": 1,
        "rotationX": 0,
        "rotationY": 0,
        "rotationZ": 0,
        "viewLayout": "model",
        "showSmallViewport": true,
        "smallViewportWidth": 224,
        "smallViewportHeight": 162,
        "previewResolution": 1600,
        "selectionDragThreshold": 6,
        "textureResolution": 2048,
        "interactiveTextureResolution": 1024,
        "textureRefineDelay": 160,
        "textureAnisotropy": 8,
        "adaptiveTextureQuality": {
            "enabled": false,
            "maxResolution": 4096,
            "zoomThreshold": 1.25,
            "settleDelay": 180
        },
        "clothPhysics": {
            "enabled": false,
            "horizontal": true,
            "vertical": true,
            "strength": 0.65,
            "stiffness": 0.45,
            "damping": 0.5,
            "pinTop": 0.18,
            "maxDisplacement": 0.12
        },
        "controls": {
            "enableDamping": true,
            "dampingFactor": 0.075,
            "enablePan": true,
            "enableRotate": true,
            "enableZoom": true,
            "rotateSpeed": 0.7,
            "zoomSpeed": 0.8,
            "minPolarAngle": 0.15,
            "maxPolarAngle": 2.991592653589793,
            "autoRotate": false,
            "autoRotateSpeed": 0.8
        },
        "castShadow": false,
        "receiveShadow": false,
        "showFloor": false,
        "floorShadowColor": "#000000",
        "floorShadowOpacity": 0.18,
        "floorShadowWidth": 1.15,
        "floorShadowDepth": 0.45,
        "floorShadowSoftness": 0.72,
        "materials": {
            "enabled": true,
            "presets": [
                "original",
                "fabric",
                "matte",
                "glossy",
                "metal",
                "glass",
                "diamond",
                "plastic",
                "rubber",
                "leather",
                "ceramic"
            ],
            "presetSettings": {
                "fabric": {
                    "roughness": 0.9,
                    "sheen": 0.35,
                    "sheenRoughness": 0.8
                },
                "matte": {
                    "roughness": 0.85,
                    "metalness": 0
                },
                "glossy": {
                    "roughness": 0.18,
                    "metalness": 0,
                    "clearcoat": 1,
                    "clearcoatRoughness": 0.12
                },
                "metal": {
                    "roughness": 0.22,
                    "metalness": 1
                },
                "diamond": {
                    "roughness": 0.015,
                    "ior": 2.417,
                    "fire": 0.25,
                    "rayBounces": 5,
                    "brilliance": 1
                },
                "glass": {
                    "roughness": 0.06,
                    "transmission": 0.98,
                    "thickness": 0,
                    "ior": 1.5
                },
                "plastic": {
                    "roughness": 0.32,
                    "clearcoat": 0.25,
                    "clearcoatRoughness": 0.2,
                    "ior": 1.46
                },
                "rubber": {
                    "roughness": 0.72
                },
                "leather": {
                    "roughness": 0.58,
                    "sheen": 0.1,
                    "sheenRoughness": 0.7,
                    "clearcoat": 0.08,
                    "clearcoatRoughness": 0.5
                },
                "ceramic": {
                    "roughness": 0.48,
                    "clearcoat": 0.18,
                    "clearcoatRoughness": 0.38
                }
            },
            "environment": {
                "enabled": false,
                "intensity": 1,
                "applyToOriginal": false
            }
        }
    },
    "views": [
        {
            "title": "front_outside",
            "thumbnail": "preview-front_outside.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "front_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "material": {
                    "preset": "original",
                    "adjustments": {}
                },
                "texture": {
                    "flipY": false,
                    "preserveOriginal": true,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                },
                "camera": {
                    "position": [-0.017049684086866735, 0.0815178860726034, 1.290426671938822],
                    "target": [0.0013587239624590615, 0.008540308125866699, -0.0038930596911759766],
                    "up": [0.0017795799907414298, 0.9984141020113535, -0.056268232599924584],
                    "fov": 34,
                    "cameraTransitionDuration": 0.65,
                    "maxZoomIn": 2
                }
            },
            "elements": [
                {
                    "title": "Base",
                    "type": "image",
                    "source": "base-front_outside.svg",
                    "globalParameters": {
                        "fill": "#8a9099",
                        "colors": [
                            "#8a9099"
                        ],
                        "originX": "center",
                        "originY": "center",
                        "visible": true,
                        "opacity": 1,
                        "locked": false,
                        "showInLayers": true,
                        "colorPicker": true,
                        "canDuplicate": false,
                        "canReset": true,
                        "canMove": false,
                        "canRotate": false,
                        "canResize": false,
                        "canDelete": false,
                        "canReorder": false,
                        "syncColorType": "none",
                        "price": 0,
                        "excludeFromExport": false,
                        "excludeFromProductionExport": true,
                        "alwaysOnTop": false,
                        "removable": false,
                        "autoSelect": false,
                        "boundingBoxMode": "none",
                        "lockUniScaling": true,
                        "minScaleLimit": 0.01,
                        "shadow": {
                            "color": "#000000",
                            "blur": 0,
                            "offsetX": 0,
                            "offsetY": 0
                        }
                    },
                    "parameters2D": {
                        "left": 276,
                        "top": 400,
                        "scaleX": 1,
                        "scaleY": 1,
                        "angle": 0,
                        "autoCenter": false,
                        "preserveLayout": true,
                        "scaleMode": "fit",
                        "skewX": 0,
                        "skewY": 0,
                        "flipX": false,
                        "flipY": false
                    },
                    "parameters3D": {}
                }
            ],
            "options": {
                "hidden": false,
                "disableContentEditing": false,
                "rulerMode": "print-area",
                "rulerSpacing": {
                    "top": 0,
                    "left": 0,
                    "right": 0
                },
                "canvas_width_mm": 424,
                "canvas_height_mm": 615,
                "view_width_mm": 424,
                "view_height_mm": 615,
                "print_width_mm": 424,
                "print_height_mm": 615,
                "dpi": 300,
                "bleed": 0,
                "printing_box": {
                    "left": 0,
                    "top": 0,
                    "width": 552,
                    "height": 800
                }
            }
        },
        {
            "title": "sleeve_left_outside",
            "thumbnail": "preview-sleeve_left_outside.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "sleeve_left_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "material": {
                    "preset": "original",
                    "adjustments": {}
                },
                "texture": {
                    "flipY": false,
                    "preserveOriginal": true,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                },
                "camera": {
                    "position": [1.0728045574127836, 0.7379040916486523, -0.031449988357561465],
                    "target": [0.01846986131649905, 0.007019998779988541, -0.004579763594666942],
                    "up": [-0.569540610788688, 0.8190450438139252, -0.06920049758704017],
                    "fov": 34,
                    "cameraTransitionDuration": 0.65,
                    "maxZoomIn": 2
                }
            },
            "elements": [
                {
                    "title": "Base",
                    "type": "image",
                    "source": "base-sleeve_left_outside.svg",
                    "globalParameters": {
                        "fill": "#343638",
                        "colors": [
                            "#343638"
                        ],
                        "originX": "center",
                        "originY": "center",
                        "visible": true,
                        "opacity": 1,
                        "locked": false,
                        "showInLayers": true,
                        "colorPicker": true,
                        "canDuplicate": false,
                        "canReset": true,
                        "canMove": false,
                        "canRotate": false,
                        "canResize": false,
                        "canDelete": false,
                        "canReorder": false,
                        "syncColorType": "none",
                        "price": 0,
                        "excludeFromExport": false,
                        "excludeFromProductionExport": true,
                        "alwaysOnTop": false,
                        "removable": false,
                        "autoSelect": false,
                        "boundingBoxMode": "none",
                        "lockUniScaling": true,
                        "minScaleLimit": 0.01,
                        "shadow": {
                            "color": "#000000",
                            "blur": 0,
                            "offsetX": 0,
                            "offsetY": 0
                        }
                    },
                    "parameters2D": {
                        "left": 400,
                        "top": 195,
                        "scaleX": 1,
                        "scaleY": 1,
                        "angle": 0,
                        "autoCenter": false,
                        "preserveLayout": true,
                        "scaleMode": "fit",
                        "skewX": 0,
                        "skewY": 0,
                        "flipX": false,
                        "flipY": false
                    },
                    "parameters3D": {}
                }
            ],
            "options": {
                "hidden": false,
                "disableContentEditing": false,
                "rulerMode": "print-area",
                "rulerSpacing": {
                    "top": 0,
                    "left": 0,
                    "right": 0
                },
                "canvas_width_mm": 220,
                "canvas_height_mm": 107,
                "view_width_mm": 220,
                "view_height_mm": 107,
                "print_width_mm": 220,
                "print_height_mm": 107,
                "dpi": 300,
                "bleed": 0,
                "printing_box": {
                    "left": 0,
                    "top": 0,
                    "width": 800,
                    "height": 390
                }
            }
        },
        {
            "title": "back_outside",
            "thumbnail": "preview-back_outside.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "back_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "material": {
                    "preset": "original",
                    "adjustments": {}
                },
                "texture": {
                    "flipY": false,
                    "preserveOriginal": true,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                },
                "camera": {
                    "position": [0.01917584891214796, 0.01216491841317795, -1.2543035437082248],
                    "target": [0.0004423293127343954, 0.021001695658389496, 0.00640979942961731],
                    "up": [0.007966933995445739, 0.9999445224390666, -0.006890573765635748],
                    "fov": 34,
                    "cameraTransitionDuration": 0.65,
                    "maxZoomIn": 2
                }
            },
            "elements": [
                {
                    "title": "Base",
                    "type": "image",
                    "source": "base-back_outside.svg",
                    "globalParameters": {
                        "fill": "#8a9099",
                        "colors": [
                            "#8a9099"
                        ],
                        "originX": "center",
                        "originY": "center",
                        "visible": true,
                        "opacity": 1,
                        "locked": false,
                        "showInLayers": true,
                        "colorPicker": true,
                        "canDuplicate": false,
                        "canReset": true,
                        "canMove": false,
                        "canRotate": false,
                        "canResize": false,
                        "canDelete": false,
                        "canReorder": false,
                        "syncColorType": "none",
                        "price": 0,
                        "excludeFromExport": false,
                        "excludeFromProductionExport": true,
                        "alwaysOnTop": false,
                        "removable": false,
                        "autoSelect": false,
                        "boundingBoxMode": "none",
                        "lockUniScaling": true,
                        "minScaleLimit": 0.01,
                        "shadow": {
                            "color": "#000000",
                            "blur": 0,
                            "offsetX": 0,
                            "offsetY": 0
                        }
                    },
                    "parameters2D": {
                        "left": 273,
                        "top": 400,
                        "scaleX": 1,
                        "scaleY": 1,
                        "angle": 0,
                        "autoCenter": false,
                        "preserveLayout": true,
                        "scaleMode": "fit",
                        "skewX": 0,
                        "skewY": 0,
                        "flipX": false,
                        "flipY": false
                    },
                    "parameters3D": {}
                }
            ],
            "options": {
                "hidden": false,
                "disableContentEditing": false,
                "rulerMode": "print-area",
                "rulerSpacing": {
                    "top": 0,
                    "left": 0,
                    "right": 0
                },
                "canvas_width_mm": 421,
                "canvas_height_mm": 617,
                "view_width_mm": 421,
                "view_height_mm": 617,
                "print_width_mm": 421,
                "print_height_mm": 617,
                "dpi": 300,
                "bleed": 0,
                "printing_box": {
                    "left": 0,
                    "top": 0,
                    "width": 546,
                    "height": 800
                }
            }
        },
        {
            "title": "rib_outside",
            "thumbnail": "preview-rib_outside.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "rib_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "material": {
                    "preset": "original",
                    "adjustments": {}
                },
                "texture": {
                    "flipY": false,
                    "preserveOriginal": true,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                },
                "camera": {
                    "position": [-1.187262545113531, 0.15168771409034934, -0.3681551395860842],
                    "target": [-0.018399239101305996, -0.0007622136376396129, 0.008840388332458347],
                    "up": [0.027003992054254057, 0.953032043879048, 0.30166323566662556],
                    "fov": 34,
                    "cameraTransitionDuration": 0.65,
                    "maxZoomIn": 2
                }
            },
            "elements": [
                {
                    "title": "Base",
                    "type": "image",
                    "source": "base-rib_outside.svg",
                    "globalParameters": {
                        "fill": "#343638",
                        "colors": [
                            "#343638"
                        ],
                        "originX": "center",
                        "originY": "center",
                        "visible": true,
                        "opacity": 1,
                        "locked": false,
                        "showInLayers": true,
                        "colorPicker": true,
                        "canDuplicate": false,
                        "canReset": true,
                        "canMove": false,
                        "canRotate": false,
                        "canResize": false,
                        "canDelete": false,
                        "canReorder": false,
                        "syncColorType": "none",
                        "price": 0,
                        "excludeFromExport": false,
                        "excludeFromProductionExport": true,
                        "alwaysOnTop": false,
                        "removable": false,
                        "autoSelect": false,
                        "boundingBoxMode": "none",
                        "lockUniScaling": true,
                        "minScaleLimit": 0.01,
                        "shadow": {
                            "color": "#000000",
                            "blur": 0,
                            "offsetX": 0,
                            "offsetY": 0
                        }
                    },
                    "parameters2D": {
                        "left": 400,
                        "top": 19,
                        "scaleX": 1,
                        "scaleY": 1,
                        "angle": 0,
                        "autoCenter": false,
                        "preserveLayout": true,
                        "scaleMode": "fit",
                        "skewX": 0,
                        "skewY": 0,
                        "flipX": false,
                        "flipY": false
                    },
                    "parameters3D": {}
                }
            ],
            "options": {
                "hidden": false,
                "disableContentEditing": false,
                "rulerMode": "print-area",
                "rulerSpacing": {
                    "top": 0,
                    "left": 0,
                    "right": 0
                },
                "canvas_width_mm": 166,
                "canvas_height_mm": 8,
                "view_width_mm": 166,
                "view_height_mm": 8,
                "print_width_mm": 166,
                "print_height_mm": 8,
                "dpi": 300,
                "bleed": 0,
                "printing_box": {
                    "left": 0,
                    "top": 0,
                    "width": 800,
                    "height": 38
                }
            }
        },
        {
            "title": "sleeve_right_outside",
            "thumbnail": "preview-sleeve_right_outside.png",
            "model3D": {
                "selectViewOnClick": true,
                "targets": [
                    {
                        "mesh": "sleeve_right_outside"
                    }
                ],
                "mapping": {
                    "surface": "Base"
                },
                "material": {
                    "preset": "original",
                    "adjustments": {}
                },
                "texture": {
                    "flipY": false,
                    "preserveOriginal": true,
                    "offset": [0, 0],
                    "repeat": [1, 1],
                    "center": [0.5, 0.5],
                    "rotation": 0
                },
                "camera": {
                    "position": [-1.068257089962192, 0.7364098909118255, -0.04966218621887668],
                    "target": [-0.020310901294252143, 0.007987512883270879, 0.005274488934068622],
                    "up": [0.5697367931993939, 0.8214728554983264, 0.02413160074045837],
                    "fov": 34,
                    "cameraTransitionDuration": 0.65,
                    "maxZoomIn": 2
                }
            },
            "elements": [
                {
                    "title": "Base",
                    "type": "image",
                    "source": "base-sleeve_right_outside.svg",
                    "globalParameters": {
                        "fill": "#343638",
                        "colors": [
                            "#343638"
                        ],
                        "originX": "center",
                        "originY": "center",
                        "visible": true,
                        "opacity": 1,
                        "locked": false,
                        "showInLayers": true,
                        "colorPicker": true,
                        "canDuplicate": false,
                        "canReset": true,
                        "canMove": false,
                        "canRotate": false,
                        "canResize": false,
                        "canDelete": false,
                        "canReorder": false,
                        "syncColorType": "none",
                        "price": 0,
                        "excludeFromExport": false,
                        "excludeFromProductionExport": true,
                        "alwaysOnTop": false,
                        "removable": false,
                        "autoSelect": false,
                        "boundingBoxMode": "none",
                        "lockUniScaling": true,
                        "minScaleLimit": 0.01,
                        "shadow": {
                            "color": "#000000",
                            "blur": 0,
                            "offsetX": 0,
                            "offsetY": 0
                        }
                    },
                    "parameters2D": {
                        "left": 400,
                        "top": 195,
                        "scaleX": 1,
                        "scaleY": 1,
                        "angle": 0,
                        "autoCenter": false,
                        "preserveLayout": true,
                        "scaleMode": "fit",
                        "skewX": 0,
                        "skewY": 0,
                        "flipX": false,
                        "flipY": false
                    },
                    "parameters3D": {}
                }
            ],
            "options": {
                "hidden": false,
                "disableContentEditing": false,
                "rulerMode": "print-area",
                "rulerSpacing": {
                    "top": 0,
                    "left": 0,
                    "right": 0
                },
                "canvas_width_mm": 222,
                "canvas_height_mm": 108,
                "view_width_mm": 222,
                "view_height_mm": 108,
                "print_width_mm": 222,
                "print_height_mm": 108,
                "dpi": 300,
                "bleed": 0,
                "printing_box": {
                    "left": 0,
                    "top": 0,
                    "width": 800,
                    "height": 390
                }
            }
        }
    ]
}
PropertyType / valuesDescription
titleNon-empty stringProduct title shown in Products and used for filenames.
thumbnailRelative or absolute image-URL stringProduct-module preview, resolved relative to this JSON.
basePriceNumber >= 0 | currency-free numeric stringGlobal product base price. Do not include the currency symbol. Defaults to 0.
startAtViewInteger >= 0 | exact view-title stringInitial view. Defaults to the first view.
startModuleproducts, build, savedDesigns, layers, text, images, shapes, artworkLibrary or noneChooses which module opens when this product or saved design loads. The interface modules.toggled setting must allow automatic opening, and compact layouts remain closed. With automatic opening enabled, a new product can open its requested module even if the previous panel was closed manually. None, an unavailable module, or a content library disabled for the starting view leaves the panel closed. A disabled module bar stays disabled. Omit this field to retain the existing interface startup behavior. Create Product writes products. The ring and watch examples use build.
unit'mm' | 'cm' | 'inch' | 'px'Product working unit used by measurements and output metadata. When omitted, the constructor unit setting is used. The ruler displays pixels whenever boundingBoxEditable is enabled.
model3DObjectRequired model source and product scene configuration, including materials and optional cloth physics. See the 3D workflow.
selectionOutlineObject containing 2D and 3DProduct-specific selected-object outline settings for both viewports. Each group has enabled, color, thickness and opacity.
fontsArray of font descriptorsProduct-specific families and optional file descriptors {url, weight, style}. Export collects used webfonts here. Product-relative font URLs are resolved with the product assets.
viewsArray of {title, thumbnail, model3D, elements, options} objectsOrdered product surfaces with their target meshes, camera, material, elements and production settings.

Selection outlines

Selection outlines belong at the product root, alongside model3D and views:

"selectionOutline": {
    "2D": {
        "enabled": true,
        "color": "#CF4035",
        "thickness": 1,
        "opacity": 1
    },
    "3D": {
        "enabled": true,
        "color": "#CF4035",
        "thickness": 0.5,
        "opacity": 1
    }
}

Both groups default to an enabled red outline with thickness 1 and opacity 1. Thickness is measured in CSS pixels and accepts values from 0 to 4, including fractions such as 0.25 and 0.5. Thin 3D outlines use subpixel coverage so reducing thickness remains visible. Opacity ranges from 0 to 1. Setting thickness to zero or enabled to false hides that outline without disabling selection or the object's controls. The dashed printing box is separate and still uses boundingBoxColor.

Create Product writes both groups here and starts their thickness at 1, while retaining the current product's outline colors, opacity and enabled flags. Product export and saved designs preserve both groups. Older model3D.selectionOutline values are moved into selectionOutline.3D on import, with explicit root values taking precedence. The old page-level canvas.objBg and canvasWindowObjectBackgroundColor settings are no longer read. Control button colors still use canvas.iconBg, iconBgOver, icon and iconOver.

"options": {
    "disableContentEditing": false,
    "rulerMode": "print-area",
    "rulerSpacing": {"top": 0, "left": 0, "right": 0},
    "canvas_width_mm": 600,
    "canvas_height_mm": 650,
    "view_width_mm": 600,
    "view_height_mm": 650,
    "print_width_mm": 240,
    "print_height_mm": 400,
    "dpi": 300,
    "bleed": 3,
    "printing_box": {
        "left": 178,
        "top": 139,
        "width": 240,
        "height": 400
    }
}
PropertyType / supported valuesDescription
hiddenBoolean, default falseOmits the view from navigation, selection and separate previews, without removing its model part, data or synchronization. See hidden views.
disableContentEditingBoolean, default falseWhen true, disables adding/editing content and hides the printing box. Color, material and permitted reset actions remain available in both viewports. Create Product enables it automatically for parts without a usable UV silhouette.
rulerModeprint-area, model-dimensions or hiddenPer-view measurement display. Create Product chooses a default. Explicit choices override detection. See Ruler modes and spacing.
rulerSpacing{"top": 0, "left": 0, "right": 0}Additional 3D ruler offsets in screen pixels. Zero keeps existing spacing. Left or right is used according to the visible side. Preserved by export.
view_width_mm, view_height_mmPositive integers in millimetersPhysical dimensions of the mapped base in millimeters. Supply both. New saves and exports round to whole millimeters (minimum 1) and place them after canvas_height_mm. The small example above assumes base bounds of 600 × 650 px.
canvas_width_mm, canvas_height_mmPositive integers in millimetersPhysical workspace size, independent of the mapped surface size. Saved as numbers. Old pixel canvas fields remain accepted when loading legacy products.
printing_box.left, printing_box.topFinite number | numeric string, in editor pixelsPrinting-box position inside the canvas.
printing_box.width, printing_box.heightNumber > 0 | positive numeric string, in editor pixels, including fractionsPrinting-box crop dimensions. Physical view dimensions retain this geometry. Legacy print-only files normalize its ratio.
print_width_mm, print_height_mmPositive integer JSON number, in millimetersPhysical output dimensions represented by the printing box. With physical view dimensions, these are synchronized from the crop. Otherwise they provide legacy calibration. Legacy output_width/output_height values remain accepted.
dpiFinite number >= 0Production raster density. A value of 0 uses the printing-box pixel dimensions.
bleedFinite number >= 0, in millimetersPer-view physical bleed override. When omitted, the global constructor bleed value is used.
Product conversion: millimeters per pixel are calculated per axis from the physical view dimension divided by its loaded surface bounds. The conversion can be fractional even though the saved view dimensions are integers. It is not fixed at 0.5. Legacy conversion and print-size settings are fallbacks for older files.

Element schema

New product elements use the exact, case-sensitive keys globalParameters, parameters2D and parameters3D. Older flat parameters objects are still accepted and migrated, explicit grouped values take precedence over legacy values. Saved product elements use the grouped structure.

JSON groupWhat belongs here
globalParametersShared permissions, fill/colors, opacity, visibility, locking, price, synchronization and export exclusions. Also originX, originY, boundingBoxMode, shadow, alwaysOnTop, autoSelect, lockUniScaling, minScaleLimit and typography: font, size, fontWeight, fontStyle, textAlign, lineH and letter.
parameters2DCanvas position, scale, angle, skew, flips, curve/stroke effects, image filters and layout settings such as preserveLayout. These define the same artwork rendered onto the 3D surface.
parameters3DSaved model-specific surface edits, including material with its preset and adjustments. Leave empty when unused. Initial view materials, mesh bindings, cameras and scene settings belong in the corresponding model3D blocks. This does not create independent decal meshes for text or images.
{
    "title": "Logo",
    "source": "logo.svg",
    "type": "image",
    "globalParameters": {
        "originX": "center",
        "originY": "center",
        "fill": "#CF4035",
        "colors": ["#CF4035", "#B65A3C", "#D8A24A"],
        "colorPicker": true,
        "showInLayers": true,
        "canDuplicate": true,
        "canReset": true,
        "canMove": true,
        "canRotate": true,
        "canResize": true,
        "canDelete": true,
        "canReorder": true,
        "price": 0,
        "opacity": 1,
        "locked": false,
        "excludeFromExport": false,
        "excludeFromProductionExport": false,
        "visible": true,
        "minScaleLimit": 0.04,
        "lockUniScaling": true,
        "boundingBoxMode": "clipping",
        "alwaysOnTop": false
    },
    "parameters2D": {
        "left": 300,
        "top": 325,
        "scaleX": 1,
        "scaleY": 1,
        "angle": 0,
        "skewX": 0,
        "skewY": 0
    },
    "parameters3D": {}
}
Parameter groupProperties and supported valuesBehavior
Position/originleft, top: finite numbers, originX: 'left' | 'center' | 'right', originY: 'top' | 'center' | 'bottom', autoCenter: true | falseCanvas position and transform origin. Center origins are recommended.
TransformscaleX, scaleY, minScaleLimit: numbers > 0, angle, skewX, skewY: finite degree values, lockUniScaling, flipX, flipY: true | falseInitial transform and limits.
PermissionscanDuplicate, canReset, canMove, canRotate, canResize, canDelete, canReorder: each true | falseControls both behavior and visible UI actions.
Layer stateshowInLayers, locked, visible, alwaysOnTop, autoSelect: each true | falseLayer-list visibility, editing lock, canvas visibility, permanent visual top order and initial selection.
Appearancefill: #hex | rgb(a) | hsl(a) | named | transparent | empty string, colors, svgFill: arrays of those color values, colorPicker: true | false, opacity: 0 to 1, filter: filter name/object or array, shadow: {color, blur, offsetX, offsetY}Color, transparency, filters and shadow.
Product behaviorprice: number >= 0 | currency-free numeric string, syncColorType: 'none' | 'allViews' | 'activeBuild', excludeFromExport, excludeFromProductionExport: true | false, boundingBoxMode: 'none' | 'inside' | 'clipping' | object-title stringPrice, cross-view synchronization, bounds, all-export exclusion and production-only exclusion.
Loading/renderingscaleMode: 'fit' | 'cover' | 'stretch', mimeType: MIME string such as 'image/svg+xml'Media fitting and metadata used by module-added content.

globalParameters.shadow accepts {color, blur, offsetX, offsetY} for ordinary design content. Blur 0 removes the visible shadow while preserving its color. Bound structural surfaces do not show the Shadow toolbar control in either viewport, model lighting and the floor shadow are configured separately.

parameters2D.preserveLayout: true tells loading to retain the saved transform rather than applying initial fitting/auto-centering again. Saved and exported layers use it to preserve placement, scale and skew. It does not lock an element, editing permissions remain independent.

Shared exclusion flagEffect
excludeFrom3DTextureOmits optional 2D-only artwork from model textures, without excluding it from a normal preview download. The mapped surface is excluded automatically and does not need this property.
excludeFromExportOmits the element from preview/download outputs that apply export filtering and from the editable product ZIP. Do not enable it on a structural Base you need to retain in the reusable product.
excludeFromProductionExportOmits the element from print-ready production output. Normal preview downloads ignore this production-only flag.

Place exclusion flags on each element's existing globalParameters, not on the view or in view.options. Excluding the Base from production does not exclude artwork above it. Production skips a nonempty view when no visible, nondeleted, exportable layers remain. A genuinely empty view can still produce blank output. Keep structural Base layers production-excluded and customer artwork included. Product ZIP export retains production-excluded elements so the product remains editable, but respects excludeFromExport.

filter accepts one filter or a list. Supported important filters include grayscale, sepia, invert, brightness, contrast, saturation, vibrance, blur, pixelate and noise.

Locking is independent from permissions: an element with every can* property set to false can still be selected when locked is false. Set locked: true only when selection and editing must be blocked.

Color palettes and SVG fills

  • fill is the initial single fill/tint.
  • Module-level colors define the default swatches for Text, Images, Shapes or Artwork.
  • Item-level colors, either directly on the item or inside parameters, replace the module palette for that item.
  • An explicit empty colors: [] removes preset swatches from that item.
  • svgFill stores one color per detected SVG fill group for multi-fill SVG editing.
  • colorPicker: false keeps the Color section and swatches but prevents opening the custom picker.
  • For a mapped model surface with texture.preserveOriginal: true, combine colorPicker: false with colors: [] to hide its Color button and keep the original finish fixed. This also hides that surface's transparency controls. Added artwork keeps its own color permissions.
  • To keep a product's materials fixed, set model3D.materials.enabled: false. This disables material editing without replacing the configured finishes. Set the Base layer's globalParameters.canReset: false separately if Reset should also be unavailable. These controls do not remove Build choices or change added artwork permissions.
  • An empty fill with an empty colors array leaves a PNG/SVG in its original appearance.
  • The transparent/reset swatch restores original image pixels or original SVG fill colors.
"fill": "",
"colors": ["#CF4035", "#B65A3C", "#D8A24A"],
"svgFill": ["#3c33e0", "#818cf8", "#0d277a", "#120e69"],
"colorPicker": true

Text parameters

Curve and stroke effects live in parameters2D. Typography, fill, origin and permissions live in globalParameters. Export uses compact names such as font, size, lineH, letter, curveType, radius, offset, stroke and strokeW, the longer aliases below remain accepted when loading. Normal, Curved Up, Curved Down, Oblique and Bridge are canvas effects rendered into the model texture, not extruded 3D lettering. Oblique/Bridge with curve: 0 intentionally look normal, change Curve to see the effect.

Text uses source as its editable content. This keeps the element schema consistent and avoids a separate top-level text property.

{
    "title": "Personalized name",
    "source": "YOUR TEXT",
    "type": "text",
    "globalParameters": {
        "fill": "#CF4035",
        "font": "Roboto",
        "size": 42,
        "fontWeight": 700,
        "fontStyle": "normal",
        "textAlign": "center",
        "lineH": 1.2,
        "letter": 0
    },
    "parameters2D": {
        "underline": false,
        "strokeColor": "#000000",
        "strokeWidth": 0,
        "curvable": true,
        "curvedType": "normal",
        "curveRadius": 80,
        "curve": 0,
        "curveOffset": 0,
        "maxLines": 0,
        "minFontSize": 1,
        "maxFontSize": 1000,
        "textTransform": "none",
        "widthFontSize": false
    },
    "parameters3D": {}
}
PropertyValuesDescription
curvedType'normal' | 'curvedUp' | 'curvedDown' | 'oblique' | 'bridge'Text geometry mode.
curveRadiusNumber from 0 to 300Arc radius for curved up/down. Smaller values create a stronger curve.
curveNumber from -100 to 100Oblique/bridge deformation strength.
curveOffsetNumber from 0 to 100Internal vertical deformation offset without changing object position.
letterSpacingFinite number, negative, zero or positiveFabric character spacing, synchronized between Format Text and Curved Text.
maxLinesInteger >= 00 means unlimited lines, otherwise limits multiline input.
textTransform'none' | 'uppercase' | 'lowercase'Rendered character transform while preserving editable source text.
widthFontSizetrue | false | finite number > 0When enabled, font size is automatically constrained to the stored target width.

Bounding modes

  • none: no clipping or inside constraint.
  • inside: keep the object's transformed bounds inside the current view printing box without masking it.
  • clipping: clip the object to the current view printing box.
  • An element title such as Overlay: use that view element's visible SVG shape or PNG alpha as a clipping mask.

SVG targets use their vector alpha. Transparent PNG targets use image alpha, transparent areas clip while partially/fully opaque pixels preserve the edited object at full visual opacity.

When printing_box exists, the red dotted guide always displays its exact rectangle. Object-title modes affect the clipping target only.


Library JSON

These library examples retain the supported parameters item format. For saved product-view elements, prefer the grouped structure described in Element schema.

Precedence rule: an item's direct properties or parameters override the module defaults. Values omitted from the item inherit the corresponding addText, addImg, addShapes or artworkLibrary setting.

Image, text, shape, artwork and artwork-category entries can define an optional itemBg CSS color. It changes only that entry's thumbnail background, it does not affect the canvas object, overlay, price badge or close button.

Images JSON

{
    "removable": true,
    "images": [
        {
            "type": "svg",
            "name": "Mushroom",
            "src": "./content/svg/mushroom.svg",
            "itemBg": "#e2e2e2",
            "price": 9.99,
            "removable": false,
            "parameters": {
                "canMove": true,
                "canDelete": true,
                "colors": ["#CF4035", "#B65A3C"]
            }
        }
    ]
}

The browser upload list is also maintained in memory. Duplicate detection uses src, no separate key is required. Set an item's optional itemBg CSS color to override the global thumbnail background, which is useful for displaying white artwork.

Text templates JSON

{
    "removable": true,
    "texts": [
        {
            "text": "YOUR\nTEXT",
            "parameters": {
                "fontFamily": "MDNichrome",
                "fontSize": 42,
                "fontWeight": 700,
                "fill": "#CF4035",
                "colors": ["#CF4035", "#B65A3C", "#D8A24A"],
                "textAlign": "center",
                "curvedType": "bridge",
                "curve": 45,
                "curveOffset": 50,
                "price": 10
            }
        }
    ]
}

Template thumbnails are rendered from the same text parameters used by the canvas, including custom fonts, multiple lines, curved up/down, oblique and bridge modes.

Shapes JSON

{
    "removable": true,
    "shapes": [
        {
            "label": "Circle",
            "button": "circle",
            "svg": "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='38' fill='currentColor'/></svg>",
            "price": 4,
            "parameters": {
                "fill": "#CF4035",
                "colors": ["#CF4035", "#B65A3C", "#D8A24A"]
            }
        }
    ]
}

Use inline svg or a source URL. Inline paths should use currentColor so the configured shape fill and color toolbar can update them.

Each shape can define a currency-free, non-negative price directly on the shape or inside parameters. A value greater than 0 is used for that shape. A missing value or 0 inherits addShapes.price. The global currency, addShapes.priceColor and addShapes.priceBg control its thumbnail badge.

Artwork Library JSON

{
    "search": true,
    "categories": [
        {
            "label": "Animals",
            "button": "animals",
            "removable": false,
            "search": ["animals", "wildlife", "pets"]
        }
    ],
    "artworks": [
        {
            "label": "Panda",
            "button": "animals_panda",
            "category": "animals",
            "src": "./artwork_library/animals/panda.svg",
            "price": 0,
            "search": ["panda", "animal", "bear"],
            "parameters": {
                "colors": ["#CF4035", "#B65A3C", "#D8A24A"]
            }
        }
    ]
}

The main window shows category thumbnails first. Opening a category reveals its artwork, back button and search field. Category removable controls removal for artwork in that category unless an item overrides it.

Saved designs JSON

{
    "removable": true,
    "designs": []
}

Each runtime design contains id, title, createdAt, productIndex, productTitle, productBasePath, currentViewIndex, thumbnail and a complete productData snapshot.


Important Workflows

Preview download

download() renders the current visual product. It supports PNG, JPG, SVG and PDF and can include the current view or all views. This is intended for customer previews and normal downloads.

Product ZIP export

export() synchronizes the editable printing box, serializes the live product state, removes deprecated/internal properties and respects excludeFromExport. Its ZIP contains product.json, the original GLB and referenced resources, including images and available webfont files. Materials, adjustments, cameras, mapping, lights, physics and supported layer settings are retained in JSON rather than baked into new model geometry.

Module library JSON and unused Add Images media are not included. Product root basePrice, startAtView and startModule are always exported. Authored choices are preserved. Older products use the configured initial module or none when the module panel starts closed.

Build exports retain every alternative inside this one product, including inactive parts. Each view keeps its group identifiers, selected state, price and preserveDesign flag. The GLB therefore contains all alternatives, not just the visible combination. This is different from exporting unrelated products, which are not included. Reload the ZIP and switch Build options to check each design, material and camera.

Export also preserves fonts explicitly stored in the product, including unused faces, alongside the fonts used by artwork. Disabled settings, zero values and empty collections are retained. Temporary selections and undo history are not product settings. Explicit export exclusions still apply, so leave excludeFromExport false on layers that must remain editable after import.

The archive keeps each view's saved camera, including up, fov and cameraTransitionDuration. Export generates fresh transparent PNG thumbnails with the latest artwork, colors and materials. The active view uses the current 3D camera, including orbit and zoom. Other views use their saved position, target, up direction and field of view. Each grouped alternative is captured with its matching parts selected. Editor controls, rulers and selection outlines are not included.

The product thumbnail uses the starting view, or the first remaining visible view if that view is excluded. model3D.previewResolution controls the longest image edge, subject to the existing preview and GPU limits. Captures run in a separate temporary instance, leaving the open design, camera, selected parts and history unchanged. Current orbit and zoom affect the active thumbnail only, they do not overwrite its saved camera. The generated JSON uses four-space indentation and compact numeric vectors, including correctly nested group entries.

Export captures edits in active and inactive views without rounding transforms or material adjustments. It retains false, zero and fractional values. Retired fields such as per-view material.enabled and the empty presetSettings.original entry are omitted. Extract the ZIP and load its JSON over HTTP/HTTPS to check the exported product independently of the original asset folder.

Font descriptors used by text are written to product.fonts, with collected webfont files included in the archive. System fonts still depend on the receiving device. Configure font file/CSS URLs and allow CORS so export can collect them. Missing model, image or required webfont resources abort the export rather than silently creating a partial package. Only redistribute assets and fonts you are licensed to package.

Preview image storage

The standalone designer runs in the browser. Capturing a preview does not upload it to a server, create a Media Library entry or write to a database. A downloaded preview is saved through the browser. Product ZIP export packages the product data and referenced assets for download, without publishing them to your website.

Saved-design snapshots stay in memory until your application stores them. A generated thumbnail can be included as image data in that snapshot. Your integration is responsible for saving it and supplying the saved design again after a page reload.

For a custom backend, store validated preview images as files and keep their URLs in the saved data. Reuse identical files, write a new file when the image changes, and check all references before deleting older files. These are integration responsibilities, not automatic features of the standalone edition. Back up both your stored design data and its image files.

Saved designs

Saving creates an in-memory snapshot and dispatches an event containing everything required for database persistence. No localStorage is used. Reloading the page starts from the configured saved-design JSON unless your application supplies saved records again.

New saved-design thumbnails are transparent PNG captures of the current 3D view, including its camera rotation and zoom. They exclude editor controls, rulers and selection outlines. Saving does not change the authored view camera, and existing saved thumbnails are left unchanged.

Loading a saved design establishes its own reset baseline. Global Reset returns to that saved design's starting version, not the unrelated catalog product. A full product reload can restore the catalog version. Asset paths must remain accessible when restoring a saved record, the snapshot does not contain a portable ZIP of its dependencies.


Keyboard Shortcuts

ShortcutRequirementAction
Arrow keysMovable object selectedMove by keuboardMoveNudgeAmmount.
Shift + Arrow keysMovable object selectedLarge movement.
Arrow keysEditable printing box selectedMove the printing box by the configured canvas-pixel nudge amount. Physical inputs display the converted distance.
Ctrl/Cmd + Arrow keysEditable printing box selectedChange crop width by the normal canvas-pixel nudge amount. Up/Right enlarge, Down/Left shrink. Height follows only for legacy print-only views.
Ctrl/Cmd + Shift + Arrow keysEditable printing box selectedChange crop width using the configured large canvas-pixel step. Height follows only for legacy print-only views.
Alt + Left / RightRotatable object selectedRotate by the normal configured step.
Shift + Alt + Left / RightRotatable object selectedLarge rotation step.
Ctrl/Cmd + Up / DownResizable object selectedUniform scale up/down.
Ctrl/Cmd + Left / RightResizable object selectedScale X down/up.
Ctrl/Cmd + Shift + Up / DownResizable object selectedScale Y up/down.
Delete / BackspaceDeletable object selectedRequest object deletion.
Ctrl/Cmd + DDuplicable object selectedDuplicate object.
Ctrl/Cmd + ZHistory availableUndo.
Ctrl/Cmd + YRedo availableRedo.
Ctrl/Cmd + Shift + ZRedo availableRedo.
EscapeAny editor modeClose picker/toolbar mode and deselect the object.
Shift while rotatingCanvas rotate handleSnap rotation in 90-degree increments.
Shift + drag artwork or its Move handleEditable 2D or 3D viewThe first deliberate movement locks horizontal or vertical movement in the design plane. Keep Shift held to follow that straight line. Release Shift for free movement. On curved 3D parts, the line follows the UV surface.
Mouse wheelPointer over the editable 2D canvasZoom without a modifier. The minimized preview is not editable.
Drag empty canvas or an immovable surface2D canvas zoomed inPan without a modifier. Editable artwork and its handles retain their normal actions.
Shift or Space + drag / wheelKeyboard navigation enabled, applicable 2D ruler/navigation modeNavigate empty canvas with Shift, or pan over artwork with Space. Shift-drag on editable artwork locks its movement axis. Typing fields are excluded.
Drag / right-drag / wheelEditable 3D viewport, corresponding controls enabledOrbit / pan / zoom. Dragging an artwork handle edits that object instead.
Input safety: normal shortcuts do not take over typing in inputs, textareas or active canvas text editing. Each editable viewport keeps independent zoom in split mode, the zoom slider follows the active one. The minimized window is a noneditable preview.

Methods

Every instance is available under its configured ID. The examples below use designer0, replace it with your instance name, such as fwdi3dpd0. Use the public instance methods instead of modifying internal Three.js or Fabric objects.

Wait for the designer

Register FWDI3DPD.READY after constructing the instance. Do not use a fixed timeout, model, texture and font loading times vary. isReady becomes true only after successful startup, including the 3D model. With initializeWhenVisible or lightbox display, initialization can wait until the instance is shown.

let started = false;

function onDesignerReady() {
    if (started) return;
    started = true;
    console.log(designer0.getProductData());
}

designer0.addEventListener(FWDI3DPD.READY, onDesignerReady);
if (designer0.isReady) onDesignerReady();

Await asynchronous product and view changes before starting exports or another dependent operation. The initial READY event is not a product-change notification. Do not call methods after destroy().

createProduct

designer0.createProduct() / await designer0.createProduct(file, options = {})

Named alternatives use group__option__part, or group__option for a single part. The dialog reviews detected groups before generation. API calls can pass builderDefaults: { sleeves: 'regular', collar: 'crew' } with group and option IDs from the model. Without explicit defaults, the first encountered option is used. Every option must contain matching parts. Editable parts require usable UVs, explicit appearance assemblies do not. See Build: interchangeable parts for the complete convention.

Without arguments, opens the GLB file dialog once ready. With a browser File, generates a product without opening a dialog or replacing the current design. A URL string, filesystem path, bare Blob or ArrayBuffer is not accepted by this overload. Fetch a server-accessible GLB and wrap the response in a File:

let creationStarted = false;

async function createTestProduct() {
    if (creationStarted) return;
    creationStarted = true;

    try {
        const response = await fetch('./products/cap/model.glb');
        if (!response.ok) {
            throw new Error(`Unable to load model: ${response.status}`);
        }

        const file = new File([await response.blob()], 'model.glb', {
            type: 'model/gltf-binary'
        });
        const result = await designer0.createProduct(file, { download: true });
        console.log(result.product, result.fileName);
    } catch (error) {
        console.error('Unable to create the product.', error);
    }
}

designer0.addEventListener(FWDI3DPD.READY, createTestProduct);
if (designer0.isReady) createTestProduct();

The promise resolves to { blob, fileName, product, files }. blob is the ZIP, product is the generated JSON object and files contains { name, data } entries with Uint8Array contents. Omit download or set it to false to receive the archive without downloading it. Limits and messages come from the constructor's createProduct settings, not the per-call options. generateProductFromGLB(file, options) is the equivalent file-generation method. Errors reject the promise, use try/catch.

Browser file access: a path such as C:\Users\name\Desktop\model.glb cannot be fetched by an ordinary web page. Put the file on your web server or use a browser-selected File. The example URL is relative to the embedding page. Verify the response is a real GLB, development servers can return an HTML fallback with status 200 for a missing file.

Product data and pricing

MethodReturnsBehavior
getProductData()Object or nullA JSON copy of the current editable product. This is not a ZIP and does not bundle assets. Editing the returned copy does not update the live instance.
getViewRulerMode(viewIndex?)StringReturns the explicit or resolved ruler mode for a zero-based view index, current view when omitted.
setViewRulerMode(mode, viewIndex?)StringStores a supported per-view ruler choice and refreshes the current view. Returns that mode. Invalid modes or indexes throw RangeError. Call after READY. This does not enable the global ruler toggle.
getProductTotalPrice()NumberBase price plus selected Build part prices and eligible layer prices in active views. Inactive alternatives and hidden/deleted layers are excluded. Does not calculate store tax, shipping or take a payment.
setBasePrice(price)NumberSets the host base-price override and updates the displayed total. Pass a nonnegative number without a currency symbol. The override is retained across product loads.
setPriceCalculationEnabled(enabled)BooleanPass false to use only the base price, true to include selected-part and layer prices again.

Listen for PRICE_CHANGED and DONE to integrate with your storefront. Revalidate prices and permissions on your server, browser-calculated totals are not payment authorization.

download

await designer0.download(format = 'png', options = {})

Downloads normal rendered views. Formats: png, jpg/jpeg, svg, pdf. Omit previewMode for 2D, pass '3d' for model captures. The API does not open the 2D/3D choice dialog.

await designer0.download('png', { currentViewOnly: true });
await designer0.download('png', {
    previewMode: '3d',
    currentViewOnly: true,
    transparentBackground: true,
    fileName: 'model-preview.png'
});
await designer0.download('pdf', {
    previewMode: '3d',
    currentViewOnly: false,
    fileName: 'customer-design.pdf'
});

Scope options are currentViewOnly, viewIndex or viewIndexes, using original zero-based product view indexes. Hidden views are skipped. With no scope override, all visible views are included. One raster/SVG view downloads directly, multiple raster/SVG views use a ZIP and PDF combines views into one file. Resolves to { format, files, blob } or null on error. A 3D SVG embeds the raster model capture, it is not a vector or GLB export.

Scope precedence is viewIndexes, then currentViewOnly: true, then viewIndex. Use one scope selector per call. A 3D capture's pixel size comes from product model3D.previewResolution, not production DPI. Downloading an image does not export an editable product or a modified GLB.

productionExport

await designer0.productionExport(format = 'png', options = {})

Downloads print-ready cropped output. Formats: PNG, JPG and PDF. Options can include currentViewOnly, viewIndex, fileName, dpi, bleed, outputWidth, outputHeight and unit/outputUnit. Runtime bleed is in millimeters and overrides both the view JSON and global fallback.

await designer0.productionExport('png', {
    currentViewOnly: true,
    dpi: 300,
    bleed: 3,
    unit: 'mm'
});

await designer0.print(options = {})

Renders product previews into an isolated print document and opens the browser's normal print dialog. The current view is printed by default, pass {currentViewOnly: false} for all views.

await designer0.print({ previewMode: '3d', currentViewOnly: false });

Omit previewMode for 2D. Returns { items } or null on error. These are visual previews, use productionExport() for calibrated print-area files.

Use title to override the print document's title. If supplying viewIndex, also set currentViewOnly: false so the default current-view scope does not take precedence. A viewIndexes array takes precedence over both.

export

await designer0.export(options = {})

Downloads the editable product JSON, original GLB, referenced image assets and collected font resources as a ZIP. Preserves view cameras, mapping, scene settings, materials, saved adjustments, physics, print areas and layer transforms including skew. Model geometry is not baked with the customer's artwork or temporary cloth deformation, the exported JSON recreates that design when loaded by the designer.

Options: fileName for the ZIP and productFileName for the JSON inside it. Resolves to { productData, assets, blob } or null on error. This method starts a download, unlike createProduct(file), which only downloads when requested.

saveDesign

designer0.saveDesign(title = '')

Creates an in-memory saved-design snapshot, refreshes all saved-design windows and dispatches FWDI3DPD.SAVED_DESIGN_CREATED. Returns the design object, or null if saving is disabled or its cooldown has not elapsed. Persist the event payload in your application if it must survive a page reload.

Product and view methods

MethodReturnsDescription
await loadProductByIndex(index, forceReload = false)PromiseLoads a product by zero-based numeric index. With forceReload: true, reloads even the active index from its cached initial snapshot. This does not force a network refetch.
await loadProductView(index)PromiseLoads a specific view and resets view history.
await goToNextView()PromiseMoves to the next visible view, wrapping from last to first.
await goToPrevView()PromiseMoves to the previous visible view, wrapping from first to last.
await resetCurrentProduct()PromiseRestores the loaded saved design's starting snapshot, or the original product when no saved design is active.
await loadSavedDesign(design)Promise<boolean>Loads a saved-design record with productData and its asset base path. Returns success/failure and establishes that snapshot as the reset baseline.
await requestResetCurrentProduct()PromiseUses the configured reset confirmation before resetting.
await showPreviewWindow(mode)PromisePass '2d' or '3d' to open that preview. Omit mode to show the choice dialog first.
showDownloadWindow(mode)voidOpens download controls for '2d' or '3d'. Omit mode for the choice dialog.
await toggleFullscreen()PromiseRequests or exits browser fullscreen. Call from a user click, subject to browser support and permission.
isFullscreenSupported(), isFullscreenActive()BooleanCheck whether fullscreen can be requested and whether this instance is currently fullscreen.
resize()voidRecalculate the instance layout after its container changes size. Normal browser resize handling is automatic.
showSavedDesignsLightbox()voidOpens the independent full-browser saved-design window.
showLightbox()voidOpens an instance configured with display: 'lightbox'. The first call initializes it, later calls reuse its existing products, edits and history.
hideLightbox()voidHides a lightbox instance without destroying or reinitializing the editor.
showInfoWindow(text)voidShows a message in the shared dialog window.

undo / redo

await designer0.undo() / await designer0.redo()

Moves through the current view's history. Slider and color-picker drags commit one history entry when released rather than one entry for every pointer movement.

getCurrentPrintingBoxInfo

designer0.getCurrentPrintingBoxInfo(viewIndex?)

Returns debugging/integration data for a view. millimetersPerPixel is calculated independently for each axis from the physical view size and surface bounds captured at load time. It can contain decimals and is not an authored JSON setting. The example below assumes a 600 × 650 mm view mapped onto 600 × 650 px:

{
    viewIndex: 0,
    viewTitle: 'Front',
    millimetersPerPixel: {x: 1, y: 1},
    canvasPx: {width: 600, height: 650},
    printingBoxPx: {left: 178, top: 139, width: 240, height: 400},
    output: {unit: 'mm', width: 240, height: 400},
    sourceOutput: {unit: 'mm', width: 240, height: 400}
}

destroy

designer0.destroy()

Removes listeners, timers, dialogs, responsive windows, color pickers, Fabric canvas resources, component references, the global instance and all generated DOM. In normal embedded modes it also removes the configured parent holder, in lightbox mode it removes the generated launch button and overlay.


Events

Register events on the instance with addEventListener. Event constants are available on the global FWDI3DPD class after initialization.

This is the designer's event dispatcher, not a DOM EventTarget. Read payload fields directly from the event, not event.detail. For cleanup outside event dispatch, remove a listener with removeEventListener(type, handler) using the same function reference. The dispatcher does not accept DOM options such as { once: true }, use a guard as in the ready-state example.

designer0.addEventListener(FWDI3DPD.SAVED_DESIGN_CREATED, function(event){
    console.log(event.design);
    // Send event.design to your database/API.
});

designer0.addEventListener(FWDI3DPD.MODULE_ITEM_REMOVED, function(event){
    console.log(event.section, event.type, event.item || event.media);
});

designer0.addEventListener(FWDI3DPD.PRICE_CHANGED, function(event){
    console.log(event.total);
});

designer0.addEventListener(FWDI3DPD.DONE, function(event){
    console.log(event.total);
});

FWDI3DPD.READY

'ready'

Dispatched once successful startup has prepared the product and 3D model. Use it with the isReady check in the ready-state example. Loading failures do not dispatch a successful READY event.

FWDI3DPD.SAVED_DESIGN_CREATED

'savedDesignCreated'

Dispatched after a design snapshot is created. Payload: design, savedDesign, id, title, createdAt, productIndex, productTitle, productBasePath, currentViewIndex, thumbnail and productData.

FWDI3DPD.SAVED_DESIGN_REMOVED

'savedDesignRemoved'

Dispatched after a saved design is removed through the UI. Payload: design, savedDesign and id. Handle persistence and authorization in your application. Deleting the active saved design returns to the configured initial product.

FWDI3DPD.MODULE_ITEM_REMOVED

'moduleItemRemoved'

Dispatched when a removable Text, Image, Shape or Artwork Library item/category is deleted. Payload always includes section and type, plus the relevant item/media/category data.

FWDI3DPD.PRICE_CHANGED

'priceChanged'

Dispatched when the product total changes. The total payload contains the current base price plus selected grouped parts and priced design elements in active views.

FWDI3DPD.DONE

'done'

Dispatched when the Done action is clicked. The total payload contains the current product total and can be used by storefront integrations.