Files
dxf-to-3d-gasiseol/out/viewer-correct.html
T
Claude Haiku 0802285951 3D 모델 렌더링 디버깅 추가
개선사항:
1. 상세한 콘솔 로그 추가
   - 뷰어 초기화 정보
   - JSON 로드 상태
   - 부재 추가 진행 상황

2. 테스트용 샘플 부재
   - 녹색 박스 1개 (위치: 부재 중심)
   - emissive 색상 추가 (어두운 곳에도 보임)

3. 추가 조명
   - PointLight 추가 (밝기 강화)

4. 상세한 에러 추적
   - fetch 응답 상태 확인
   - JSON 파싱 상태 확인
   - 부재별 추가 로그

사용자가 콘솔(F12)을 확인할 수 있도록 함
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-19 17:21:24 +09:00

533 lines
18 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>
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 > * {
flex-shrink: 0;
width: 100%;
}
.panel > :not(:last-child) {
margin-bottom: 8px;
}
.panel > :nth-last-child(1) {
margin-top: auto;
}
.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;
}
.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: 11px;
color: #cbd5e0;
margin: 3px 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;
}
</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 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);
// 부재 중심을 향하도록 설정 (정확한 좌표 범위)
// 중심: (-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)
// 디버깅 정보 추가
console.log('=== 3D 뷰어 초기화 ===');
console.log('카메라 위치:', camera.position);
console.log('카메라 타겟:', controls.target);
// 배경 밝게 변경해서 테스트
scene.background = new THREE.Color(0x1a3a52);
console.log('배경색 설정 완료');
// 샘플 부재 추가 (테스트용)
console.log('샘플 부재 생성 중...');
const testGeometry = new THREE.BoxGeometry(4, 4, 80); // 400×400×8000mm → 4×4×80
const testMaterial = new THREE.MeshStandardMaterial({
color: 0x7ed321,
roughness: 0.7,
emissive: 0x2d5a2d
});
const testMesh = new THREE.Mesh(testGeometry, testMaterial);
testMesh.position.set(0, -2554, 100); // 부재 중심 좌표
scene.add(testMesh);
console.log('✓ 샘플 부재 @ (0, -2554, 100) - 녹색');
// 추가 조명
const testLight = new THREE.PointLight(0xffffff, 1.0, 10000);
testLight.position.set(500, 500, 500);
scene.add(testLight);
console.log('✓ 테스트 조명 추가됨');
// 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);
console.log('배치 부재:', data.parts.length);
let addedCount = 0;
// 각 부재를 3D 메시로 생성
data.parts.forEach((part, idx) => {
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++;
if (addedCount <= 3 || addedCount % 100 === 0) {
console.log(`[${addedCount}] ${blockType} @ (${position.x.toFixed(0)}, ${position.y.toFixed(0)}, ${position.z})`);
}
});
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(3000, 20, 0x444444, 0x222222);
gridHelper.position.y = -2300;
scene.add(gridHelper);
const axisHelper = new THREE.AxesHelper(500);
scene.add(axisHelper);
// OrbitControls로 카메라 제어
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(-216, -2554, 0); // 부재 중심을 타겟으로
controls.autoRotate = false;
controls.autoRotateSpeed = 2;
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.zoomSpeed = 1.0;
controls.enablePan = true;
controls.panSpeed = 0.5;
controls.update();
// 렌더링
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>