원본 좌표와 브라우저 검증 결과 및 실행 수정 안내 기록

This commit is contained in:
김지영 committed 2026-10-20 11:03:43 +09:00
1 parent d01ef8b0e8
commit fac29cb010
17 files changed
+358 -17

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
<!doctype html><meta charset="utf-8"><title>뷰어 자동 검증</title><pre id="result">검증 중</pre><iframe src="/" style="width:1280px;height:1000px;border:0"></iframe><script type="module">
const iframe = document.querySelector('iframe'), result = document.querySelector('#result');
const pause = () => new Promise(resolve => setTimeout(resolve, 120));
const checks = [];
const check = (condition, name) => { checks.push({ name, pass: !!condition }); if (!condition) throw Error(name); };
try {
for (let i = 0; i < 80 && !iframe.contentDocument?.querySelector('canvas'); i++) await pause();
const doc = iframe.contentDocument;
const click = async text => { const button = [...doc.querySelectorAll('button')].find(e => e.textContent === text); check(button, `버튼 존재: ${text}`); button.click(); await pause(); };
const canvas = doc.querySelector('canvas'); check(canvas?.width > 0, '3D 캔버스 생성');
check(doc.body.textContent.includes('154'), '원본 154 객체 표시');
for (const name of ['평면','정면','측면','A-A','B-B','전체']) await click(name);
await click('모두 숨김'); check(doc.querySelector('.panel-heading').textContent.includes('0 / 154'), '전체 숨김 반영');
await click('모두 표시'); check(doc.querySelector('.panel-heading').textContent.includes('94 / 154'), '반복선 제외한 전체 표시');
const details = doc.querySelector('details'); details.open = true;
const row = doc.querySelector('.part-row button'); row.click(); await pause();
check(doc.querySelector('.selection').textContent.includes('원본 선 번호'), '선택하면 원본 근거 표시');
await click('선택한 윤곽 숨기기'); check(doc.querySelector('.panel-heading').textContent.includes('93 / 154'), '개별 숨김 반영');
let captured; iframe.contentWindow.URL.createObjectURL = blob => { captured = blob; return 'blob:viewer-test'; }; doc.addEventListener('click', e => { if (e.target.closest('a[download]')) e.preventDefault(); }, true);
await click('3D OBJ ↓'); const obj = await captured.text(); check(obj.split('\n').filter(s => s.startsWith('o ')).length === 153, 'OBJ 내보내기 153개 객체'); check(!obj.includes('NaN'), 'OBJ 유효한 좌표'); check(obj.split('\n').filter(s => s.startsWith('f ')).length === 466, 'OBJ 연결 삼각면 466개');
const before = canvas.toDataURL(); canvas.dispatchEvent(new iframe.contentWindow.KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })); await pause(); check(canvas.toDataURL() !== before, '키보드 회전 반영');
const beforeZoom = canvas.toDataURL(); canvas.dispatchEvent(new iframe.contentWindow.WheelEvent('wheel', { deltaY: -200, bubbles: true, cancelable: true })); await pause(); check(canvas.toDataURL() !== beforeZoom, '휠 확대 반영');
const input = doc.querySelector('.search'), setter = Object.getOwnPropertyDescriptor(iframe.contentWindow.HTMLInputElement.prototype, 'value').set;
setter.call(input, '없는부재'); input.dispatchEvent(new iframe.contentWindow.Event('input', { bubbles: true })); await pause();
check(doc.querySelector('.empty'), '윤곽 검색 빈 결과 표시');
await click('도면'); check(doc.querySelector('.drawing-preview'), '도면 발췌 표시');
await click('측구상세'); await pause(); check(doc.querySelector('.drawing-preview').getAttribute('src').includes('측구상세'), '상세도 전환');
await click('검증'); check(doc.querySelectorAll('.checks .pending').length === 3, '미검증 항목 구분');
await click('렌더링 보기'); check(doc.querySelector('.canvas-tag').textContent.includes('도면 검토용'), '시각화 구분');
result.textContent = JSON.stringify({ status: 'PASS', checks }, null, 2);
} catch(e) { result.textContent = JSON.stringify({ status:'FAIL', error:e.message, checks }, null, 2); }
</script>
+7
View File
@@ -0,0 +1,7 @@
<!doctype html><meta charset="utf-8"><pre id="result">검증 중</pre><iframe src="/" style="width:390px;height:1450px;border:0"></iframe><script type="module">
const iframe=document.querySelector('iframe');
await new Promise(resolve=>iframe.addEventListener('load',resolve));
await new Promise(resolve=>setTimeout(resolve,1000));
const doc=iframe.contentDocument;
document.querySelector('#result').textContent=JSON.stringify({viewport:doc.documentElement.clientWidth,content:doc.documentElement.scrollWidth,body:doc.body.scrollWidth,pass:doc.documentElement.scrollWidth===doc.documentElement.clientWidth});
</script>
+4
View File
@@ -0,0 +1,4 @@
<!doctype html><meta charset="utf-8"><title>검토용 렌더링 미리보기</title><style>body{margin:0}iframe{border:0;width:100vw;height:100vh;display:block}</style><iframe src="/"></iframe><script type="module">
const iframe=document.querySelector('iframe');
for(let i=0;i<100;i++) { await new Promise(r=>setTimeout(r,100)); const button=[...(iframe.contentDocument?.querySelectorAll('button')||[])].find(b=>b.textContent==='렌더링 보기'); if(button){button.click();break;} }
</script>
+9
View File
@@ -0,0 +1,9 @@
import pathlib,json,html,re
root=pathlib.Path(__file__).resolve().parents[1]
for name in ['browser','mobile']:
file=root/f'docs/{name}-check-output.html'
source=file.read_text(encoding='utf-8-sig')
text=html.unescape(re.search(r'<pre id="result">(.*?)</pre>',source,re.S).group(1))
result=json.loads(text)
(root/f'docs/{name}-check-result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
print(name,result.get('status',result.get('pass')),len(result.get('checks',[])))
+27
View File
@@ -0,0 +1,27 @@
import assert from 'node:assert/strict'
import { writeFileSync, mkdirSync } from 'node:fs'
import { model, exportOBJ, partFaces, profileVertices } from '../src/model/geometry.js'
const faces = model.parts.flatMap(partFaces)
assert.equal(faces.length, 468)
assert.equal(model.connection.status, '확인 필요')
for (const part of model.parts) {
const a = profileVertices(part, 0), b = profileVertices(part, 1)
assert.equal(a.length,b.length)
for(let i=0;i<a.length;i++) {
assert.ok(Math.abs(b[i][2]-a[i][2]-19.98)<1e-10)
assert.equal(a[i][0],part.a[i][0]); assert.equal(b[i][0],part.b[i][0])
assert.ok(Math.abs(a[i][1]+100-part.a[i][1])<1e-10)
assert.ok(Math.abs(b[i][1]+100-part.b[i][1])<1e-10)
}
}
const obj=exportOBJ()
const rows=obj.split('\n'), vertices=rows.filter(r=>r.startsWith('v '))
assert.equal(rows.filter(r=>r.startsWith('o ')).length,154)
assert.equal(rows.filter(r=>r.startsWith('f ')).length,468)
for(const row of rows.filter(r=>r.startsWith('f ')||r.startsWith('l '))) {
for(const n of row.split(' ').slice(1)) assert.ok(Number(n)>=1&&Number(n)<=vertices.length)
}
assert.equal(exportOBJ(new Set(model.parts.map(p=>p.id))).split('\n').filter(r=>r.startsWith('v ')).length,0)
mkdirSync('public/models',{recursive:true})
writeFileSync('public/models/BLOCK7_검토용모델.obj',obj)
console.log('직선 연결 19.980m, 468개 삼각면, 154개 객체, OBJ 참조·숨김 반영 검증 통과')
+30
View File
@@ -0,0 +1,30 @@
import json,math,pathlib
root=pathlib.Path(__file__).resolve().parents[1]
model=json.loads((root/'src/model/block7.json').read_text(encoding='utf8'))
parts=model['parts']; assert len(parts)==154
assert len({p['id'] for p in parts})==154
results=[]
for s,top,el in [('A',-74271.37855593952,106.675),('B',-91211.37855593952,107.307)]:
raw=json.loads((root/f'docs/extracted/section-{s}.json').read_text(encoding='utf8'))
assert len(raw)==114
assert sum(l['layer']=='CS-CONC-MAJR' for l in raw)==108
assert sum(l['layer']=='CS-CONC-MINR' for l in raw)==6
for original,part in zip(raw,parts[:114]):
assert original['handle']==part['handles'][0 if s=='A' else 1]
for key,point in zip(['a','b'],part[s.lower()]):
expected=[(original[key][0]-1128486.218066491)/1000,el+(original[key][1]-top)/1000]
assert all(abs(a-b)<0.000000051 for a,b in zip(point,expected))
results.append(f'{s}: 114개 선 HANDLE·레이어·양 끝점 원본 대조 통과 (좌표 반올림 오차 0.000051mm 이하)')
for p in parts:
assert len(p['a'])==len(p['b'])
assert all(math.isfinite(v) for s in ['a','b'] for point in p[s] for v in point)
assert sum(p['kind']=='arc' for p in parts)==4
assert abs(model['longitudinalStations'][1]-model['longitudinalStations'][0]-model['length'])<1e-10
assert model['sectionStations'][1]-model['sectionStations'][0]==20
for i,el in enumerate(model['sectionTops']):
assert abs(max(pt[1] for p in parts[:114] for pt in p['a' if i==0 else 'b'])-el)<1e-7
assert abs((parts[1]['a'][1][1]-parts[1]['a'][0][1]))<1e-7
results+=['154개 ID 중복 없음 / 모든 좌표 유한수 / 대응 꼭짓점 수 일치','원호 4개, 좌우 삽입 블록 40쌍 반영','벽체 상단 EL 및 종단 측점 차 19.980m 검산 통과','원본 단면 STA 간격 20.000m 보존; 사용자 허용에 따라 배치 간격만 19.980m로 대응 (verify_geometry.mjs에서 별도 검증)']
print('\n'.join(results))
(root/'docs/좌표검증결과.txt').write_text('\n'.join(results)+'\n',encoding='utf8')