From d2ad62921341118b1293adfb27ac806173563204 Mon Sep 17 00:00:00 2001 From: Claude Haiku Date: Mon, 19 Oct 2026 17:37:05 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B6=80=EC=9E=AC=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=20=ED=8C=A8=EB=84=90=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EC=98=A8=EC=98=A4=ED=94=84=20=EB=B0=8F=20=EC=A4=8C=ED=95=8F=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능 추가: 1. 패널에 부재 구성 표시 ✓ 부재명 (한글) ✓ 규격 정보 (mm 단위) ✓ 부재 개수 ✓ 색상 표시 2. 부재 온오프 제어 ✓ 부재 타입별 체크박스 ✓ 체크 해제하면 해당 부재 숨김 ✓ 실시간 표시/숨김 3. 줌핏 기능 ✓ "🎯 줌핏" 버튼 추가 ✓ 활성화된 부재만 경계 계산 ✓ 자동 카메라 조정 ✓ 선택된 부재들이 화면 중앙에 표시 부재 그룹 관리: - memberGroups 객체로 부재 타입별 메시 추적 - 메시 userData에 groupName 저장 - 체크박스와 메시 가시성 연동 사용법: 1. 패널의 체크박스로 부재 표시/숨김 2. "줌핏" 버튼으로 선택된 부재에 자동 줌 Co-Authored-By: Claude Haiku 4.5 --- out/viewer-correct.html | 130 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 6 deletions(-) diff --git a/out/viewer-correct.html b/out/viewer-correct.html index f4852ae..5dcc12a 100644 --- a/out/viewer-correct.html +++ b/out/viewer-correct.html @@ -191,13 +191,39 @@

🏗️ 부재 구성

-
-
가시설4 52개
-
h-pile2 244개
-
토류판 240개
-
H-PILE 26개
-
스터럿 26개
+
+ + + + +
+
@@ -338,12 +364,23 @@ const DISPLAY_SCALE = 0.01; + // 부재 그룹 추적 (온오프 제어용) + const memberGroups = { + '가시설4': [], + 'h-pile2': [], + '토류판': [], + 'H-PILE': [], + '스터럿': [] + }; + // 샘플 부재 (테스트용) 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); + testMesh.userData.groupName = 'h-pile2'; scene.add(testMesh); + memberGroups['h-pile2'].push(testMesh); console.log('✓ 샘플 부재 추가됨'); // 그리드 & 축 @@ -426,7 +463,14 @@ const mesh = new THREE.Mesh(geometry, getMaterial(type)); mesh.position.set(position.x, position.y, position.z + 10); + mesh.userData = { blockType, type, spec, volume, groupName: blockType }; scene.add(mesh); + + // 부재 그룹에 추가 + if (memberGroups[blockType]) { + memberGroups[blockType].push(mesh); + } + count++; if (count <= 3 || count % 100 === 0) { @@ -447,6 +491,80 @@ document.getElementById('loadingMsg').innerHTML = '❌ 로드 실패
' + 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() { requestAnimationFrame(animate);