This commit is contained in:
admin 2025-04-18 14:03:36 +08:00
parent d596db7e29
commit 441a7d1bd7
9 changed files with 162 additions and 405 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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; }
</style>
</head>
<body>
@ -37,6 +47,8 @@
</div>
<button onclick="calculate()">开始计算</button>
<div id="result"></div>
<div id="instructions" style="margin-top:20px; max-width:300px; line-height:1.5;"></div>
<div class="layer-checkboxes"></div>
</div>
<div id="axisInfo" class="axis">视角:前视图</div>
@ -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 = `
装箱方案说明:<br>
1. 选择最优旋转方式:长${result.boxLength}mm × 宽${result.boxWidth}mm × 高${result.boxHeight}mm<br>
2. 排列策略:${result.strategy}<br>
3. 排列密度:${result.density.toFixed(2)}%<br>
4. 空间利用率:${result.spaceUtilization.toFixed(2)}%<br>
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}<br>
位置:(${position.x.toFixed(0)}, ${position.y.toFixed(0)}, ${position.z.toFixed(0)})<br>
尺寸:${dimensions.width}×${dimensions.height}×${dimensions.depth}<br>
旋转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) => {

50
main.go
View File

@ -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 {

View File

@ -1,66 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>集装箱装箱计算器</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
#container {
display: flex;
gap: 20px;
padding: 20px;
}
#controls {
width: 300px;
padding: 20px;
background: #f0f0f0;
}
.input-group {
margin: 10px 0;
}
input {
width: 100%;
padding: 5px;
margin: 5px 0;
}
button {
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
#visualization {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="container">
<div id="controls">
<h2>集装箱参数</h2>
<div class="input-group">
<label>长度: <input type="number" id="contLength" step="0.1"></label>
<label>宽度: <input type="number" id="contWidth" step="0.1"></label>
<label>高度: <input type="number" id="contHeight" step="0.1"></label>
<label>承重上限: <input type="number" id="contWeight" step="0.1"></label>
</div>
<h2>纸箱参数</h2>
<div class="input-group">
<label>长度: <input type="number" id="boxLength" step="0.1"></label>
<label>宽度: <input type="number" id="boxWidth" step="0.1"></label>
<label>高度: <input type="number" id="boxHeight" step="0.1"></label>
<label>重量: <input type="number" id="boxWeight" step="0.1"></label>
</div>
<button onclick="calculate()">开始计算</button>
</div>
<div id="visualization"></div>
</div>
<script src="main.js"></script>
</body>
</html>

View File

@ -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);
}

View File

@ -1,4 +0,0 @@
</h1>
<p>Sorry, the page you are looking for does not exist.</p>
<a href="/">Go back to the homepage</a>
</div>

View File

@ -1,196 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>装货方案计算</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold text-center text-blue-600 mb-8">装货方案计算</h1>
<div class="bg-white p-6 rounded-md shadow-md">
<h2 class="text-xl font-bold mb-4">输入纸箱信息</h2>
<div id="cartons" class="mb-4">
<div class="flex mb-2">
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="重量" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<button onclick="addCartonInput()" class="bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600">添加纸箱</button>
</div>
</div>
<h2 class="text-xl font-bold mb-4">输入集装箱信息</h2>
<div id="containers" class="mb-4">
<div class="flex mb-2">
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="最大载重" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<button onclick="addContainerInput()" class="bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600">添加集装箱</button>
</div>
</div>
<button onclick="calculate()" class="bg-green-500 text-white p-2 rounded-md hover:bg-green-600">计算最优方案</button>
</div>
<div id="result" class="mt-8 bg-white p-6 rounded-md shadow-md"></div>
<div class="mt-8 bg-white p-6 rounded-md shadow-md">
<h3 class="text-xl font-bold mb-4">3D摆放视图</h3>
<div id="3d-view"></div>
</div>
</div>
<script>
function addCartonInput() {
const cartonsDiv = document.getElementById('cartons');
const newCarton = document.createElement('div');
newCarton.classList.add('flex', 'mb-2');
newCarton.innerHTML = `
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="重量" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
`;
cartonsDiv.appendChild(newCarton);
}
function addContainerInput() {
const containersDiv = document.getElementById('containers');
const newContainer = document.createElement('div');
newContainer.classList.add('flex', 'mb-2');
newContainer.innerHTML = `
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
<input type="number" placeholder="最大载重" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
`;
containersDiv.appendChild(newContainer);
}
function calculate() {
const cartonsInputs = document.querySelectorAll('#cartons input');
const containersInputs = document.querySelectorAll('#containers input');
let cartons = [];
for (let i = 0; i < cartonsInputs.length; i += 4) {
const length = parseFloat(cartonsInputs[i].value);
const width = parseFloat(cartonsInputs[i + 1].value);
const height = parseFloat(cartonsInputs[i + 2].value);
const weight = parseFloat(cartonsInputs[i + 3].value);
if (!isNaN(length) && !isNaN(width) && !isNaN(height) && !isNaN(weight)) {
cartons.push({ length, width, height, weight });
}
}
let containers = [];
for (let i = 0; i < containersInputs.length; i += 4) {
const length = parseFloat(containersInputs[i].value);
const width = parseFloat(containersInputs[i + 1].value);
const height = parseFloat(containersInputs[i + 2].value);
const maxWeight = parseFloat(containersInputs[i + 3].value);
if (!isNaN(length) && !isNaN(width) && !isNaN(height) && !isNaN(maxWeight)) {
containers.push({ length, width, height, maxWeight });
}
}
fetch('/calculate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ cartons, containers })
})
.then(response => response.json())
.then(data => {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
data.forEach((container, index) => {
const containerDiv = document.createElement('div');
containerDiv.classList.add('border', 'border-gray-300', 'p-4', 'mb-4', 'rounded-md');
containerDiv.innerHTML = `
<h3 class="text-lg font-bold">集装箱 ${index + 1}</h3>
<p>当前重量: ${container.currentWeight}</p>
<h4 class="text-md font-bold">装入的纸箱:</h4>
<ul>
${container.cartons.map(carton => `<li>长: ${carton.length}, 宽: ${carton.width}, 高: ${carton.height}, 重量: ${carton.weight}</li>`).join('')}
</ul>
`;
resultDiv.appendChild(containerDiv);
});
});
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
let scene, camera, renderer;
let containerGroup;
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, 500);
document.getElementById('3d-view').appendChild(renderer.domElement);
// 灯光设置
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10);
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));
camera.position.z = 15;
camera.position.y = 10;
camera.lookAt(0, 0, 0);
}
function renderContainer(container) {
if(containerGroup) scene.remove(containerGroup);
containerGroup = new THREE.Group();
// 绘制集装箱
const geometry = new THREE.BoxGeometry(
container.length,
container.height,
container.width
);
const edges = new THREE.EdgesGeometry(geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000 }));
containerGroup.add(line);
// 绘制纸箱
container.cartons.forEach(c => {
const box = new THREE.Mesh(
new THREE.BoxGeometry(c.length, c.height, c.width),
new THREE.MeshPhongMaterial({ color: 0x2194f3 })
);
box.position.set(c.position[0], c.position[1], c.position[2]);
containerGroup.add(box);
});
scene.add(containerGroup);
stopAnimation();
animate();
}
let animationId;
function animate() {
animationId = requestAnimationFrame(animate);
renderer.render(scene, camera);
containerGroup.rotation.y += 0.005;
}
function stopAnimation() {
if(animationId) cancelAnimationFrame(animationId);
}
// 初始化3D场景
window.addEventListener('load', initThree);
</script>
</body>
</html>

View File

@ -1 +0,0 @@
login