177 lines
7.7 KiB
Python
177 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Comprehensive End-to-End Pipeline Verification Test
|
|
Verifies 2D drawing classification, DWG parsing, 3D solid & rebar modeling,
|
|
mesh watertightness, exporter functionality, and Flask API endpoints.
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import unittest
|
|
|
|
import cad_parser
|
|
import model_3d
|
|
import rebar_model
|
|
import exporters
|
|
import app
|
|
|
|
|
|
class Test3DDrawingPipeline(unittest.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
cls.project_root = os.path.abspath(os.path.join(current_dir, '..', '..'))
|
|
cls.rag_dir = os.path.join(cls.project_root, 'RAG')
|
|
cls.general_dwg = os.path.join(cls.rag_dir, '13. 수로암거 일반도(STA.13+611.21)(02).dwg')
|
|
app.app.testing = True
|
|
cls.flask_app = app.app.test_client()
|
|
|
|
def test_01_drawing_classification(self):
|
|
"""1.2 도면 종류 키워드 기반 자동 판별 검증"""
|
|
# 일반도 판별
|
|
res_gen = cad_parser.classify_drawing_by_name('13. 수로암거 일반도(STA.13+611.21)(02).dwg')
|
|
self.assertEqual(res_gen['auto_type'], 'GENERAL')
|
|
self.assertEqual(res_gen['matched_keyword'], '일반')
|
|
self.assertFalse(res_gen['needs_manual_selection'])
|
|
|
|
# 배근도/구조도 판별
|
|
res_rebar1 = cad_parser.classify_drawing_by_name('14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg')
|
|
self.assertEqual(res_rebar1['auto_type'], 'REBAR')
|
|
self.assertIn('구조', res_rebar1['matched_keyword'])
|
|
self.assertFalse(res_rebar1['needs_manual_selection'])
|
|
|
|
res_rebar2 = cad_parser.classify_drawing_by_name('날개벽 배근도(종점 좌).dwg')
|
|
self.assertEqual(res_rebar2['auto_type'], 'REBAR')
|
|
self.assertIn('배근', res_rebar2['matched_keyword'])
|
|
|
|
# 미분류 도면
|
|
res_unclass = cad_parser.classify_drawing_by_name('기타 도면#01.dwg')
|
|
self.assertEqual(res_unclass['auto_type'], 'UNCLASSIFIED')
|
|
self.assertTrue(res_unclass['needs_manual_selection'])
|
|
|
|
def test_02_dwg_parsing(self):
|
|
"""1.1 DWG 도면 파싱 및 파라미터 추출 검증"""
|
|
if not os.path.exists(self.general_dwg):
|
|
self.skipTest(f"DWG file not found at {self.general_dwg}")
|
|
|
|
drawing_data = cad_parser.parse_dwg(self.general_dwg)
|
|
self.assertIsNotNone(drawing_data)
|
|
self.assertIn('entities', drawing_data)
|
|
self.assertIn('structuralParams', drawing_data)
|
|
self.assertGreater(len(drawing_data['entities']), 100)
|
|
|
|
# 수로암거 제원 확인
|
|
sp = drawing_data['structuralParams']
|
|
self.assertEqual(sp.get('station'), 'STA.13+611.21')
|
|
self.assertAlmostEqual(sp.get('culvert_span', 2.0), 2.0, delta=0.1)
|
|
|
|
def test_03_3d_solid_modeling_and_watertightness(self):
|
|
"""2단계 일반도 기반 3D 외형 모델링 및 수밀성(Watertight) 검증"""
|
|
model_res = model_3d.generate_full_model()
|
|
self.assertIsNotNone(model_res)
|
|
self.assertIn('objects', model_res)
|
|
|
|
objects = model_res['objects']
|
|
self.assertIn('culvert', objects)
|
|
self.assertIn('ww_end_left', objects)
|
|
self.assertIn('ww_end_right', objects)
|
|
|
|
# 도면에 없는 시점부 날개벽 임의 생성 차단 확인
|
|
self.assertNotIn('ww_start_left', objects)
|
|
self.assertNotIn('ww_start_right', objects)
|
|
|
|
# 수밀성 (Watertight 2-Manifold Mesh) 검증
|
|
for part_key in ['culvert', 'ww_end_left', 'ww_end_right']:
|
|
part_data = objects[part_key]
|
|
vertices = part_data['vertices']
|
|
faces = part_data['faces']
|
|
self.assertGreater(len(vertices), 0, f"{part_key} has no vertices")
|
|
self.assertGreater(len(faces), 0, f"{part_key} has no faces")
|
|
|
|
# Edge manifold check: Every edge in a watertight 2-manifold closed surface is shared by exactly 2 faces
|
|
edge_count = {}
|
|
for f in faces:
|
|
for i in range(3):
|
|
v1, v2 = f[i], f[(i + 1) % 3]
|
|
edge = tuple(sorted([v1, v2]))
|
|
edge_count[edge] = edge_count.get(edge, 0) + 1
|
|
|
|
non_manifold = [e for e, count in edge_count.items() if count != 2]
|
|
self.assertEqual(len(non_manifold), 0,
|
|
f"{part_key} mesh is not watertight! Non-manifold edges: {len(non_manifold)}")
|
|
|
|
def test_04_3d_rebar_modeling(self):
|
|
"""3단계 구조도/배근도 기반 날개벽 내부 3D 철근 배근(508본) 검증"""
|
|
rebar_data = rebar_model.generate_full_rebar_model()
|
|
self.assertIsNotNone(rebar_data)
|
|
self.assertIn('wingwalls', rebar_data)
|
|
self.assertIn('summary', rebar_data)
|
|
|
|
summary = rebar_data['summary']
|
|
total_bars = summary.get('total_bars', 0)
|
|
self.assertEqual(total_bars, 508, f"Expected 508 total rebars, got {total_bars}")
|
|
|
|
# 좌측(231본) 및 우측(277본) 검증
|
|
left_bars = summary.get('left_bars', 0)
|
|
right_bars = summary.get('right_bars', 0)
|
|
self.assertEqual(left_bars, 231)
|
|
self.assertEqual(right_bars, 277)
|
|
|
|
# W1 철근 3D 형상 검증: 벽체-저판 접합부에서 꺾이지 않고 저판 하면에서 1회만 꺾이는 ㄱ자 형상(3개 정점) 확인
|
|
for ww_key in ['WW_END_LEFT', 'WW_END_RIGHT']:
|
|
ww_groups = rebar_data['wingwalls'][ww_key]['groups']
|
|
w1_group = next((g for g in ww_groups if g['mark'] == 'W1'), None)
|
|
self.assertIsNotNone(w1_group)
|
|
for poly in w1_group['polylines']:
|
|
self.assertEqual(len(poly), 3, f"{ww_key} W1 bar must have 3 points (1 bend at footing bottom)")
|
|
# 저판 하면 구간(poly[0] -> poly[1]) 동일 Z 표고 유지
|
|
self.assertAlmostEqual(poly[0][2], poly[1][2], places=4)
|
|
# 벽체 긴 구간(poly[1] -> poly[2]) 접합부 꺾임 없이 일직선 연장 확인
|
|
self.assertAlmostEqual(poly[1][0], poly[2][0], places=4)
|
|
self.assertAlmostEqual(poly[1][1], poly[2][1], places=4)
|
|
|
|
def test_05_exporters(self):
|
|
"""3D 모델 OBJ, STL, DXF 내보내기 검증"""
|
|
model_res = model_3d.generate_full_model()
|
|
|
|
obj_str = exporters.export_obj(model_res)
|
|
self.assertIn('v ', obj_str)
|
|
self.assertIn('f ', obj_str)
|
|
|
|
stl_str = exporters.export_stl(model_res)
|
|
self.assertIn('solid ', stl_str)
|
|
self.assertIn('facet normal', stl_str)
|
|
|
|
dxf_str = exporters.export_dxf_3d(model_res)
|
|
self.assertIn('SECTION', dxf_str)
|
|
self.assertIn('ENTITIES', dxf_str)
|
|
|
|
csv_str = exporters.export_rebar_bom_csv(model_res)
|
|
self.assertIn('구분(날개벽)', csv_str)
|
|
self.assertIn('기호(Mark)', csv_str)
|
|
|
|
def test_06_flask_web_endpoints(self):
|
|
"""Flask API 및 웹 엔드포인트 응답 검증"""
|
|
# 1. 메인 웹페이지
|
|
r_index = self.flask_app.get('/')
|
|
self.assertEqual(r_index.status_code, 200)
|
|
|
|
# 2. 기본 도면 데이터 조회 API
|
|
r_def = self.flask_app.get('/api/default-drawing')
|
|
self.assertEqual(r_def.status_code, 200)
|
|
data_def = json.loads(r_def.data)
|
|
self.assertEqual(data_def.get('status'), 'success')
|
|
|
|
# 3. 도면 분류 API
|
|
r_cls = self.flask_app.post('/api/classify',
|
|
data=json.dumps({'drawingName': '13. 수로암거 일반도.dwg'}),
|
|
content_type='application/json')
|
|
self.assertEqual(r_cls.status_code, 200)
|
|
data_cls = json.loads(r_cls.data)
|
|
self.assertEqual(data_cls['classification']['selectedType'], 'GENERAL')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|