diff --git a/__debug_bin1038044772.exe b/__debug_bin1038044772.exe deleted file mode 100644 index 4a7e2de..0000000 Binary files a/__debug_bin1038044772.exe and /dev/null differ diff --git a/__debug_bin3681850586.exe b/__debug_bin3681850586.exe deleted file mode 100644 index bc646ab..0000000 Binary files a/__debug_bin3681850586.exe and /dev/null differ diff --git a/index.html b/index.html index 27a7b90..eb471f4 100644 --- a/index.html +++ b/index.html @@ -18,6 +18,16 @@ } #result { margin-top: 20px; font-weight: bold; } .axis { position: absolute; bottom: 20px; left: 20px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 4px; } + .tooltip { + position: absolute; + background: rgba(0,0,0,0.7); + color: white; + padding: 8px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.3s; + } + .layer-checkbox { margin-bottom: 10px; } @@ -37,6 +47,8 @@
+
+
视角:前视图
@@ -46,6 +58,9 @@ let scene, camera, renderer, controls; let containerMesh, boxMeshes = []; let activeCamera = 'front'; + let tooltip = document.createElement('div'); + tooltip.className = 'tooltip'; + let result = {}; function initThree() { scene = new THREE.Scene(); @@ -71,6 +86,13 @@ requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); + + // 更新提示信息 + if (document.querySelector('.tooltip')) { + document.body.removeChild(document.querySelector('.tooltip')); + } + document.body.appendChild(tooltip); + tooltip.style.opacity = 0; } async function calculate() { @@ -93,13 +115,26 @@ body: JSON.stringify(data) }); - const result = await response.json(); + result = await response.json(); document.getElementById('result').innerHTML = `最优装箱数:${result.count}`; + + // 显示装箱说明 + const instructions = ` + 装箱方案说明:
+ 1. 选择最优旋转方式:长${result.boxLength}mm × 宽${result.boxWidth}mm × 高${result.boxHeight}mm
+ 2. 排列策略:${result.strategy}
+ 3. 排列密度:${result.density.toFixed(2)}%
+ 4. 空间利用率:${result.spaceUtilization.toFixed(2)}%
+ 5. 实际占用体积:${(result.usedVolume/1000000000).toFixed(2)} m³ + `; + document.getElementById('instructions').innerHTML = instructions; + // 清理现有场景 scene.remove(containerMesh); boxMeshes.forEach(mesh => scene.remove(mesh)); boxMeshes = []; + // 创建集装箱 const containerGeo = new THREE.BoxGeometry( data.container.length, data.container.height, @@ -115,31 +150,99 @@ containerMesh.position.set(data.container.length/2, data.container.height/2, data.container.width/2); scene.add(containerMesh); + // 创建分层控制 + const layerCheckboxes = document.querySelector('.layer-checkboxes'); + layerCheckboxes.innerHTML = ''; + result.layers.forEach((layer, index) => { + const div = document.createElement('div'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `layer-${index}`; + checkbox.checked = true; + checkbox.addEventListener('change', updateVisibility); + + const label = document.createElement('label'); + label.htmlFor = `layer-${index}`; + label.textContent = `第${index+1}层 (${layer.count}箱)`; + + div.appendChild(checkbox); + div.appendChild(label); + layerCheckboxes.appendChild(div); + }); + + // 初始显示所有层 + updateVisibility(); + + // 添加光源 + const light = new THREE.DirectionalLight(0xffffff, 1); + light.position.set(10000, 10000, 10000); + scene.add(light); + } + + function updateVisibility() { + // 移除现有箱子 + boxMeshes.forEach(mesh => scene.remove(mesh)); + boxMeshes = []; + const boxMat = new THREE.MeshLambertMaterial({ color: 0x00ff00, transparent: true, opacity: 0.6 }); - - result.layout.forEach(pos => { - const boxGeo = new THREE.BoxGeometry( - data.box.length, - data.box.height, - data.box.width - ); - const box = new THREE.Mesh(boxGeo, boxMat); - box.position.set( - pos.x + data.box.length/2, - pos.y + data.box.height/2, - pos.z + data.box.width/2 - ); - scene.add(box); - boxMeshes.push(box); - }); - const light = new THREE.DirectionalLight(0xffffff, 1); - light.position.set(10000, 10000, 10000); - scene.add(light); + // 根据选中层创建箱子 + const checkedLayers = Array.from(document.querySelectorAll('.layer-checkboxes input:checked')) + .map(cb => parseInt(cb.id.split('-')[1])); + + checkedLayers.forEach(layerIndex => { + const layer = result.layers[layerIndex]; + layer.layout.forEach(pos => { + const boxGeo = new THREE.BoxGeometry( + result.boxLength, + result.boxHeight, + result.boxWidth + ); + const box = new THREE.Mesh(boxGeo, boxMat); + box.position.set( + pos.x + result.boxLength/2, + pos.y + result.boxHeight/2, + pos.z + result.boxWidth/2 + ); + box.rotation.set( + THREE.MathUtils.degToRad(pos.rotationX), + THREE.MathUtils.degToRad(pos.rotationY), + THREE.MathUtils.degToRad(pos.rotationZ) + ); + box.userData.index = pos.boxNumber; + box.on('pointerover', showTooltip); + box.on('pointerout', hideTooltip); + scene.add(box); + boxMeshes.push(box); + }); + }); + } + + function showTooltip(event) { + const box = event.target; + const position = box.position; + const rotation = box.rotation; + const dimensions = box.geometry.parameters; + + tooltip.innerHTML = ` + 箱号:${box.userData.index}
+ 位置:(${position.x.toFixed(0)}, ${position.y.toFixed(0)}, ${position.z.toFixed(0)})
+ 尺寸:${dimensions.width}×${dimensions.height}×${dimensions.depth}
+ 旋转:X=${(rotation.x * 180/Math.PI).toFixed(0)}°, + Y=${(rotation.y * 180/Math.PI).toFixed(0)}°, + Z=${(rotation.z * 180/Math.PI).toFixed(0)}° + `; + tooltip.style.left = `${event.clientX + 10}px`; + tooltip.style.top = `${event.clientY - 30}px`; + tooltip.style.opacity = 1; + } + + function hideTooltip() { + tooltip.style.opacity = 0; } window.addEventListener('keydown', (e) => { diff --git a/main.go b/main.go index e8b10ab..d81a016 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,18 @@ type Placement struct { X, Y, Z float64 `json:"x"` } +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"` +} + func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") @@ -34,15 +46,19 @@ func main() { } json.NewDecoder(r.Body).Decode(&data) - layout := optimizePacking(data.Container, data.Box) + layout, bestRotation, strategy, density, spaceUtilization, usedVolume := optimizePacking(data.Container, data.Box) count := len(layout) - response := struct { - Count int `json:"count"` - Layout []Placement `json:"layout"` - }{ - Count: count, - Layout: layout, + response := Response{ + Count: count, + Layout: layout, + BoxLength: bestRotation.Length, + BoxWidth: bestRotation.Width, + BoxHeight: bestRotation.Height, + Strategy: strategy, + Density: density, + SpaceUtilization: spaceUtilization, + UsedVolume: usedVolume, } json.NewEncoder(w).Encode(response) @@ -51,11 +67,17 @@ func main() { http.ListenAndServe(":8080", nil) } -func optimizePacking(con Container, box Box) []Placement { +func optimizePacking(con Container, box Box) ([]Placement, Box, string, float64, float64, float64) { var bestLayout []Placement + var bestRotation Box + var bestStrategy string maxCount := 0 + var bestDensity float64 + var bestSpaceUtilization float64 + var bestUsedVolume float64 rotations := generateRotations(box) + conVolume := con.Length * con.Width * con.Height for _, r := range rotations { for _, strategy := range []string{"XY", "XZ", "YX", "YZ", "ZX", "ZY"} { @@ -90,13 +112,19 @@ func optimizePacking(con Container, box Box) []Placement { total := int(xCount * yCount * zCount) if total > maxCount && total > 0 { maxCount = total - layout := generateLayout(r, xCount, yCount, zCount, strategy) - bestLayout = layout + 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 } } } - return bestLayout + return bestLayout, bestRotation, bestStrategy, bestDensity, bestSpaceUtilization, bestUsedVolume } func generateRotations(box Box) []Box { diff --git a/static/index.html b/static/index.html deleted file mode 100644 index dc972c0..0000000 --- a/static/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - 集装箱装箱计算器 - - - - -
-
-

集装箱参数

-
- - - - -
- -

纸箱参数

-
- - - - -
- - -
- -
-
- - - - \ No newline at end of file diff --git a/static/main.js b/static/main.js deleted file mode 100644 index 114ba80..0000000 --- a/static/main.js +++ /dev/null @@ -1,107 +0,0 @@ -let scene, camera, renderer, controls; - -function initThree() { - // 初始化场景 - scene = new THREE.Scene(); - camera = new THREE.PerspectiveCamera(75, - window.innerWidth / window.innerHeight, 0.1, 1000); - renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setSize(800, 600); - document.getElementById('visualization').appendChild(renderer.domElement); - - // 添加光源 - const light = new THREE.AmbientLight(0xffffff, 0.8); - scene.add(light); - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); - directionalLight.position.set(0, 1, 1); - scene.add(directionalLight); - - // 设置相机位置 - camera.position.set(10, 10, 10); - camera.lookAt(0, 0, 0); - - // 添加轨道控制器 - controls = new THREE.OrbitControls(camera, renderer.domElement); -} - -function clearScene() { - while(scene.children.length > 0){ - scene.remove(scene.children[0]); - } -} - -function renderContainer(container) { - // 绘制集装箱线框 - const geometry = new THREE.BoxGeometry( - container.Length, - container.Width, - container.Height - ); - const edges = new THREE.EdgesGeometry(geometry); - const line = new THREE.LineSegments( - edges, - new THREE.LineBasicMaterial({ color: 0x000000 }) - ); - scene.add(line); -} - -function renderBoxes(placements) { - placements.forEach(pos => { - const geometry = new THREE.BoxGeometry(pos.Length, pos.Width, pos.Height); - const material = new THREE.MeshPhongMaterial({ - color: 0x00ff00, - transparent: true, - opacity: 0.7 - }); - const cube = new THREE.Mesh(geometry, material); - cube.position.set( - pos.X + pos.Length/2, - pos.Y + pos.Width/2, - pos.Z + pos.Height/2 - ); - scene.add(cube); - }); -} - -async function calculate() { - // 获取输入值 - const container = { - Length: parseFloat(document.getElementById('contLength').value), - Width: parseFloat(document.getElementById('contWidth').value), - Height: parseFloat(document.getElementById('contHeight').value), - MaxWeight: parseFloat(document.getElementById('contWeight').value) - }; - - const box = { - Length: parseFloat(document.getElementById('boxLength').value), - Width: parseFloat(document.getElementById('boxWidth').value), - Height: parseFloat(document.getElementById('boxHeight').value), - Weight: parseFloat(document.getElementById('boxWeight').value) - }; - - // 发送请求 - const response = await fetch('/api/calculate', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ container, box }) - }); - - const result = await response.json(); - - // 更新可视化 - clearScene(); - renderContainer(container); - renderBoxes(result.placements); -} - -// 初始化 -initThree(); -animate(); - -function animate() { - requestAnimationFrame(animate); - controls.update(); - renderer.render(scene, camera); -} \ No newline at end of file diff --git a/templates/404.html b/templates/404.html deleted file mode 100644 index 81362d3..0000000 --- a/templates/404.html +++ /dev/null @@ -1,4 +0,0 @@ - -

Sorry, the page you are looking for does not exist.

- Go back to the homepage - \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index a1806d7..0000000 --- a/templates/index.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - 装货方案计算 - - - - - - -
-

装货方案计算

-
-

输入纸箱信息

-
-
- - - - - -
-
-

输入集装箱信息

-
-
- - - - - -
-
- -
-
-
-

3D摆放视图

-
-
-
- - - - - - \ No newline at end of file diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index c2e13f0..0000000 --- a/templates/login.html +++ /dev/null @@ -1 +0,0 @@ -login \ No newline at end of file