refactor: k2 슬라이서를 k2-app 오버레이 → 전용 팝업창으로 분리
"🔪 슬라이싱" 버튼이 k2-app 위에 오버레이를 띄우던 방식을 window.open() 전용 팝업창(slicer-app.html)으로 분리. k2-app(카메라+Fluidd 모니터)과 슬라이서 (3D 편집 도구) 역할 분리 + 오버레이 블랙스크린 원인 해결. - 블랙스크린 원인: 옛 방식은 #slicer-overlay 가 display:none 이다가 .show로 토글되는데, 그 직후 initScene이 실행되면서 뷰포트 clientWidth/Height가 0이라 renderer.setSize가 스킵→캔버스 0size. 전용 팝업은 #slicer-root 가 처음부터 position:fixed;inset:0 이라 크기가 바로 잡힘. - web-ui/html/slicer-app.html 신규: Three.js+슬라이서 모듈만 로드하는 풀뷰포트 팝업 페이지. 인증은 같은 오리진 smallclaw_session 쿠키가 팝업에 자동 상속. - slicer-view.js: openSlicer/closeSlicer 오버레이 토글 → initSlicer 자동 초기화 (DOMContentLoaded). .sv-shell을 #slicer-root에 빌드. 캔버스 CSS width/height 100%로 셀 채우도록 수정. ✕버튼=window.close(). - k2-app.html: #slicer-overlay div·Three.js 스크립트 제거, 버튼→openSlicerPopup(). CAD 모달·카메라·Fluidd iframe은 그대로. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+10
-11
@@ -50,9 +50,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
.slice-err{margin-top:10px;color:#ef4444;font-size:12px;display:none;}
|
||||
.slice-err.show{display:block;}
|
||||
|
||||
/* ── 3D 슬라이서(Cura 스타일) 풀뷰포트 오버레이 — 실제 스타일은 slicer-view.js가 주입 ── */
|
||||
#slicer-overlay{display:none;position:fixed;inset:0;z-index:55;}
|
||||
#slicer-overlay.show{display:block;}
|
||||
/* ── 3D 슬라이서(Cura 스타일) — 전용 팝업창으로 분리(slicer-app.html). k2-app엔 오버레이 없음 ── */
|
||||
|
||||
/* ── CAD(STL 확인) 모달 ── */
|
||||
.cad-modal{max-width:520px;}
|
||||
@@ -75,7 +73,7 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
<a href="/" style="text-decoration:none;font-size:13px;color:var(--muted)">← 홈</a>
|
||||
<h1>🖨️ K2 콤보</h1>
|
||||
<span class="hdr-sp"></span>
|
||||
<button class="hdr-btn" onclick="openSlicer()">🔪 슬라이싱</button>
|
||||
<button class="hdr-btn" onclick="openSlicerPopup()">🔪 슬라이싱</button>
|
||||
<button class="hdr-btn" onclick="openCadModal()">🔧 STL 확인</button>
|
||||
<button class="hdr-btn" onclick="reconnectCam()">📷 카메라 재연결</button>
|
||||
<button class="hdr-btn" onclick="reloadFrame()">↺ 새로고침</button>
|
||||
@@ -97,8 +95,6 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="slicer-overlay"></div>
|
||||
|
||||
<div class="slice-overlay" id="cad-overlay">
|
||||
<div class="slice-modal cad-modal">
|
||||
<button class="slice-close" onclick="closeCadModal()">✕</button>
|
||||
@@ -141,6 +137,14 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── 슬라이서 팝업 — Cura 스타일 3D GUI는 전용창(slicer-app.html)으로 분리 ──
|
||||
// k2-app(카메라+Fluidd 모니터)과 슬라이서(편집 도구) 역할 분리. 인증은 같은 오리진
|
||||
// smallclaw_session 쿠키가 팝업에 자동 상속되어 별도 토큰 전달 불필요.
|
||||
function openSlicerPopup(){
|
||||
const w=1320,h=860;
|
||||
const left=Math.max(0,(screen.width-w)/2), top=Math.max(0,(screen.height-h)/2);
|
||||
window.open('/html/slicer-app.html','k2-slicer',`width=${w},height=${h},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no,resizable=yes`);
|
||||
}
|
||||
function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('homeclaw_theme',n);}catch{}}
|
||||
function reloadFrame(){const f=document.getElementById('k2-frame');f.src=f.src;}
|
||||
let loaded=false;
|
||||
@@ -325,10 +329,5 @@ async function runAddHole(){
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- Three.js r128 UMD + 로더(Cura 스타일 3D 슬라이서 GUI용) — /vendor/three/ 정적 서빙 -->
|
||||
<script src="/vendor/three/three.min.js"></script>
|
||||
<script src="/vendor/three/STLLoader.js"></script>
|
||||
<script src="/vendor/three/OrbitControls.js"></script>
|
||||
<script type="module" src="/js/k2/slicer-view.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔪 K2 슬라이서</title>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="/homeclaw_logo.png">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<script>try{var _t=localStorage.getItem('homeclaw_theme')||'dark';document.documentElement.setAttribute('data-theme',_t);}catch(e){}</script>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;padding:0;overflow:hidden;background:var(--bg,#1a1a1a);}
|
||||
/* #slicer-root 가 풀뷰포트 — slicer-view.js 가 .sv-shell 을 이 안에 빌드.
|
||||
position:fixed;inset:0 이라 페이지 로드 직후부터 크기가 잡혀서 initScene 시점에
|
||||
뷰포트 clientWidth/Height 가 0 이 되는 블랙스크린 문제가 안 생김(옛 오버레이 방식의
|
||||
display:none → 0size 함정 회피). */
|
||||
#slicer-root{position:fixed;inset:0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slicer-root"></div>
|
||||
<!-- Three.js r128 UMD + 로더 — /vendor/three/ 정적 서빙(전역 THREE 부착) -->
|
||||
<script src="/vendor/three/three.min.js"></script>
|
||||
<script src="/vendor/three/STLLoader.js"></script>
|
||||
<script src="/vendor/three/OrbitControls.js"></script>
|
||||
<script type="module" src="/js/k2/slicer-view.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+29
-22
@@ -195,6 +195,7 @@
|
||||
if (!renderer || !vpEl) return;
|
||||
const w = vpEl.clientWidth, h = vpEl.clientHeight;
|
||||
if (w < 2 || h < 2) return;
|
||||
// updateStyle=false: CSS가 canvas를 셀에 맞춰 100%로 늘리므로 버퍼만 설정.
|
||||
renderer.setSize(w, h, false);
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
@@ -635,16 +636,17 @@
|
||||
} catch (e) { alert('출력 시작 실패: ' + e.message); }
|
||||
}
|
||||
|
||||
// ── 오버레이 DOM 구축 ─────────────────────────────────────────────────
|
||||
// ── 페이지 DOM 구축 ─────────────────────────────────────────────────
|
||||
function buildOverlay() {
|
||||
overlayEl = document.getElementById('slicer-overlay');
|
||||
overlayEl = document.getElementById('slicer-root');
|
||||
if (!overlayEl) { console.error('[slicer-view] #slicer-root 컨테이너가 없습니다.'); return; }
|
||||
overlayEl.innerHTML = `
|
||||
<div class="sv-shell">
|
||||
<div class="vp" id="sv-vp"></div>
|
||||
<aside class="sv-panel" id="sv-panel">
|
||||
<div class="sv-panel-hdr">
|
||||
<h2>🔧 슬라이싱 프로파일</h2>
|
||||
<button class="sv-x" onclick="window.closeSlicer()">✕</button>
|
||||
<button class="sv-x" onclick="window.close()">✕</button>
|
||||
</div>
|
||||
<div class="sv-prof-bar">
|
||||
<select id="sv-prof-select"><option value="">— 기본(베이스) —</option></select>
|
||||
@@ -688,8 +690,8 @@
|
||||
st.id = 'sv-styles';
|
||||
st.textContent = `
|
||||
.sv-shell{position:absolute;inset:0;display:grid;grid-template-columns:1fr 300px;grid-template-rows:1fr auto;background:var(--bg,#1a1a1a);z-index:60;}
|
||||
.sv-shell .vp{grid-column:1;grid-row:1;min-height:0;position:relative;background:#1a1a1a;}
|
||||
.sv-shell .vp canvas{display:block;}
|
||||
.sv-shell .vp{grid-column:1;grid-row:1;min-height:0;position:relative;background:#1a1a1a;overflow:hidden;}
|
||||
.sv-shell .vp canvas{display:block;width:100%;height:100%;}
|
||||
.sv-panel{grid-column:2;grid-row:1/3;background:var(--panel,#222);border-left:1px solid var(--line,#333);overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:8px;}
|
||||
.sv-panel-hdr{display:flex;align-items:center;justify-content:space-between;}
|
||||
.sv-panel-hdr h2{font-size:13px;margin:0;}
|
||||
@@ -737,30 +739,35 @@
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
|
||||
// ── 글로벌 진입점 ─────────────────────────────────────────────────────
|
||||
// ── 진입점 — 팝업 페이지 로드시 자동 초기화 ─────────────────────────
|
||||
// k2-app.html의 "🔪 슬라이싱" 버튼이 window.open('/html/slicer-app.html')으로
|
||||
// 이 페이지를 팝업으로 연다. 오버레이 토글 방식이 아니라 페이지 진입 = 곧 슬라이서
|
||||
// 시작. #slicer-root가 position:fixed;inset:0 이라 initScene 시점에 뷰포트 크기가
|
||||
// 바로 잡혀(옛 오버레이 방식의 display:none→0size 블랙스크린 문제 회피).
|
||||
let inited = false;
|
||||
async function openSlicer() {
|
||||
async function initSlicer() {
|
||||
if (inited) return;
|
||||
if (!document.getElementById('slicer-root')) return;
|
||||
injectStyles();
|
||||
buildOverlay();
|
||||
document.getElementById('slicer-overlay').classList.add('show');
|
||||
if (!inited) {
|
||||
initScene();
|
||||
await profInit();
|
||||
inited = true;
|
||||
// 창 크기 변화 대응
|
||||
const ro = new ResizeObserver(() => resize());
|
||||
ro.observe(vpEl);
|
||||
} else {
|
||||
resize();
|
||||
}
|
||||
if (!vpEl) return;
|
||||
initScene();
|
||||
await profInit();
|
||||
inited = true;
|
||||
// 창 크기 변화 대응(팝업 창 리사이즈 포함).
|
||||
const ro = new ResizeObserver(() => resize());
|
||||
ro.observe(vpEl);
|
||||
// 초기 렌더 확정 — 다음 프레임에 한 번 더 resize(레이아웃 완료 보장).
|
||||
requestAnimationFrame(() => { resize(); requestAnimationFrame(resize); });
|
||||
}
|
||||
function closeSlicer() {
|
||||
document.getElementById('slicer-overlay').classList.remove('show');
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initSlicer);
|
||||
} else {
|
||||
initSlicer();
|
||||
}
|
||||
|
||||
// 슬라이더/버튼 콜백을 글로벌로 노출(인라인 onclick 호환).
|
||||
window.openSlicer = openSlicer;
|
||||
window.closeSlicer = closeSlicer;
|
||||
window.__slicerSlice = runSlice;
|
||||
window.__slicerUpload = uploadGcode;
|
||||
window.__slicerPrint = startPrint;
|
||||
|
||||
Reference in New Issue
Block a user