2025-04-18 13:20:01 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"math"
|
|
|
|
|
"net/http"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Container struct {
|
|
|
|
|
Length float64 `json:"length"`
|
|
|
|
|
Width float64 `json:"width"`
|
|
|
|
|
Height float64 `json:"height"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Box struct {
|
|
|
|
|
Length float64 `json:"length"`
|
|
|
|
|
Width float64 `json:"width"`
|
|
|
|
|
Height float64 `json:"height"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Placement struct {
|
|
|
|
|
X, Y, Z float64 `json:"x"`
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 14:03:36 +08:00
|
|
|
type Response struct {
|
|
|
|
|
Count int `json:"count"`
|
|
|
|
|
Layout []Placement `json:"layout"`
|
|
|
|
|
BoxLength float64 `json:"boxLength"`
|
|
|
|
|
BoxWidth float64 `json:"boxWidth"`
|
|
|
|
|
BoxHeight float64 `json:"boxHeight"`
|
|
|
|
|
Strategy string `json:"strategy"`
|
|
|
|
|
Density float64 `json:"density"`
|
|
|
|
|
SpaceUtilization float64 `json:"spaceUtilization"`
|
|
|
|
|
UsedVolume float64 `json:"usedVolume"`
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 13:20:01 +08:00
|
|
|
func main() {
|
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
http.ServeFile(w, r, "index.html")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
http.HandleFunc("/calculate", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
var data struct {
|
|
|
|
|
Container Container `json:"container"`
|
|
|
|
|
Box Box `json:"box"`
|
|
|
|
|
}
|
|
|
|
|
json.NewDecoder(r.Body).Decode(&data)
|
|
|
|
|
|
2025-04-18 14:03:36 +08:00
|
|
|
layout, bestRotation, strategy, density, spaceUtilization, usedVolume := optimizePacking(data.Container, data.Box)
|
2025-04-18 13:20:01 +08:00
|
|
|
count := len(layout)
|
|
|
|
|
|
2025-04-18 14:03:36 +08:00
|
|
|
response := Response{
|
|
|
|
|
Count: count,
|
|
|
|
|
Layout: layout,
|
|
|
|
|
BoxLength: bestRotation.Length,
|
|
|
|
|
BoxWidth: bestRotation.Width,
|
|
|
|
|
BoxHeight: bestRotation.Height,
|
|
|
|
|
Strategy: strategy,
|
|
|
|
|
Density: density,
|
|
|
|
|
SpaceUtilization: spaceUtilization,
|
|
|
|
|
UsedVolume: usedVolume,
|
2025-04-18 13:20:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json.NewEncoder(w).Encode(response)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
http.ListenAndServe(":8080", nil)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 14:03:36 +08:00
|
|
|
func optimizePacking(con Container, box Box) ([]Placement, Box, string, float64, float64, float64) {
|
2025-04-18 13:20:01 +08:00
|
|
|
var bestLayout []Placement
|
2025-04-18 14:03:36 +08:00
|
|
|
var bestRotation Box
|
|
|
|
|
var bestStrategy string
|
2025-04-18 13:20:01 +08:00
|
|
|
maxCount := 0
|
2025-04-18 14:03:36 +08:00
|
|
|
var bestDensity float64
|
|
|
|
|
var bestSpaceUtilization float64
|
|
|
|
|
var bestUsedVolume float64
|
2025-04-18 13:20:01 +08:00
|
|
|
|
|
|
|
|
rotations := generateRotations(box)
|
2025-04-18 14:03:36 +08:00
|
|
|
conVolume := con.Length * con.Width * con.Height
|
2025-04-18 13:20:01 +08:00
|
|
|
|
|
|
|
|
for _, r := range rotations {
|
|
|
|
|
for _, strategy := range []string{"XY", "XZ", "YX", "YZ", "ZX", "ZY"} {
|
|
|
|
|
var xCount, yCount, zCount float64
|
|
|
|
|
switch strategy {
|
|
|
|
|
case "XY":
|
|
|
|
|
xCount = math.Floor(con.Length / r.Length)
|
|
|
|
|
yCount = math.Floor(con.Width / r.Width)
|
|
|
|
|
zCount = math.Floor(con.Height / r.Height)
|
|
|
|
|
case "XZ":
|
|
|
|
|
xCount = math.Floor(con.Length / r.Length)
|
|
|
|
|
zCount = math.Floor(con.Height / r.Height)
|
|
|
|
|
yCount = math.Floor(con.Width / r.Width)
|
|
|
|
|
case "YX":
|
|
|
|
|
yCount = math.Floor(con.Width / r.Length)
|
|
|
|
|
xCount = math.Floor(con.Length / r.Width)
|
|
|
|
|
zCount = math.Floor(con.Height / r.Height)
|
|
|
|
|
case "YZ":
|
|
|
|
|
yCount = math.Floor(con.Width / r.Length)
|
|
|
|
|
zCount = math.Floor(con.Height / r.Height)
|
|
|
|
|
xCount = math.Floor(con.Length / r.Width)
|
|
|
|
|
case "ZX":
|
|
|
|
|
zCount = math.Floor(con.Height / r.Length)
|
|
|
|
|
xCount = math.Floor(con.Length / r.Width)
|
|
|
|
|
yCount = math.Floor(con.Width / r.Height)
|
|
|
|
|
case "ZY":
|
|
|
|
|
zCount = math.Floor(con.Height / r.Length)
|
|
|
|
|
yCount = math.Floor(con.Width / r.Height)
|
|
|
|
|
xCount = math.Floor(con.Length / r.Width)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total := int(xCount * yCount * zCount)
|
|
|
|
|
if total > maxCount && total > 0 {
|
|
|
|
|
maxCount = total
|
2025-04-18 14:03:36 +08:00
|
|
|
bestRotation = r
|
|
|
|
|
bestStrategy = strategy
|
|
|
|
|
bestLayout = generateLayout(r, xCount, yCount, zCount, strategy)
|
|
|
|
|
|
|
|
|
|
boxVolume := r.Length * r.Width * r.Height
|
|
|
|
|
bestUsedVolume = float64(total) * boxVolume
|
|
|
|
|
bestDensity = (bestUsedVolume / conVolume) * 100
|
|
|
|
|
bestSpaceUtilization = bestDensity
|
2025-04-18 13:20:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 14:03:36 +08:00
|
|
|
return bestLayout, bestRotation, bestStrategy, bestDensity, bestSpaceUtilization, bestUsedVolume
|
2025-04-18 13:20:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func generateRotations(box Box) []Box {
|
|
|
|
|
return []Box{
|
|
|
|
|
{box.Length, box.Width, box.Height},
|
|
|
|
|
{box.Length, box.Height, box.Width},
|
|
|
|
|
{box.Width, box.Length, box.Height},
|
|
|
|
|
{box.Width, box.Height, box.Length},
|
|
|
|
|
{box.Height, box.Length, box.Width},
|
|
|
|
|
{box.Height, box.Width, box.Length},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func generateLayout(r Box, xCount, yCount, zCount float64, strategy string) []Placement {
|
|
|
|
|
var layout []Placement
|
|
|
|
|
switch strategy {
|
|
|
|
|
case "XY":
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Length,
|
|
|
|
|
Y: z * r.Height,
|
|
|
|
|
Z: y * r.Width,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "XZ":
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Length,
|
|
|
|
|
Y: z * r.Height,
|
|
|
|
|
Z: y * r.Width,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "YX":
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Width,
|
|
|
|
|
Y: z * r.Height,
|
|
|
|
|
Z: y * r.Length,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "YZ":
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Width,
|
|
|
|
|
Y: z * r.Height,
|
|
|
|
|
Z: y * r.Length,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "ZX":
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Width,
|
|
|
|
|
Y: z * r.Length,
|
|
|
|
|
Z: y * r.Height,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "ZY":
|
|
|
|
|
for z := 0.0; z < zCount; z++ {
|
|
|
|
|
for y := 0.0; y < yCount; y++ {
|
|
|
|
|
for x := 0.0; x < xCount; x++ {
|
|
|
|
|
layout = append(layout, Placement{
|
|
|
|
|
X: x * r.Width,
|
|
|
|
|
Y: z * r.Length,
|
|
|
|
|
Z: y * r.Height,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return layout
|
|
|
|
|
}
|