diff --git a/app.py b/app.py index 1a2cb4d..3cb5bfe 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,4 @@ import os -import json import tempfile from flask import Flask, request, jsonify, send_file, Response from flask_cors import CORS @@ -17,6 +16,27 @@ current_drawing_data = None current_model_data = None +def _resolve_classification(drawing_name, manual_type=None): + """도면명 기반 분류 및 사용자 수동 오버라이드 객체 생성 헬퍼""" + res = cad_parser.classify_drawing_by_name(drawing_name) + selected_type = manual_type if manual_type in ('GENERAL', 'REBAR') else res['auto_type'] + label_map = { + 'GENERAL': '일반도', + 'REBAR': '배근도/구조도', + 'UNCLASSIFIED': '미분류 (수동 선택 필요)' + } + return { + 'autoType': res['auto_type'], + 'autoLabel': res['auto_label'], + 'matchedKeyword': res['matched_keyword'], + 'reason': res['reason'], + 'needsManualSelection': res['needs_manual_selection'], + 'selectedType': selected_type, + 'selectedLabel': label_map.get(selected_type, res['auto_label']), + 'isManualOverride': bool(manual_type and manual_type != res['auto_type']) + } + + @app.route('/') def index(): return send_file('index.html') @@ -62,24 +82,7 @@ def api_classify(): drawing_name = body.get('drawingName', '') manual_type = body.get('manualType') # Optional: 'GENERAL' or 'REBAR' - res = cad_parser.classify_drawing_by_name(drawing_name) - selected_type = manual_type if manual_type in ('GENERAL', 'REBAR') else res['auto_type'] - label_map = { - 'GENERAL': '일반도', - 'REBAR': '배근도/구조도', - 'UNCLASSIFIED': '미분류 (수동 선택 필요)' - } - - classification_obj = { - 'autoType': res['auto_type'], - 'autoLabel': res['auto_label'], - 'matchedKeyword': res['matched_keyword'], - 'reason': res['reason'], - 'needsManualSelection': res['needs_manual_selection'], - 'selectedType': selected_type, - 'selectedLabel': label_map.get(selected_type, res['auto_label']), - 'isManualOverride': bool(manual_type and manual_type != res['auto_type']) - } + classification_obj = _resolve_classification(drawing_name, manual_type) # Update active drawing classification in memory and regenerate 3D model for d in current_drawings_list: @@ -135,12 +138,19 @@ def api_upload(): # 2. Parse each uploaded DWG file parsed_drawings = [] - for filename, temp_path in saved_files: - try: + try: + for filename, temp_path in saved_files: drawing_data = cad_parser.parse_dwg(temp_path, custom_filename=filename) parsed_drawings.append(drawing_data) - except Exception as e: - return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생 ({filename}): {str(e)}'}), 500 + except Exception as e: + return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생: {str(e)}'}), 500 + finally: + for _, temp_path in saved_files: + try: + if os.path.exists(temp_path): + os.remove(temp_path) + except OSError: + pass global current_drawing_data, current_model_data, current_drawings_list if reset_flag or current_drawings_list is None: diff --git a/cad_parser.py b/cad_parser.py index 18a20d3..158f346 100644 --- a/cad_parser.py +++ b/cad_parser.py @@ -1,7 +1,6 @@ import os import re import math -import json import ezdwg diff --git a/model_3d.py b/model_3d.py index bac206d..fb9e8ba 100644 --- a/model_3d.py +++ b/model_3d.py @@ -1,6 +1,5 @@ import math import numpy as np -import json import rebar_model @@ -122,7 +121,6 @@ def get_wingwall_mesh(side='LEFT', custom_params=None, culvert_params=None, num_ pos_str = 'START_RIGHT' is_left_side = ('LEFT' in pos_str) - is_start = ('START' in pos_str) cp = custom_params or {} culv = culvert_params or {} @@ -554,6 +552,7 @@ def generate_full_model(params=None, drawings_list=None): if __name__ == '__main__': + import json res = generate_full_model() print("Full model generated:") print(" Total volume:", res['summary']['total_volume'], "m3") diff --git a/static/js/viewer3d.js b/static/js/viewer3d.js index 0732869..43725a3 100644 --- a/static/js/viewer3d.js +++ b/static/js/viewer3d.js @@ -887,27 +887,10 @@ class Viewer3D { new THREE.Vector3(d2.x, d2.y, zDim + tickLen) ); - const geom = new THREE.BufferGeometry().setFromPoints(linePoints); - const mat = new THREE.LineBasicMaterial({ - color: 0xffffff, // Requirement 4: 모든 치수선 흰색 - linewidth: 2, - depthTest: false, - transparent: true, - opacity: 0.95 - }); - const lineSegs = new THREE.LineSegments(geom, mat); - lineSegs.renderOrder = 999; - this.dimensionGroup.add(lineSegs); - - // 치수값 텍스트 라벨 (수평 치수선 상단 중앙 배치) + // 치수선 및 텍스트 렌더링 const mid = new THREE.Vector3().addVectors(d1, d2).multiplyScalar(0.5); const spritePos = new THREE.Vector3(mid.x, mid.y, zDim + 0.28); - - const sprite = this.createDimensionTextSprite(text); - sprite.position.copy(spritePos); - sprite.renderOrder = 1000; - this.scene.add(sprite); - this.dimensionSprites.push(sprite); + this._renderDimensionEntry(linePoints, spritePos, text); } // 높이 측정용 3D 치수선 생성 (Requirement 3 & 4: 흰색 치수선 및 간소화 표기) @@ -936,6 +919,14 @@ class Viewer3D { linePoints.push(d1.clone().addScaledVector(tickDir, tickLen), d1.clone().addScaledVector(tickDir, -tickLen)); linePoints.push(d2.clone().addScaledVector(tickDir, tickLen), d2.clone().addScaledVector(tickDir, -tickLen)); + // 치수값 텍스트 라벨 (치수선 외측 중앙에 명확히 대응되도록 배치) + const mid = new THREE.Vector3().addVectors(d1, d2).multiplyScalar(0.5); + const spritePos = mid.clone().addScaledVector(offsetDir.clone().normalize(), 0.32); + this._renderDimensionEntry(linePoints, spritePos, text); + } + + // 치수선 LineSegments 및 텍스트 Sprite 씬 등록 공통 헬퍼 + _renderDimensionEntry(linePoints, spritePos, text) { const geom = new THREE.BufferGeometry().setFromPoints(linePoints); const mat = new THREE.LineBasicMaterial({ color: 0xffffff, // Requirement 4: 모든 치수선 흰색 @@ -948,10 +939,6 @@ class Viewer3D { lineSegs.renderOrder = 999; this.dimensionGroup.add(lineSegs); - // 치수값 텍스트 라벨 (치수선 외측 중앙에 명확히 대응되도록 배치) - const mid = new THREE.Vector3().addVectors(d1, d2).multiplyScalar(0.5); - const spritePos = mid.clone().addScaledVector(offsetDir.clone().normalize(), 0.32); - const sprite = this.createDimensionTextSprite(text); sprite.position.copy(spritePos); sprite.renderOrder = 1000; diff --git a/test_pipeline.py b/test_pipeline.py index 7be5b5d..c3588cb 100644 --- a/test_pipeline.py +++ b/test_pipeline.py @@ -5,7 +5,6 @@ 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 @@ -156,6 +155,7 @@ class Test3DDrawingPipeline(unittest.TestCase): # 1. 메인 웹페이지 r_index = self.flask_app.get('/') self.assertEqual(r_index.status_code, 200) + r_index.close() # 2. 기본 도면 데이터 조회 API r_def = self.flask_app.get('/api/default-drawing')