diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..0481ea5
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,160 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+**deepnest** is an Electron-based desktop application for nesting parts for CNC tools, laser cutters, and plotters. It's a fork of the original SVGNest and deepnest projects with performance improvements and new features.
+
+Key technologies:
+- **Electron** with Node.js backend
+- **TypeScript** for type safety (compiled to JavaScript)
+- **Custom nesting engine** with C/C++ components via native modules
+- **Web-based UI** with SVG rendering
+- **Genetic algorithm** for optimization
+- **Clipper library** for polygon operations
+
+## Common Development Commands
+
+### Building and Running
+```bash
+# Install dependencies
+npm install
+
+# Build TypeScript to JavaScript
+npm run build
+
+# Start the application
+npm run start
+
+# Clean build artifacts
+npm run clean
+
+# Full clean including node_modules
+npm run clean-all
+```
+
+### Testing
+```bash
+# Run Playwright tests (requires one-time setup)
+npx playwright install chromium
+npm run test
+
+# Generate new tests interactively
+npm run pw:codegen
+```
+
+### Code Quality
+```bash
+# Lint and format code (runs automatically via pre-commit hooks)
+prettier --write **/*.{ts,html,css,scss,less,json}
+eslint --fix **/*.{ts,html,css,scss,less,json}
+```
+
+### Distribution
+```bash
+# Create distribution package
+npm run dist
+
+# Build everything and create distribution
+npm run dist-all
+```
+
+## Architecture
+
+### Application Structure
+- **main.js** - Electron main process entry point
+- **main/** - Core application code
+ - **deepnest.js** - Main nesting algorithm and genetic optimization
+ - **background.js** - Background worker for intensive calculations
+ - **index.html** - Main UI
+ - **util/** - Utility modules (geometry, matrix operations, etc.)
+
+### Key Components
+
+1. **Main Process (main.js)**
+ - Creates Electron windows
+ - Handles IPC communication
+ - Manages background workers
+ - Handles file operations and settings
+
+2. **Nesting Engine (deepnest.js)**
+ - `DeepNest` class - Main nesting logic
+ - `GeneticAlgorithm` class - Optimization algorithm
+ - SVG parsing and polygon processing
+ - Clipper library integration for geometry operations
+
+3. **Background Workers**
+ - Separate renderer processes for CPU-intensive tasks
+ - Communicates via IPC with main process
+ - Prevents UI blocking during calculations
+
+4. **TypeScript Utilities (main/util/)**
+ - Geometry operations
+ - Point, Vector, Matrix classes
+ - Polygon hull calculations
+ - SVG parsing utilities
+
+### Key Algorithms
+- **Genetic Algorithm** for part placement optimization
+- **No-Fit Polygon (NFP)** calculation for collision detection
+- **Polygon offsetting** using Clipper library
+- **Curve simplification** with Douglas-Peucker algorithm
+
+## Development Notes
+
+### TypeScript Configuration
+- Strict mode enabled with comprehensive type checking
+- Outputs to `./build` directory
+- Targets ES2023 with DOM and Node.js types
+
+### Electron Configuration
+- Uses `@electron/remote` for renderer process access
+- Context isolation disabled for legacy compatibility
+- Node integration enabled in renderers
+
+### Testing
+- Uses Playwright for end-to-end testing
+- Headless mode disabled by default for debugging
+- Screenshots and videos captured on test failure
+
+### Native Dependencies
+- Requires C++ build tools (Visual Studio on Windows)
+- Uses `@deepnest/calculate-nfp` for performance-critical calculations
+- Electron rebuild required after native module changes
+
+### Environment Variables
+- `deepnest_debug=1` - Opens dev tools
+- `SAVE_PLACEMENTS_PATH` - Custom export directory
+- `DEEPNEST_LONGLIST` - Keep more nesting results
+
+## Important File Locations
+
+- **Entry point**: `main.js`
+- **Main UI**: `main/index.html`
+- **Core logic**: `main/deepnest.js`
+- **Background worker**: `main/background.js`
+- **TypeScript source**: `main/util/*.ts`
+- **Tests**: `tests/`
+- **Build output**: `build/`
+
+## Performance Considerations
+
+- Nesting calculations run in background processes to prevent UI freezing
+- Polygon simplification reduces complexity for better performance
+- Genetic algorithm parameters can be tuned via configuration
+- Native modules handle computationally intensive operations
+
+## Debugging
+
+Set `deepnest_debug=1` environment variable to enable Chrome DevTools in all Electron windows.
+
+## Known Issues and Recent Fixes
+
+### Boundary Condition Bug (Fixed)
+- **Issue**: A 100mm x 100mm part could not be placed in a 100mm x 100mm bin
+- **Root Cause**: The `noFitPolygonRectangle` function was never called from `noFitPolygon`, and exact-fit cases created degenerate polygons
+- **Fix**:
+ - Added rectangle detection check in `noFitPolygon` function (`main/util/geometryutil.js:1594-1599`)
+ - Added special handling for exact-fit cases in `noFitPolygonRectangle` (`main/util/geometryutil.js:1581-1592`)
+- **Files Modified**: `main/util/geometryutil.js`
\ No newline at end of file
diff --git a/debug_placement_1752156063846.log b/debug_placement_1752156063846.log
new file mode 100644
index 0000000..f72cc34
--- /dev/null
+++ b/debug_placement_1752156063846.log
@@ -0,0 +1,884 @@
+=== DEBUG LOG STARTED 2025-07-10T14:01:03.846Z ===
+[2025-07-10T14:01:03.846Z] Debug logging initialized
+[2025-07-10T14:01:05.670Z] Background-start event received, processing data...
+[2025-07-10T14:01:05.670Z] Data contains: 1 parts
+[2025-07-10T14:01:05.671Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:05.671Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:05.672Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:05.672Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.672Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.673Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:05.674Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:05.674Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:05.674Z] WATCH
+[2025-07-10T14:01:05.674Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:05.675Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:05.675Z] placeParts completed, placement result: success
+[2025-07-10T14:01:05.762Z] Background-start event received, processing data...
+[2025-07-10T14:01:05.763Z] Data contains: 1 parts
+[2025-07-10T14:01:05.763Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:05.763Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:05.763Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:05.763Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.763Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.764Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:05.764Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:05.764Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:05.764Z] WATCH
+[2025-07-10T14:01:05.764Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:05.764Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:05.764Z] placeParts completed, placement result: success
+[2025-07-10T14:01:05.869Z] Background-start event received, processing data...
+[2025-07-10T14:01:05.869Z] Data contains: 1 parts
+[2025-07-10T14:01:05.869Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:05.869Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:05.869Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:05.869Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.869Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.870Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:05.870Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:05.870Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:05.870Z] WATCH
+[2025-07-10T14:01:05.870Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:05.870Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:05.870Z] placeParts completed, placement result: success
+[2025-07-10T14:01:05.961Z] Background-start event received, processing data...
+[2025-07-10T14:01:05.961Z] Data contains: 1 parts
+[2025-07-10T14:01:05.961Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:05.961Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:05.961Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:05.961Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.961Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:05.962Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:05.962Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:05.962Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:05.962Z] WATCH
+[2025-07-10T14:01:05.962Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:05.962Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:05.963Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.061Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.061Z] Data contains: 1 parts
+[2025-07-10T14:01:06.061Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.062Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.062Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.062Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.062Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.062Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.062Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.063Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.063Z] WATCH
+[2025-07-10T14:01:06.063Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.063Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.063Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.161Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.162Z] Data contains: 1 parts
+[2025-07-10T14:01:06.162Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.162Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.162Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.162Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.163Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.163Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.163Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.163Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.163Z] WATCH
+[2025-07-10T14:01:06.164Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.164Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.164Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.262Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.262Z] Data contains: 1 parts
+[2025-07-10T14:01:06.262Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.262Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.262Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.262Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.263Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.263Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.263Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.263Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.263Z] WATCH
+[2025-07-10T14:01:06.263Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.263Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.264Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.361Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.361Z] Data contains: 1 parts
+[2025-07-10T14:01:06.361Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.362Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.362Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.362Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.362Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.362Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.362Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.362Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.363Z] WATCH
+[2025-07-10T14:01:06.363Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.363Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.363Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.462Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.462Z] Data contains: 1 parts
+[2025-07-10T14:01:06.462Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.462Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.463Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.463Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.463Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.463Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.463Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.463Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.463Z] WATCH
+[2025-07-10T14:01:06.464Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.464Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.464Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.565Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.565Z] Data contains: 1 parts
+[2025-07-10T14:01:06.565Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.565Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.565Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.565Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.566Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.566Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.566Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.566Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.566Z] WATCH
+[2025-07-10T14:01:06.566Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.566Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.567Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.668Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.668Z] Data contains: 1 parts
+[2025-07-10T14:01:06.669Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.669Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.669Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.669Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.669Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.669Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.669Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.670Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.670Z] WATCH
+[2025-07-10T14:01:06.670Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.670Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.670Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.768Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.769Z] Data contains: 1 parts
+[2025-07-10T14:01:06.769Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.769Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.769Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.769Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.769Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.770Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.770Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.770Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.770Z] WATCH
+[2025-07-10T14:01:06.770Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.770Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.770Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.868Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.869Z] Data contains: 1 parts
+[2025-07-10T14:01:06.869Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.869Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.869Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.869Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.869Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.870Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.870Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.870Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.870Z] WATCH
+[2025-07-10T14:01:06.870Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.870Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.870Z] placeParts completed, placement result: success
+[2025-07-10T14:01:06.961Z] Background-start event received, processing data...
+[2025-07-10T14:01:06.961Z] Data contains: 1 parts
+[2025-07-10T14:01:06.961Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:06.961Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:06.962Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:06.962Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.962Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:06.962Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:06.962Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:06.962Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:06.963Z] WATCH
+[2025-07-10T14:01:06.963Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:06.963Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:06.963Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.061Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.061Z] Data contains: 1 parts
+[2025-07-10T14:01:07.061Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.061Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.061Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.062Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.062Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.062Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.062Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.062Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.062Z] WATCH
+[2025-07-10T14:01:07.062Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.063Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.063Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.161Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.161Z] Data contains: 1 parts
+[2025-07-10T14:01:07.161Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.161Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.161Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.162Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.162Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.162Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.162Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.162Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.162Z] WATCH
+[2025-07-10T14:01:07.162Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.163Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.163Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.266Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.266Z] Data contains: 1 parts
+[2025-07-10T14:01:07.267Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.267Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.267Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.267Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.267Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.267Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.267Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.267Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.268Z] WATCH
+[2025-07-10T14:01:07.268Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.268Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.268Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.361Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.361Z] Data contains: 1 parts
+[2025-07-10T14:01:07.361Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.362Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.362Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.362Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.362Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.362Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.362Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.362Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.362Z] WATCH
+[2025-07-10T14:01:07.363Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.363Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.363Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.469Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.469Z] Data contains: 1 parts
+[2025-07-10T14:01:07.469Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.469Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.469Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.469Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.470Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.470Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.470Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.470Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.470Z] WATCH
+[2025-07-10T14:01:07.470Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.470Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.470Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.562Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.562Z] Data contains: 1 parts
+[2025-07-10T14:01:07.562Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.562Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.562Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.563Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.563Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.563Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.563Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.563Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.563Z] WATCH
+[2025-07-10T14:01:07.563Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.564Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.564Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.669Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.669Z] Data contains: 1 parts
+[2025-07-10T14:01:07.669Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.669Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.669Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.669Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.670Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.670Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.670Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.670Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.670Z] WATCH
+[2025-07-10T14:01:07.670Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.670Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.670Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.761Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.761Z] Data contains: 1 parts
+[2025-07-10T14:01:07.761Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.761Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.761Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.762Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.762Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.762Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.762Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.762Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.762Z] WATCH
+[2025-07-10T14:01:07.762Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.762Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.763Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.862Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.862Z] Data contains: 1 parts
+[2025-07-10T14:01:07.862Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.862Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.862Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.863Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.863Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.863Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.863Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.863Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.863Z] WATCH
+[2025-07-10T14:01:07.863Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.864Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.864Z] placeParts completed, placement result: success
+[2025-07-10T14:01:07.961Z] Background-start event received, processing data...
+[2025-07-10T14:01:07.961Z] Data contains: 1 parts
+[2025-07-10T14:01:07.962Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:07.962Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:07.962Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:07.962Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.962Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:07.962Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:07.962Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:07.962Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:07.963Z] WATCH
+[2025-07-10T14:01:07.963Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:07.963Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:07.963Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.062Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.062Z] Data contains: 1 parts
+[2025-07-10T14:01:08.062Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.062Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.063Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.063Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.063Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.063Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.063Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.063Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.063Z] WATCH
+[2025-07-10T14:01:08.064Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.064Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.064Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.169Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.169Z] Data contains: 1 parts
+[2025-07-10T14:01:08.169Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.169Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.169Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.169Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.170Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.170Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.170Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.170Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.170Z] WATCH
+[2025-07-10T14:01:08.170Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.170Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.171Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.268Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.269Z] Data contains: 1 parts
+[2025-07-10T14:01:08.269Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.269Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.269Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.269Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.269Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.269Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.269Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.270Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.270Z] WATCH
+[2025-07-10T14:01:08.270Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.270Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.270Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.361Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.361Z] Data contains: 1 parts
+[2025-07-10T14:01:08.362Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.362Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.362Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.362Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.362Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.362Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.362Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.362Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.362Z] WATCH
+[2025-07-10T14:01:08.363Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.363Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.363Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.469Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.469Z] Data contains: 1 parts
+[2025-07-10T14:01:08.469Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.469Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.469Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.470Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.470Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.470Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.470Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.470Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.470Z] WATCH
+[2025-07-10T14:01:08.470Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.470Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.470Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.561Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.561Z] Data contains: 1 parts
+[2025-07-10T14:01:08.561Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.561Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.561Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.562Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.562Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.562Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.562Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.562Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.562Z] WATCH
+[2025-07-10T14:01:08.562Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.562Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.563Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.661Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.661Z] Data contains: 1 parts
+[2025-07-10T14:01:08.661Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.661Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.661Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.661Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.662Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.662Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.662Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.662Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.662Z] WATCH
+[2025-07-10T14:01:08.662Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.662Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.662Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.766Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.767Z] Data contains: 1 parts
+[2025-07-10T14:01:08.767Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.767Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.767Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.767Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.767Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.767Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.768Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.768Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.768Z] WATCH
+[2025-07-10T14:01:08.768Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.768Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.768Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.868Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.869Z] Data contains: 1 parts
+[2025-07-10T14:01:08.869Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.869Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.869Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.869Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.869Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.869Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.870Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.870Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.870Z] WATCH
+[2025-07-10T14:01:08.870Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.870Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.870Z] placeParts completed, placement result: success
+[2025-07-10T14:01:08.968Z] Background-start event received, processing data...
+[2025-07-10T14:01:08.968Z] Data contains: 1 parts
+[2025-07-10T14:01:08.968Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:08.969Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:08.969Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:08.969Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.969Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:08.969Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:08.969Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:08.969Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:08.970Z] WATCH
+[2025-07-10T14:01:08.970Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:08.970Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:08.970Z] placeParts completed, placement result: success
+[2025-07-10T14:01:09.061Z] Background-start event received, processing data...
+[2025-07-10T14:01:09.061Z] Data contains: 1 parts
+[2025-07-10T14:01:09.061Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:09.061Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:09.061Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:09.062Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:09.062Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:09.062Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:09.062Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:09.062Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:09.062Z] WATCH
+[2025-07-10T14:01:09.062Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:09.062Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:09.062Z] placeParts completed, placement result: success
+[2025-07-10T14:01:10.061Z] Background-start event received, processing data...
+[2025-07-10T14:01:10.061Z] Data contains: 1 parts
+[2025-07-10T14:01:10.062Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:10.062Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:10.062Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:10.062Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:10.062Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:10.062Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:10.062Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:10.063Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:10.063Z] WATCH
+[2025-07-10T14:01:10.063Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:10.063Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:10.063Z] placeParts completed, placement result: success
+[2025-07-10T14:01:11.055Z] Background-start event received, processing data...
+[2025-07-10T14:01:11.055Z] Data contains: 1 parts
+[2025-07-10T14:01:11.056Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:11.056Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:11.056Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:11.056Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:11.056Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:11.056Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:11.056Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:11.057Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:11.057Z] WATCH
+[2025-07-10T14:01:11.057Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:11.057Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:11.057Z] placeParts completed, placement result: success
+[2025-07-10T14:01:12.053Z] Background-start event received, processing data...
+[2025-07-10T14:01:12.053Z] Data contains: 1 parts
+[2025-07-10T14:01:12.053Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:12.053Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:12.054Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:12.054Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:12.054Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:12.054Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:12.054Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:12.054Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:12.054Z] WATCH
+[2025-07-10T14:01:12.054Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:12.054Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:12.055Z] placeParts completed, placement result: success
+[2025-07-10T14:01:13.060Z] Background-start event received, processing data...
+[2025-07-10T14:01:13.060Z] Data contains: 1 parts
+[2025-07-10T14:01:13.060Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:13.060Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:13.060Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:13.060Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.061Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.061Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:13.061Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:13.061Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:13.061Z] WATCH
+[2025-07-10T14:01:13.061Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:13.061Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:13.061Z] placeParts completed, placement result: success
+[2025-07-10T14:01:13.858Z] Background-start event received, processing data...
+[2025-07-10T14:01:13.858Z] Data contains: 1 parts
+[2025-07-10T14:01:13.858Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:13.858Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:13.858Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:13.858Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.858Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.859Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:13.859Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:13.859Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:13.859Z] WATCH
+[2025-07-10T14:01:13.859Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:13.859Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:13.859Z] placeParts completed, placement result: success
+[2025-07-10T14:01:13.961Z] Background-start event received, processing data...
+[2025-07-10T14:01:13.961Z] Data contains: 1 parts
+[2025-07-10T14:01:13.962Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:13.962Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:13.962Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:13.962Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.962Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:13.962Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:13.962Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:13.963Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:13.963Z] WATCH
+[2025-07-10T14:01:13.963Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:13.963Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:13.963Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.061Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.061Z] Data contains: 1 parts
+[2025-07-10T14:01:14.061Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.062Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.062Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.062Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.062Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.062Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.062Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.062Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.063Z] WATCH
+[2025-07-10T14:01:14.063Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.063Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.063Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.164Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.164Z] Data contains: 1 parts
+[2025-07-10T14:01:14.164Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.164Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.165Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.165Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.165Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.165Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.165Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.165Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.165Z] WATCH
+[2025-07-10T14:01:14.166Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.166Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.166Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.269Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.269Z] Data contains: 1 parts
+[2025-07-10T14:01:14.269Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.269Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.270Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.270Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.270Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.270Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.270Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.270Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.271Z] WATCH
+[2025-07-10T14:01:14.271Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.271Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.271Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.361Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.361Z] Data contains: 1 parts
+[2025-07-10T14:01:14.362Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.362Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.362Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.362Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.362Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.362Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.362Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.362Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.363Z] WATCH
+[2025-07-10T14:01:14.363Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.363Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.363Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.463Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.463Z] Data contains: 1 parts
+[2025-07-10T14:01:14.464Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.464Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.464Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.464Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.464Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.464Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.464Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.464Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.464Z] WATCH
+[2025-07-10T14:01:14.465Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.465Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.465Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.561Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.561Z] Data contains: 1 parts
+[2025-07-10T14:01:14.561Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.561Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.562Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.562Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.562Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.562Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.562Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.562Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.562Z] WATCH
+[2025-07-10T14:01:14.563Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.563Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.563Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.661Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.662Z] Data contains: 1 parts
+[2025-07-10T14:01:14.662Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.662Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.662Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.662Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.662Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.662Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.662Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.663Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.663Z] WATCH
+[2025-07-10T14:01:14.663Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.663Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.663Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.769Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.769Z] Data contains: 1 parts
+[2025-07-10T14:01:14.769Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.769Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.769Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.769Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.770Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.770Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.770Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.770Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.770Z] WATCH
+[2025-07-10T14:01:14.770Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.770Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.770Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.861Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.861Z] Data contains: 1 parts
+[2025-07-10T14:01:14.862Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.862Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.862Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.862Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.862Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.862Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.862Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.862Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.863Z] WATCH
+[2025-07-10T14:01:14.863Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.863Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.863Z] placeParts completed, placement result: success
+[2025-07-10T14:01:14.969Z] Background-start event received, processing data...
+[2025-07-10T14:01:14.969Z] Data contains: 1 parts
+[2025-07-10T14:01:14.969Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:14.969Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:14.969Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:14.970Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.970Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:14.970Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:14.970Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:14.970Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:14.970Z] WATCH
+[2025-07-10T14:01:14.970Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:14.971Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:14.971Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.063Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.063Z] Data contains: 1 parts
+[2025-07-10T14:01:15.064Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.064Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.064Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.064Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.064Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.064Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.064Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.064Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.065Z] WATCH
+[2025-07-10T14:01:15.065Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.065Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.065Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.161Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.161Z] Data contains: 1 parts
+[2025-07-10T14:01:15.161Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.161Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.162Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.162Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.162Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.162Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.162Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.162Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.162Z] WATCH
+[2025-07-10T14:01:15.163Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.163Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.163Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.264Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.264Z] Data contains: 1 parts
+[2025-07-10T14:01:15.265Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.265Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.265Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.265Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.265Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.265Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.265Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.265Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.265Z] WATCH
+[2025-07-10T14:01:15.266Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.266Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.266Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.369Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.369Z] Data contains: 1 parts
+[2025-07-10T14:01:15.369Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.370Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.370Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.370Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.370Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.370Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.370Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.370Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.370Z] WATCH
+[2025-07-10T14:01:15.371Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.371Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.371Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.467Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.467Z] Data contains: 1 parts
+[2025-07-10T14:01:15.467Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.467Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.467Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.468Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.468Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.468Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.468Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.468Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.468Z] WATCH
+[2025-07-10T14:01:15.468Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.469Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.469Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.569Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.569Z] Data contains: 1 parts
+[2025-07-10T14:01:15.569Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.569Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.570Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.570Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.570Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.570Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.570Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.570Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.570Z] WATCH
+[2025-07-10T14:01:15.570Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.570Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.571Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.669Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.669Z] Data contains: 1 parts
+[2025-07-10T14:01:15.669Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.669Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.670Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.670Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.670Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.670Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.670Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.670Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.670Z] WATCH
+[2025-07-10T14:01:15.671Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.671Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.671Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.763Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.764Z] Data contains: 1 parts
+[2025-07-10T14:01:15.764Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.764Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.764Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.764Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.764Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.764Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.764Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.764Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.765Z] WATCH
+[2025-07-10T14:01:15.765Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.765Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.765Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.869Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.869Z] Data contains: 1 parts
+[2025-07-10T14:01:15.870Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.870Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.870Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.870Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.870Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.870Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.871Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.871Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.871Z] WATCH
+[2025-07-10T14:01:15.871Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.871Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.871Z] placeParts completed, placement result: success
+[2025-07-10T14:01:15.964Z] Background-start event received, processing data...
+[2025-07-10T14:01:15.964Z] Data contains: 1 parts
+[2025-07-10T14:01:15.965Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:15.965Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:15.965Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:15.965Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.965Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:15.965Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:15.965Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:15.965Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:15.965Z] WATCH
+[2025-07-10T14:01:15.966Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:15.966Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:15.966Z] placeParts completed, placement result: success
+[2025-07-10T14:01:16.069Z] Background-start event received, processing data...
+[2025-07-10T14:01:16.069Z] Data contains: 1 parts
+[2025-07-10T14:01:16.069Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:16.069Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:16.070Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:16.070Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:16.070Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:16.070Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:16.070Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:16.070Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:16.070Z] WATCH
+[2025-07-10T14:01:16.071Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:16.071Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:16.071Z] placeParts completed, placement result: success
+[2025-07-10T14:01:16.165Z] Background-start event received, processing data...
+[2025-07-10T14:01:16.165Z] Data contains: 1 parts
+[2025-07-10T14:01:16.165Z] Created 0 NFP pairs for processing
+[2025-07-10T14:01:16.165Z] About to call placeParts with 1 parts and 1 sheets
+[2025-07-10T14:01:16.166Z] PlaceParts started with 1 parts and 1 sheets
+[2025-07-10T14:01:16.166Z] First part dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:16.166Z] First sheet dimensions: 2834.6456693 x 2834.6456693
+[2025-07-10T14:01:16.166Z] === PROCESSING SHEET 0 bounds: {"x":0,"y":0,"width":2834.6456693,"height":2834.6456693} area: 8035216.070481245
+[2025-07-10T14:01:16.166Z] Processing new sheet, current parts remaining: 0
+[2025-07-10T14:01:16.166Z] UNPLACED PARTS 0 of 1
+[2025-07-10T14:01:16.166Z] WATCH
+[2025-07-10T14:01:16.166Z] Utilisation of the sheet(s): 0.00%
+[2025-07-10T14:01:16.167Z] Fitness calculation resulted in NaN, using fallback value
+[2025-07-10T14:01:16.167Z] placeParts completed, placement result: success
diff --git a/main/background.js b/main/background.js
index 7c61d22..8c71f2f 100755
--- a/main/background.js
+++ b/main/background.js
@@ -11,6 +11,27 @@ window.onload = function () {
window.path = require('path')
window.url = require('url')
window.fs = require('graceful-fs');
+
+ // Create debug log file
+ const debugLogPath = window.path.join(process.cwd(), `debug_placement_${Date.now()}.log`);
+ window.debugLog = function(...message) {
+ const timestamp = new Date().toISOString();
+ const logEntry = `[${timestamp}] ${message.join(' ')}\n`;
+ console.log(...message); // Still log to console
+ try {
+ window.fs.appendFileSync(debugLogPath, logEntry);
+ } catch (e) {
+ console.error('Failed to write to debug log:', e);
+ }
+ };
+
+ // Clear previous log at start
+ try {
+ window.fs.writeFileSync(debugLogPath, `=== DEBUG LOG STARTED ${new Date().toISOString()} ===\n`);
+ window.debugLog('Debug logging initialized');
+ } catch (e) {
+ console.error('Failed to initialize debug log:', e);
+ }
/*
add package 'filequeue 0.5.0' if you enable this
window.FileQueue = require('filequeue');
@@ -19,6 +40,9 @@ window.onload = function () {
window.db = new NfpCache();
ipcRenderer.on('background-start', (event, data) => {
+ window.debugLog('Background-start event received, processing data...');
+ window.debugLog('Data contains: ' + data.individual.placement.length + ' parts');
+
var index = data.index;
var individual = data.individual;
@@ -81,13 +105,32 @@ window.onload = function () {
}
}
- // console.log('pairs: ', pairs.length);
+ window.debugLog('Created ' + pairs.length + ' NFP pairs for processing');
+ if (pairs.length > 1000) {
+ window.debugLog('Very large number of NFP pairs - this will take a long time!');
+ }
var process = function (pair) {
+ window.debugLog(' PAIR: A=' + pair.Asource + ' B=' + pair.Bsource + ' Arot=' + pair.Arotation + ' Brot=' + pair.Brotation);
+
var A = rotatePolygon(pair.A, pair.Arotation);
var B = rotatePolygon(pair.B, pair.Brotation);
+ // TEMPORARILY DISABLE rectangle optimization to test
+ if (false && GeometryUtil.isRectangle(pair.A) && !pair.inside) {
+ var rectangleNfp = GeometryUtil.noFitPolygonRectangle(A, B);
+ if (rectangleNfp && rectangleNfp.length > 0) {
+ return {
+ Asource: pair.Asource,
+ Bsource: pair.Bsource,
+ Arotation: pair.Arotation,
+ Brotation: pair.Brotation,
+ nfp: rectangleNfp
+ };
+ }
+ }
+
var clipper = new ClipperLib.Clipper();
var Ac = toClipperCoordinates(A);
@@ -116,10 +159,14 @@ window.onload = function () {
clipperNfp[i].y += B[0].y;
}
- pair.A = null;
- pair.B = null;
- pair.nfp = clipperNfp;
- return pair;
+ window.debugLog(' NFP RESULT for A=' + pair.Asource + ' rotation=' + pair.Arotation + ': ' + JSON.stringify(clipperNfp));
+ return {
+ Asource: pair.Asource,
+ Bsource: pair.Bsource,
+ Arotation: pair.Arotation,
+ Brotation: pair.Brotation,
+ nfp: clipperNfp
+ };
function toClipperCoordinates(polygon) {
var clone = [];
@@ -163,13 +210,16 @@ window.onload = function () {
// run the placement synchronously
function sync() {
- //console.log('starting synchronous calculations', Object.keys(window.nfpCache).length);
- // console.log('in sync');
+ //window.debugLog('starting synchronous calculations', Object.keys(window.nfpCache).length);
+ // window.debugLog('in sync');
var c = window.db.getStats();
- // console.log('nfp cached:', c);
- // console.log()
+ // window.debugLog('nfp cached:', c);
+ // window.debugLog()
+ window.debugLog('About to call placeParts with ' + parts.length + ' parts and ' + data.sheets.length + ' sheets');
ipcRenderer.send('test', [data.sheets, parts, data.config, index]);
+
var placement = placeParts(data.sheets, parts, data.config, index);
+ window.debugLog('placeParts completed, placement result: ' + (placement ? 'success' : 'null'));
placement.index = data.index;
ipcRenderer.send('background-response', placement);
@@ -179,6 +229,7 @@ window.onload = function () {
if (pairs.length > 0) {
+ window.debugLog('Starting parallel NFP processing for', pairs.length, 'pairs');
var p = new Parallel(pairs, {
evalPath: '../build/util/eval.js',
synchronous: false
@@ -195,65 +246,95 @@ window.onload = function () {
p.require('../../main/util/clipper.js');
p.require('../../main/util/geometryutil.js');
+ window.debugLog('Starting p.map processing...');
+
+ // Add timeout for parallel processing
+ var parallelTimeout = setTimeout(function () {
+ console.error('Parallel processing timeout after 60 seconds, continuing with sync');
+ sync();
+ }, 60000);
+
p.map(process).then(function (processed) {
- function getPart(source) {
- for (let k = 0; k < parts.length; k++) {
- if (parts[k].source == source) {
- return parts[k];
+ clearTimeout(parallelTimeout);
+ window.debugLog('Parallel processing completed, got', processed.length, 'results');
+ window.debugLog('Processed NFPs:', processed);
+ try {
+ function getPart(source) {
+ for (let k = 0; k < parts.length; k++) {
+ if (parts[k].source == source) {
+ return parts[k];
+ }
}
+ return null;
}
- return null;
- }
- // store processed data in cache
- for (let i = 0; i < processed.length; i++) {
- // returned data only contains outer nfp, we have to account for any holes separately in the synchronous portion
- // this is because the c++ addon which can process interior nfps cannot run in the worker thread
- var A = getPart(processed[i].Asource);
- var B = getPart(processed[i].Bsource);
-
- var Achildren = [];
-
- var j;
- if (A.children) {
- for (let j = 0; j < A.children.length; j++) {
- Achildren.push(rotatePolygon(A.children[j], processed[i].Arotation));
+
+ // store processed data in cache
+ for (let i = 0; i < processed.length; i++) {
+ window.debugLog('Processing NFP result', i, 'Asource:', processed[i].Asource, 'Bsource:', processed[i].Bsource);
+
+ // returned data only contains outer nfp, we have to account for any holes separately in the synchronous portion
+ // this is because the c++ addon which can process interior nfps cannot run in the worker thread
+ var A = getPart(processed[i].Asource);
+ var B = getPart(processed[i].Bsource);
+
+ // Add null checks for A and B
+ if (!A || !B) {
+ window.debugLog('Skipping NFP result', i, 'due to null parts. A:', A, 'B:', B, 'Asource:', processed[i].Asource, 'Bsource:', processed[i].Bsource);
+ continue;
}
- }
- if (Achildren.length > 0) {
- var Brotated = rotatePolygon(B, processed[i].Brotation);
- var bbounds = GeometryUtil.getPolygonBounds(Brotated);
- var cnfp = [];
+ var Achildren = [];
+
+ var j;
+ if (A.children && A.children.length > 0) {
+ for (let j = 0; j < A.children.length; j++) {
+ Achildren.push(rotatePolygon(A.children[j], processed[i].Arotation));
+ }
+ }
- for (let j = 0; j < Achildren.length; j++) {
- var cbounds = GeometryUtil.getPolygonBounds(Achildren[j]);
- if (cbounds.width > bbounds.width && cbounds.height > bbounds.height) {
- var n = getInnerNfp(Achildren[j], Brotated, data.config);
- if (n && n.length > 0) {
- cnfp = cnfp.concat(n);
+ var cnfp = [];
+ if (Achildren.length > 0) {
+ var Brotated = rotatePolygon(B, processed[i].Brotation);
+ var bbounds = GeometryUtil.getPolygonBounds(Brotated);
+
+ for (let j = 0; j < Achildren.length; j++) {
+ var cbounds = GeometryUtil.getPolygonBounds(Achildren[j]);
+ if (cbounds.width > bbounds.width && cbounds.height > bbounds.height) {
+ var n = getInnerNfp(Achildren[j], Brotated, data.config);
+ if (n && n.length > 0) {
+ cnfp = cnfp.concat(n);
+ }
}
}
+
+ processed[i].nfp.children = cnfp;
}
- processed[i].nfp.children = cnfp;
- }
+ // Only cache if we have valid sources
+ if (processed[i].Asource !== undefined && processed[i].Bsource !== undefined) {
+ var doc = {
+ A: processed[i].Asource,
+ B: processed[i].Bsource,
+ Arotation: processed[i].Arotation,
+ Brotation: processed[i].Brotation,
+ nfp: processed[i].nfp
+ };
+ window.db.insert(doc);
+ } else {
+ window.debugLog('Skipping cache insert due to undefined sources:', processed[i].Asource, processed[i].Bsource);
+ }
- var doc = {
- A: processed[i].Asource,
- B: processed[i].Bsource,
- Arotation: processed[i].Arotation,
- Brotation: processed[i].Brotation,
- nfp: processed[i].nfp
- };
- window.db.insert(doc);
+ }
+ } catch (e) {
+ console.error('Error processing NFP results:', e);
}
// console.timeEnd('Total');
- // console.log('before sync');
+ // window.debugLog('before sync');
+ window.debugLog('About to call sync after parallel processing');
sync();
});
- }
- else {
+ } else {
sync();
}
});
@@ -482,23 +563,23 @@ function toNestCoordinates(polygon, scale) {
};
function getHull(polygon) {
- // Convert the polygon points to proper Point objects for HullPolygon
- var points = [];
- for (let i = 0; i < polygon.length; i++) {
- points.push({
- x: polygon[i].x,
- y: polygon[i].y
- });
- }
-
- var hullpoints = HullPolygon.hull(points);
-
- // If hull calculation failed, return original polygon
- if (!hullpoints) {
- return polygon;
- }
-
- return hullpoints;
+ // Convert the polygon points to proper Point objects for HullPolygon
+ var points = [];
+ for (let i = 0; i < polygon.length; i++) {
+ points.push({
+ x: polygon[i].x,
+ y: polygon[i].y
+ });
+ }
+
+ var hullpoints = HullPolygon.hull(points);
+
+ // If hull calculation failed, return original polygon
+ if (!hullpoints) {
+ return polygon;
+ }
+
+ return hullpoints;
}
function rotatePolygon(polygon, degrees) {
@@ -545,22 +626,33 @@ function getOuterNfp(A, B, inside) {
return doc;
}
+ // TEMPORARILY DISABLE rectangle optimization to test
+ if (false && !inside && GeometryUtil.isRectangle(A) && !A.children) {
+ var rectangleNfp = GeometryUtil.noFitPolygonRectangle(A, B);
+ if (rectangleNfp && rectangleNfp.length > 0) {
+ nfp = rectangleNfp;
+ // Save to cache
+ window.db.insert({ A: A.source, B: B.source, Arotation: A.rotation, Brotation: B.rotation, nfp: nfp });
+ return nfp;
+ }
+ }
+
// not found in cache
if (inside || (A.children && A.children.length > 0)) {
- //console.log('computing minkowski: ',A.length, B.length);
+ //window.debugLog('computing minkowski: ',A.length, B.length);
if (!A.children) {
A.children = [];
}
if (!B.children) {
B.children = [];
}
- //console.log('computing minkowski: ', JSON.stringify(Object.assign({}, {A:Object.assign({},A)},{B:Object.assign({},B)})));
+ //window.debugLog('computing minkowski: ', JSON.stringify(Object.assign({}, {A:Object.assign({},A)},{B:Object.assign({},B)})));
//console.time('addon');
nfp = addon.calculateNFP({ A: A, B: B });
//console.timeEnd('addon');
}
else {
- // console.log('minkowski', A.length, B.length, A.source, B.source);
+ // window.debugLog('minkowski', A.length, B.length, A.source, B.source);
// console.time('clipper');
var Ac = toClipperCoordinates(A);
@@ -572,7 +664,7 @@ function getOuterNfp(A, B, inside) {
Bc[i].Y *= -1;
}
var solution = ClipperLib.Clipper.MinkowskiSum(Ac, Bc, true);
- //console.log(solution.length, solution);
+ //window.debugLog(solution.length, solution);
//var clipperNfp = toNestCoordinates(solution[0], 10000000);
var clipperNfp;
@@ -592,12 +684,12 @@ function getOuterNfp(A, B, inside) {
}
nfp = [clipperNfp];
- //console.log('clipper nfp', JSON.stringify(nfp));
+ //window.debugLog('clipper nfp', JSON.stringify(nfp));
// console.timeEnd('clipper');
}
if (!nfp || nfp.length == 0) {
- //console.log('holy shit', nfp, A, B, JSON.stringify(A), JSON.stringify(B));
+ //window.debugLog('holy shit', nfp, A, B, JSON.stringify(A), JSON.stringify(B));
return null
}
@@ -649,11 +741,48 @@ function getInnerNfp(A, B, config) {
var doc = window.db.find({ A: A.source, B: B.source, Arotation: 0, Brotation: B.rotation }, true);
if (doc) {
- //console.log('fetch inner', A.source, B.source, doc);
+ //window.debugLog('fetch inner', A.source, B.source, doc);
return doc;
}
}
+ // Check for exact-fit or near-exact-fit case
+ // Only apply this optimization if A has no children (i.e., it's an empty sheet)
+ if (GeometryUtil.isRectangle(A) && GeometryUtil.isRectangle(B) && (!A.children || A.children.length === 0)) {
+ var ABounds = GeometryUtil.getPolygonBounds(A);
+ var BBounds = GeometryUtil.getPolygonBounds(B);
+
+ var widthDiff = Math.abs(ABounds.width - BBounds.width);
+ var heightDiff = Math.abs(ABounds.height - BBounds.height);
+
+ // If part is very close to sheet size (within tolerance, accounting for scale)
+ var tolerance = Math.max(0.001, Math.max(ABounds.width, ABounds.height) * 0.0001); // Dynamic tolerance
+ if (widthDiff < tolerance && heightDiff < tolerance) {
+ window.debugLog('Exact-fit detected for empty sheet:', A.source, 'and part:', B.source);
+ // For exact-fit, return a single point NFP
+ // The NFP point should be where the part's reference point needs to be
+ // to place the part at the sheet's top-left corner
+ var result = [[{
+ x: A[0].x + (ABounds.width - BBounds.width) / 2,
+ y: A[0].y + (ABounds.height - BBounds.height) / 2
+ }]];
+
+ // Cache the result
+ if (typeof A.source !== 'undefined' && typeof B.source !== 'undefined') {
+ var doc = {
+ A: A.source,
+ B: B.source,
+ Arotation: 0,
+ Brotation: B.rotation,
+ nfp: result
+ };
+ window.db.insert(doc, true);
+ }
+
+ return result;
+ }
+ }
+
var frame = getFrame(A);
var nfp = getOuterNfp(frame, B, true);
@@ -700,7 +829,7 @@ function getInnerNfp(A, B, config) {
if (typeof A.source !== 'undefined' && typeof B.source !== 'undefined') {
// insert into db
- // console.log('inserting inner: ', A.source, B.source, B.rotation, f);
+ // window.debugLog('inserting inner: ', A.source, B.source, B.rotation, f);
var doc = {
A: A.source,
B: B.source,
@@ -719,10 +848,36 @@ function placeParts(sheets, parts, config, nestindex) {
return null;
}
+ window.debugLog('PlaceParts started with ' + parts.length + ' parts and ' + sheets.length + ' sheets');
+
+ // Log part and sheet dimensions for debugging
+ if (parts.length > 0) {
+ var partBounds = GeometryUtil.getPolygonBounds(parts[0]);
+ window.debugLog('First part dimensions: ' + partBounds.width + ' x ' + partBounds.height);
+ }
+ if (sheets.length > 0) {
+ var sheetBounds = GeometryUtil.getPolygonBounds(sheets[0]);
+ window.debugLog('First sheet dimensions: ' + sheetBounds.width + ' x ' + sheetBounds.height);
+ }
+
+ // Check if we have too many parts for efficient processing
+ if (parts.length > 50) {
+ window.debugLog('Processing ' + parts.length + ' parts - this may take a long time');
+
+ // Check if all parts are identical (same source)
+ var firstSource = parts[0].source;
+ var allIdentical = parts.every(function (part) { return part.source === firstSource; });
+
+ if (allIdentical) {
+ window.debugLog('All parts are identical - consider using batch processing optimization');
+ }
+ }
+
var i, j, k, m, n, part;
var totalnum = parts.length;
var totalsheetarea = 0;
+ var totalPlacedPartArea = 0; // Track total area of placed parts
// total length of merged lines
var totalMerged = 0;
@@ -749,49 +904,67 @@ function placeParts(sheets, parts, config, nestindex) {
// Pre-analyze holes in all sheets
const sheetHoleAnalysis = analyzeSheetHoles(sheets);
- // Analyze all parts to identify those with holes and potential fits
- const { mainParts, holeCandidates } = analyzeParts(parts, sheetHoleAnalysis.averageHoleArea, config);
-
- // console.log(`Analyzed parts: ${mainParts.length} main parts, ${holeCandidates.length} hole candidates`);
-
+ // TEMPORARILY DISABLE hole analysis to test
+ window.debugLog('Skipping hole analysis - using all parts as main parts');
+ window.debugLog('Parts before hole analysis: ' + parts.length);
+
var allplacements = [];
var fitness = 0;
- // Now continue with the original placeParts logic, but use our sorted parts
-
- // Combine main parts and hole candidates back into a single array
- // mainParts first since we want to place them first
- parts = [...mainParts, ...holeCandidates];
+ // Skip hole analysis - use all parts as-is
+ window.debugLog('After skipping hole analysis: ' + parts.length + ' total parts for placement');
// Continue with the original placeParts logic
// var binarea = Math.abs(GeometryUtil.polygonArea(self.binPolygon));
var key, nfp;
var part;
-
+var p = 0;
while (parts.length > 0) {
-
+ var part = parts.shift();
var placed = [];
var placements = [];
// open a new sheet
var sheet = sheets.shift();
+
+ // Check if we have a valid sheet
+ if (!sheet) {
+ window.debugLog('No more sheets available, but', parts.length, 'parts remaining');
+ break; // Exit the loop if no more sheets are available
+ }
+
var sheetarea = Math.abs(GeometryUtil.polygonArea(sheet));
+ var sheetBounds = GeometryUtil.getPolygonBounds(sheet);
totalsheetarea += sheetarea;
fitness += sheetarea; // add 1 for each new sheet opened (lower fitness is better)
var clipCache = [];
- //console.log('new sheet');
+ window.debugLog('=== PROCESSING SHEET ' + p + ' bounds: ' + JSON.stringify(sheetBounds) + ' area: ' + sheetarea);
+ window.debugLog('Processing new sheet, current parts remaining: ' + parts.length);
for (let i = 0; i < parts.length; i++) {
// console.time('placement');
part = parts[i];
+ window.debugLog('Attempting to place part ' + i + ' with source: ' + part.source);
+
+ // Add timeout for NFP calculation to prevent infinite loops
+ var nfpStartTime = Date.now();
+ var NFP_TIMEOUT = 30000; // 30 seconds timeout per part
// inner NFP
var sheetNfp = null;
// try all possible rotations until it fits
// (only do this for the first part of each sheet, to ensure that all parts that can be placed are, even if we have to to open a lot of sheets)
for (let j = 0; j < config.rotations; j++) {
+ // Check timeout
+ if (Date.now() - nfpStartTime > NFP_TIMEOUT) {
+ window.debugLog('NFP calculation timeout for part ' + part.source + ' rotation ' + j);
+ break;
+ }
+
+ window.debugLog('Getting NFP for part ' + part.source + ' rotation ' + j + ' current part rotation: ' + part.rotation);
sheetNfp = getInnerNfp(sheet, part, config);
+ window.debugLog('NFP result for part ' + part.source + ' rotation ' + part.rotation + ': ' + JSON.stringify(sheetNfp));
if (sheetNfp) {
break;
@@ -822,10 +995,13 @@ function placeParts(sheets, parts, config, nestindex) {
// first placement, put it on the top left corner
for (let j = 0; j < sheetNfp.length; j++) {
for (let k = 0; k < sheetNfp[j].length; k++) {
- if (position === null || sheetNfp[j][k].x - part[0].x < position.x || (GeometryUtil.almostEqual(sheetNfp[j][k].x - part[0].x, position.x) && sheetNfp[j][k].y - part[0].y < position.y)) {
+ var candidateX = sheetNfp[j][k].x - part[0].x;
+ var candidateY = sheetNfp[j][k].y - part[0].y;
+ window.debugLog('FIRST PLACEMENT DEBUG: NFP point ' + JSON.stringify(sheetNfp[j][k]) + ' part[0] ' + JSON.stringify(part[0]) + ' candidate position ' + candidateX + ',' + candidateY);
+ if (position === null || candidateX < position.x || (GeometryUtil.almostEqual(candidateX, position.x) && candidateY < position.y)) {
position = {
- x: sheetNfp[j][k].x - part[0].x,
- y: sheetNfp[j][k].y - part[0].y,
+ x: candidateX,
+ y: candidateY,
id: part.id,
rotation: part.rotation,
source: part.source,
@@ -835,11 +1011,16 @@ function placeParts(sheets, parts, config, nestindex) {
}
}
if (position === null) {
- // console.log(sheetNfp);
+ // window.debugLog(sheetNfp);
}
+
+ window.debugLog('SELECTED POSITION for part ' + part.id + ' rotation ' + part.rotation + ': ' + JSON.stringify(position));
placements.push(position);
placed.push(part);
+ // Track placed part area for utilisation calculation
+ totalPlacedPartArea += Math.abs(GeometryUtil.polygonArea(part));
+
continue;
}
@@ -977,7 +1158,7 @@ function placeParts(sheets, parts, config, nestindex) {
}
}
} catch (e) {
- // console.log('Error processing hole:', e);
+ // window.debugLog('Error processing hole:', e);
// Continue with next hole
}
}
@@ -985,7 +1166,7 @@ function placeParts(sheets, parts, config, nestindex) {
}
}
} catch (e) {
- // console.log('Error in hole detection:', e);
+ // window.debugLog('Error in hole detection:', e);
// Continue with normal placement, ignoring holes
}
@@ -1004,11 +1185,11 @@ function placeParts(sheets, parts, config, nestindex) {
validHolePositions.push(holePositions[j]);
}
} catch (e) {
- // console.log('Error validating hole position:', e);
+ // window.debugLog('Error validating hole position:', e);
}
}
holePositions = validHolePositions;
- // console.log(`Found ${holePositions.length} valid hole positions for part ${part.source}`);
+ // window.debugLog(`Found ${holePositions.length} valid hole positions for part ${part.source}`);
}
var clipperSheetNfp = innerNfpToClipperCoordinates(sheetNfp, config);
@@ -1052,7 +1233,7 @@ function placeParts(sheets, parts, config, nestindex) {
}
if (error || !clipper.Execute(ClipperLib.ClipType.ctUnion, combinedNfp, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) {
- // console.log('clipper error', error);
+ // window.debugLog('clipper error', error);
continue;
}
@@ -1060,7 +1241,7 @@ function placeParts(sheets, parts, config, nestindex) {
nfp: combinedNfp,
index: placed.length - 1
};
- // console.log('save cache', placed.length - 1);
+ // window.debugLog('save cache', placed.length - 1);
// difference with sheet polygon
var finalNfp = new ClipperLib.Paths();
@@ -1172,7 +1353,7 @@ function placeParts(sheets, parts, config, nestindex) {
}
if (!combinedHull) {
- // console.warn("Failed to calculate convex hull");
+ // window.debugLog("Failed to calculate convex hull");
continue;
}
@@ -1312,7 +1493,7 @@ function placeParts(sheets, parts, config, nestindex) {
return 0;
});
- // console.log(`Sorted hole positions. Prioritizing distribution across ${holeUtilization.size} used holes out of ${new Set(holePositions.map(h => `${h.parentIndex}_${h.holeIndex}`)).size} total holes`);
+ // window.debugLog(`Sorted hole positions. Prioritizing distribution across ${holeUtilization.size} used holes out of ${new Set(holePositions.map(h => `${h.parentIndex}_${h.holeIndex}`)).size} total holes`);
for (let j = 0; j < holePositions.length; j++) {
let holeShift = holePositions[j];
@@ -1351,7 +1532,7 @@ function placeParts(sheets, parts, config, nestindex) {
// Apply a small bonus for unused holes (just enough to break ties)
if (partsInThisHole === 0) {
area *= 0.99; // 1% bonus for prioritizing empty holes
- // console.log(`Small priority bonus for unused hole ${holeKey}`);
+ // window.debugLog(`Small priority bonus for unused hole ${holeKey}`);
}
}
else if (config.placementType == 'convexhull') {
@@ -1434,7 +1615,7 @@ function placeParts(sheets, parts, config, nestindex) {
var partArea = Math.abs(ClipperLib.Clipper.Area(clipperPart));
if (Math.abs(intersectionArea - partArea) > (partArea * 0.01)) { // 1% tolerance
isValidHolePlacement = false;
- // console.log(`Part not fully contained in hole: ${part.source}`);
+ // window.debugLog(`Part not fully contained in hole: ${part.source}`);
}
} else {
isValidHolePlacement = false;
@@ -1463,7 +1644,7 @@ function placeParts(sheets, parts, config, nestindex) {
if (dx < proximityThreshold || dy < proximityThreshold) {
// This placement uses contour of another part - give it a bonus
contourScore += 5.0; // This value can be tuned
- // console.log(`Found contour alignment in hole between ${part.source} and ${placed[m].source}`);
+ // window.debugLog(`Found contour alignment in hole between ${part.source} and ${placed[m].source}`);
}
}
}
@@ -1484,15 +1665,15 @@ function placeParts(sheets, parts, config, nestindex) {
// if (fillRatio > 0.6) {
// // Very large parts (60%+ of hole) get maximum benefit
// area *= 0.4; // 60% reduction
- // // console.log(`Large part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying maximum packing bonus`);
+ // // window.debugLog(`Large part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying maximum packing bonus`);
// } else if (fillRatio > 0.3) {
// // Medium parts (30-60% of hole) get significant benefit
// area *= 0.5; // 50% reduction
- // // console.log(`Medium part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying major packing bonus`);
+ // // window.debugLog(`Medium part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying major packing bonus`);
// } else if (fillRatio > 0.1) {
// // Smaller parts (10-30% of hole) get moderate benefit
// area *= 0.6; // 40% reduction
- // // console.log(`Small part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying standard packing bonus`);
+ // // window.debugLog(`Small part ${part.source} fills ${Math.round(fillRatio*100)}% of hole - applying standard packing bonus`);
// }
// Now apply standard sheet-like placement optimization for parts already in the hole
const partsInSameHole = [];
@@ -1558,7 +1739,7 @@ function placeParts(sheets, parts, config, nestindex) {
// Better alignments get lower multipliers (better scores)
const qualityMultiplier = Math.max(0.7, 0.9 - (bestAlignment / 100) - (alignmentCount * 0.05));
area *= qualityMultiplier;
- // console.log(`Applied sheet-like alignment strategy in hole with quality ${(1-qualityMultiplier)*100}%`);
+ // window.debugLog(`Applied sheet-like alignment strategy in hole with quality ${(1-qualityMultiplier)*100}%`);
}
}
}
@@ -1580,17 +1761,17 @@ function placeParts(sheets, parts, config, nestindex) {
ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) {
if (clipSolution.length > 0) {
isValidHolePlacement = false;
- // console.log(`Part overlaps with other part: ${part.source} with ${placed[m].source}`);
+ // window.debugLog(`Part overlaps with other part: ${part.source} with ${placed[m].source}`);
break;
}
}
}
}
if (isValidHolePlacement) {
- // console.log(`Valid hole placement found for part ${part.source} in hole of ${parentPart.source}`);
+ // window.debugLog(`Valid hole placement found for part ${part.source} in hole of ${parentPart.source}`);
}
} catch (e) {
- // console.log('Error in hole containment check:', e);
+ // window.debugLog('Error in hole containment check:', e);
isValidHolePlacement = false;
}
@@ -1613,30 +1794,34 @@ function placeParts(sheets, parts, config, nestindex) {
}
}
} catch (e) {
- // console.log('Error processing hole positions:', e);
+ // window.debugLog('Error processing hole positions:', e);
}
// Continue with best non-hole position if available
if (position) {
// Debug placement with less verbose logging
if (position.inHole) {
- // console.log(`Placed part ${position.source} in hole of part ${placed[position.parentIndex].source}`);
+ // window.debugLog(`Placed part ${position.source} in hole of part ${placed[position.parentIndex].source}`);
// Adjust the part placement specifically for hole placement
// This prevents the part from being considered as overlapping with its parent
var parentPart = placed[position.parentIndex];
- // console.log(`Hole placement - Parent: ${parentPart.source}, Child: ${part.source}`);
+ // window.debugLog(`Hole placement - Parent: ${parentPart.source}, Child: ${part.source}`);
// Mark the relationship to prevent overlap checks between them in future placements
position.parentId = parentPart.id;
}
placed.push(part);
placements.push(position);
+
+ // Track placed part area for utilisation calculation
+ totalPlacedPartArea += Math.abs(GeometryUtil.polygonArea(part));
+
if (position.mergedLength) {
totalMerged += position.mergedLength;
}
} else {
// Just log part source without additional details
- // console.log(`No placement for part ${part.source}`);
+ // window.debugLog(`No placement for part ${part.source}`);
}
// send placement progress signal
@@ -1644,14 +1829,18 @@ function placeParts(sheets, parts, config, nestindex) {
for (let j = 0; j < allplacements.length; j++) {
placednum += allplacements[j].sheetplacements.length;
}
- //console.log(placednum, totalnum);
+ //window.debugLog(placednum, totalnum);
ipcRenderer.send('background-progress', { index: nestindex, progress: 0.5 + 0.5 * (placednum / totalnum) });
// console.timeEnd('placement');
}
- //if(minwidth){
- fitness += (minwidth / sheetarea) + minarea;
- //}
+ // Add fitness components, protecting against NaN from division by zero
+ if (minwidth !== null && minarea !== null && sheetarea > 0) {
+ fitness += (minwidth / sheetarea) + minarea;
+ } else if (minwidth !== null && minarea !== null) {
+ // If sheetarea is 0 or very small, just add the minarea component
+ fitness += minarea;
+ }
for (let i = 0; i < placed.length; i++) {
var index = parts.indexOf(placed[i]);
@@ -1670,17 +1859,23 @@ function placeParts(sheets, parts, config, nestindex) {
if (sheets.length == 0) {
break;
}
+
+ p++;
}
// there were parts that couldn't be placed
// scale this value high - we really want to get all the parts in, even at the cost of opening new sheets
- console.log('UNPLACED PARTS', parts.length, 'of', totalnum);
+ window.debugLog('UNPLACED PARTS', parts.length, 'of', totalnum);
for (let i = 0; i < parts.length; i++) {
- // console.log(`Fitness before unplaced penalty: ${fitness}`);
- const penalty = 100000000 * ((Math.abs(GeometryUtil.polygonArea(parts[i])) * 100) / totalsheetarea);
- // console.log(`Penalty for unplaced part ${parts[i].source}: ${penalty}`);
+ // window.debugLog(`Fitness before unplaced penalty: ${fitness}`);
+ const partArea = Math.abs(GeometryUtil.polygonArea(parts[i]));
+ // Protect against division by zero
+ const penalty = totalsheetarea > 0 ?
+ 100000000 * ((partArea * 100) / totalsheetarea) :
+ 100000000 * partArea; // Use partArea as base penalty when totalsheetarea is 0
+ // window.debugLog(`Penalty for unplaced part ${parts[i].source}: ${penalty}`);
fitness += penalty;
- // console.log(`Fitness after unplaced penalty: ${fitness}`);
+ // window.debugLog(`Fitness after unplaced penalty: ${fitness}`);
}
// Enhance fitness calculation to encourage more efficient hole usage
@@ -1702,8 +1897,8 @@ function placeParts(sheets, parts, config, nestindex) {
area: Math.abs(GeometryUtil.polygonArea(placed[partIndex])) * 2
});
// Base reward for any part placed in a hole
- // console.log(`Part ${placed[partIndex].source} placed in hole of part ${placed[placements[j].parentIndex].source}`);
- // console.log(`Part area: ${Math.abs(GeometryUtil.polygonArea(placed[partIndex]))}, Hole area: ${Math.abs(GeometryUtil.polygonArea(placed[placements[j].parentIndex]))}`);
+ // window.debugLog(`Part ${placed[partIndex].source} placed in hole of part ${placed[placements[j].parentIndex].source}`);
+ // window.debugLog(`Part area: ${Math.abs(GeometryUtil.polygonArea(placed[partIndex]))}, Hole area: ${Math.abs(GeometryUtil.polygonArea(placed[placements[j].parentIndex]))}`);
fitness -= (Math.abs(GeometryUtil.polygonArea(placed[partIndex])) / totalsheetarea / 100);
}
}
@@ -1740,12 +1935,27 @@ function placeParts(sheets, parts, config, nestindex) {
// send finish progress signal
ipcRenderer.send('background-progress', { index: nestindex, progress: -1 });
- console.log('WATCH', allplacements);
+ window.debugLog('WATCH', allplacements);
+
+ // Use the tracked total placed part area for utilisation calculation
+ const utilisation = totalsheetarea > 0 && !isNaN(totalPlacedPartArea) ?
+ (totalPlacedPartArea / totalsheetarea) * 100 : 0;
+ window.debugLog(`Utilisation of the sheet(s): ${utilisation.toFixed(2)}%`);
- const utilisation = totalsheetarea > 0 ? (area / totalsheetarea) * 100 : 0;
- console.log(`Utilisation of the sheet(s): ${utilisation.toFixed(2)}%`);
+ // Ensure fitness is never NaN - this would break the genetic algorithm
+ if (isNaN(fitness)) {
+ window.debugLog('Fitness calculation resulted in NaN, using fallback value');
+ fitness = 1000000; // High penalty value as fallback
+ }
+
+ // Ensure utilisation is never NaN
+ let finalUtilisation = utilisation;
+ if (isNaN(utilisation)) {
+ window.debugLog('Utilisation calculation resulted in NaN, using 0');
+ finalUtilisation = 0;
+ }
- return { placements: allplacements, fitness: fitness, area: sheetarea, totalarea: totalsheetarea, mergedLength: totalMerged, utilisation: utilisation };
+ return { placements: allplacements, fitness: fitness, area: sheetarea, totalarea: totalsheetarea, mergedLength: totalMerged, utilisation: finalUtilisation };
}
// New helper function to analyze sheet holes
@@ -1827,7 +2037,7 @@ function analyzeParts(parts, averageHoleArea, config) {
};
}
- // console.log(`Found ${partsWithHoles.length} parts with holes`);
+ // window.debugLog(`Found ${partsWithHoles.length} parts with holes`);
// Second pass: check which parts fit into other parts' holes
for (let i = 0; i < parts.length; i++) {
@@ -1917,5 +2127,5 @@ function analyzeParts(parts, averageHoleArea, config) {
// clipperjs uses alerts for warnings
function alert(message) {
- console.log('alert: ', message);
+ window.debugLog('alert: ', message);
}
diff --git a/main/deepnest.js b/main/deepnest.js
index b63896f..e4c67d4 100755
--- a/main/deepnest.js
+++ b/main/deepnest.js
@@ -1289,7 +1289,7 @@ export class DeepNest {
filenames[j] = filename;
}
- this.eventEmitter.send("background-start", {
+ let send = {
index: i,
sheets: sheets,
sheetids: sheetids,
@@ -1301,7 +1301,9 @@ export class DeepNest {
sources: sources,
children: children,
filenames: filenames,
- });
+ }
+ console.log("background-start", send);
+ this.eventEmitter.send("background-start", send);
running++;
}
}
diff --git a/main/util/geometryutil.js b/main/util/geometryutil.js
index 95a4315..a85c97b 100644
--- a/main/util/geometryutil.js
+++ b/main/util/geometryutil.js
@@ -1572,12 +1572,31 @@
return null;
}
+ // Calculate NFP corners
+ var nfpMinX = minAx - minBx + B[0].x;
+ var nfpMaxX = maxAx - maxBx + B[0].x;
+ var nfpMinY = minAy - minBy + B[0].y;
+ var nfpMaxY = maxAy - maxBy + B[0].y;
+
+ // Handle exact fit case where NFP would be degenerate
+ if (_almostEqual(nfpMinX, nfpMaxX) && _almostEqual(nfpMinY, nfpMaxY)) {
+ // Part exactly fits - return a single point NFP
+ return [
+ [
+ { x: nfpMinX, y: nfpMinY },
+ { x: nfpMinX + TOL, y: nfpMinY },
+ { x: nfpMinX + TOL, y: nfpMinY + TOL },
+ { x: nfpMinX, y: nfpMinY + TOL },
+ ],
+ ];
+ }
+
return [
[
- { x: minAx - minBx + B[0].x, y: minAy - minBy + B[0].y },
- { x: maxAx - maxBx + B[0].x, y: minAy - minBy + B[0].y },
- { x: maxAx - maxBx + B[0].x, y: maxAy - maxBy + B[0].y },
- { x: minAx - minBx + B[0].x, y: maxAy - maxBy + B[0].y },
+ { x: nfpMinX, y: nfpMinY },
+ { x: nfpMaxX, y: nfpMinY },
+ { x: nfpMaxX, y: nfpMaxY },
+ { x: nfpMinX, y: nfpMaxY },
],
];
},
@@ -1590,6 +1609,14 @@
return null;
}
+ // Use optimized rectangle algorithm for rectangular bins (interior NFP only)
+ if (!inside && this.isRectangle(A)) {
+ var rectangleNfp = this.noFitPolygonRectangle(A, B);
+ if (rectangleNfp) {
+ return rectangleNfp;
+ }
+ }
+
A.offsetx = 0;
A.offsety = 0;
diff --git a/package-lock.json b/package-lock.json
index 9dd688f..7f9ae91 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,7 +29,7 @@
"@electron/rebuild": "4.0.1",
"@eslint/js": "^9.26.0",
"@playwright/test": "1.52.0",
- "@types/node": "22.15.17",
+ "@types/node": "^22.15.17",
"cross-replace": "0.2.0",
"electron": "34.5.5",
"electron-builder": "26.0.15",
diff --git a/package.json b/package.json
index c5e57ce..860dac1 100644
--- a/package.json
+++ b/package.json
@@ -50,13 +50,13 @@
],
"devDependencies": {
"@electron/packager": "18.3.6",
- "electron-builder": "26.0.15",
"@electron/rebuild": "4.0.1",
"@eslint/js": "^9.26.0",
"@playwright/test": "1.52.0",
- "@types/node": "22.15.17",
+ "@types/node": "^22.15.17",
"cross-replace": "0.2.0",
"electron": "34.5.5",
+ "electron-builder": "26.0.15",
"eslint": "^9.26.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.2",
diff --git a/test_cases/failed_1000x1000_1000x1000.svg b/test_cases/failed_1000x1000_1000x1000.svg
new file mode 100644
index 0000000..69fcf85
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/failed_1000x1000_1000x1000_2.svg b/test_cases/failed_1000x1000_1000x1000_2.svg
new file mode 100644
index 0000000..f45cca8
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000_2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/failed_1000x1000_1000x1000_3.svg b/test_cases/failed_1000x1000_1000x1000_3.svg
new file mode 100644
index 0000000..9be27a6
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000_3.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/failed_1000x1000_1000x1000_4.svg b/test_cases/failed_1000x1000_1000x1000_4.svg
new file mode 100644
index 0000000..54e36c9
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000_4.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/failed_1000x1000_1000x1000_5.svg b/test_cases/failed_1000x1000_1000x1000_5.svg
new file mode 100644
index 0000000..3323f3c
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000_5.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/failed_1000x1000_1000x1000_6.svg b/test_cases/failed_1000x1000_1000x1000_6.svg
new file mode 100644
index 0000000..945ff6a
--- /dev/null
+++ b/test_cases/failed_1000x1000_1000x1000_6.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/success_1000x1000_1000x1000.svg b/test_cases/success_1000x1000_1000x1000.svg
new file mode 100644
index 0000000..aea5a04
--- /dev/null
+++ b/test_cases/success_1000x1000_1000x1000.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test_cases/success_1000x1000_1000x1000_2.svg b/test_cases/success_1000x1000_1000x1000_2.svg
new file mode 100644
index 0000000..20dec1f
--- /dev/null
+++ b/test_cases/success_1000x1000_1000x1000_2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file