Files
dxf-to-3d-gasiseol/out/viewer-correct.html
T
Claude Haiku 7ab882fe2a 뷰어 재작성 - 오류 수정 및 기능 정리
문제 해결:
1. ReferenceError 수정 (controls 초기화 순서)
2. 불필요한 디버깅 코드 제거
3. 코드 구조 단순화

유지된 기능:
✓ OrbitControls (향상된 카메라 제어)
✓ 샘플 부재 (테스트용 녹색 박스)
✓ 부재 로드 (stats.json에서 588개)
✓ 완벽한 레이아웃 (16:9 대응)
✓ 정보 패널 (자동 폭 조정)
✓ 상세한 콘솔 로그

이제 3D 모델이 정상적으로 렌더링될 것입니다.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-19 17:23:57 +09:00

385 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>가시설 3D 모델 뷰어</title>
<style>
html, body {
margin: 0;
padding: 0;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0f1419;
display: flex;
color: #fff;
}
.container {
width: 100%;
height: 100%;
display: flex;
overflow: hidden;
}
#canvas {
flex: 1;
background: linear-gradient(135deg, #1a3a52 0%, #0d1f2d 100%);
overflow: hidden;
}
.panel {
width: fit-content;
min-width: 300px;
max-width: 650px;
height: 100%;
background: #1a1f2e;
padding: 12px;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #2d3748;
display: flex;
flex-direction: column;
font-size: 12px;
}
.panel::-webkit-scrollbar {
width: 6px;
}
.panel::-webkit-scrollbar-track {
background: #1a1f2e;
}
.panel::-webkit-scrollbar-thumb {
background: #4a5568;
border-radius: 3px;
}
.panel::-webkit-scrollbar-thumb:hover {
background: #718096;
}
.panel h2 {
color: #10b981;
font-size: 13px;
margin-bottom: 6px;
padding-bottom: 4px;
border-bottom: 2px solid #10b981;
font-weight: 700;
}
.panel h3 {
color: #cbd5e0;
font-size: 10px;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.badge {
display: inline-block;
background: #10b981;
color: #1a1f2e;
padding: 3px 8px;
border-radius: 14px;
font-size: 9px;
font-weight: 700;
margin-bottom: 6px;
width: fit-content;
}
.loading {
padding: 8px;
background: #252d3d;
border-radius: 6px;
color: #fbbf24;
font-size: 11px;
margin-bottom: 6px;
}
.section {
margin-bottom: 8px;
padding: 8px;
background: #252d3d;
border-radius: 6px;
border-left: 3px solid #10b981;
}
.stat-row {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #cbd5e0;
margin: 3px 0;
}
.stat-label {
color: #a0aec0;
}
.stat-value {
font-weight: 600;
color: #10b981;
}
.legend {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
}
.legend-color {
width: 10px;
height: 10px;
border-radius: 2px;
}
.controls {
padding: 8px;
background: #252d3d;
border-radius: 6px;
margin-top: auto;
font-size: 10px;
color: #cbd5e0;
line-height: 1.5;
}
</style>
</head>
<body>
<div class="container">
<canvas id="canvas"></canvas>
<div class="panel">
<h2>📊 모델 정보</h2>
<span class="badge">가시설 계획도 3D</span>
<div id="loadingMsg" class="loading">
⏳ 모델 로드 중...
</div>
<div class="section">
<h3>📈 부재 수량</h3>
<div class="stat-row">
<span class="stat-label">총 부재:</span>
<span class="stat-value" id="totalParts">-</span>
</div>
<div class="stat-row">
<span class="stat-label">총 부피:</span>
<span class="stat-value" id="totalVolume">-</span>
</div>
</div>
<div class="section">
<h3>🏗️ 부재 구성</h3>
<div style="font-size: 10px; line-height: 1.5; color: #cbd5e0;">
<div style="margin-bottom: 4px;"><strong style="color: #ff6b6b;">가시설4</strong> 52개</div>
<div style="margin-bottom: 4px;"><strong style="color: #7ed321;">h-pile2</strong> 244개</div>
<div style="margin-bottom: 4px;"><strong style="color: #4a90e2;">토류판</strong> 240개</div>
<div style="margin-bottom: 4px;"><strong style="color: #7ed321;">H-PILE</strong> 26개</div>
<div><strong style="color: #f5a623;">스터럿</strong> 26개</div>
</div>
</div>
<div class="section">
<h3>🎨 범례</h3>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background: #ff6b6b;"></div>
<span>H-빔</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #7ed321;"></div>
<span>파일</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #4a90e2;"></div>
<span>판재</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #f5a623;"></div>
<span>버팀목</span>
</div>
</div>
</div>
<div class="controls">
<h3>⌨️ 조작</h3>
좌클릭 드래그: 회전<br>
우클릭 드래그: 이동<br>
휠: 확대/축소
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/controls/OrbitControls.min.js"></script>
<script>
const canvas = document.getElementById('canvas');
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a3a52);
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 500000);
camera.position.set(-216, -2554, 3000);
camera.lookAt(-216, -2554, 0);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
// 조명
const directional = new THREE.DirectionalLight(0xffffff, 1.0);
directional.position.set(100000, 100000, 100000);
directional.castShadow = true;
scene.add(directional);
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const hemis = new THREE.HemisphereLight(0x87ceeb, 0x333333, 0.4);
scene.add(hemis);
// 추가 조명
const pointLight = new THREE.PointLight(0xffffff, 0.8, 10000);
pointLight.position.set(500, 500, 500);
scene.add(pointLight);
// OrbitControls (먼저 초기화!)
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(-216, -2554, 0);
controls.autoRotate = false;
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.zoomSpeed = 1.0;
controls.enablePan = true;
controls.panSpeed = 0.5;
controls.update();
console.log('=== 3D 뷰어 초기화 ===');
console.log('✓ OrbitControls 준비 완료');
// 재질
const getMaterial = (type) => {
const colors = {
'H-Beam': 0xff6b6b,
'Pile': 0x7ed321,
'Panel': 0x4a90e2,
'Strut': 0xf5a623
};
const color = colors[type] || 0x999999;
return new THREE.MeshStandardMaterial({ color, roughness: 0.7, metalness: 0.2 });
};
const DISPLAY_SCALE = 0.01;
// 샘플 부재 (테스트용)
const testGeo = new THREE.BoxGeometry(4, 4, 80);
const testMat = new THREE.MeshStandardMaterial({ color: 0x7ed321, roughness: 0.7, emissive: 0x2d5a2d });
const testMesh = new THREE.Mesh(testGeo, testMat);
testMesh.position.set(0, -2554, 100);
scene.add(testMesh);
console.log('✓ 샘플 부재 추가됨');
// 그리드 & 축
const gridHelper = new THREE.GridHelper(3000, 20, 0x444444, 0x222222);
gridHelper.position.y = -2300;
scene.add(gridHelper);
const axisHelper = new THREE.AxesHelper(500);
scene.add(axisHelper);
// stats.json 로드
console.log('stats.json 로드 시작...');
fetch('gasiseol-correct-v2-stats.json')
.then(r => {
console.log('응답:', r.status);
return r.json();
})
.then(data => {
console.log('✓ JSON 파싱 완료');
console.log('총 부재:', data.totalParts);
let count = 0;
data.parts.forEach(part => {
let geometry;
const { blockType, type, spec, position, volume } = part;
if (type === 'Pile') {
geometry = new THREE.BoxGeometry(4, 4, 80);
} else if (type === 'Panel') {
geometry = new THREE.BoxGeometry(7, 3.6, 0.14);
} else if (type === 'H-Beam') {
geometry = new THREE.BoxGeometry(4, 4, 60);
} else if (type === 'Strut') {
geometry = new THREE.BoxGeometry(2, 2, 40);
} else {
geometry = new THREE.BoxGeometry(3, 3, 30);
}
const mesh = new THREE.Mesh(geometry, getMaterial(type));
mesh.position.set(position.x, position.y, position.z);
scene.add(mesh);
count++;
if (count <= 3 || count % 100 === 0) {
console.log(`[${count}] ${blockType} @ (${position.x.toFixed(0)}, ${position.y.toFixed(0)})`);
}
});
console.log('✓ ' + count + '개 부재 추가됨');
document.getElementById('totalParts').textContent = data.totalParts + '개';
document.getElementById('totalVolume').textContent = Math.round(data.totalVolume).toLocaleString() + ' m³';
document.getElementById('loadingMsg').style.display = 'none';
console.log('✅ 완료!');
})
.catch(err => {
console.error('❌ 오류:', err);
document.getElementById('loadingMsg').innerHTML = '❌ 로드 실패<br>' + err.message;
});
// 렌더링
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// 리사이징
window.addEventListener('resize', () => {
const newWidth = canvas.clientWidth;
const newHeight = canvas.clientHeight;
camera.aspect = newWidth / newHeight;
camera.updateProjectionMatrix();
renderer.setSize(newWidth, newHeight);
});
</script>
</body>
</html>