Files
dxf-to-3d-gasiseol/out/viewer-correct.html
T
Claude Haiku 4e7c4e2972 우측 패널 재설계 - 명확한 정보 구성
개선사항:
- 패널 너비 축소 (450→350px)
- 명확한 섹션 구분
- 부재 구성 상세 정보 추가
- 조작 가이드 명시화
- 색상 범례 개선

역할:
• 📈 부재 수량: 전체 통계
• 🏗️ 부재 구성: 각 타입별 상세 정보
• 🎨 범례: 색상 의미
• ⌨️ 조작: 마우스/화면 사용 방법
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-19 15:31:00 +09:00

494 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0f1419;
height: 100vh;
display: flex;
color: #fff;
}
.container {
width: 100%;
height: 100%;
display: flex;
}
#canvas {
flex: 1;
background: linear-gradient(135deg, #1a3a52 0%, #0d1f2d 100%);
}
.panel {
width: 350px;
background: #1a1f2e;
padding: 20px;
overflow-y: auto;
border-left: 1px solid #2d3748;
display: flex;
flex-direction: column;
font-size: 12px;
}
.panel h2 {
color: #10b981;
font-size: 14px;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid #10b981;
font-weight: 700;
}
.panel h3 {
color: #cbd5e0;
font-size: 11px;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge {
display: inline-block;
background: #10b981;
color: #1a1f2e;
padding: 4px 10px;
border-radius: 16px;
font-size: 10px;
font-weight: 700;
margin-bottom: 12px;
width: fit-content;
}
.loading {
padding: 15px;
background: #252d3d;
border-radius: 8px;
color: #fbbf24;
font-size: 13px;
margin-bottom: 15px;
}
.section {
margin-bottom: 20px;
padding: 15px;
background: #252d3d;
border-radius: 8px;
border-left: 3px solid #10b981;
}
.section h3 {
color: #10b981;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
}
.stat-row {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #cbd5e0;
margin: 8px 0;
}
.stat-label {
color: #a0aec0;
}
.stat-value {
font-weight: 600;
color: #10b981;
}
.info-box {
padding: 12px;
background: #1a3a1a;
border-left: 3px solid #10b981;
border-radius: 6px;
font-size: 12px;
color: #7fd87f;
margin-bottom: 12px;
line-height: 1.6;
}
.part-type {
padding: 10px;
margin: 8px 0;
background: #1a1f2e;
border-radius: 6px;
border-left: 4px solid;
font-size: 12px;
}
.part-type.h-beam { border-left-color: #ff6b6b; }
.part-type.pile { border-left-color: #7ed321; }
.part-type.panel { border-left-color: #4a90e2; }
.part-type.strut { border-left-color: #f5a623; }
.part-name {
font-weight: 600;
color: #e2e8f0;
margin-bottom: 4px;
}
.part-info {
color: #718096;
font-size: 11px;
}
.controls {
padding: 12px;
background: #252d3d;
border-radius: 8px;
margin-top: 15px;
font-size: 11px;
color: #cbd5e0;
line-height: 1.6;
}
.legend {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #1a1f2e;
}
::-webkit-scrollbar-thumb {
background: #4a5568;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #718096;
}
</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" style="margin-top: 10px;">
<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: 11px; line-height: 1.6; color: #cbd5e0;">
<div style="margin-bottom: 8px;">
<strong style="color: #ff6b6b;">가시설4</strong> (H-빔)<br>
<span style="color: #a0aec0;">52개</span>
</div>
<div style="margin-bottom: 8px;">
<strong style="color: #7ed321;">h-pile2</strong> (기초파일)<br>
<span style="color: #a0aec0;">244개, H-400×400×8000mm</span>
</div>
<div style="margin-bottom: 8px;">
<strong style="color: #4a90e2;">토류판</strong> (흙막이판)<br>
<span style="color: #a0aec0;">240개, PL-700×360×14mm</span>
</div>
<div style="margin-bottom: 8px;">
<strong style="color: #7ed321;">H-PILE</strong> (파일)<br>
<span style="color: #a0aec0;">26개</span>
</div>
<div>
<strong style="color: #f5a623;">스터럿</strong> (버팀목)<br>
<span style="color: #a0aec0;">26개</span>
</div>
</div>
</div>
<!-- 범례 섹션 -->
<div class="section">
<h3>🎨 범례</h3>
<div class="legend" style="grid-template-columns: 1fr 1fr; gap: 8px;">
<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>
<div style="font-size: 11px; color: #cbd5e0; line-height: 1.8;">
<strong>마우스:</strong><br>
• 좌클릭 드래그 = 회전<br>
• 휠 스크롤 = 확대/축소<br>
<br>
<strong>화면:</strong><br>
• 3D 배치 확인<br>
• 부재 형상 검증<br>
• 구조 파악
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.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);
// 부재 중심을 향하도록 설정 (정확한 좌표 범위)
// 중심: (-216, -2554), 폭: 1373, 높이: 552
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 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; // mm → cm (1:100)
// stats.json 로드 및 부재 생성
fetch('gasiseol-correct-v2-stats.json')
.then(r => r.json())
.then(data => {
console.log('📊 로드됨:', data.totalParts, '개 부재');
let addedCount = 0;
// 각 부재를 3D 메시로 생성
data.parts.forEach(part => {
let geometry;
const { blockType, type, spec, position, volume } = part;
// 부재 타입별 3D 형상 생성
if (type === 'Pile') {
// H-PILE 형상 (H-beam 박스)
geometry = new THREE.BoxGeometry(
400 * DISPLAY_SCALE,
400 * DISPLAY_SCALE,
8000 * DISPLAY_SCALE
);
} else if (type === 'Panel') {
// 판재 - 규격에서 가로×세로×두께 추출
let w = 700, h = 360, d = 14;
if (spec && spec.includes('×')) {
const match = spec.match(/PL\.-(\d+)×(\d+)×(\d+)/);
if (match) {
w = parseInt(match[1]);
h = parseInt(match[2]);
d = parseInt(match[3]);
}
}
geometry = new THREE.BoxGeometry(
w * DISPLAY_SCALE,
h * DISPLAY_SCALE,
d * DISPLAY_SCALE
);
} else if (type === 'H-Beam') {
// H-빔 - H-400×400×13×21
let w = 400, h = 400, d = 6000;
if (spec && spec.includes('H-')) {
const match = spec.match(/H-(\d+)×(\d+)/);
if (match) {
w = parseInt(match[1]);
h = parseInt(match[2]);
}
}
geometry = new THREE.BoxGeometry(
w * DISPLAY_SCALE,
h * DISPLAY_SCALE,
d * DISPLAY_SCALE
);
} else if (type === 'Strut') {
// 버팀목 - 200×200×4000
geometry = new THREE.BoxGeometry(
200 * DISPLAY_SCALE,
200 * DISPLAY_SCALE,
4000 * DISPLAY_SCALE
);
} else {
geometry = new THREE.BoxGeometry(
300 * DISPLAY_SCALE,
300 * DISPLAY_SCALE,
3000 * DISPLAY_SCALE
);
}
const mesh = new THREE.Mesh(geometry, getMaterial(type));
mesh.position.set(position.x, position.y, position.z);
mesh.userData = { blockType, type, spec, volume };
scene.add(mesh);
addedCount++;
});
console.log('✅ ' + addedCount + '개 부재 추가됨');
// UI 업데이트
document.getElementById('totalParts').textContent = data.totalParts + '개';
document.getElementById('totalVolume').textContent = Math.round(data.totalVolume).toLocaleString() + ' m³';
document.getElementById('loadingMsg').style.display = 'none';
console.log('✅ 3D 모델 생성 완료');
})
.catch(err => {
console.error('❌ 로드 오류:', err);
document.getElementById('loadingMsg').innerHTML = '❌ 부재 데이터 로드 실패<br>' + err.message;
});
// 그리드 & 축
const gridHelper = new THREE.GridHelper(300000, 20, 0x444444, 0x222222);
gridHelper.position.y = -1000;
scene.add(gridHelper);
const axisHelper = new THREE.AxesHelper(50000);
scene.add(axisHelper);
// 마우스 조작
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
previousMousePosition = { x: e.clientX, y: e.clientY };
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - previousMousePosition.x;
const deltaY = e.clientY - previousMousePosition.y;
const targetPoint = new THREE.Vector3(0, 0, 0);
const direction = camera.position.clone().sub(targetPoint);
const rotationMatrix = new THREE.Matrix4()
.makeRotationAxis(new THREE.Vector3(0, 1, 0), deltaX * 0.005);
const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
rotationMatrix.multiply(new THREE.Matrix4().makeRotationAxis(rightVector, deltaY * 0.005));
direction.applyMatrix4(rotationMatrix);
camera.position.copy(targetPoint).add(direction);
camera.lookAt(targetPoint);
previousMousePosition = { x: e.clientX, y: e.clientY };
});
canvas.addEventListener('mouseup', () => { isDragging = false; });
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const targetPoint = new THREE.Vector3(0, 0, 0);
const direction = camera.position.clone().sub(targetPoint).normalize();
const distance = camera.position.distanceTo(targetPoint);
const newDistance = distance + e.deltaY * 20;
camera.position.copy(targetPoint).addScaledVector(direction, newDistance);
});
// 렌더링
function animate() {
requestAnimationFrame(animate);
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>