Appearance
Embedded e-commerce cut-list calculator
An embedded JavaScript widget that provides a complete cut-list calculator for your e-commerce, estimation or quotation website functionality.
Overview
SmartCut Checkout allows you to:
- Embed a cut-list calculator directly on your website
- Calculate prices / estimates in real-time on the client side
- Filter & sort products in your catalogue
- Customize the UI to match your brand
- Support multiple materials, thicknesses, dimensions, extras, and configurations
- No server-side integration required
Quick Start
Installation
Include the script and default css styling in your HTML:
html
<link rel="stylesheet" href="https://cutlistevo.com/checkout/checkout.css" />
<script type="module" src="https://cutlistevo.com/checkout/checkout.js"></script>Basic Usage
html
<!-- Container for the calculator -->
<div id="smartcut-app"></div>
<script>
// Configuration
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutType: 'efficiency',
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD'
}
}
// Wait for the ready event and then call init
window.addEventListener('smartcut/ready', () => {
window.smartcutCheckout.init(initData)
})
// Listen for calculation events
window.addEventListener('smartcut/calculating', () => {
console.log('Calculation started')
})
// Listen for results
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
console.log('Optimization complete:', result)
console.log('Total stock cost:', result.checkout.formattedTotalStockCost)
})
</script>Configuration
Stock Configuration
Define the available stock materials:
javascript
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD',
apiVersion: 3 // 2 or 3 (default: 3)
}
}Parts Configuration
Pre-populate the calculator with parts:
javascript
const initData = {
stock: [...],
parts: [
{
l: 600,
w: 400,
t: 18,
q: 4,
name: 'Shelf',
material: 'Plywood',
orientationLock: 'l', // 'l', 'w', or '' (free)
banding: {
sides: { l1: 'oak|1mm', l2: 'oak|1mm', w1: '', w2: '' }
},
finish: {
faces: { a: 'spray|matt', b: '' }
},
customData: { projectId: '12345' },
order_id: 'ORD-001'
}
],
options: {...}
}Part Properties:
| Property | Type | Description |
|---|---|---|
l | number | Part length (required) |
w | number | Part width (required) |
t | number | string | Thickness (optional, inherits from stock if not set) |
q | number | Quantity (default: 1) |
name | string | Part name/label |
material | string | Material identifier for stock matching |
orientationLock | 'l' | 'w' | '' | Orientation constraint: length-locked, width-locked, or free |
banding | object | Edge banding configuration |
finish | object | Surface finish configuration |
planing | object | Planing configuration |
customData | object | Custom key-value data passed through to results |
stockId | string | Lock part to specific stock ID |
order_id | string | Order ID for grouping parts |
Saw Options
Saw configuration should be provided in the saw property (recommended) or in options for backward compatibility.
Complete Saw Configuration
javascript
saw: {
stockType: 'sheet', // 'sheet', 'linear', or 'roll'
bladeWidth: 3.2, // Blade width in mm
cutType: 'guillotine', // 'efficiency', 'guillotine', 'beam'
cutPreference: 'l', // 'l' (length), 'w' (width)
stackHeight: 60, // Maximum stack height in mm (beam saws only)
efficiencyOptions: { // Optional efficiency settings
primaryCompression: 'l' // 'l' (length) or 'w' (width) - primary compression direction
},
guillotineOptions: { // Optional guillotine settings
strategy: 'efficiency', // 'efficiency' or 'time'
maxPhase: 0, // Maximum cutting phase depth (0 = unlimited, 1-10)
headCuts: false // Enable head cuts for optimization
},
options: { // Additional saw options
stockSelection: 'efficiency', // 'efficiency' or 'smallest' - stock selection method
stackingMode: 'identical', // 'dimensions', 'identical', or 'none' - part stacking mode
minSpacing: 0 // Minimum spacing between parts in mm
}
}Cut Type Values:
'efficiency'- Standard efficiency optimization'guillotine'- Guillotine cutting (only straight cuts across entire stock)'beam'- Beam saw optimization
Cut Preference Values:
'l'- Prefer length-first cuts'w'- Prefer width-first cuts
Stock Selection Values:
'efficiency'- Select stock that maximizes material efficiency (default)'smallest'- Prefer smaller stock sizes when possible
Stacking Mode Values:
'identical'- Stack only parts with identical dimensions and properties (default)'dimensions'- Stack parts with same dimensions regardless of other properties'none'- Disable part stacking
Configuration Options
API Version
javascript
options: {
apiVersion: 3 // 2 or 3 (default: 3)
}API Version Differences:
- Version 2: Uses flat extras structure (
{ banding: { x1: string, x2: string, y1: string, y2: string } }) - Version 3: Uses nested extras structure (
{ extras: { banding: { sides: { l1: string, l2: string } } } })
See Result Format for details on the differences between versions.
Emit API Result
javascript
options: {
enable: {
emitAPIResult: true // Include full API v3 response in result (default: false)
}
}When enabled, the result object will include an apiResultV3 property containing the complete API v3 response format. This is useful when you need the standardized API response structure regardless of which apiVersion is configured for the checkout result.
See API Result V3 in the Result Format section for the structure.
Debug Mode
javascript
options: {
enable: {
debug: true // Enable debug output in console (default: false)
}
}When enabled, additional debug information will be logged to the browser console during calculations.
Number Format
javascript
options: {
numberFormat: 'decimal', // 'decimal' or 'fraction'
decimalPlaces: 2,
fractionRoundTo: 0 // For fraction formatting
}Orientation Model
javascript
options: {
orientationModel: 0, // 0, 1, or 2 (input parts)
resultOrientationModel: 0 // 0, 1, or 2 (result parts)
}Orientation Model Values:
0- No orientation control1- Grain lock (parts maintain grain direction)2- Dimension lock (length always maps to longer dimension)
Part Limits
javascript
options: {
maxParts: 100, // Maximum parts per order (0 = unlimited)
minDimension: 10, // Minimum allowed part dimension in mm
partTrim: 0 // Amount to trim from parts (mm)
}Pagination
javascript
options: {
partsPerPage: 10, // Parts shown per page when pagination enabled
enable: {
pagination: true // Enable pagination for parts list
}
}Field Order
javascript
options: {
fieldOrder: 'l, w, t, q, name, orientation' // Custom field display order
}Control the order of input fields in the parts form. Provide a comma-separated list of field IDs.
Custom Fields
javascript
options: {
customFields: [
{
type: 'string',
label: 'Project Name',
id: 'projectName',
placeholder: 'Enter project name'
},
{
type: 'integer',
label: 'Quantity',
id: 'quantity',
default: 1
},
{
type: 'select',
label: 'Finish',
id: 'finish',
outputType: 'string',
options: [
{ label: 'Matt', value: 'matt' },
{ label: 'Gloss', value: 'gloss' }
]
},
{
type: 'checkbox',
label: 'Rush Order',
id: 'rush',
trueValue: 'yes',
falseValue: 'no',
default: 'no'
}
]
}UI Customization
Control colors, enabled features, and field order:
javascript
options: {
enable: {
banding: true, // Edge banding options
finish: true, // Finishing options
planing: true, // Planing options
machining: true, // Machining operations (holes, corners)
diagram: true, // Show cutting diagram
orientation: true, // Part orientation control
csvImport: false, // CSV import for parts
partName: true, // Allow naming parts
pagination: false, // Paginate parts list
progressNumber: true // Show progress/step numbers in UI
},
colors: {
partA: '#118ab2',
partB: '#06d6a0', // Alternating part color
stock: '#ffd166',
button: '#118ab2',
buttonText: '#ffffff',
headerBackground: '#ffffff', // Header background color
headerText: '#212529' // Header text color
}
}Extras Configuration
Extras are additional services like edge banding, finishing, planing, and machining that can be applied to parts with custom pricing.
Basic Extras Setup
javascript
const initData = {
stock: [...],
// Edge banding configuration
banding: {
labels: ['type', 'thickness'],
pricing: {
'oak|1mm': 1.0,
'oak|2mm': 1.1,
'pine|1mm': 2.0,
'maple|1mm': 3.0
}
},
// Finish configuration
finish: {
labels: ['type', 'style'],
pricing: {
'spray|matt': 1.0,
'spray|satin': 1.1,
'lacquer|matt': 2.0,
'lacquer|satin': 2.2
}
},
// Planing configuration
planing: {
labels: ['type'],
pricing: {
'standard': 0.5,
'premium': 0.8
}
},
options: {...}
}Extras Location Filtering
Control which sides and faces are available for each extra type by adding a locations property directly to each extra configuration:
javascript
// Available sides: 'side.l1', 'side.l2', 'side.w1', 'side.w2' (main edges)
// 'side.a', 'side.b', 'side.c', 'side.d' (corners)
// Available faces: 'face.a', 'face.b'
banding: {
labels: ['material', 'thickness'],
pricing: {...},
locations: ['side.l1', 'side.l2', 'side.w1', 'side.w2'] // Only main edges
},
finish: {
labels: ['type'],
pricing: {...},
locations: ['face.a', 'face.b'] // Both faces
},
planing: {
labels: ['type'],
pricing: {...},
locations: ['side.w1', 'side.w2', 'face.a', 'face.b'] // Edges and faces
}Extras Location Groups
Create custom groupings of locations for bulk operations with optional pricing by adding a groups array directly to each extra configuration:
javascript
planing: {
labels: ['type'],
pricing: {
'standard': 0.5,
'premium': 0.8
},
locations: ['side.w1', 'side.w2', 'face.a', 'face.b'],
groups: [
{
id: 'two-sided',
label: '2 sided',
locations: ['face.a', 'face.b'],
price: 100,
hideIndividualLocations: true // Optional: hide individual location controls when group is available
},
{
id: 'four-sided',
label: '4 sided',
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
price: 200,
hideIndividualLocations: true,
// Optional: Group-level validation rules
rules: {
longSide: { min: 200 },
shortSide: { min: 150 },
message: '4-sided planing requires minimum 200x150mm parts'
}
}
]
},
banding: {
labels: ['material'],
pricing: {...},
groups: [
{
id: 'all-corners',
label: 'All Corners',
locations: ['side.a', 'side.b', 'side.c', 'side.d']
},
{
id: 'long-sides',
label: 'Long Sides',
locations: ['side.l1', 'side.l2']
}
]
}Group Properties:
id(required) - Unique identifier for the grouplabel(required) - Display name shown in the UIlocations(required) - Array of location strings included in this groupprice(optional) - Fixed price for the entire group (overrides individual location pricing)hideIndividualLocations(optional) - Whentrue, hides the individual location controls for locations in this grouprules(optional) - Validation rules that apply when using this group (see Group-Level Validation Rules)
Extras Validation Rules
Apply dimension constraints to control which parts can have extras applied by adding a rules property directly to each extra configuration:
javascript
planing: {
labels: ['type'],
pricing: {...},
locations: [...],
rules: {
t: {
min: 8,
max: 230
},
shortSide: {
min: 10,
max: 620
},
longSide: {
max: 1000
},
message: 'Planing requires thickness 8-230mm, short side 10-620mm, and long side max 1000mm'
}
},
banding: {
labels: ['material'],
pricing: {...},
rules: {
t: {
min: 12,
max: 30
},
message: 'Banding is only available for 12-30mm thick materials'
}
},
finish: {
labels: ['type'],
pricing: {...},
rules: {
longSide: {
max: 2400
},
shortSide: {
max: 1200
},
message: 'Finish is only available for parts smaller than 2400x1200mm'
}
}Validation Rule Fields:
longSide.min/longSide.max- Constraints on the longer dimensionshortSide.min/shortSide.max- Constraints on the shorter dimensiont.min/t.max- Thickness constraintsformula(optional) - JavaScript expression for complex validation (can referencelongSide,shortSide,t)message- Custom error message shown when validation failslocations(optional) - Array of location-specific rules (see Location-Specific Validation Rules)
When a part doesn't meet the validation rules, the extras options will be disabled and an error message displayed.
Location-Specific Validation Rules
Apply different validation constraints to individual locations within an extra type. This allows fine-grained control over which parts can use specific edges or faces:
javascript
banding: {
labels: ['material'],
pricing: {...},
rules: {
// Type-level rules (apply to all locations by default)
longSide: { min: 100 },
shortSide: { min: 50 },
message: 'Banding requires minimum 100x50mm parts',
// Location-specific rules (override type-level for specific locations)
locations: [
{
location: 'side.l1',
longSide: { min: 500 },
message: 'L1 banding only available for parts >= 500mm long side'
},
{
location: 'side.l2',
longSide: { min: 500 },
message: 'L2 banding only available for parts >= 500mm long side'
}
]
}
}Location Rule Properties:
location(required) - The location this rule applies to (e.g.,'side.l1','face.a')longSide/shortSide/t- Dimension constraints for this locationformula(optional) - JavaScript expression for complex validationmessage(optional) - Custom error message for this location
Group-Level Validation Rules
Groups can have their own validation rules that apply when any location in the group is selected:
javascript
planing: {
labels: ['type'],
pricing: {...},
groups: [
{
id: 'four-sided',
label: '4 Sided',
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
price: 200,
rules: {
longSide: { min: 200 },
shortSide: { min: 150 },
t: { min: 18 },
message: '4-sided planing requires minimum 200x150x18mm parts'
}
}
]
}Rule Precedence
When multiple rules could apply to a location, the most specific rule wins:
- Location-specific rule - Rules defined in
rules.locations[]for a specific location - Group rule - Rules defined on a group that contains the location
- Type-level rule - The base rules for the extra type
Only one rule level applies per location - the most specific one completely overrides less specific rules.
Example:
javascript
banding: {
rules: {
longSide: { min: 100 }, // Type-level: default for all locations
locations: [
{ location: 'side.l1', longSide: { min: 500 } } // Location-specific: overrides for l1
]
},
groups: [
{
id: 'all-sides',
locations: ['side.l1', 'side.l2', 'side.w1', 'side.w2'],
rules: { longSide: { min: 200 } } // Group rule: applies to group members without location rules
}
]
}In this example:
side.l1: Uses location rule (min 500mm) - most specificside.l2,side.w1,side.w2: Use group rule (min 200mm) - group is more specific than type- Any other location: Uses type-level rule (min 100mm) - fallback
Part Validation Rules
Apply dimension constraints to parts themselves to control which parts can be processed by adding a partRules property to the init data:
javascript
const initData = {
stock: [...],
// Part validation rules
partRules: {
// Basic dimension constraints
l: {
min: 50,
max: 2400
},
w: {
min: 20,
max: 1200
},
t: {
min: 12,
max: 30
},
longSide: {
min: 100,
max: 2400
},
shortSide: {
min: 50,
max: 1200
},
// Cross-dimensional rule: at least one side must meet primaryMin,
// and the other side must meet secondaryMin
crossDimensionalRule: {
primaryMin: 50,
secondaryMin: 20
},
// Optional custom message
message: 'At least one side must be ≥ 50 mm and the other side must be ≥ 20 mm'
},
options: {...}
}Part Validation Rule Fields:
l.min/l.max- Length constraintsw.min/w.max- Width constraintst.min/t.max- Thickness constraintslongSide.min/longSide.max- Constraints on the longer dimensionshortSide.min/shortSide.max- Constraints on the shorter dimensioncrossDimensionalRule(optional) - Cross-dimensional validationprimaryMin- At least one side must be ≥ this valuesecondaryMin- The other side must be ≥ this value
formula(optional) - Formula expression for complex validation (can referencel,w,t,longSide,shortSide)- Supports arithmetic operators:
+,-,*,/ - Supports comparison operators:
>,<,>=,<=,== - Supports logical operators:
&&(AND),||(OR) - Supports ternary operator:
condition ? trueValue : falseValue
- Supports arithmetic operators:
message- Custom error message shown when validation fails
Example with formula:
javascript
partRules: {
formula: '(l >= 50 && w >= 20) || (w >= 50 && l >= 20)',
message: 'At least one side must be ≥ 50 mm and the other side must be ≥ 20 mm'
}Complex formula example:
javascript
partRules: {
formula: '(l * w) > 100000 && t >= 12',
message: 'Part must have area > 100,000 mm² and thickness ≥ 12 mm'
}When a part doesn't meet the validation rules, an error message will be displayed below the part and calculation will be blocked until the issue is resolved.
Custom Validation Rules
For more complex validation scenarios, you can use custom validation rules that support full access to part properties including extras and machining. This is configured via the EcommerceSettings admin page or by adding a customValidation property to the init data:
javascript
const initData = {
stock: [...],
// Custom validation rules
customValidation: {
enabled: true,
rules: [
{
id: 'min-size',
enabled: true,
name: 'Minimum Part Size',
formula: 'longSide >= 100 && shortSide >= 50',
message: 'Parts must be at least 100mm x 50mm'
},
{
id: 'banding-thickness',
enabled: true,
name: 'Banding Thickness Check',
formula: '!hasBanding || t >= 12',
message: 'Banding requires minimum 12mm thickness'
},
{
id: 'machining-thickness',
enabled: true,
name: 'Machining Thickness Check',
formula: '!hasMachining || t >= 18',
message: 'Machining operations require minimum 18mm thickness'
}
]
},
options: {...}
}Custom Validation Rule Fields:
id- Unique identifier for the ruleenabled- Whether the rule is active (default: true)name- User-friendly name for the rule (optional)formula- Condition that must evaluate to true for the part to be validmessage- Error message shown when validation fails
Available Variables:
| Variable | Type | Description |
|---|---|---|
l | number | Part length |
w | number | Part width |
t | number | Part thickness |
q | number | Part quantity |
longSide | number | max(l, w) |
shortSide | number | min(l, w) |
material | string | Material name |
name | string | Part name |
grain | string | Grain direction |
fullStock | boolean | Full stock purchase flag |
hasBanding | boolean | True if any banding applied |
hasFinish | boolean | True if any finish applied |
hasPlaning | boolean | True if any planing applied |
hasMachining | boolean | True if any machining operations |
extras.banding.sides.l1 | string | Banding on long side 1 |
extras.banding.sides.l2 | string | Banding on long side 2 |
extras.banding.sides.w1 | string | Banding on short side 1 |
extras.banding.sides.w2 | string | Banding on short side 2 |
extras.finish.faces.a | string | Finish on face A |
extras.finish.faces.b | string | Finish on face B |
machining.holes | number | Number of holes |
machining.corners | number | Number of corner operations |
Supported Operators:
- Arithmetic:
+,-,*,/ - Comparison:
>,<,>=,<=,== - Logical:
&&(AND),||(OR),!(NOT) - Ternary:
condition ? trueValue : falseValue - Parentheses:
(expression)
Example Rules:
javascript
// Minimum area requirement
{
formula: '(l * w) >= 10000',
message: 'Parts must have minimum 10,000mm² area'
}
// Combined extras check
{
formula: '!(hasBanding && hasFinish) || longSide >= 200',
message: 'Parts with both banding and finish must be at least 200mm'
}
// Specific banding requirement
{
formula: "extras.banding.sides.l1 == '' || t >= 15",
message: 'Banding on long side 1 requires minimum 15mm thickness'
}
// Machining hole limit
{
formula: 'machining.holes <= 10',
message: 'Maximum 10 holes per part'
}When a part fails custom validation, an error message will be displayed and calculation will be blocked until the issue is resolved.
Complete Extras Example
javascript
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
// Edge banding
banding: {
labels: ['material', 'thickness'],
pricing: {
'oak|1mm': 1.0,
'oak|2mm': 1.2,
'pine|1mm': 0.8,
'maple|1mm': 1.5
},
locations: ['side.l1', 'side.l2', 'side.w1', 'side.w2'],
rules: {
t: { min: 12, max: 30 },
message: 'Banding only available for 12-30mm thick materials'
}
},
// Finishing
finish: {
labels: ['type', 'style'],
pricing: {
'spray|matt': 5.0,
'spray|satin': 5.5,
'lacquer|matt': 8.0,
'lacquer|satin': 8.5
},
locations: ['face.a', 'face.b'],
rules: {
longSide: { max: 2400 },
shortSide: { max: 1200 },
message: 'Finish is only available for parts smaller than 2400x1200mm'
}
},
// Planing
planing: {
labels: ['type'],
pricing: {
'standard': 2.0,
'premium': 3.5
},
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
groups: [
{
id: 'two-sided',
label: 'Two Sides (Top & Bottom)',
locations: ['face.a', 'face.b'],
price: 100,
hideIndividualLocations: true
},
{
id: 'four-sided',
label: 'Four Sides (All Faces & Edges)',
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
price: 200,
hideIndividualLocations: true
}
],
rules: {
t: { min: 8, max: 230 },
shortSide: { min: 10, max: 620 },
longSide: { max: 1000 },
message: 'Planing requires: thickness 8-230mm, width 10-620mm, length max 1000mm'
}
},
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutType: 'guillotine',
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD',
apiVersion: 3, // 2 or 3 (default: 3)
// Enable extras features
enable: {
banding: true,
finish: true,
planing: true,
machining: false
}
}
}Machining Configuration
Advanced machining features for holes, corners, and custom operations:
javascript
const initData = {
machining: {
faces: {
enabled: true
},
holes: {
enabled: true,
defaultDiameter: 5,
minDiameter: 5,
maxDiameter: 10,
enableDepth: true,
minDepth: 5
},
hingeHoles: {
enabled: true,
minimumHoleDistance: 10,
defaultDistanceFromEdge: 22,
defaultOuterSpacing: 10,
defaultHingeLength: 50
},
shelfHoles: {
enabled: true,
diameters: [5, 10]
},
corners: {
enabled: true,
types: ['radius', 'bevel'],
enableBanding: true
}
},
options: {
enable: {
machining: true
}
}
}Events
The Checkout API dispatches custom window events for integration:
smartcut/ready
Fired when the calculator is loaded and ready to initialize.
javascript
window.addEventListener('smartcut/ready', () => {
window.smartcutCheckout.init(initData)
})smartcut/initComplete
Fired when initialization is complete.
javascript
window.addEventListener('smartcut/initComplete', () => {
console.log('Calculator initialized')
})smartcut/calculating
Fired when a calculation starts.
javascript
window.addEventListener('smartcut/calculating', () => {
console.log('Calculation in progress...')
})smartcut/beforeCalculate
Fired after internal validation passes but before the calculation is sent to the server. This allows you to add custom validation logic using JavaScript and optionally prevent the calculation from proceeding.
Event properties:
cancelable: true- Callevent.preventDefault()to cancel the calculationevent.detail.data- The calculation data that would be sent (parts, stock, saw config, etc.)event.detail.error- Set this to a string to display a custom error message
javascript
window.addEventListener('smartcut/beforeCalculate', (event) => {
const calculationData = event.detail.data
// Example: Custom validation - check total quantity
const totalQuantity = calculationData.inputShapes.reduce(
(sum, part) => sum + (part.q || 1),
0
)
if (totalQuantity > 500) {
// Prevent the calculation
event.preventDefault()
// Set a custom error message to display
event.detail.error = 'Maximum 500 parts per order. Please split your order.'
return
}
// Example: Check part dimensions
for (const part of calculationData.inputShapes) {
if (part.l > 3000 || part.w > 2000) {
event.preventDefault()
event.detail.error = `Part "${part.name || 'Unnamed'}" exceeds maximum dimensions (3000x2000mm)`
return
}
}
// Validation passed - calculation will proceed
console.log('Custom validation passed, calculating...')
})When the calculation is prevented:
- The calculate button is re-enabled (thinking state is reset)
- If
event.detail.erroris set, the error message is displayed to the user - The
smartcut/resultevent will not fire
Calculation data structure (event.detail.data):
typescript
{
inputSaw: {
stockType: string,
bladeWidth: number,
cutType: string,
cutPreference: string,
// ... other saw properties
},
inputShapes: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
name: string | null,
orientationLock: string | null,
extras?: {...},
customData?: Record<string, any>
}>,
inputStock: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
cost: number | null,
// ... other stock properties
}>,
extrasOptions: {...} | null
}smartcut/result
Fired when optimization results are available.
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Optimization metadata
console.log(result.metadata)
// Parts used in optimization
console.log(result.parts)
// Stock sheets used
console.log(result.stock)
// Formatted pricing with currency
console.log(result.checkout.formattedTotalStockCost)
console.log(result.checkout.formattedBandingCost)
console.log(result.checkout.formattedFinishCost)
})smartcut/validationError
Fired when input validation fails.
javascript
window.addEventListener('smartcut/validationError', () => {
console.log('Please check your inputs')
})Result Format
The result object returned via the smartcut/result event contains different structures based on the apiVersion setting.
API Version 2 Result Structure
When apiVersion: 2 is set, results use a flat extras structure:
typescript
{
jobId: number,
metadata: {
totalStockCost: number,
bandingLengthByType: Record<string, number>,
finishAreaByType: Record<string, number>,
planingAreaByType: Record<string, number>,
addedPartTally: Record<string, number>,
usedStockTally: Record<string, number>,
unplacedParts: Array<Part>
},
parts: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
name: string | null,
orientationLock: '' | 'l' | 'w' | null,
// Flat extras structure (API v2)
banding?: Record<string, string | boolean>, // e.g., { x1: 'oak|1mm', x2: 'oak|1mm', y1: '', y2: '' }
finish?: Record<string, string | boolean>, // e.g., { a: true, b: false }
planing?: Record<string, string | boolean>,
customData?: Record<string, any>
}>,
stock: Array<{
id: string,
name: string | null,
l: number,
w: number,
t: number | null,
material: string | null,
q: number,
trim?: { x1: number, x2: number, y1: number, y2: number },
cost?: number,
// Analysis is aggregated across all stock with same parentId when stacking
// Most values are summed; areaEfficiency is averaged
analysis?: {
areaEfficiency: number, // Average efficiency across all stacked stock
finishArea: number, // Total finish area
bandingLength: number, // Total banding length
partArea: number, // Total part area
totalParts: number, // Total number of parts
stackedNumberOfCuts: number,
numberOfCuts: number,
stackedCutLength: number,
cutLength: number,
rollLength: number
},
customData?: Record<string, any>
}>,
offcuts: Array<{
l: number,
w: number,
t: number | null,
q: number,
stockId: string
}>,
inputs: {
parts: Array<Part> // Same structure as parts above
}
}API Version 3 Result Structure
When apiVersion: 3 is set (default), results use a nested extras structure:
typescript
{
jobId: number,
metadata: {
totalStockCost: number,
bandingLengthByType: Record<string, number>,
finishAreaByType: Record<string, number>,
planingAreaByType: Record<string, number>,
addedPartTally: Record<string, number>,
usedStockTally: Record<string, number>,
unplacedParts: Array<Part>
},
parts: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
name: string | null,
orientationLock: '' | 'l' | 'w' | null,
// Nested extras structure (API v3)
extras?: {
banding?: {
sides: Record<string, string | boolean> // e.g., { l1: 'oak|1mm', l2: 'oak|1mm', w1: '', w2: '' }
},
finish?: {
faces: Record<string, string | boolean> // e.g., { a: true, b: false }
},
planing?: {
sides?: Record<string, string | boolean>,
faces?: Record<string, string | boolean>
}
},
customData?: Record<string, any>
}>,
stock: Array<{
id: string,
name: string | null,
l: number,
w: number,
t: number | null,
material: string | null,
q: number,
trim?: { l1: number, l2: number, w1: number, w2: number }, // Note: uses l1/l2/w1/w2 in v3
cost?: number,
// Analysis is aggregated across all stock with same parentId when stacking
// Most values are summed; areaEfficiency is averaged
analysis?: {
areaEfficiency: number, // Average efficiency across all stacked stock
finishArea: number, // Total finish area
bandingLength: number, // Total banding length
partArea: number, // Total part area
totalParts: number, // Total number of parts
stackedNumberOfCuts: number,
numberOfCuts: number,
stackedCutLength: number,
cutLength: number,
rollLength: number
},
// Additional v3 fields
color?: { hex: string, name: string } | string,
weight?: number,
imageUrl?: string,
tags?: string[],
available?: boolean,
customData?: Record<string, any>
}>,
offcuts: Array<{
l: number,
w: number,
t: number | null,
q: number,
stockId: string
}>,
inputs: {
parts: Array<Part> // Same structure as parts above
}
}Using Result Data
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Get total cost from metadata
const totalCost = result.metadata.totalStockCost
// Get parts with optimization data
const optimizedParts = result.parts
// Get stock usage
const stockUsed = result.stock
// Get offcuts for reuse
const offcuts = result.offcuts
// Access extras based on API version
const apiVersion = 3 // Your configured version
result.parts.forEach(part => {
if (apiVersion === 2) {
// API v2: Flat structure
const bandingX1 = part.banding?.x1
const finishFaceA = part.finish?.a
} else {
// API v3: Nested structure
const bandingL1 = part.extras?.banding?.sides?.l1
const finishFaceA = part.extras?.finish?.faces?.a
}
})
// Get extras costs breakdown from metadata
const bandingCosts = result.metadata.bandingLengthByType
const finishCosts = result.metadata.finishAreaByType
const planingCosts = result.metadata.planingAreaByType
})API Result V3
When the emitAPIResult option is enabled, the result object includes an apiResultV3 property containing the complete API v3 response. This provides a standardized format matching the SmartCut API v3 specification:
typescript
{
// Standard checkout result properties...
jobId: number,
metadata: {...},
parts: [...],
stock: [...],
offcuts: [...],
inputs: {...},
// Additional API v3 response (when emitAPIResult: true)
apiResultV3?: {
jobId: number,
saw: {
stockType: string,
bladeWidth: number,
cutType: string,
cutPreference: string
},
stockList: Array<{...}>,
shapeList: Array<{...}>,
cutList: Array<{...}>,
offcuts: Array<{...}>,
unusableShapes: Array<{...}>,
metadata: {...}
}
}This is useful when integrating with systems that expect the full API v3 response format, or when you need access to additional data like cutList that isn't included in the standard checkout result.
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Access the API v3 response if available
if (result.apiResultV3) {
console.log('Cut list:', result.apiResultV3.cutList)
console.log('Shape list:', result.apiResultV3.shapeList)
}
})Product Filtering
Enable dynamic stock selection with filtering and search:
javascript
const initData = {
stock: [
{
l: 2800,
w: 2070,
t: 18,
cost: 95,
material: 'MDF',
name: 'White MDF 2800×2070×18mm',
category: 'Sheet Materials',
tags: ['mdf', 'white', 'standard'],
available: true
}
],
stockFilter: {
enabled: true,
config: {
displayMode: 'grid', // 'grid' or 'list'
enableSearch: true,
itemsPerPage: 20,
allowMultipleSelection: true,
availableFilters: [
{
field: 'material',
type: 'multiselect',
label: 'Material'
},
{
field: 't',
type: 'range',
label: 'Thickness (mm)',
min: 0,
max: 30
},
{
field: 'category',
type: 'multiselect',
label: 'Category'
}
]
}
},
options: {...}
}Support
- Email: hello@cutrevolution.com
- Chat: https://smartcut.dev/