부재 컨트롤 패널 추가 - 온오프 및 줌핏 기능
기능 추가: 1. 패널에 부재 구성 표시 ✓ 부재명 (한글) ✓ 규격 정보 (mm 단위) ✓ 부재 개수 ✓ 색상 표시 2. 부재 온오프 제어 ✓ 부재 타입별 체크박스 ✓ 체크 해제하면 해당 부재 숨김 ✓ 실시간 표시/숨김 3. 줌핏 기능 ✓ "🎯 줌핏" 버튼 추가 ✓ 활성화된 부재만 경계 계산 ✓ 자동 카메라 조정 ✓ 선택된 부재들이 화면 중앙에 표시 부재 그룹 관리: - memberGroups 객체로 부재 타입별 메시 추적 - 메시 userData에 groupName 저장 - 체크박스와 메시 가시성 연동 사용법: 1. 패널의 체크박스로 부재 표시/숨김 2. "줌핏" 버튼으로 선택된 부재에 자동 줌 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7031932edd
commit
d2ad629213
1 file changed
+124
-6
+124
-6
@@ -191,13 +191,39 @@
|
|||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>🏗️ 부재 구성</h3>
|
<h3>🏗️ 부재 구성</h3>
|
||||||
<div style="font-size: 10px; line-height: 1.5; color: #cbd5e0;">
|
<div style="font-size: 9px; line-height: 1.8; color: #cbd5e0;">
|
||||||
<div style="margin-bottom: 4px;"><strong style="color: #ff6b6b;">가시설4</strong> 52개</div>
|
<label style="display: flex; align-items: center; margin-bottom: 4px; cursor: pointer;">
|
||||||
<div style="margin-bottom: 4px;"><strong style="color: #7ed321;">h-pile2</strong> 244개</div>
|
<input type="checkbox" id="cb-gasiseol4" checked style="margin-right: 6px; cursor: pointer;">
|
||||||
<div style="margin-bottom: 4px;"><strong style="color: #4a90e2;">토류판</strong> 240개</div>
|
<span style="color: #ff6b6b;">■</span>
|
||||||
<div style="margin-bottom: 4px;"><strong style="color: #7ed321;">H-PILE</strong> 26개</div>
|
<span style="margin-left: 4px;"><strong>가시설4</strong> (H-400×400)</span>
|
||||||
<div><strong style="color: #f5a623;">스터럿</strong> 26개</div>
|
<span style="margin-left: auto; color: #10b981;">52개</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; margin-bottom: 4px; cursor: pointer;">
|
||||||
|
<input type="checkbox" id="cb-hpile2" checked style="margin-right: 6px; cursor: pointer;">
|
||||||
|
<span style="color: #7ed321;">■</span>
|
||||||
|
<span style="margin-left: 4px;"><strong>h-pile2</strong> (φ1000×8000)</span>
|
||||||
|
<span style="margin-left: auto; color: #10b981;">244개</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; margin-bottom: 4px; cursor: pointer;">
|
||||||
|
<input type="checkbox" id="cb-tolyupan" checked style="margin-right: 6px; cursor: pointer;">
|
||||||
|
<span style="color: #4a90e2;">■</span>
|
||||||
|
<span style="margin-left: 4px;"><strong>토류판</strong> (PL-700×360×14)</span>
|
||||||
|
<span style="margin-left: auto; color: #10b981;">240개</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; margin-bottom: 4px; cursor: pointer;">
|
||||||
|
<input type="checkbox" id="cb-hpile" checked style="margin-right: 6px; cursor: pointer;">
|
||||||
|
<span style="color: #7ed321;">■</span>
|
||||||
|
<span style="margin-left: 4px;"><strong>H-PILE</strong> (H-400×400)</span>
|
||||||
|
<span style="margin-left: auto; color: #10b981;">26개</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; cursor: pointer;">
|
||||||
|
<input type="checkbox" id="cb-strut" checked style="margin-right: 6px; cursor: pointer;">
|
||||||
|
<span style="color: #f5a623;">■</span>
|
||||||
|
<span style="margin-left: 4px;"><strong>스터럿</strong> (200×200×4000)</span>
|
||||||
|
<span style="margin-left: auto; color: #10b981;">26개</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="btnZoomFit" style="width: 100%; margin-top: 8px; padding: 6px; background: #10b981; color: #1a1f2e; border: none; border-radius: 4px; font-size: 10px; font-weight: 700; cursor: pointer;">🎯 줌핏</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -338,12 +364,23 @@
|
|||||||
|
|
||||||
const DISPLAY_SCALE = 0.01;
|
const DISPLAY_SCALE = 0.01;
|
||||||
|
|
||||||
|
// 부재 그룹 추적 (온오프 제어용)
|
||||||
|
const memberGroups = {
|
||||||
|
'가시설4': [],
|
||||||
|
'h-pile2': [],
|
||||||
|
'토류판': [],
|
||||||
|
'H-PILE': [],
|
||||||
|
'스터럿': []
|
||||||
|
};
|
||||||
|
|
||||||
// 샘플 부재 (테스트용)
|
// 샘플 부재 (테스트용)
|
||||||
const testGeo = new THREE.BoxGeometry(4, 4, 80);
|
const testGeo = new THREE.BoxGeometry(4, 4, 80);
|
||||||
const testMat = new THREE.MeshStandardMaterial({ color: 0x7ed321, roughness: 0.7, emissive: 0x2d5a2d });
|
const testMat = new THREE.MeshStandardMaterial({ color: 0x7ed321, roughness: 0.7, emissive: 0x2d5a2d });
|
||||||
const testMesh = new THREE.Mesh(testGeo, testMat);
|
const testMesh = new THREE.Mesh(testGeo, testMat);
|
||||||
testMesh.position.set(0, -2554, 100);
|
testMesh.position.set(0, -2554, 100);
|
||||||
|
testMesh.userData.groupName = 'h-pile2';
|
||||||
scene.add(testMesh);
|
scene.add(testMesh);
|
||||||
|
memberGroups['h-pile2'].push(testMesh);
|
||||||
console.log('✓ 샘플 부재 추가됨');
|
console.log('✓ 샘플 부재 추가됨');
|
||||||
|
|
||||||
// 그리드 & 축
|
// 그리드 & 축
|
||||||
@@ -426,7 +463,14 @@
|
|||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, getMaterial(type));
|
const mesh = new THREE.Mesh(geometry, getMaterial(type));
|
||||||
mesh.position.set(position.x, position.y, position.z + 10);
|
mesh.position.set(position.x, position.y, position.z + 10);
|
||||||
|
mesh.userData = { blockType, type, spec, volume, groupName: blockType };
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
|
|
||||||
|
// 부재 그룹에 추가
|
||||||
|
if (memberGroups[blockType]) {
|
||||||
|
memberGroups[blockType].push(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
if (count <= 3 || count % 100 === 0) {
|
if (count <= 3 || count % 100 === 0) {
|
||||||
@@ -447,6 +491,80 @@
|
|||||||
document.getElementById('loadingMsg').innerHTML = '❌ 로드 실패<br>' + err.message;
|
document.getElementById('loadingMsg').innerHTML = '❌ 로드 실패<br>' + err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 부재 토글 기능
|
||||||
|
const checkboxes = {
|
||||||
|
'가시설4': document.getElementById('cb-gasiseol4'),
|
||||||
|
'h-pile2': document.getElementById('cb-hpile2'),
|
||||||
|
'토류판': document.getElementById('cb-tolyupan'),
|
||||||
|
'H-PILE': document.getElementById('cb-hpile'),
|
||||||
|
'스터럿': document.getElementById('cb-strut')
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(checkboxes).forEach(([name, checkbox]) => {
|
||||||
|
checkbox.addEventListener('change', (e) => {
|
||||||
|
const isVisible = e.target.checked;
|
||||||
|
memberGroups[name].forEach(mesh => {
|
||||||
|
mesh.visible = isVisible;
|
||||||
|
});
|
||||||
|
console.log(`${name}: ${isVisible ? '표시' : '숨김'}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 줌핏 기능
|
||||||
|
document.getElementById('btnZoomFit').addEventListener('click', () => {
|
||||||
|
// 활성화된 부재들의 경계 계산
|
||||||
|
let minX = Infinity, maxX = -Infinity;
|
||||||
|
let minY = Infinity, maxY = -Infinity;
|
||||||
|
let minZ = Infinity, maxZ = -Infinity;
|
||||||
|
let hasVisible = false;
|
||||||
|
|
||||||
|
Object.entries(memberGroups).forEach(([name, meshes]) => {
|
||||||
|
if (checkboxes[name].checked) {
|
||||||
|
meshes.forEach(mesh => {
|
||||||
|
if (mesh.geometry.boundingBox === null) {
|
||||||
|
mesh.geometry.computeBoundingBox();
|
||||||
|
}
|
||||||
|
const box = mesh.geometry.boundingBox;
|
||||||
|
const pos = mesh.position;
|
||||||
|
|
||||||
|
minX = Math.min(minX, pos.x + box.min.x);
|
||||||
|
maxX = Math.max(maxX, pos.x + box.max.x);
|
||||||
|
minY = Math.min(minY, pos.y + box.min.y);
|
||||||
|
maxY = Math.max(maxY, pos.y + box.max.y);
|
||||||
|
minZ = Math.min(minZ, pos.z + box.min.z);
|
||||||
|
maxZ = Math.max(maxZ, pos.z + box.max.z);
|
||||||
|
hasVisible = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasVisible) {
|
||||||
|
console.log('표시된 부재가 없습니다');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 경계의 중심과 크기 계산
|
||||||
|
const center = new THREE.Vector3(
|
||||||
|
(minX + maxX) / 2,
|
||||||
|
(minY + maxY) / 2,
|
||||||
|
(minZ + maxZ) / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
const size = new THREE.Vector3(
|
||||||
|
maxX - minX,
|
||||||
|
maxY - minY,
|
||||||
|
maxZ - minZ
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
const fov = camera.fov * (Math.PI / 180);
|
||||||
|
let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
|
||||||
|
|
||||||
|
camera.position.set(center.x, center.y, center.z + cameraZ * 1.5);
|
||||||
|
camera.lookAt(center);
|
||||||
|
console.log('✓ 줌핏 완료');
|
||||||
|
});
|
||||||
|
|
||||||
// 렌더링
|
// 렌더링
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
|
|||||||
Reference in new issue
Block a user