An intelligent 3D packing optimization service with interactive visualization.
- Physics-aware heuristic packing algorithm considering:
- Weight limits and distribution
- Stability and support (60% minimum support ratio)
- Center of mass balance
- Layering (heavy objects at the bottom)
- Automatic multi-container management
- Optional object rotations (enabled via request flag or environment variable)
- Background GitHub release updater with checksum verification and configurable rate-limit handling
- Native release installers for Linux (
.deb), macOS (.pkg), and Windows (.msix) - Comprehensive unit tests
- REST API with JSON communication
- OpenAPI & Swagger UI with live documentation at
/docs - OOP principles with DRY architecture
- Fully documented code (Rust docstrings)
- Interactive 3D visualization
- OrbitControls for camera control
- Container navigation (Previous/Next buttons)
- Step-by-step animation of the packing process
- Highlighted live/animation focus for the current placement step
- Live statistics:
- Object count
- Total weight
- Volume utilization
- Center of mass position
- Packing status panel with progress and configuration readiness
- Unplaced object panel with rejection reasons
- Configuration modal with object rotation toggle
- Persistent configuration via browser local storage
- Keyboard shortcuts for batch/live runs, animation, navigation, and configuration
- Inline validation and toast notifications for faster feedback
- Responsive design
- Rust (1.70+)
- Cargo
- Modern web browser
- Python 3 (only needed for the Unix one-command installer)
cargo runThe server runs on http://localhost:8080
π‘ Configuration note: Copy
.env.exampleto.envif needed to customize the API port, host, or update parameters. Unset values automatically fall back to their defaults.
The web client is automatically served by the Rust backend. After startup, simply open http://localhost:8080/ in your browser.
π Same-origin note: The frontend intentionally calls
/packand/pack_streamon the same origin that serves the UI. This matches the default local setup (cargo run) and the production deployment model where the Rust backend serves both API and web assets.
In the browser:
- Button "π Pack (Batch)" performs a one-time optimization and displays the result.
- Button "π‘ Pack (Live)" starts the live stream of optimization steps via SSE and renders them continuously.
- Saved configurations are restored automatically after a page reload.
- The status and unplaced-object panels provide immediate feedback without blocking dialogs.
- Keyboard shortcuts:
B= batch packingL= live packingC= open configurationβ/β= switch containersSpace= start/stop animation
A GitHub Actions workflow (.github/workflows/release.yml) exists for releases that generates platform packages when tags in the format v* are created (or manually via workflow_dispatch):
- Linux (x86_64):
sort-it-now-<version>-linux-x86_64.tar.gz - Linux native installer:
sort-it-now-<version>-linux-x86_64.deb - macOS (ARM64/Apple Silicon):
sort-it-now-<version>-macos-arm64.tar.gz - macOS (x86_64/Intel):
sort-it-now-<version>-macos-x86_64.tar.gz - macOS native installer:
sort-it-now-<version>-macos-<arch>.pkg - Windows (x86_64):
sort-it-now-<version>-windows-x86_64.zip - Windows native installer:
sort-it-now-<version>-windows-x86_64.msix
Each archive package contains the pre-compiled binary, the current README.md, and installation/uninstallation scripts.
The artifacts are uploaded both as workflow artifacts and automatically added to the GitHub release for the corresponding tag version.
For reproducible installs, prefer a release tag (or commit SHA) instead of the mutable main branch.
Replace every <version> placeholder below with the same release tag, including the v prefix (for example v1.3.0).
The examples download the script first so you can review it before executing.
You can additionally set SORT_IT_NOW_VERSION=<version> to instruct the install scripts to download that specific release.
-
Linux / macOS install:
curl -fsSLo /tmp/sort-it-now-install-unix.sh \ https://raw.githubusercontent.com/JosunLP/sort-it-now/<version>/scripts/install-unix.sh chmod +x /tmp/sort-it-now-install-unix.sh SORT_IT_NOW_VERSION=<version> /tmp/sort-it-now-install-unix.sh
-
Linux / macOS uninstall:
curl -fsSLo /tmp/sort-it-now-uninstall-unix.sh \ https://raw.githubusercontent.com/JosunLP/sort-it-now/<version>/scripts/uninstall-unix.sh chmod +x /tmp/sort-it-now-uninstall-unix.sh /tmp/sort-it-now-uninstall-unix.sh
-
Windows install (PowerShell):
$version = "<version>" $script = Join-Path $env:TEMP "sort-it-now-install-windows.ps1" irm "https://raw.githubusercontent.com/JosunLP/sort-it-now/$version/scripts/install-windows.ps1" -OutFile $script $env:SORT_IT_NOW_VERSION = $version & $script
-
Windows uninstall (PowerShell):
$version = "<version>" $script = Join-Path $env:TEMP "sort-it-now-uninstall-windows.ps1" irm "https://raw.githubusercontent.com/JosunLP/sort-it-now/$version/scripts/uninstall-windows.ps1" -OutFile $script & $script
Both installer scripts also continue to work locally from an extracted release bundle. Set INSTALL_DIR (Unix) or -Destination (PowerShell) to override the default target.
- Linux/macOS: Run
./install.shin the extracted folder (optionally withsudo) to copysort_it_nowto/usr/local/bin. - Linux/macOS: Run
./uninstall.shin the extracted folder to remove a prior archive-based installation again. - Windows: Run
install.ps1(PowerShell). By default, it installs to%ProgramFiles%\sort-it-nowand adds the path to the user environment variable. - Windows: Run
uninstall.ps1to remove the installed binary and clean the user PATH entry again.
- Linux (
.deb): Install withsudo dpkg -i sort-it-now-<version>-linux-x86_64.deb, uninstall withsudo dpkg -r sort-it-now. - macOS (
.pkg): Install withsudo installer -pkg sort-it-now-<version>-macos-<arch>.pkg -target /. Use the uninstall shell script afterwards if you want to remove the binary from/usr/local/bin. - Windows (
.msix): Each release workflow run produces a signed MSIX together with a matching.cercertificate for that specific release. Import the certificate for the version you want to install into the trusted people store, then install the package withAdd-AppxPackage .\sort-it-now-<version>-windows-x86_64.msix. Because the workflow currently signs with a repository-generated self-signed certificate, you may need to repeat the import step for a different release, and you should only trust a certificate when the release came from the official repository and the published checksums were verified.
For each release, a Docker image is automatically published to Docker Hub. Images are provided for multiple architectures (linux/amd64, linux/arm64).
π Setup guide: See DOCKER_SETUP.md for a detailed guide on setting up the Docker Hub deployment pipeline.
Run Docker image:
Note: Replace
<username>withjosunlp(or the corresponding Docker Hub username of the project maintainer).
docker run -p 8080:8080 -e SORT_IT_NOW_SKIP_UPDATE_CHECK=1 <username>/sort-it-now:latestWith environment variables:
docker run -p 8080:8080 \
-e SORT_IT_NOW_API_HOST=0.0.0.0 \
-e SORT_IT_NOW_API_PORT=8080 \
-e SORT_IT_NOW_SKIP_UPDATE_CHECK=1 \
<username>/sort-it-now:latestBuild your own image:
docker build -t sort-it-now .
docker run -p 8080:8080 -e SORT_IT_NOW_SKIP_UPDATE_CHECK=1 sort-it-nowThe server is then available at http://localhost:8080.
On startup, the service checks for the latest GitHub releases (JosunLP/sort-it-now) in the background. If a newer version is found, the updater downloads the archive package matching the current platform and updates the installed binary in place. Native installers (.deb, .pkg, .msix) are published alongside the archive assets for manual installation flows. On Windows, if sort_it_now.exe is locked, a sort_it_now.new.exe is placed instead.
- The check can be disabled via the environment variable
SORT_IT_NOW_SKIP_UPDATE_CHECK=1(e.g., for offline installations or CI). - GitHub limits unauthenticated API calls to 60 per hour. If the limit is reached, the check is skipped and info is displayed. Optionally set
SORT_IT_NOW_GITHUB_TOKEN(orGITHUB_TOKEN) to a Personal Access Token to get higher limits; the updater also uses the token when downloading release artifacts. - To avoid unexpectedly large downloads, the updater limits release artifacts to 200 MB by default. Adjust the limit via
SORT_IT_NOW_MAX_DOWNLOAD_MB(value0disables the limit). - Repo/owner and timeout can be configured via
SORT_IT_NOW_GITHUB_OWNER,SORT_IT_NOW_GITHUB_REPO, andSORT_IT_NOW_HTTP_TIMEOUT_SECSβ defaults apply automatically if no.envis present.
GET /docsdelivers an interactive Swagger UI with Subresource Integrity-protected assets.GET /docs/openapi.jsonprovides the OpenAPI schema (v3) and can be used for code generators.
Packs objects into containers.
Request:
{
"containers": [
{ "name": "Standard", "dims": [100.0, 100.0, 70.0], "max_weight": 500.0 },
{ "name": "Compact", "dims": [60.0, 80.0, 50.0], "max_weight": 320.0 }
],
"objects": [
{ "id": 1, "dims": [30.0, 30.0, 10.0], "weight": 50.0 },
{ "id": 2, "dims": [20.0, 50.0, 15.0], "weight": 30.0 }
],
"allow_rotations": true
}The optional field allow_rotations enables 90Β° rotations per request. If omitted, the default setting from the environment variable SORT_IT_NOW_PACKING_ALLOW_ROTATIONS (default: false) applies.
Response:
{
"results": [
{
"id": 1,
"template_id": 0,
"label": "Standard",
"dims": [100.0, 100.0, 70.0],
"max_weight": 500.0,
"total_weight": 80.0,
"placed": [
{
"id": 1,
"pos": [0.0, 0.0, 0.0],
"weight": 50.0,
"dims": [30.0, 30.0, 10.0]
}
]
}
]
}Streams progress events in real-time as text/event-stream. Each event is a JSON object with a type field:
ContainerStarted{ id, dims, max_weight, label, template_id }ObjectPlaced{ container_id, id, pos, weight, dims, total_weight }Finished
Note: In the frontend, you can start live mode with the "π‘ Pack (Live)" button.
cargo testAll tests should pass successfully:
- β heavy_boxes_stay_below_lighter
- β single_box_snaps_to_corner
- β creates_additional_containers_when_weight_exceeded
- β reject_heavier_on_light_support
- β sample_pack_respects_weight_order
- Application entry point
- Starts the Tokio runtime and API server
Box3D: Represents a 3D object with ID, dimensions, and weightPlacedBox: Object with position in the containerContainer: Packaging container with capacity limits- Methods:
volume(),base_area(),total_weight(),remaining_weight(),utilization_percent()
intersects(): AABB collision detection between two objectsoverlap_1d(): Calculates 1D overlapoverlap_area_xy(): Calculates XY overlap areapoint_inside(): Point-in-box test
PackingConfig: Configurable parameters (grid, support ratio, tolerances)pack_objects(): Main packing algorithmpack_objects_with_config(): Version with customizable parametersfind_stable_position(): Finds stable position for an objectsupports_weight_correctly(): Checks weight hierarchyhas_sufficient_support(): Checks minimum support ratiocalculate_balance_after(): Calculates center of mass deviation
- REST API with Axum framework
- CORS support for frontend communication
- JSON serialization/deserialization
- Three.js scene setup
- OrbitControls for camera
- Functions:
clearScene(): Clears scenedrawContainerFrame(): Draws container wireframedrawBox(): Renders individual objectvisualizeContainer(): Shows complete containeranimateContainer(): Step-by-step animationupdateStats(): Updates statistics panelfetchPacking(): API communication
PackingConfigstructure instead of scattered constants- Reusable functions for geometry calculations
- Centralized error handling
- Clear separation of data models and logic
- Encapsulation in modules
- Trait implementation for common behavior
- Rust docstrings for all public functions
- JSDoc comments in frontend
- Inline comments for complex algorithms
The application optionally loads a .env file on startup (using dotenvy). Unset variables retain their defaults, so the service runs normally even without .env. Relevant variables:
| Variable | Default | Description |
|---|---|---|
SORT_IT_NOW_API_HOST |
0.0.0.0 |
IP address the HTTP server binds to. Set e.g. 127.0.0.1 for local access. |
SORT_IT_NOW_API_PORT |
8080 |
API server port. Values of 0 are rejected. |
SORT_IT_NOW_GITHUB_OWNER |
JosunLP |
GitHub owner/organization whose releases are queried for updates. |
SORT_IT_NOW_GITHUB_REPO |
sort-it-now |
Repository name for the updater. |
SORT_IT_NOW_HTTP_TIMEOUT_SECS |
30 |
Timeout in seconds for GitHub HTTP requests by the updater. |
SORT_IT_NOW_MAX_DOWNLOAD_MB |
200 |
Maximum size of a release asset (0 = unlimited). |
SORT_IT_NOW_GITHUB_TOKEN / GITHUB_TOKEN |
β | Optional PAT for higher GitHub rate limits and private releases. |
SORT_IT_NOW_SKIP_UPDATE_CHECK |
β | If set (any value), disables automatic update check. |
SORT_IT_NOW_PACKING_GRID_STEP |
5.0 |
|
SORT_IT_NOW_PACKING_SUPPORT_RATIO |
0.6 |
|
SORT_IT_NOW_PACKING_HEIGHT_EPSILON |
1e-3 |
|
SORT_IT_NOW_PACKING_GENERAL_EPSILON |
1e-6 |
|
SORT_IT_NOW_PACKING_BALANCE_LIMIT_RATIO |
0.45 |
|
SORT_IT_NOW_PACKING_ALLOW_ROTATIONS |
false |
Enables all 90Β° object rotations. Can also be set per request via allow_rotations. |
An example file can be found in .env.example.
PackingConfig {
grid_step: 5.0, // Position grid in units
support_ratio: 0.6, // 60% minimum support
height_epsilon: 1e-3, // Height tolerance
general_epsilon: 1e-6, // General tolerance
balance_limit_ratio: 0.45, // Max center of mass deviation
allow_item_rotation: false, // Enable object rotations (disabled by default)
}const CONTAINER_SIZE = [100, 100, 70]; // Container dimensions
const COLOR_PALETTE = [...]; // Object colors- Throughput: ~100 objects/second
- Memory: O(n) for n objects
- Complexity: O(n Γ p Γ z) where:
- n = number of objects
- p = grid positions
- z = Z-levels
- Rotation: Only 90Β° rotations; complex freeform rotations are not covered
- Dynamic stability: No physical simulation
- Optimal packing: Heuristic, no guaranteed optimum
- Browser support: Requires WebGL support
Project-specific - See license file.
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a pull request
For questions or issues, please open an issue.
Developed with β€οΈ in Rust & Three.js