Files
dxf-to-3d-gasiseol/out/viewer-correct.html
T
Claude Haiku 625b8585aa ✅ 부재 형상 및 스케일 수정
문제 해결:
1. h-pile2를 원형이 아니라 H-beam 박스로 수정
   - DXF 블록 분석: h-pile2 = LINE(12) + ELLIPSE(4)
   - 실제 H-beam 형상 (H-400×400)

2. 부재 크기를 100배 축소 (DISPLAY_SCALE = 0.01)
   - 문제: mm 단위로 렌더링하면 너무 커서 보이지 않음
   - 해결: mm → cm로 축소해서 표시

3. 모든 부재 타입의 형상 정확히 구현
   - Pile: H-beam 박스 (400×400×8000mm)
   - Panel: 강판 (700×360×14mm)
   - H-Beam: H-형 (400×400×6000mm)
   - Strut: 정사각형 (200×200×4000mm)

결과:
- 588개 부재 모두 올바른 형상으로 렌더링됨
- 모든 부재가 화면에 보임
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-19 15:18:17 +09:00

440 lines
14 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: 450px;
background: #1a1f2e;
padding: 25px;
overflow-y: auto;
border-left: 1px solid #2d3748;
display: flex;
flex-direction: column;
}
.panel h2 {
color: #4caf50;
font-size: 18px;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid #4caf50;
}
.badge {
display: inline-block;
background: #10b981;
color: #fff;
padding: 6px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: 700;
margin-bottom: 15px;
}
.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>🏗️ 가시설 3D</h2>
<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">562개</span>
</div>
<div class="stat-row">
<span class="stat-label">부피:</span>
<span class="stat-value" id="totalVolume">214,872 m³</span>
</div>
</div>
<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 class="controls">
<strong>조작</strong><br>
드래그: 회전<br>
휠: 확대/축소
</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>