using System.IO; using System.Windows; using System.Diagnostics; using System.Windows.Threading; using DwgExtractorManual.Models; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using System.Threading; using System.Text; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; namespace DwgExtractorManual { public partial class MainWindow : Window { private DispatcherTimer? _timer; private ExportExcel? _exportExcel; private SqlDatas? _sqlDatas; // 자동 처리 모드 플래그 private bool isAutoProcessing = false; public MainWindow() { InitializeComponent(); InitializeTimer(); LoadSettings(); SetBuildTime(); // 앱 종료 시 Teigha 리소스 정리 this.Closed += MainWindow_Closed; } private void LoadSettings() { LogMessage("⚙️ 설정을 불러옵니다..."); var settings = SettingsManager.LoadSettings(); if (settings != null) { if (!string.IsNullOrEmpty(settings.SourceFolderPath) && Directory.Exists(settings.SourceFolderPath)) { txtSourceFolder.Text = settings.SourceFolderPath; LogMessage($"📂 저장된 소스 폴더: {settings.SourceFolderPath}"); CheckDwgFiles(settings.SourceFolderPath); } else { LogMessage($"⚠️ 저장된 소스 폴더를 찾을 수 없습니다: {settings.SourceFolderPath}"); } if (!string.IsNullOrEmpty(settings.DestinationFolderPath) && Directory.Exists(settings.DestinationFolderPath)) { txtResultFolder.Text = settings.DestinationFolderPath; LogMessage($"💾 저장된 결과 폴더: {settings.DestinationFolderPath}"); } else { LogMessage($"⚠️ 저장된 결과 폴더를 찾을 수 없습니다: {settings.DestinationFolderPath}"); } if (!string.IsNullOrEmpty(settings.LastExportType)) { if (settings.LastExportType == "Excel") { rbExcel.IsChecked = true; } else if (settings.LastExportType == "Database") { rbDatabase.IsChecked = true; } LogMessage($"📋 저장된 출력 형식: {settings.LastExportType}"); } LogMessage("✅ 설정 불러오기 완료."); } else { LogMessage("ℹ️ 저장된 설정 파일이 없습니다. 기본값으로 시작합니다."); InitializeDefaultPaths(); // Fallback } } private void SaveSettings() { LogMessage("⚙️ 현재 설정을 저장합니다..."); var settings = new AppSettings { SourceFolderPath = txtSourceFolder.Text, DestinationFolderPath = txtResultFolder.Text, LastExportType = rbExcel.IsChecked == true ? "Excel" : "Database" }; SettingsManager.SaveSettings(settings); LogMessage("✅ 설정 저장 완료."); } private void MainWindow_Closed(object? sender, EventArgs e) { try { LogMessage("🔄 애플리케이션 종료 시 Teigha 리소스 정리 중..."); TeighaServicesManager.Instance.ForceDisposeServices(); LogMessage("✅ Teigha 리소스 정리 완료"); } catch (Exception ex) { LogMessage($"⚠️ 앱 종료 시 Teigha 정리 중 오류: {ex.Message}"); } } private void InitializeDefaultPaths() { // 기본 경로 설정 - 실제 환경에 맞게 수정 txtSourceFolder.Text = @"C:\WorkProjects\dwfpdfCompare"; txtResultFolder.Text = @"C:\WorkProjects\dwfpdfCompare"; // 경로가 존재하지 않으면 기본값으로 설정 if (!Directory.Exists(txtSourceFolder.Text)) { txtSourceFolder.Text = @"C:\"; } if (!Directory.Exists(txtResultFolder.Text)) { txtResultFolder.Text = @"C:\"; } } private void InitializeTimer() { _timer = new DispatcherTimer(); _timer.Interval = TimeSpan.FromSeconds(1); _timer.Tick += (s, e) => { if (_timer != null) { txtTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } }; _timer.Start(); } // Update the code where FolderBrowserDialog is used to ensure compatibility private void BtnBrowseSource_Click(object sender, RoutedEventArgs e) { using (var dialog = new System.Windows.Forms.FolderBrowserDialog()) { dialog.Description = "변환할 DWG 파일이 있는 폴더를 선택하세요"; dialog.ShowNewFolderButton = false; if (!string.IsNullOrEmpty(txtSourceFolder.Text) && Directory.Exists(txtSourceFolder.Text)) { dialog.SelectedPath = txtSourceFolder.Text; } if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtSourceFolder.Text = dialog.SelectedPath; LogMessage($"📂 변환할 폴더 선택: {dialog.SelectedPath}"); // 선택한 폴더의 DWG 파일 개수 확인 CheckDwgFiles(dialog.SelectedPath); } } } private void BtnBrowseResult_Click(object sender, RoutedEventArgs e) { using (var dialog = new System.Windows.Forms.FolderBrowserDialog()) { dialog.Description = "결과 파일을 저장할 폴더를 선택하세요"; dialog.ShowNewFolderButton = true; if (!string.IsNullOrEmpty(txtResultFolder.Text) && Directory.Exists(txtResultFolder.Text)) { dialog.SelectedPath = txtResultFolder.Text; } if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtResultFolder.Text = dialog.SelectedPath; LogMessage($"💾 결과 저장 폴더 선택: {dialog.SelectedPath}"); } } } private void CheckDwgFiles(string folderPath) { try { var targetDir = new DirectoryInfo(folderPath); var dwgFiles = targetDir.GetFiles("*.dwg", SearchOption.AllDirectories); var pdfFiles = targetDir.GetFiles("*.pdf", SearchOption.AllDirectories); txtFileCount.Text = $"DWG: {dwgFiles.Length}개, PDF: {pdfFiles.Length}개"; if (dwgFiles.Length > 0) { LogMessage($"✅ 총 {dwgFiles.Length}개의 DWG 파일을 발견했습니다."); // 처음 몇 개 파일명 로깅 int showCount = Math.Min(3, dwgFiles.Length); for (int i = 0; i < showCount; i++) { LogMessage($" 📄 {dwgFiles[i].Name}"); } if (dwgFiles.Length > 3) { LogMessage($" ... 외 {dwgFiles.Length - 3}개 DWG 파일"); } } else { LogMessage("⚠️ 선택한 폴더에 DWG 파일이 없습니다."); } if (pdfFiles.Length > 0) { LogMessage($"✅ 총 {pdfFiles.Length}개의 PDF 파일을 발견했습니다."); // 처음 몇 개 파일명 로깅 int showCount = Math.Min(3, pdfFiles.Length); for (int i = 0; i < showCount; i++) { LogMessage($" 📄 {pdfFiles[i].Name}"); } if (pdfFiles.Length > 3) { LogMessage($" ... 외 {pdfFiles.Length - 3}개 PDF 파일"); } } else { LogMessage("⚠️ 선택한 폴더에 PDF 파일이 없습니다."); } } catch (Exception ex) { LogMessage($"❌ 폴더 검사 중 오류: {ex.Message}"); } } private async void BtnExtract_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); // 입력 유효성 검사 if (string.IsNullOrEmpty(txtSourceFolder.Text) || !Directory.Exists(txtSourceFolder.Text)) { ShowMessageBox("유효한 변환할 폴더를 선택하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrEmpty(txtResultFolder.Text) || !Directory.Exists(txtResultFolder.Text)) { ShowMessageBox("유효한 결과 저장 폴더를 선택하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 데이터베이스 모드일 때 연결 테스트 if (rbDatabase.IsChecked == true) { LogMessage("🔍 데이터베이스 연결을 확인합니다..."); try { using (var testSql = new SqlDatas()) { if (!testSql.TestConnection()) { ShowMessageBox( "PostgreSQL 데이터베이스에 연결할 수 없습니다.\n\n" + "연결 설정을 확인하고 데이터베이스 서버가 실행 중인지 확인하세요.", "데이터베이스 연결 오류", MessageBoxButton.OK, MessageBoxImage.Error); LogMessage("❌ 데이터베이스 연결 실패"); return; } LogMessage("✅ 데이터베이스 연결 성공"); } } catch (Exception ex) { ShowMessageBox( $"데이터베이스 연결 테스트 중 오류가 발생했습니다:\n\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); LogMessage($"❌ 데이터베이스 연결 오류: {ex.Message}"); return; } } // UI 비활성화 btnExtract.IsEnabled = false; btnPdfExtract.IsEnabled = false; btnMerge.IsEnabled = false; btnAuto.IsEnabled = false; btnBrowseSource.IsEnabled = false; btnBrowseResult.IsEnabled = false; rbExcel.IsEnabled = false; rbDatabase.IsEnabled = false; progressBar.Value = 0; UpdateStatus("🚀 추출 작업을 시작합니다..."); LogMessage(new string('=', 50)); // ✅ 정상 작동 LogMessage("🔥 DWG 정보 추출 작업 시작"); LogMessage(new string('=', 50)); // ✅ 정상 작동 try { await ProcessFiles(); } catch (Exception ex) { LogMessage($"❌ 치명적 오류 발생: {ex.Message}"); UpdateStatus("오류가 발생했습니다."); ShowMessageBox($"작업 중 오류가 발생했습니다:\n\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { // UI 활성화 btnExtract.IsEnabled = true; btnPdfExtract.IsEnabled = true; btnMerge.IsEnabled = true; btnAuto.IsEnabled = true; btnBrowseSource.IsEnabled = true; btnBrowseResult.IsEnabled = true; rbExcel.IsChecked = true; rbDatabase.IsEnabled = true; progressBar.Value = 100; LogMessage(new string('=', 50)); LogMessage("🏁 작업 완료"); LogMessage(new string('=', 50)); // ✅ 정상 작동 // 자동 처리 모드 비활성화 isAutoProcessing = false; } } /// /// MessageBox 표시 (자동 모드에서는 로그만 출력) /// private void ShowMessageBox(string message, string title, MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage image = MessageBoxImage.Information) { if (isAutoProcessing) { LogMessage($"📢 {title}: {message}"); } else { System.Windows.MessageBox.Show(this, message, title, button, image); } } /// /// 확인 대화상자 표시 (자동 모드에서는 자동으로 Yes 반환) /// private MessageBoxResult ShowConfirmationDialog(string message, string title) { if (isAutoProcessing) { LogMessage($"📢 {title}: {message} - 자동 승인됨"); return MessageBoxResult.Yes; } else { return System.Windows.MessageBox.Show(this, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); } } /// /// DWG 파일 처리 - 자동 모드용 (폴더 경로 직접 지정) /// /// 처리할 폴더 경로 private async Task ProcessFiles(string sourceFolderPath) { var stopwatch = Stopwatch.StartNew(); var targetDir = new DirectoryInfo(sourceFolderPath); var files = targetDir.GetFiles("*.dwg", SearchOption.AllDirectories); if (files.Length == 0) { UpdateStatus("선택한 폴더에 DWG 파일이 없습니다."); ShowMessageBox("선택한 폴더에 DWG 파일이 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } // 결과 저장 폴더는 UI에서 가져옴 (변경하지 않음) string resultDir = txtResultFolder.Text; LogMessage($"📊 처리할 파일 수: {files.Length}개"); LogMessage($"📋 출력 모드: {(rbExcel.IsChecked == true ? "Excel" : "Database")}"); LogMessage($"📂 소스 폴더: {sourceFolderPath}"); LogMessage($"💾 결과 폴더: {resultDir}"); try { if (rbExcel.IsChecked == true) { LogMessage("📊 Excel 내보내기 모드로 시작합니다..."); ExportExcel? _exportExcel = null; var successCount = 0; var failureCount = 0; var totalProcessingTime = 0.0; try { _exportExcel = new ExportExcel(); LogMessage("📝 Excel 애플리케이션 초기화 중..."); LogMessage("✅ Excel 애플리케이션 초기화 완료"); for (int i = 0; i < files.Length; i++) { var file = files[i]; var fileStopwatch = Stopwatch.StartNew(); UpdateStatus($"처리 중: {file.Name} ({i + 1}/{files.Length})"); LogMessage($"🔄 [{i + 1}/{files.Length}] 처리 중: {file.Name}"); try { // 실제 DWG 처리 로직 var progress = new Progress(value => { // 파일별 진행률을 전체 진행률에 반영 var overallProgress = ((i * 100.0 + value) / files.Length); progressBar.Value = overallProgress; }); bool success = _exportExcel.ExportDwgToExcel(file.FullName, progress); fileStopwatch.Stop(); if (success) { successCount++; totalProcessingTime += fileStopwatch.ElapsedMilliseconds; LogMessage($"✅ {file.Name} 처리 완료 ({fileStopwatch.ElapsedMilliseconds}ms)"); } else { failureCount++; LogMessage($"❌ {file.Name} 처리 실패 ({fileStopwatch.ElapsedMilliseconds}ms)"); } } catch (Exception ex) { failureCount++; fileStopwatch.Stop(); LogMessage($"💥 {file.Name} 처리 중 예외 발생: {ex.Message}"); } // 진행률 업데이트 progressBar.Value = ((i + 1) * 100.0) / files.Length; // UI 응답성을 위한 짧은 지연 await Task.Delay(10); } // Excel 파일 및 매핑 데이터 저장 var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var excelFileName = Path.Combine(resultDir, $"{timestamp}_DwgToExcel.xlsx"); var mappingDataFile = Path.Combine(resultDir, $"{timestamp}_mapping_data.json"); LogMessage("💾 Excel 파일과 매핑 데이터를 저장합니다..."); _exportExcel?.SaveMappingDictionary(mappingDataFile); LogMessage($"✅ 매핑 데이터 저장 완료: {Path.GetFileName(mappingDataFile)}"); // Excel 파일 저장 _exportExcel?.SaveAndCloseExcel(excelFileName); LogMessage($"✅ Excel 파일 저장 완료: {Path.GetFileName(excelFileName)}"); var elapsed = stopwatch.Elapsed; LogMessage($"✅ Excel 내보내기 완료! 소요시간: {elapsed.TotalSeconds:F1}초"); UpdateStatus($"✅ {files.Length}개 파일 처리 완료"); LogMessage($"📈 성공: {successCount}개, 실패: {failureCount}개"); LogMessage($"⏱️ 평균 처리 시간: {(successCount > 0 ? totalProcessingTime / successCount : 0):F1}ms"); if (!isAutoProcessing) { ShowMessageBox($"Excel 내보내기가 완료되었습니다!\n\n" + $"✅ 성공: {successCount}개\n" + $"❌ 실패: {failureCount}개\n" + $"⏱️ 총 소요시간: {elapsed:mm\\:ss}\n\n" + $"📄 결과 파일이 저장 폴더에 생성되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { LogMessage($"❌ Excel 처리 중 치명적 오류: {ex.Message}"); UpdateStatus("❌ Excel 처리 실패"); if (!isAutoProcessing) { ShowMessageBox("Excel 처리 중 오류가 발생했습니다. 로그를 확인해주세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } throw; } finally { _exportExcel?.Dispose(); _exportExcel = null; } } else if (rbDatabase.IsChecked == true) { LogMessage("🗄️ 데이터베이스 모드로 시작합니다..."); LogMessage("🔗 데이터베이스 연결을 초기화합니다..."); SqlDatas? _sqlDatas = null; var successCount = 0; var failureCount = 0; var totalProcessingTime = 0.0; try { _sqlDatas = new SqlDatas(); LogMessage("✅ 데이터베이스 연결 초기화 완료"); for (int i = 0; i < files.Length; i++) { var file = files[i]; var fileStopwatch = Stopwatch.StartNew(); UpdateStatus($"처리 중: {file.Name} ({i + 1}/{files.Length})"); LogMessage($"🔄 [{i + 1}/{files.Length}] 처리 중: {file.Name}"); try { // 실제 DWG 처리 로직 (DwgToDB는 실패시 true 반환) bool failed = _sqlDatas?.DwgToDB(file.FullName) ?? true; bool success = !failed; fileStopwatch.Stop(); if (success) { successCount++; totalProcessingTime += fileStopwatch.ElapsedMilliseconds; LogMessage($"✅ {file.Name} DB 저장 완료 ({fileStopwatch.ElapsedMilliseconds}ms)"); } else { failureCount++; LogMessage($"❌ {file.Name} DB 저장 실패 ({fileStopwatch.ElapsedMilliseconds}ms)"); } } catch (Exception ex) { failureCount++; fileStopwatch.Stop(); LogMessage($"💥 {file.Name} 처리 중 예외 발생: {ex.Message}"); } // 진행률 업데이트 progressBar.Value = ((i + 1) * 100.0) / files.Length; // UI 응답성을 위한 짧은 지연 await Task.Delay(10); } var elapsed = stopwatch.Elapsed; LogMessage($"✅ 데이터베이스 저장 완료! 소요시간: {elapsed.TotalSeconds:F1}초"); UpdateStatus($"✅ {files.Length}개 파일 처리 완료"); LogMessage($"📈 성공: {successCount}개, 실패: {failureCount}개"); LogMessage($"⏱️ 평균 처리 시간: {(successCount > 0 ? totalProcessingTime / successCount : 0):F1}ms"); if (!isAutoProcessing) { ShowMessageBox($"데이터베이스 저장이 완료되었습니다!\n\n" + $"✅ 성공: {successCount}개\n" + $"❌ 실패: {failureCount}개\n" + $"⏱️ 총 소요시간: {elapsed:mm\\:ss}", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { LogMessage($"❌ 데이터베이스 처리 중 치명적 오류: {ex.Message}"); UpdateStatus("❌ 데이터베이스 처리 실패"); if (!isAutoProcessing) { ShowMessageBox("데이터베이스 처리 중 오류가 발생했습니다. 로그를 확인해주세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } throw; } finally { _sqlDatas?.Dispose(); _sqlDatas = null; } } } catch (Exception ex) { LogMessage($"❌ 처리 중 오류 발생: {ex.Message}"); UpdateStatus("❌ 처리 실패"); throw; } } /// /// DWG 파일 처리 - 기존 메서드 /// private async Task ProcessFiles() { var stopwatch = Stopwatch.StartNew(); var targetDir = new DirectoryInfo(txtSourceFolder.Text); var files = targetDir.GetFiles("*.dwg", SearchOption.AllDirectories); if (files.Length == 0) { UpdateStatus("선택한 폴더에 DWG 파일이 없습니다."); ShowMessageBox("선택한 폴더에 DWG 파일이 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } UpdateStatus($"총 {files.Length}개의 DWG 파일을 처리합니다..."); LogMessage($"📊 처리할 파일 수: {files.Length}개"); bool isExcelMode = rbExcel.IsChecked == true; string outputMode = isExcelMode ? "Excel" : "PostgreSQL 데이터베이스"; LogMessage($"📋 출력 모드: {outputMode}"); LogMessage($"📂 소스 폴더: {txtSourceFolder.Text}"); LogMessage($"💾 결과 폴더: {txtResultFolder.Text}"); if (isExcelMode) { await ProcessExcelExport(files, stopwatch); } else { await ProcessDatabaseExport(files, stopwatch); } } private async Task ProcessExcelExport(FileInfo[] files, Stopwatch totalStopwatch) { LogMessage("📊 Excel 내보내기 모드로 시작합니다..."); LogMessage("📝 Excel 애플리케이션을 초기화합니다..."); var successCount = 0; var failureCount = 0; var totalProcessingTime = 0.0; try { // ExportExcel 클래스 초기화 _exportExcel = new ExportExcel(); LogMessage("✅ Excel 애플리케이션 초기화 완료"); for (int i = 0; i < files.Length; i++) { var file = files[i]; var fileStopwatch = Stopwatch.StartNew(); UpdateStatus($"처리 중: {file.Name} ({i + 1}/{files.Length})"); LogMessage($"🔄 [{i + 1}/{files.Length}] 처리 중: {file.Name}"); try { // 실제 DWG 처리 로직 var progress = new Progress(value => { // 파일별 진행률을 전체 진행률에 반영 var overallProgress = ((i * 100.0 + value) / files.Length); progressBar.Value = overallProgress; }); bool success = _exportExcel.ExportDwgToExcel(file.FullName, progress); fileStopwatch.Stop(); if (success) { successCount++; totalProcessingTime += fileStopwatch.ElapsedMilliseconds; LogMessage($"✅ {file.Name} 처리 완료 ({fileStopwatch.ElapsedMilliseconds}ms)"); } else { failureCount++; LogMessage($"❌ {file.Name} 처리 실패 ({fileStopwatch.ElapsedMilliseconds}ms)"); } } catch (Exception ex) { failureCount++; fileStopwatch.Stop(); LogMessage($"💥 {file.Name} 처리 중 예외 발생: {ex.Message}"); } // 진행률 업데이트 progressBar.Value = ((i + 1) * 100.0) / files.Length; // UI 응답성을 위한 짧은 지연 await Task.Delay(10); } // Excel 파일 및 매핑 데이터 저장 var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var excelFileName = Path.Combine(txtResultFolder.Text, $"{timestamp}_DwgToExcel.xlsx"); var mappingDataFile = Path.Combine(txtResultFolder.Text, $"{timestamp}_mapping_data.json"); LogMessage("💾 Excel 파일과 매핑 데이터를 저장합니다..."); // 매핑 딕셔너리를 JSON 파일로 저장 (PDF 데이터 병합용) _exportExcel.SaveMappingDictionary(mappingDataFile); LogMessage($"✅ 매핑 데이터 저장 완료: {Path.GetFileName(mappingDataFile)}"); // Excel 파일 저장 _exportExcel.SaveAndCloseExcel(excelFileName); LogMessage($"✅ Excel 파일 저장 완료: {Path.GetFileName(excelFileName)}"); LogMessage($"✅ Excel 내보내기 완료!"); UpdateStatus($"✅ {files.Length}개 파일 처리 완료"); LogMessage($"📈 성공: {successCount}개, 실패: {failureCount}개"); LogMessage($"⏱️ 평균 처리 시간: {(successCount > 0 ? totalProcessingTime / successCount : 0):F1}ms"); if (!isAutoProcessing) { ShowMessageBox($"Excel 내보내기가 완료되었습니다!\n\n" + $"✅ 성공: {successCount}개\n" + $"❌ 실패: {failureCount}개\n" + $"✅ Excel 내보내기 완료!" + $"📄 결과 파일이 저장 폴더에 생성되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { LogMessage($"❌ Excel 처리 중 치명적 오류: {ex.Message}"); throw; } finally { _exportExcel?.Dispose(); _exportExcel = null; } totalStopwatch.Stop(); UpdateStatus("Excel 내보내기 완료!"); var excelFile = Path.Combine(txtResultFolder.Text, $"{DateTime.Now:yyyyMMdd_HHmmss}_DwgToExcel.xlsx"); LogMessage($"📈 성공: {successCount}개, 실패: {failureCount}개"); LogMessage($"⏱️ 평균 처리 시간: {(successCount > 0 ? totalProcessingTime / successCount : 0):F1}ms"); LogMessage($"🕐 총 소요 시간: {totalStopwatch.ElapsedMilliseconds}ms"); ShowMessageBox( $"Excel 내보내기가 완료되었습니다!\n\n" + $"✅ 성공: {successCount}개\n" + $"❌ 실패: {failureCount}개\n" + $"⏱️ 총 소요시간: {totalStopwatch.Elapsed:mm\\:ss}\n\n" + $"📄 결과 파일이 저장 폴더에 생성되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } private async Task ProcessDatabaseExport(FileInfo[] files, Stopwatch totalStopwatch) { LogMessage("🗄️ 데이터베이스 내보내기 모드로 시작합니다..."); LogMessage("🔗 데이터베이스 연결을 초기화합니다..."); var successCount = 0; var failureCount = 0; var totalProcessingTime = 0.0; try { // SqlDatas 클래스 초기화 _sqlDatas = new SqlDatas(); LogMessage("✅ 데이터베이스 연결 초기화 완료"); for (int i = 0; i < files.Length; i++) { var file = files[i]; var fileStopwatch = Stopwatch.StartNew(); UpdateStatus($"처리 중: {file.Name} ({i + 1}/{files.Length})"); LogMessage($"🔄 [{i + 1}/{files.Length}] 처리 중: {file.Name}"); try { // 실제 DWG 처리 로직 (DwgToDB는 실패시 true 반환) bool failed = _sqlDatas.DwgToDB(file.FullName); bool success = !failed; fileStopwatch.Stop(); if (success) { successCount++; totalProcessingTime += fileStopwatch.ElapsedMilliseconds; LogMessage($"✅ {file.Name} DB 저장 완료 ({fileStopwatch.ElapsedMilliseconds}ms)"); } else { failureCount++; LogMessage($"❌ {file.Name} DB 저장 실패 ({fileStopwatch.ElapsedMilliseconds}ms)"); } } catch (Exception ex) { failureCount++; fileStopwatch.Stop(); LogMessage($"💥 {file.Name} 처리 중 예외 발생: {ex.Message}"); } // 진행률 업데이트 progressBar.Value = ((i + 1) * 100.0) / files.Length; // UI 응답성을 위한 짧은 지연 await Task.Delay(10); } } catch (Exception ex) { LogMessage($"❌ 데이터베이스 처리 중 치명적 오류: {ex.Message}"); throw; } finally { _sqlDatas?.Dispose(); _sqlDatas = null; } totalStopwatch.Stop(); UpdateStatus("데이터베이스 내보내기 완료!"); LogMessage($"🗄️ PostgreSQL 데이터베이스에 저장 완료"); LogMessage($"📈 성공: {successCount}개, 실패: {failureCount}개"); LogMessage($"⏱️ 평균 처리 시간: {(successCount > 0 ? totalProcessingTime / successCount : 0):F1}ms"); LogMessage($"🕐 총 소요 시간: {totalStopwatch.ElapsedMilliseconds}ms"); ShowMessageBox( $"데이터베이스 내보내기가 완료되었습니다!\n\n" + $"✅ 성공: {successCount}개\n" + $"❌ 실패: {failureCount}개\n" + $"⏱️ 총 소요시간: {totalStopwatch.Elapsed:mm\\:ss}\n\n" + $"🗄️ PostgreSQL 데이터베이스에 저장됨", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } private async void BtnPdfExtract_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); // 입력 유효성 검사 if (string.IsNullOrEmpty(txtSourceFolder.Text) || !Directory.Exists(txtSourceFolder.Text)) { ShowMessageBox("유효한 변환할 폴더를 선택하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrEmpty(txtResultFolder.Text) || !Directory.Exists(txtResultFolder.Text)) { ShowMessageBox("유효한 결과 저장 폴더를 선택하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // PDF 파일 존재 여부 확인 var targetDir = new DirectoryInfo(txtSourceFolder.Text); var pdfFiles = targetDir.GetFiles("*.pdf", SearchOption.AllDirectories); if (pdfFiles.Length == 0) { ShowMessageBox("선택한 폴더에 PDF 파일이 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } // UI 비활성화 btnExtract.IsEnabled = false; btnPdfExtract.IsEnabled = false; btnMerge.IsEnabled = false; btnAuto.IsEnabled = false; btnBrowseSource.IsEnabled = false; btnBrowseResult.IsEnabled = false; rbExcel.IsEnabled = false; rbDatabase.IsEnabled = false; progressBar.Value = 0; UpdateStatus("📄 PDF 추출 작업을 시작합니다..."); LogMessage(new string('=', 50)); LogMessage("📄 PDF 정보 추출 작업 시작"); LogMessage(new string('=', 50)); try { await ProcessPdfFiles(pdfFiles); } catch (Exception ex) { LogMessage($"❌ PDF 추출 중 치명적 오류 발생: {ex.Message}"); UpdateStatus("PDF 추출 중 오류가 발생했습니다."); ShowMessageBox($"PDF 추출 중 오류가 발생했습니다:\n\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { // UI 활성화 btnExtract.IsEnabled = true; btnPdfExtract.IsEnabled = true; btnMerge.IsEnabled = true; btnAuto.IsEnabled = true; btnBrowseSource.IsEnabled = true; btnBrowseResult.IsEnabled = true; rbExcel.IsEnabled = true; rbDatabase.IsEnabled = true; progressBar.Value = 100; LogMessage(new string('=', 50)); LogMessage("🏁 PDF 추출 작업 완료"); LogMessage(new string('=', 50)); } } private async Task ProcessPdfFiles(FileInfo[] pdfFiles) { var stopwatch = Stopwatch.StartNew(); UpdateStatus($"총 {pdfFiles.Length}개의 PDF 파일을 처리합니다..."); LogMessage($"📊 처리할 PDF 파일 수: {pdfFiles.Length}개"); LogMessage($"📂 소스 폴더: {txtSourceFolder.Text}"); LogMessage($"💾 결과 폴더: {txtResultFolder.Text}"); // 임시 파일 리스트 생성 (긴 명령줄 방지) string tempFileList = Path.Combine(Path.GetTempPath(), $"pdf_files_{Guid.NewGuid():N}.txt"); try { // PDF 파일 경로를 임시 파일에 저장 await File.WriteAllLinesAsync(tempFileList, pdfFiles.Select(f => f.FullName)); LogMessage($"📄 임시 파일 리스트 생성됨: {pdfFiles.Length}개 파일"); // Python 스크립트 실행 설정 - 가상환경의 Python 사용 string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; string fletAnalysisPath = Path.Combine(baseDirectory, "fletimageanalysis"); string pythonPath = Path.Combine(fletAnalysisPath, "venv", "Scripts", "python.exe"); string scriptPath = Path.Combine(fletAnalysisPath, "batch_cli.py"); string schema = "한국도로공사"; // Valid schema option int concurrent = 3; bool batchMode = true; bool saveIntermediate = false; bool includeErrors = true; string outputPath = Path.Combine(txtResultFolder.Text, $"{DateTime.Now:yyyyMMdd_HHmmss}_PdfExtraction.csv"); // 파일 리스트 방식으로 변경 (긴 명령줄 방지) string arguments = $"--file-list \"{tempFileList}\" --schema \"{schema}\" --concurrent {concurrent} " + $"--batch-mode {batchMode.ToString().ToLower()} " + $"--save-intermediate {saveIntermediate.ToString().ToLower()} " + $"--include-errors {includeErrors.ToString().ToLower()} " + $"--output \"{outputPath}\""; LogMessage("🐍 Python 스크립트 실행 준비..."); LogMessage($"📁 Python 경로: {pythonPath}"); LogMessage($"📁 스크립트 경로: {scriptPath}"); LogMessage($"📄 파일 리스트: {Path.GetFileName(tempFileList)}"); // 가상환경 Python 확인 if (!File.Exists(pythonPath)) { throw new Exception($"가상환경 Python을 찾을 수 없습니다: {pythonPath}\n\n" + "cleanup_and_setup.bat을 실행하여 가상환경을 설정하세요."); } // 스크립트 파일 존재 확인 if (!File.Exists(scriptPath)) { throw new Exception($"Python 스크립트를 찾을 수 없습니다: {scriptPath}"); } ProcessStartInfo startInfo = new ProcessStartInfo { FileName = pythonPath, // 가상환경의 Python 사용 Arguments = $"\"{scriptPath}\" {arguments}", WorkingDirectory = fletAnalysisPath, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; using (Process process = Process.Start(startInfo)) { if (process == null) { throw new Exception("Python 프로세스를 시작할 수 없습니다."); } LogMessage("🚀 Python 스크립트 실행 시작"); // 비동기로 출력 읽기 var outputTask = ReadProcessOutputAsync(process); // 프로세스 완료 대기 await Task.Run(() => process.WaitForExit()); var (output, errors) = await outputTask; stopwatch.Stop(); if (process.ExitCode == 0) { LogMessage("✅ PDF 추출 완료!"); LogMessage($"📄 결과 파일: {Path.GetFileName(outputPath)}"); LogMessage($"🕐 총 소요 시간: {stopwatch.ElapsedMilliseconds}ms"); UpdateStatus("PDF 추출 완료!"); // 결과 파일 존재 확인 if (File.Exists(outputPath)) { LogMessage($"✅ 결과 파일 생성 확인됨: {outputPath}"); } else { LogMessage($"⚠️ 결과 파일이 예상 위치에 없습니다: {outputPath}"); } // 자동 Excel 매핑 업데이트 (새로운 효율적 방식) await AutoUpdateExcelMappingWithPdfResults(outputPath); ShowMessageBox( $"PDF 추출이 완료되었습니다!\n\n" + $"📄 처리된 파일: {pdfFiles.Length}개\n" + $"⏱️ 총 소요시간: {stopwatch.Elapsed:mm\\:ss}\n\n" + $"📊 결과 파일: {Path.GetFileName(outputPath)}", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } else { LogMessage($"❌ PDF 추출 실패 (Exit Code: {process.ExitCode})"); if (!string.IsNullOrEmpty(errors)) { LogMessage($"❌ 오류 내용: {errors}"); } throw new Exception($"Python 스크립트 실행 실패 (Exit Code: {process.ExitCode})\n\n{errors}"); } } } finally { // 임시 파일 정리 try { if (File.Exists(tempFileList)) { File.Delete(tempFileList); LogMessage("🗑️ 임시 파일 리스트 정리 완료"); } } catch (Exception ex) { LogMessage($"⚠️ 임시 파일 정리 중 오류: {ex.Message}"); } } } private async Task<(string output, string errors)> ReadProcessOutputAsync(Process process) { var outputBuilder = new System.Text.StringBuilder(); var errorBuilder = new System.Text.StringBuilder(); var outputTask = Task.Run(async () => { string line; while ((line = await process.StandardOutput.ReadLineAsync()) != null) { outputBuilder.AppendLine(line); // 진행 상태 업데이트 처리 if (line.StartsWith("PROGRESS:")) { try { var progressPart = line.Substring("PROGRESS:".Length).Trim(); var parts = progressPart.Split('/'); if (parts.Length == 2 && int.TryParse(parts[0], out int current) && int.TryParse(parts[1], out int total)) { var percentage = (current * 100.0) / total; await Dispatcher.InvokeAsync(() => { progressBar.Value = percentage; UpdateStatus($"PDF 처리 중: {current}/{total} ({percentage:F1}%)"); }); } } catch { // 파싱 오류 무시 } } else if (line.StartsWith("START:")) { var message = line.Substring("START:".Length).Trim(); await Dispatcher.InvokeAsync(() => LogMessage($"🔄 {message}")); } else if (line.StartsWith("COMPLETED:")) { var message = line.Substring("COMPLETED:".Length).Trim(); await Dispatcher.InvokeAsync(() => LogMessage($"✅ {message}")); } else if (line.StartsWith("ERROR:")) { var errorInfo = line.Substring("ERROR:".Length).Trim(); await Dispatcher.InvokeAsync(() => LogMessage($"❌ 오류: {errorInfo}")); } } }); var errorTask = Task.Run(async () => { string line; while ((line = await process.StandardError.ReadLineAsync()) != null) { errorBuilder.AppendLine(line); await Dispatcher.InvokeAsync(() => LogMessage($"⚠️ {line}")); } }); await Task.WhenAll(outputTask, errorTask); return (outputBuilder.ToString(), errorBuilder.ToString()); } private void UpdateStatus(string message) { txtStatus.Text = message; txtStatusBar.Text = message; } private void LogMessage(string message) { var timestamp = DateTime.Now.ToString("HH:mm:ss"); txtLog.AppendText($"[{timestamp}] {message}\n"); txtLog.ScrollToEnd(); } /// /// PDF 추출 결과를 사용하여 Excel 매핑을 효율적으로 업데이트합니다 (새로운 방식). /// /// CSV 결과 파일 경로 private async Task AutoUpdateExcelMappingWithPdfResults(string csvFilePath) { try { LogMessage("🔄 Excel 매핑 자동 업데이트 시작 (효율적 방식)..."); // JSON 파일 경로 구성 string jsonFilePath = csvFilePath.Replace(".csv", ".json"); if (!File.Exists(jsonFilePath)) { LogMessage($"⚠️ JSON 파일이 없습니다: {Path.GetFileName(jsonFilePath)}"); return; } LogMessage($"📄 JSON 파일 확인됨: {Path.GetFileName(jsonFilePath)}"); // 최신 매핑 데이터 파일 찾기 string? resultDir = Path.GetDirectoryName(csvFilePath); if (string.IsNullOrEmpty(resultDir)) { LogMessage("⚠️ 결과 디렉터리를 찾을 수 없습니다."); return; } var mappingDataFiles = Directory.GetFiles(resultDir, "*_mapping_data.json", SearchOption.TopDirectoryOnly) .OrderByDescending(f => File.GetCreationTime(f)) .ToArray(); if (mappingDataFiles.Length == 0) { LogMessage("⚠️ 매핑 데이터 파일이 없습니다. DWG 파일을 먼저 처리하세요."); LogMessage("💡 'DWG 정보 추출' 버튼을 먼저 실행하여 매핑 데이터를 생성하세요."); return; } string latestMappingDataFile = mappingDataFiles[0]; LogMessage($"📊 최신 매핑 데이터 파일 발견: {Path.GetFileName(latestMappingDataFile)}"); // 새로운 ExportExcel 인스턴스 생성 및 데이터 로드 ExportExcel? exportExcel = null; try { LogMessage("🔄 매핑 데이터 로드 및 PDF 값 업데이트 중..."); exportExcel = new ExportExcel(); // 기존 매핑 딕셔너리 로드 exportExcel.LoadMappingDictionary(latestMappingDataFile); // PDF 데이터로 업데이트 exportExcel.UpdateWithPdfData(jsonFilePath); // 업데이트된 매핑 데이터 저장 (Excel 파일은 생성하지 않음) exportExcel.SaveMappingDictionary(latestMappingDataFile); // Excel 객체 정리 exportExcel.CloseExcelObjectsWithoutSaving(); LogMessage("✅ 매핑 데이터 자동 업데이트 완료"); LogMessage("📊 DWG 값과 PDF 값이 매핑 데이터에 저장되었습니다. '합치기' 버튼으로 Excel 파일을 생성하세요."); } catch (Exception ex) { LogMessage($"❌ Excel 매핑 자동 업데이트 중 오류: {ex.Message}"); Debug.WriteLine($"Excel 매핑 자동 업데이트 오류: {ex}"); } } catch (Exception ex) { LogMessage($"❌ Excel 매핑 자동 업데이트 중 오류: {ex.Message}"); Debug.WriteLine($"Excel 매핑 자동 업데이트 오류: {ex}"); } } /// /// PDF 추출 결과 JSON 파일을 사용하여 Excel 매핑 시트를 업데이트합니다 (기존 방식 - 사용 안함). /// /// CSV 결과 파일 경로 [Obsolete("이 메서드는 비효율적입니다. AutoUpdateExcelMappingWithPdfResults를 사용하세요.")] private async Task UpdateExcelMappingWithPdfResults(string csvFilePath) { try { LogMessage("🔄 Excel 매핑 시트 업데이트 시작..."); // JSON 파일 경로 구성 string jsonFilePath = csvFilePath.Replace(".csv", ".json"); if (!File.Exists(jsonFilePath)) { LogMessage($"⚠️ JSON 파일이 없습니다: {Path.GetFileName(jsonFilePath)}"); return; } LogMessage($"📄 JSON 파일 확인됨: {Path.GetFileName(jsonFilePath)}"); // 기존 Excel 매핑 파일 검색 (임시 파일 제외) string? resultDir = Path.GetDirectoryName(csvFilePath); if (string.IsNullOrEmpty(resultDir)) { LogMessage("⚠️ 결과 디렉터리를 찾을 수 없습니다."); return; } var allExcelFiles = Directory.GetFiles(resultDir, "*_Mapping.xlsx", SearchOption.TopDirectoryOnly); // 임시 파일(~$로 시작하는 파일) 필터링 var excelFiles = allExcelFiles .Where(f => !Path.GetFileName(f).StartsWith("~$")) .ToArray(); ExportExcel? exportExcel = null; string excelFilePath = ""; LogMessage($"🔍 Excel 파일 검색 결과: 전체 {allExcelFiles.Length}개, 유효 {excelFiles.Length}개"); if (excelFiles.Length > 0) { // 기존 매핑 파일이 있는 경우 (가장 최근 파일 선택) excelFilePath = excelFiles.OrderByDescending(f => File.GetCreationTime(f)).First(); LogMessage($"📊 기존 Excel 매핑 파일 발견: {Path.GetFileName(excelFilePath)}"); // 파일이 다른 프로그램에서 열려 있는지 확인 if (IsFileInUse(excelFilePath)) { LogMessage("⚠️ Excel 파일이 다른 프로그램에서 열려 있습니다. 파일을 닫고 다시 시도하세요."); return; } // 기존 파일을 복사하여 새 버전 생성 string newExcelPath = Path.Combine(resultDir, $"{DateTime.Now:yyyyMMdd_HHmmss}_Mapping_Updated.xlsx"); File.Copy(excelFilePath, newExcelPath, true); excelFilePath = newExcelPath; LogMessage($"📋 Excel 파일 복사됨: {Path.GetFileName(newExcelPath)}"); } else { LogMessage("⚠️ 기존 Excel 매핑 파일이 없습니다. DWG 파일을 먼저 처리하세요."); LogMessage("💡 'DWG 정보 추출' 버튼을 먼저 실행하여 매핑 파일을 생성하세요."); return; } // ExportExcel 클래스로 JSON 데이터 업데이트 LogMessage("🔄 PDF 값으로 Excel 업데이트 중..."); exportExcel = new ExportExcel(); // 기존 Excel 파일을 열어 JSON 값으로 업데이트 bool success = exportExcel.UpdateExistingExcelWithJson(excelFilePath, jsonFilePath); if (success) { // 업데이트된 Excel 파일 저장 bool saveSuccess = exportExcel.SaveExcel(); if (saveSuccess) { LogMessage($"✅ Excel 매핑 시트 업데이트 완료: {Path.GetFileName(excelFilePath)}"); LogMessage("📊 PDF 값이 Excel 매핑 시트의 'Pdf_value' 컬럼에 추가되었습니다."); } else { LogMessage("❌ Excel 파일 저장 실패"); } } else { LogMessage("❌ Excel 매핑 시트 업데이트 실패"); } exportExcel?.Dispose(); } catch (Exception ex) { LogMessage($"❌ Excel 매핑 업데이트 중 오류: {ex.Message}"); Debug.WriteLine($"Excel 매핑 업데이트 오류: {ex}"); } } /// /// 파일이 다른 프로세스에서 사용 중인지 확인합니다. /// /// 확인할 파일 경로 /// 사용 중이면 true, 아니면 false private bool IsFileInUse(string filePath) { try { using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.None)) { // 파일을 열 수 있으면 사용 중이 아님 return false; } } catch (IOException) { // 파일을 열 수 없으면 사용 중 return true; } catch (Exception) { // 다른 오류가 발생하면 안전하게 사용 중으로 간주 return true; } } /// /// 가장 최근의 매핑 데이터 파일을 찾습니다. /// /// 검색할 디렉토리 /// 최신 매핑 데이터 파일 경로 또는 null private string? FindLatestMappingDataFile(string resultDir) { try { var mappingDataFiles = Directory.GetFiles(resultDir, "*_mapping_data.json", SearchOption.TopDirectoryOnly) .OrderByDescending(f => File.GetCreationTime(f)) .ToArray(); return mappingDataFiles.Length > 0 ? mappingDataFiles[0] : null; } catch (Exception ex) { LogMessage($"❌ 매핑 데이터 파일 검색 중 오류: {ex.Message}"); return null; } } private async void BtnMerge_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); // 입력 유효성 검사 if (string.IsNullOrEmpty(txtResultFolder.Text) || !Directory.Exists(txtResultFolder.Text)) { ShowMessageBox("유효한 결과 저장 폴더를 선택하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // UI 비활성화 btnExtract.IsEnabled = false; btnPdfExtract.IsEnabled = false; btnMerge.IsEnabled = false; btnAuto.IsEnabled = false; btnBrowseSource.IsEnabled = false; btnBrowseResult.IsEnabled = false; rbExcel.IsEnabled = false; rbDatabase.IsEnabled = false; progressBar.Value = 0; UpdateStatus("🔗 Excel 매핑 병합 작업을 시작합니다..."); LogMessage(new string('=', 50)); LogMessage("🔗 Excel 매핑 병합 작업 시작"); LogMessage(new string('=', 50)); try { await MergeLatestPdfResults(); } catch (Exception ex) { LogMessage($"❌ 매핑 병합 중 치명적 오류 발생: {ex.Message}"); UpdateStatus("매핑 병합 중 오류가 발생했습니다."); ShowMessageBox($"매핑 병합 중 오류가 발생했습니다:\n\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { // UI 활성화 btnExtract.IsEnabled = true; btnPdfExtract.IsEnabled = true; btnMerge.IsEnabled = true; btnAuto.IsEnabled = true; btnBrowseSource.IsEnabled = true; btnBrowseResult.IsEnabled = true; rbExcel.IsEnabled = true; rbDatabase.IsEnabled = true; progressBar.Value = 100; LogMessage(new string('=', 50)); LogMessage("🏁 매핑 병합 작업 완료"); LogMessage(new string('=', 50)); } } /// /// 가장 최근의 PDF 추출 JSON 파일을 찾아 Excel 매핑을 효율적으로 업데이트합니다. /// private async Task MergeLatestPdfResults() { try { LogMessage("🔍 최신 PDF 추출 결과 파일 검색 중..."); string resultDir = txtResultFolder.Text; // 가장 최근의 PDF 추출 JSON 파일 찾기 var jsonFiles = Directory.GetFiles(resultDir, "*_PdfExtraction.json", SearchOption.TopDirectoryOnly) .OrderByDescending(f => File.GetCreationTime(f)) .ToArray(); if (jsonFiles.Length == 0) { LogMessage("❌ PDF 추출 JSON 파일을 찾을 수 없습니다."); LogMessage("💡 먼저 'PDF 추출' 버튼을 실행하여 JSON 파일을 생성하세요."); ShowMessageBox( "PDF 추출 JSON 파일을 찾을 수 없습니다.\n\n" + "먼저 'PDF 추출' 버튼을 실행하여 JSON 파일을 생성하세요.", "파일 없음", MessageBoxButton.OK, MessageBoxImage.Information); return; } // 최신 매핑 데이터 파일 찾기 var mappingDataFiles = Directory.GetFiles(resultDir, "*_mapping_data.json", SearchOption.TopDirectoryOnly) .OrderByDescending(f => File.GetCreationTime(f)) .ToArray(); if (mappingDataFiles.Length == 0) { LogMessage("❌ 매핑 데이터 파일을 찾을 수 없습니다."); LogMessage("💡 먼저 'DWG 정보 추출' 버튼을 실행하여 매핑 데이터를 생성하세요."); ShowMessageBox( "매핑 데이터 파일을 찾을 수 없습니다.\n\n" + "먼저 'DWG 정보 추출' 버튼을 실행하여 매핑 데이터를 생성하세요.", "파일 없음", MessageBoxButton.OK, MessageBoxImage.Information); return; } string latestJsonFile = jsonFiles[0]; string latestMappingDataFile = mappingDataFiles[0]; LogMessage($"✅ 최신 JSON 파일 발견: {Path.GetFileName(latestJsonFile)}"); LogMessage($"📅 생성일시: {File.GetCreationTime(latestJsonFile):yyyy-MM-dd HH:mm:ss}"); LogMessage($"📊 최신 매핑 데이터 파일: {Path.GetFileName(latestMappingDataFile)}"); // JSON 파일 내용 미리보기 await ShowJsonPreview(latestJsonFile); // 사용자 확인 var result = ShowConfirmationDialog( $"다음 파일들로 Excel 매핑을 업데이트하시겠습니까?\n\n" + $"📄 PDF JSON: {Path.GetFileName(latestJsonFile)}\n" + $"📊 매핑 데이터: {Path.GetFileName(latestMappingDataFile)}\n" + $"📅 생성: {File.GetCreationTime(latestJsonFile):yyyy-MM-dd HH:mm:ss}\n" + $"📏 크기: {new FileInfo(latestJsonFile).Length / 1024:N0} KB", "매핑 업데이트 확인"); if (result != MessageBoxResult.Yes) { LogMessage("❌ 사용자에 의해 매핑 업데이트가 취소되었습니다."); UpdateStatus("매핑 업데이트가 취소되었습니다."); return; } // 효율적인 Excel 매핑 업데이트 실행 UpdateStatus("🔄 Excel 매핑 업데이트 중 (효율적 방식)..."); progressBar.Value = 25; ExportExcel? exportExcel = null; try { LogMessage("🔄 매핑 데이터 로드 및 PDF 값 업데이트 중..."); exportExcel = new ExportExcel(); progressBar.Value = 40; // 기존 매핑 딕셔너리 로드 exportExcel.LoadMappingDictionary(latestMappingDataFile); progressBar.Value = 60; // PDF 데이터로 업데이트 exportExcel.UpdateWithPdfData(latestJsonFile); progressBar.Value = 80; // 완전한 Excel 파일 생성 (DWG + PDF 데이터) - 합치기 전용 var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string completeExcelPath = Path.Combine(resultDir, $"{timestamp}_Complete_Mapping_Merged.xlsx"); LogMessage("📊 통합 Excel 파일 생성 중..."); // ⭐ 중요: 매핑 데이터를 Excel 시트에 기록 exportExcel.WriteCompleteMapping(); // 매핑 워크북만 저장 (완전한 매핑 데이터용) exportExcel.SaveMappingWorkbookOnly(completeExcelPath); // 업데이트된 매핑 데이터 저장 exportExcel.SaveMappingDictionary(latestMappingDataFile); // Excel 객체 정리 exportExcel.CloseExcelObjectsWithoutSaving(); progressBar.Value = 100; UpdateStatus("✅ 매핑 병합 완료!"); LogMessage($"✅ Excel 매핑 병합 완료: {Path.GetFileName(completeExcelPath)}"); LogMessage("📊 DWG 값과 PDF 값이 모두 포함된 통합 Excel 파일이 생성되었습니다."); ShowMessageBox( $"Excel 매핑 병합이 완료되었습니다!\n\n" + $"📄 사용된 JSON: {Path.GetFileName(latestJsonFile)}\n" + $"📊 생성된 파일: {Path.GetFileName(completeExcelPath)}\n" + $"✅ DWG 값과 PDF 값이 모두 포함된 통합 Excel 파일", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { LogMessage($"❌ 매핑 병합 중 오류: {ex.Message}"); throw; } } catch (Exception ex) { LogMessage($"❌ 매핑 병합 중 오류: {ex.Message}"); throw; } } /// /// JSON 파일의 내용을 미리보기로 보여줍니다. /// /// JSON 파일 경로 private async Task ShowJsonPreview(string jsonFilePath) { try { LogMessage("📋 JSON 파일 내용 미리보기:"); string jsonContent = await File.ReadAllTextAsync(jsonFilePath); using var jsonDocument = JsonDocument.Parse(jsonContent); var root = jsonDocument.RootElement; // 메타데이터 정보 표시 if (root.TryGetProperty("metadata", out var metadata)) { if (metadata.TryGetProperty("total_files", out var totalFiles)) { LogMessage($" 📊 총 파일 수: {totalFiles}"); } if (metadata.TryGetProperty("success_files", out var successFiles)) { LogMessage($" ✅ 성공 파일: {successFiles}"); } if (metadata.TryGetProperty("failed_files", out var failedFiles)) { LogMessage($" ❌ 실패 파일: {failedFiles}"); } if (metadata.TryGetProperty("generated_at", out var generatedAt)) { LogMessage($" 📅 생성시간: {generatedAt}"); } } // 결과 파일 수 표시 if (root.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array) { var resultsArray = results.EnumerateArray().ToArray(); LogMessage($" 📄 분석 결과: {resultsArray.Length}개 파일"); // 처음 몇 개 파일 이름 표시 int showCount = Math.Min(3, resultsArray.Length); for (int i = 0; i < showCount; i++) { if (resultsArray[i].TryGetProperty("file_info", out var fileInfo) && fileInfo.TryGetProperty("name", out var fileName)) { LogMessage($" - {fileName.GetString()}"); } } if (resultsArray.Length > 3) { LogMessage($" ... 외 {resultsArray.Length - 3}개 파일"); } } LogMessage("📋 JSON 미리보기 완료"); } catch (Exception ex) { LogMessage($"⚠️ JSON 미리보기 중 오류: {ex.Message}"); } } /// /// 자동 처리 버튼 클릭 이벤트 /// private async void BtnAuto_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); try { // 입력 검증 if (string.IsNullOrEmpty(txtSourceFolder.Text)) { ShowMessageBox("소스 폴더를 선택해주세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrEmpty(txtResultFolder.Text)) { ShowMessageBox("결과 저장 폴더를 선택해주세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } string rootFolder = txtSourceFolder.Text; string resultFolder = txtResultFolder.Text; if (!Directory.Exists(rootFolder)) { ShowMessageBox("소스 폴더가 존재하지 않습니다.", "오류", MessageBoxButton.OK, MessageBoxImage.Error); return; } if (!Directory.Exists(resultFolder)) { ShowMessageBox("결과 저장 폴더가 존재하지 않습니다.", "오류", MessageBoxButton.OK, MessageBoxImage.Error); return; } // 리프 폴더들 찾기 LogMessage("🔍 리프 폴더 검색 중..."); var leafFolders = FindLeafFolders(rootFolder); if (leafFolders.Count == 0) { ShowMessageBox("처리할 리프 폴더가 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } LogMessage($"📁 발견된 리프 폴더: {leafFolders.Count}개"); foreach (var folder in leafFolders) { LogMessage($" └─ {folder}"); } // 사용자 확인 var result = ShowConfirmationDialog( $"총 {leafFolders.Count}개의 리프 폴더를 자동 처리하시겠습니까?\n\n" + "각 폴더마다 DWG 추출 → PDF 추출 → 합치기가 순차적으로 실행됩니다.", "자동 처리 확인"); if (result != MessageBoxResult.Yes) { return; } // UI 상태 변경 SetButtonsEnabled(false); progressBar.Value = 0; UpdateStatus("🤖 자동 처리 시작..."); // 자동 처리 모드 활성화 isAutoProcessing = true; await ProcessLeafFoldersAutomatically(leafFolders, resultFolder); ShowMessageBox( $"자동 처리가 완료되었습니다!\n\n" + $"처리된 폴더: {leafFolders.Count}개\n" + $"결과 저장 위치: {resultFolder}", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { LogMessage($"❌ 자동 처리 중 오류: {ex.Message}"); ShowMessageBox($"자동 처리 중 오류가 발생했습니다:\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetButtonsEnabled(true); progressBar.Value = 0; UpdateStatus("✅ 준비"); // 자동 처리 모드 비활성화 isAutoProcessing = false; } } /// /// DWG 전용 폴더별 추출 버튼 클릭 이벤트 /// private async void BtnDwgOnly_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); try { // 경로 검증 string sourceFolder = txtSourceFolder.Text; string resultFolder = txtResultFolder.Text; if (string.IsNullOrWhiteSpace(sourceFolder) || !Directory.Exists(sourceFolder)) { ShowMessageBox("올바른 소스 폴더를 선택해주세요.", "경로 오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrWhiteSpace(resultFolder) || !Directory.Exists(resultFolder)) { ShowMessageBox("올바른 결과 저장 폴더를 선택해주세요.", "경로 오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 리프 폴더 찾기 var leafFolders = FindLeafFolders(sourceFolder); if (leafFolders.Count == 0) { ShowMessageBox("처리할 리프 폴더가 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } LogMessage($"🔍 발견된 리프 폴더: {leafFolders.Count}개"); foreach (var folder in leafFolders) { LogMessage($" - {folder}"); } // 사용자 확인 var result = ShowConfirmationDialog( $"총 {leafFolders.Count}개의 리프 폴더에서 DWG만 추출하시겠습니까?\n\n" + "각 폴더마다 DWG 추출 작업이 실행되고, 폴더별로 Excel 파일이 생성됩니다.", "DWG 전용 추출 확인"); if (result != MessageBoxResult.Yes) { return; } // UI 상태 변경 SetButtonsEnabled(false); progressBar.Value = 0; UpdateStatus("🔧 DWG 전용 폴더별 추출 시작..."); // 자동 처리 모드 활성화 isAutoProcessing = true; await ProcessLeafFoldersDwgOnly(leafFolders, resultFolder); ShowMessageBox( $"DWG 전용 추출이 완료되었습니다!\n\n" + $"처리된 폴더: {leafFolders.Count}개\n" + $"결과 저장 위치: {resultFolder}", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { LogMessage($"❌ DWG 전용 추출 중 오류: {ex.Message}"); ShowMessageBox($"DWG 전용 추출 중 오류가 발생했습니다:\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetButtonsEnabled(true); progressBar.Value = 0; UpdateStatus("✅ 준비"); // 자동 처리 모드 비활성화 isAutoProcessing = false; } } /// /// DWG 추출 (Height 정렬) 버튼 클릭 이벤트 /// private async void BtnDwgHeightSort_Click(object sender, RoutedEventArgs e) { // 설정 저장 SaveSettings(); // 시각화 데이터 초기화 ClearVisualizationData(); LogMessage("🧹 시각화 데이터 초기화 완료"); try { // 경로 검증 string sourceFolder = txtSourceFolder.Text; string resultFolder = txtResultFolder.Text; if (string.IsNullOrWhiteSpace(sourceFolder) || !Directory.Exists(sourceFolder)) { ShowMessageBox("올바른 소스 폴더를 선택해주세요.", "경로 오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrWhiteSpace(resultFolder) || !Directory.Exists(resultFolder)) { ShowMessageBox("올바른 결과 저장 폴더를 선택해주세요.", "경로 오류", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 리프 폴더 찾기 var leafFolders = FindLeafFolders(sourceFolder); if (leafFolders.Count == 0) { ShowMessageBox("처리할 리프 폴더가 없습니다.", "정보", MessageBoxButton.OK, MessageBoxImage.Information); return; } LogMessage($"🔍 발견된 리프 폴더: {leafFolders.Count}개"); foreach (var folder in leafFolders) { LogMessage($" - {folder}"); } // 사용자 확인 var result = ShowConfirmationDialog( $"총 {leafFolders.Count}개의 리프 폴더에서 DWG Height 정렬 추출을 하시겠습니까?\n\n" + "각 폴더마다 DWG 파일별로 시트가 생성되고, 텍스트 높이 순으로 정렬된 Excel 파일이 생성됩니다.", "DWG Height 정렬 추출 확인"); if (result != MessageBoxResult.Yes) { return; } // UI 상태 변경 SetButtonsEnabled(false); progressBar.Value = 0; UpdateStatus("📏 DWG Height 정렬 추출 시작..."); // 자동 처리 모드 활성화 isAutoProcessing = true; await ProcessLeafFoldersDwgHeightSort(leafFolders, resultFolder); ShowMessageBox( $"DWG Height 정렬 추출이 완료되었습니다!\n\n" + $"처리된 폴더: {leafFolders.Count}개\n" + $"결과 저장 위치: {resultFolder}", "완료", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { LogMessage($"❌ DWG Height 정렬 추출 중 오류: {ex.Message}"); ShowMessageBox($"DWG Height 정렬 추출 중 오류가 발생했습니다:\n{ex.Message}", "오류", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetButtonsEnabled(true); progressBar.Value = 0; UpdateStatus("✅ 준비"); // 자동 처리 모드 비활성화 isAutoProcessing = false; } } /// /// 폴더에서 리프 폴더들(하위 폴더가 없는 폴더)을 재귀적으로 찾습니다. /// /// 루트 폴더 경로 /// 리프 폴더 경로 목록 private List FindLeafFolders(string rootPath) { var leafFolders = new List(); try { FindLeafFoldersRecursive(rootPath, leafFolders); } catch (Exception ex) { LogMessage($"❌ 리프 폴더 검색 중 오류: {ex.Message}"); } return leafFolders; } /// /// 재귀적으로 리프 폴더를 찾는 헬퍼 메서드 /// /// 현재 폴더 경로 /// 리프 폴더 목록 private void FindLeafFoldersRecursive(string currentPath, List leafFolders) { try { var subDirectories = Directory.GetDirectories(currentPath); if (subDirectories.Length == 0) { // 하위 폴더가 없으면 리프 폴더 leafFolders.Add(currentPath); } else { // 하위 폴더가 있으면 재귀 호출 foreach (var subDir in subDirectories) { FindLeafFoldersRecursive(subDir, leafFolders); } } } catch (UnauthorizedAccessException) { LogMessage($"⚠️ 접근 권한 없음: {currentPath}"); } catch (Exception ex) { LogMessage($"❌ 폴더 처리 중 오류 ({currentPath}): {ex.Message}"); } } /// /// 리프 폴더들을 자동으로 처리합니다. /// /// 처리할 리프 폴더 목록 /// 결과 저장 기본 폴더 private async Task ProcessLeafFoldersAutomatically(List leafFolders, string resultBaseFolder) { int totalFolders = leafFolders.Count; int currentFolderIndex = 0; foreach (var leafFolder in leafFolders) { currentFolderIndex++; try { LogMessage($"📁 [{currentFolderIndex}/{totalFolders}] 처리 시작: {leafFolder}"); // 폴더 경로를 파일명으로 변환 (경로 구분자를 '_'로 변경) string rootFolderPath = txtSourceFolder.Text.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string relativePath = Path.GetRelativePath(rootFolderPath, leafFolder); string excelFileName = relativePath.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_') + ".xlsx"; string excelFilePath = Path.Combine(resultBaseFolder, excelFileName); // 진행률 업데이트 double baseProgress = ((double)(currentFolderIndex - 1) / totalFolders) * 100; progressBar.Value = baseProgress; // 1. DWG 추출 LogMessage($"🔧 [{currentFolderIndex}/{totalFolders}] DWG 추출 시작..."); UpdateStatus($"🔧 DWG 추출 중... ({currentFolderIndex}/{totalFolders})"); await ProcessFiles(leafFolder); progressBar.Value = baseProgress + (100.0 / totalFolders) * 0.33; LogMessage($"✅ [{currentFolderIndex}/{totalFolders}] DWG 추출 완료"); // 2초 대기 (원래 타이밍 복원 - 싱글톤 Services로 충돌 해결됨) await Task.Delay(2000); // 2. PDF 추출 LogMessage($"📄 [{currentFolderIndex}/{totalFolders}] PDF 추출 시작..."); UpdateStatus($"📄 PDF 추출 중... ({currentFolderIndex}/{totalFolders})"); // 현재 폴더에서 PDF 파일 찾기 var pdfFiles = new DirectoryInfo(leafFolder) .GetFiles("*.pdf", SearchOption.AllDirectories) .ToArray(); if (pdfFiles.Length == 0) { LogMessage($"⚠️ [{currentFolderIndex}/{totalFolders}] PDF 파일이 없음 - PDF 추출 건너뜀"); } else { await ProcessPdfFiles(pdfFiles); } progressBar.Value = baseProgress + (100.0 / totalFolders) * 0.66; LogMessage($"✅ [{currentFolderIndex}/{totalFolders}] PDF 추출 완료"); // 3초 대기 await Task.Delay(3000); // 3. 합치기 및 Excel 파일 이름 변경 LogMessage($"🔗 [{currentFolderIndex}/{totalFolders}] 합치기 시작..."); UpdateStatus($"🔗 합치기 중... ({currentFolderIndex}/{totalFolders})"); await MergeLatestPdfResults(); // 생성된 Excel 파일을 목표 이름으로 변경 await RenameLatestExcelFile(resultBaseFolder, excelFilePath); progressBar.Value = baseProgress + (100.0 / totalFolders); LogMessage($"✅ [{currentFolderIndex}/{totalFolders}] 처리 완료: {excelFileName}"); // 2초 대기 후 다음 폴더 처리 if (currentFolderIndex < totalFolders) { await Task.Delay(2000); } } catch (Exception ex) { LogMessage($"❌ [{currentFolderIndex}/{totalFolders}] 폴더 처리 실패 ({leafFolder}): {ex.Message}"); LogMessage($"⏭️ [{currentFolderIndex}/{totalFolders}] 다음 폴더로 계속 진행"); // 오류 발생 시 잠시 대기 후 다음 폴더 처리 await Task.Delay(2000); continue; } } LogMessage($"🎉 자동 처리 완료! 총 {totalFolders}개 폴더 처리됨"); UpdateStatus("✅ 자동 처리 완료"); } /// /// 리프 폴더들에 대해 DWG 전용 추출을 수행합니다. /// /// 처리할 리프 폴더 목록 /// 결과 파일 저장 기본 폴더 private async Task ProcessLeafFoldersDwgOnly(List leafFolders, string resultBaseFolder) { int totalFolders = leafFolders.Count; int currentFolderIndex = 0; LogMessage($"🔧 DWG 전용 폴더별 추출 시작: {totalFolders}개 폴더"); foreach (string leafFolder in leafFolders) { currentFolderIndex++; progressBar.Value = (double)currentFolderIndex / totalFolders * 100; try { LogMessage($"📁 [{currentFolderIndex}/{totalFolders}] DWG 추출 시작: {leafFolder}"); // 1. DWG 추출만 수행 await ProcessFilesDwgOnly(leafFolder); LogMessage($"✅ [{currentFolderIndex}/{totalFolders}] DWG 추출 완료"); // 2초 대기 await Task.Delay(2000); // 2. DWG 전용 Excel 파일 생성 및 이름 변경 await RenameDwgOnlyExcelFile(resultBaseFolder, leafFolder); LogMessage($"📋 [{currentFolderIndex}/{totalFolders}] Excel 파일 생성 완료"); // 1초 대기 await Task.Delay(1000); } catch (Exception ex) { LogMessage($"❌ [{currentFolderIndex}/{totalFolders}] 폴더 처리 실패 ({leafFolder}): {ex.Message}"); LogMessage($"⏭️ [{currentFolderIndex}/{totalFolders}] 다음 폴더로 계속 진행"); // 오류 발생 시 잠시 대기 후 다음 폴더 처리 await Task.Delay(2000); continue; } } LogMessage($"🎉 DWG 전용 추출 완료! 총 {totalFolders}개 폴더 처리됨"); UpdateStatus("✅ DWG 전용 추출 완료"); } /// /// DWG Height 정렬 처리를 위한 리프 폴더 처리 /// /// 처리할 리프 폴더 목록 /// 결과 저장 기본 폴더 private async Task ProcessLeafFoldersDwgHeightSort(List leafFolders, string resultBaseFolder) { int totalFolders = leafFolders.Count; LogMessage($"📏 DWG Height 정렬 추출 시작: {totalFolders}개 폴더"); try { // 모든 폴더의 DWG 파일을 수집 var allDwgFiles = new List<(string filePath, string folderName)>(); for (int i = 0; i < leafFolders.Count; i++) { string leafFolder = leafFolders[i]; progressBar.Value = (double)(i + 1) / totalFolders * 50; // 첫 50%는 파일 수집용 LogMessage($"📁 [{i + 1}/{totalFolders}] 폴더 스캔 중: {leafFolder}"); var dwgFiles = Directory.GetFiles(leafFolder, "*.dwg", SearchOption.TopDirectoryOnly); string folderName = Path.GetFileName(leafFolder); foreach (var dwgFile in dwgFiles) { allDwgFiles.Add((dwgFile, folderName)); } LogMessage($"📊 [{i + 1}/{totalFolders}] 발견된 DWG 파일: {dwgFiles.Length}개"); // UI 응답성을 위한 양보 await Task.Yield(); } LogMessage($"📊 총 발견된 DWG 파일: {allDwgFiles.Count}개"); if (allDwgFiles.Count > 0) { // Height 정렬 Excel 파일 생성 (Note 데이터 포함) LogMessage("📏 Height 정렬 Excel 파일 생성 중 (Note 표 데이터 포함)..."); await ProcessAllFilesDwgHeightSort(allDwgFiles, resultBaseFolder); LogMessage("✅ Height 정렬 Excel 파일 생성 완료"); } else { LogMessage("⚠️ 처리할 DWG 파일이 없습니다."); } progressBar.Value = 100; } catch (Exception ex) { LogMessage($"❌ DWG Height 정렬 추출 중 오류: {ex.Message}"); throw; } LogMessage($"🎉 DWG Height 정렬 추출 완료! 총 {totalFolders}개 폴더 처리됨"); UpdateStatus("✅ DWG Height 정렬 추출 완료"); } /// /// 모든 DWG 파일을 단일 Excel 파일에서 Height 정렬하여 처리합니다. /// /// 모든 DWG 파일 정보 (파일경로, 폴더명) /// 결과 폴더 private async Task ProcessAllFilesDwgHeightSort(List<(string filePath, string folderName)> allDwgFiles, string resultFolder) { ExportExcel exportExcel = null; try { LogMessage($"📊 총 DWG 파일 수: {allDwgFiles.Count}개"); LogMessage($"📋 출력 모드: Excel (Height 정렬)"); LogMessage($"💾 결과 폴더: {resultFolder}"); LogMessage("📊 Excel Height 정렬 내보내기 모드로 시작합니다..."); LogMessage("📝 Excel 애플리케이션을 초기화합니다..."); exportExcel = new ExportExcel(); // UI 응답성을 위한 양보 await Task.Yield(); // Height 정렬된 Excel 파일 생성 LogMessage("📏 Height 정렬 Excel 파일 생성 중..."); // 단일 타임스탬프로 파일명 생성 string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string savePath = Path.Combine(resultFolder, $"{timestamp}_AllDWG_HeightSorted.xlsx"); exportExcel.ExportAllDwgToExcelHeightSorted(allDwgFiles, savePath); // 시각화 데이터 캐시 초기화 MainWindow.ClearVisualizationData(); LogMessage("[DEBUG] 시각화 데이터 캐시 초기화 완료."); foreach (var (filePath, folderName) in allDwgFiles) { LogMessage($"[DEBUG] DWG 파일에서 Note 추출 시작: {Path.GetFileName(filePath)}"); var noteExtractionResult = exportExcel.DwgExtractor.ExtractNotesFromDrawing(filePath); var noteEntities = noteExtractionResult.NoteEntities; LogMessage($"[DEBUG] 추출된 Note 엔티티 수: {noteEntities.Count}"); LogMessage($"[DEBUG] 추출된 IntersectionPoints 수: {noteExtractionResult.IntersectionPoints.Count}"); LogMessage($"[DEBUG] 추출된 DiagonalLines 수: {noteExtractionResult.DiagonalLines.Count}"); LogMessage($"[DEBUG] 추출된 TableSegments 수: {noteExtractionResult.TableSegments.Count}"); if (noteEntities.Any()) { // 테이블이 있는 Note만 가시화 데이터 생성 (최소 4개 셀 이상) var notesWithTables = noteEntities.Where(ne => ne.Type == "Note" && ne.Cells != null && ne.Cells.Count >= 4 && // 최소 4개 셀이 있어야 테이블로 인정 ne.TableSegments != null && ne.TableSegments.Count >= 4).ToList(); // 최소 4개 선분이 있어야 함 LogMessage($"[DEBUG] 테이블이 있는 Note: {notesWithTables.Count}개"); foreach (var noteWithTable in notesWithTables) { var visualizationData = new TableCellVisualizationData { FileName = $"{Path.GetFileName(filePath)} - {noteWithTable.Text}", NoteText = noteWithTable.Text, NoteBounds = ( noteWithTable.Cells.Min(c => c.MinPoint.X), noteWithTable.Cells.Min(c => c.MinPoint.Y), noteWithTable.Cells.Max(c => c.MaxPoint.X), noteWithTable.Cells.Max(c => c.MaxPoint.Y) ), Cells = noteWithTable.Cells.Select(tc => new CellBounds { MinX = tc.MinPoint.X, MinY = tc.MinPoint.Y, MaxX = tc.MaxPoint.X, MaxY = tc.MaxPoint.Y, Row = tc.Row, Column = tc.Column, Text = tc.CellText }).ToList(), TableSegments = noteWithTable.TableSegments.Select(ts => new SegmentInfo { StartX = ts.StartX, StartY = ts.StartY, EndX = ts.EndX, EndY = ts.EndY, IsHorizontal = ts.IsHorizontal }).ToList(), IntersectionPoints = noteWithTable.IntersectionPoints, DiagonalLines = noteWithTable.DiagonalLines ?? new List(), CellBoundaries = noteWithTable.CellBoundaries?.Select(cb => new CellBoundaryInfo { TopLeftX = cb.TopLeft.X, TopLeftY = cb.TopLeft.Y, TopRightX = cb.TopRight.X, TopRightY = cb.TopRight.Y, BottomLeftX = cb.BottomLeft.X, BottomLeftY = cb.BottomLeft.Y, BottomRightX = cb.BottomRight.X, BottomRightY = cb.BottomRight.Y, Label = cb.Label, Width = cb.Width, Height = cb.Height, CellText = cb.CellText ?? "" }).ToList() ?? new List(), TextEntities = noteEntities.Where(ne => ne.Type == "NoteContent").Select(ne => new TextInfo { X = ne.X, Y = ne.Y, Text = ne.Text, IsInTable = noteWithTable.Cells.Any(cell => ne.X >= cell.MinPoint.X && ne.X <= cell.MaxPoint.X && ne.Y >= cell.MinPoint.Y && ne.Y <= cell.MaxPoint.Y) }).ToList() }; MainWindow.SaveVisualizationData(visualizationData); LogMessage($"[DEBUG] 테이블 Note 시각화 데이터 추가: {visualizationData.FileName} (셀: {visualizationData.Cells.Count}개)"); } } else { LogMessage($"[DEBUG] Note 엔티티가 없어 시각화 데이터를 생성하지 않습니다: {Path.GetFileName(filePath)}"); } } LogMessage($"[DEBUG] 총 {allDwgFiles.Count}개 파일의 시각화 데이터 저장 완료."); // 최종 시각화 데이터 확인 var finalVisualizationData = MainWindow.GetVisualizationData(); LogMessage($"[DEBUG] 최종 저장된 시각화 데이터: {finalVisualizationData.Count}개 항목"); LogMessage("✅ Height 정렬 Excel 파일 생성 완료"); LogMessage($"📁 저장된 파일: {Path.GetFileName(savePath)}"); } catch (Exception ex) { LogMessage($"❌ DWG Height 정렬 처리 중 치명적 오류: {ex.Message}"); throw; } finally { exportExcel?.Dispose(); exportExcel = null; } } /// /// 지정된 폴더에서 DWG 파일들만 처리합니다. /// /// 처리할 폴더 경로 private async Task ProcessFilesDwgOnly(string sourceFolderPath) { ExportExcel exportExcel = null; try { string resultFolder = txtResultFolder.Text; if (!Directory.Exists(sourceFolderPath)) { LogMessage($"❌ 소스 폴더가 존재하지 않습니다: {sourceFolderPath}"); return; } var dwgFiles = Directory.GetFiles(sourceFolderPath, "*.dwg", SearchOption.TopDirectoryOnly); if (dwgFiles.Length == 0) { LogMessage($"📄 처리할 DWG 파일이 없습니다: {sourceFolderPath}"); return; } LogMessage($"📊 처리할 DWG 파일 수: {dwgFiles.Length}개"); LogMessage($"📋 출력 모드: Excel"); LogMessage($"📂 소스 폴더: {sourceFolderPath}"); LogMessage($"💾 결과 폴더: {resultFolder}"); LogMessage("📊 Excel 내보내기 모드로 시작합니다..."); LogMessage("📝 Excel 애플리케이션을 초기화합니다..."); exportExcel = new ExportExcel(); exportExcel.ClearAccumulatedData(); // UI 응답성을 위한 양보 await Task.Yield(); // 각 DWG 파일 처리 foreach (string dwgFile in dwgFiles) { LogMessage($"📄 DWG 파일 처리 중: {Path.GetFileName(dwgFile)}"); exportExcel.ExportDwgToExcel(dwgFile); // UI 응답성을 위한 양보 await Task.Yield(); } // DWG 전용 워크북 저장 LogMessage("💾 DWG 전용 Excel 파일 저장 중..."); exportExcel.SaveDwgOnlyMappingWorkbook(resultFolder); LogMessage("✅ DWG 전용 Excel 파일 저장 완료"); } catch (Exception ex) { LogMessage($"❌ DWG 처리 중 치명적 오류: {ex.Message}"); throw; } finally { exportExcel?.Dispose(); exportExcel = null; } } /// /// DWG 전용 Excel 파일을 목표 경로로 이름 변경 /// /// 결과 폴더 /// 리프 폴더 경로 private async Task RenameDwgOnlyExcelFile(string resultFolder, string leafFolderPath) { try { // 최신 *_DwgOnly_Mapping.xlsx 파일 찾기 var excelFiles = Directory.GetFiles(resultFolder, "*_DwgOnly_Mapping.xlsx") .Where(f => !Path.GetFileName(f).StartsWith("~$")) .OrderByDescending(f => File.GetCreationTime(f)) .ToList(); if (excelFiles.Any()) { string latestFile = excelFiles.First(); // 리프 폴더 경로를 기반으로 파일명 생성 string relativePath = Path.GetRelativePath(txtSourceFolder.Text, leafFolderPath); string safePath = relativePath.Replace('\\', '_').Replace('/', '_').Replace(':', '_'); string targetFileName = $"{safePath}_DwgMapping.xlsx"; string targetPath = Path.Combine(resultFolder, targetFileName); // 목표 파일이 이미 존재하면 삭제 if (File.Exists(targetPath)) { File.Delete(targetPath); } // 파일 이름 변경 File.Move(latestFile, targetPath); LogMessage($"📋 DWG Excel 파일 이름 변경됨: {targetFileName}"); } } catch (Exception ex) { LogMessage($"⚠️ DWG Excel 파일 이름 변경 실패: {ex.Message}"); } } /// /// 지정된 폴더에서 DWG 파일들을 Height 정렬하여 처리합니다. /// /// 처리할 폴더 경로 private async Task ProcessFilesDwgHeightSort(string sourceFolderPath) { ExportExcel exportExcel = null; try { string resultFolder = txtResultFolder.Text; if (!Directory.Exists(sourceFolderPath)) { LogMessage($"❌ 소스 폴더가 존재하지 않습니다: {sourceFolderPath}"); return; } var dwgFiles = Directory.GetFiles(sourceFolderPath, "*.dwg", SearchOption.TopDirectoryOnly); if (dwgFiles.Length == 0) { LogMessage($"📄 처리할 DWG 파일이 없습니다: {sourceFolderPath}"); return; } LogMessage($"📊 처리할 DWG 파일 수: {dwgFiles.Length}개"); LogMessage($"📋 출력 모드: Excel (Height 정렬)"); LogMessage($"📂 소스 폴더: {sourceFolderPath}"); LogMessage($"💾 결과 폴더: {resultFolder}"); LogMessage("📊 Excel Height 정렬 내보내기 모드로 시작합니다..."); LogMessage("📝 Excel 애플리케이션을 초기화합니다..."); exportExcel = new ExportExcel(); // UI 응답성을 위한 양보 await Task.Yield(); // Height 정렬된 Excel 파일 생성 LogMessage("📏 Height 정렬 Excel 파일 생성 중..."); exportExcel.ExportDwgToExcelHeightSorted(dwgFiles, resultFolder); LogMessage("✅ Height 정렬 Excel 파일 생성 완료"); } catch (Exception ex) { LogMessage($"❌ DWG Height 정렬 처리 중 치명적 오류: {ex.Message}"); throw; } finally { exportExcel?.Dispose(); exportExcel = null; } } /// /// DWG Height 정렬 Excel 파일을 목표 경로로 이름 변경 /// /// 결과 폴더 /// 리프 폴더 경로 private async Task RenameDwgHeightSortExcelFile(string resultFolder, string leafFolderPath) { try { // 최신 *_HeightSorted.xlsx 파일 찾기 var excelFiles = Directory.GetFiles(resultFolder, "*_HeightSorted.xlsx") .Where(f => !Path.GetFileName(f).StartsWith("~$")) .OrderByDescending(f => File.GetCreationTime(f)) .ToList(); if (excelFiles.Any()) { string latestFile = excelFiles.First(); // 리프 폴더 경로를 기반으로 파일명 생성 string relativePath = Path.GetRelativePath(txtSourceFolder.Text, leafFolderPath); string safePath = relativePath.Replace('\\', '_').Replace('/', '_').Replace(':', '_'); string targetFileName = $"{safePath}_HeightSorted.xlsx"; string targetPath = Path.Combine(resultFolder, targetFileName); // 목표 파일이 이미 존재하면 삭제 if (File.Exists(targetPath)) { File.Delete(targetPath); } // 파일 이름 변경 File.Move(latestFile, targetPath); LogMessage($"📋 Height 정렬 Excel 파일 이름 변경됨: {targetFileName}"); } } catch (Exception ex) { LogMessage($"⚠️ Height 정렬 Excel 파일 이름 변경 실패: {ex.Message}"); } } /// /// 최신 Excel 파일을 목표 경로로 이름 변경 /// /// 결과 폴더 /// 목표 파일 경로 private async Task RenameLatestExcelFile(string resultFolder, string targetPath) { try { // 최신 *_Complete_Mapping_Merged.xlsx 파일 찾기 var excelFiles = Directory.GetFiles(resultFolder, "*_Complete_Mapping_Merged.xlsx") .Where(f => !Path.GetFileName(f).StartsWith("~$")) .OrderByDescending(f => File.GetCreationTime(f)) .ToList(); if (excelFiles.Any()) { string latestFile = excelFiles.First(); // 목표 파일이 이미 존재하면 삭제 if (File.Exists(targetPath)) { File.Delete(targetPath); } // 파일 이름 변경 File.Move(latestFile, targetPath); LogMessage($"📋 Excel 파일 이름 변경됨: {Path.GetFileName(targetPath)}"); } } catch (Exception ex) { LogMessage($"⚠️ Excel 파일 이름 변경 실패: {ex.Message}"); } } /// /// 버튼들의 활성화 상태를 설정합니다. /// /// 활성화 여부 private void SetButtonsEnabled(bool enabled) { btnExtract.IsEnabled = enabled; btnPdfExtract.IsEnabled = enabled; btnMerge.IsEnabled = enabled; btnAuto.IsEnabled = enabled; btnDwgOnly.IsEnabled = enabled; } protected override void OnClosed(EventArgs e) { _timer?.Stop(); _exportExcel?.Dispose(); _sqlDatas?.Dispose(); base.OnClosed(e); } /// /// Note 추출 기능 테스트 메서드 /// private async Task TestNoteExtraction() { try { LogMessage("🧪 === Note 추출 기능 테스트 시작 ==="); string sourceFolder = txtSourceFolder.Text; string resultFolder = txtResultFolder.Text; if (string.IsNullOrEmpty(sourceFolder) || !Directory.Exists(sourceFolder)) { LogMessage("❌ 소스 폴더가 선택되지 않았거나 존재하지 않습니다."); UpdateStatus("소스 폴더를 선택해주세요."); return; } if (string.IsNullOrEmpty(resultFolder)) { LogMessage("❌ 결과 폴더가 선택되지 않았습니다."); UpdateStatus("결과 폴더를 선택해주세요."); return; } // 결과 폴더가 없으면 생성 if (!Directory.Exists(resultFolder)) { Directory.CreateDirectory(resultFolder); LogMessage($"📁 결과 폴더 생성: {resultFolder}"); } // DWG 파일 찾기 var dwgFiles = Directory.GetFiles(sourceFolder, "*.dwg", SearchOption.AllDirectories); LogMessage($"📊 발견된 DWG 파일 수: {dwgFiles.Length}개"); if (dwgFiles.Length == 0) { LogMessage("⚠️ DWG 파일이 없습니다."); UpdateStatus("DWG 파일이 없습니다."); return; } UpdateStatus("🔧 Note 추출 중..."); progressBar.Maximum = dwgFiles.Length; progressBar.Value = 0; // Teigha 서비스 초기화 LogMessage("🔧 Teigha 서비스 초기화 중..."); TeighaServicesManager.Instance.AcquireServices(); // 간단한 빈 매핑 데이터로 FieldMapper 생성 (테스트용) var mappingData = new MappingTableData(); var fieldMapper = new FieldMapper(mappingData); var extractor = new DwgDataExtractor(fieldMapper); var csvWriter = new CsvDataWriter(); int processedCount = 0; int successCount = 0; int failureCount = 0; // 각 DWG 파일에 대해 Note 추출 테스트 foreach (string dwgFile in dwgFiles.Take(3)) // 처음 3개 파일만 테스트 { try { string fileName = Path.GetFileNameWithoutExtension(dwgFile); LogMessage($"🔍 [{processedCount + 1}/{Math.Min(3, dwgFiles.Length)}] Note 추출 중: {fileName}"); // Note 데이터 추출 var noteExtractionResult = extractor.ExtractNotesFromDrawing(dwgFile); var noteEntities = noteExtractionResult.NoteEntities; // 테이블이 있는 Note만 시각화 데이터 저장 if (noteEntities.Any()) { var notesWithTables = noteEntities.Where(ne => ne.Type == "Note" && ne.Cells != null && ne.Cells.Count >= 4 && // 최소 4개 셀이 있어야 테이블로 인정 ne.TableSegments != null && ne.TableSegments.Count >= 4).ToList(); // 최소 4개 선분이 있어야 함 LogMessage($" 테이블이 있는 Note: {notesWithTables.Count}개"); foreach (var noteWithTable in notesWithTables) { var visualizationData = new TableCellVisualizationData { FileName = $"{Path.GetFileName(dwgFile)} - {noteWithTable.Text}", NoteText = noteWithTable.Text, NoteBounds = ( noteWithTable.Cells.Min(c => c.MinPoint.X), noteWithTable.Cells.Min(c => c.MinPoint.Y), noteWithTable.Cells.Max(c => c.MaxPoint.X), noteWithTable.Cells.Max(c => c.MaxPoint.Y) ), Cells = noteWithTable.Cells.Select(tc => new CellBounds { MinX = tc.MinPoint.X, MinY = tc.MinPoint.Y, MaxX = tc.MaxPoint.X, MaxY = tc.MaxPoint.Y, Row = tc.Row, Column = tc.Column, Text = tc.CellText }).ToList(), TableSegments = noteWithTable.TableSegments.Select(ts => new SegmentInfo { StartX = ts.StartX, StartY = ts.StartY, EndX = ts.EndX, EndY = ts.EndY, IsHorizontal = ts.IsHorizontal }).ToList(), IntersectionPoints = noteWithTable.IntersectionPoints, DiagonalLines = noteExtractionResult.DiagonalLines.Where(dl => noteWithTable.Cells.Any(cell => (dl.Item1.X >= cell.MinPoint.X && dl.Item1.X <= cell.MaxPoint.X && dl.Item1.Y >= cell.MinPoint.Y && dl.Item1.Y <= cell.MaxPoint.Y) || (dl.Item2.X >= cell.MinPoint.X && dl.Item2.X <= cell.MaxPoint.X && dl.Item2.Y >= cell.MinPoint.Y && dl.Item2.Y <= cell.MaxPoint.Y))).Select(dl => new DiagonalLine { StartX = dl.Item1.X, StartY = dl.Item1.Y, EndX = dl.Item2.X, EndY = dl.Item2.Y, Label = dl.Item3 }).ToList(), CellBoundaries = noteWithTable.CellBoundaries?.Select(cb => new CellBoundaryInfo { TopLeftX = cb.TopLeft.X, TopLeftY = cb.TopLeft.Y, TopRightX = cb.TopRight.X, TopRightY = cb.TopRight.Y, BottomLeftX = cb.BottomLeft.X, BottomLeftY = cb.BottomLeft.Y, BottomRightX = cb.BottomRight.X, BottomRightY = cb.BottomRight.Y, Label = cb.Label, Width = cb.Width, Height = cb.Height, CellText = cb.CellText ?? "" }).ToList() ?? new List(), TextEntities = noteEntities.Where(ne => ne.Type == "NoteContent").Select(ne => new TextInfo { X = ne.X, Y = ne.Y, Text = ne.Text, IsInTable = noteWithTable.Cells.Any(cell => ne.X >= cell.MinPoint.X && ne.X <= cell.MaxPoint.X && ne.Y >= cell.MinPoint.Y && ne.Y <= cell.MaxPoint.Y) }).ToList() }; MainWindow.SaveVisualizationData(visualizationData); LogMessage($" ✅ 테이블 Note 시각화 데이터 저장: {visualizationData.FileName} (셀: {visualizationData.Cells.Count}개)"); } } LogMessage($" 추출된 엔터티: {noteEntities.Count}개"); var noteCount = noteEntities.Count(ne => ne.Type == "Note"); var contentCount = noteEntities.Count(ne => ne.Type == "NoteContent"); var tableCount = noteEntities.Count(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv)); LogMessage($" - Note 헤더: {noteCount}개"); LogMessage($" - Note 콘텐츠: {contentCount}개"); LogMessage($" - 테이블 포함 Note: {tableCount}개"); // CSV 파일 생성 if (noteEntities.Count > 0) { // Note 박스 텍스트 CSV var noteTextCsvPath = Path.Combine(resultFolder, $"{fileName}_note_texts.csv"); csvWriter.WriteNoteBoxTextToCsv(noteEntities, noteTextCsvPath); LogMessage($" ✅ Note 텍스트 CSV 저장: {Path.GetFileName(noteTextCsvPath)}"); // Note 테이블 CSV var noteTableCsvPath = Path.Combine(resultFolder, $"{fileName}_note_tables.csv"); csvWriter.WriteNoteTablesToCsv(noteEntities, noteTableCsvPath); LogMessage($" ✅ Note 테이블 CSV 저장: {Path.GetFileName(noteTableCsvPath)}"); // 통합 CSV var combinedCsvPath = Path.Combine(resultFolder, $"{fileName}_note_combined.csv"); csvWriter.WriteNoteDataToCombinedCsv(noteEntities, combinedCsvPath); LogMessage($" ✅ 통합 CSV 저장: {Path.GetFileName(combinedCsvPath)}"); // 개별 테이블 CSV if (tableCount > 0) { var individualTablesDir = Path.Combine(resultFolder, $"{fileName}_individual_tables"); csvWriter.WriteIndividualNoteTablesCsv(noteEntities, individualTablesDir); LogMessage($" ✅ 개별 테이블 CSV 저장: {Path.GetFileName(individualTablesDir)}"); } // 통계 CSV var statisticsCsvPath = Path.Combine(resultFolder, $"{fileName}_note_statistics.csv"); csvWriter.WriteNoteStatisticsToCsv(noteEntities, statisticsCsvPath); LogMessage($" ✅ 통계 CSV 저장: {Path.GetFileName(statisticsCsvPath)}"); // 첫 번째 테이블이 있는 Note의 내용 출력 (디버깅용) var firstTableNote = noteEntities.FirstOrDefault(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv)); if (firstTableNote != null) { LogMessage($" 📋 첫 번째 테이블 Note: '{firstTableNote.Text}'"); var tableLines = firstTableNote.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); LogMessage($" 📋 테이블 행 수: {tableLines.Length}"); for (int i = 0; i < Math.Min(3, tableLines.Length); i++) { var line = tableLines[i]; if (line.Length > 60) line = line.Substring(0, 60) + "..."; LogMessage($" 행 {i + 1}: {line}"); } } } successCount++; LogMessage($" ✅ 성공"); } catch (Exception ex) { failureCount++; LogMessage($" ❌ 실패: {ex.Message}"); } processedCount++; progressBar.Value = processedCount; // UI 업데이트를 위한 지연 await Task.Delay(100); } LogMessage($"🧪 === Note 추출 테스트 완료 ==="); LogMessage($"📊 처리 결과: 성공 {successCount}개, 실패 {failureCount}개"); LogMessage($"💾 결과 파일들이 저장되었습니다: {resultFolder}"); UpdateStatus($"Note 추출 테스트 완료: 성공 {successCount}개, 실패 {failureCount}개"); // 결과 폴더 열기 if (successCount > 0) { try { System.Diagnostics.Process.Start("explorer.exe", resultFolder); } catch { } } } catch (Exception ex) { LogMessage($"❌ Note 추출 테스트 중 오류: {ex.Message}"); UpdateStatus("Note 추출 테스트 중 오류 발생"); throw; } finally { try { TeighaServicesManager.Instance.ForceDisposeServices(); LogMessage("🔄 Teigha 서비스 정리 완료"); } catch (Exception ex) { LogMessage($"⚠️ Teigha 서비스 정리 중 오류: {ex.Message}"); } } } /// /// 빌드 시간을 상태바에 표시합니다. /// private void SetBuildTime() { try { // 현재 실행 파일의 빌드 시간을 가져옵니다 var assembly = System.Reflection.Assembly.GetExecutingAssembly(); var buildDate = System.IO.File.GetLastWriteTime(assembly.Location); txtBuildTime.Text = $"빌드: {buildDate:yyyy-MM-dd HH:mm}"; } catch (Exception ex) { txtBuildTime.Text = "빌드: 알 수 없음"; LogMessage($"⚠️ 빌드 시간 조회 오류: {ex.Message}"); } } /// /// 교차점 테스트 버튼 클릭 이벤트 /// private void BtnTestIntersection_Click(object sender, RoutedEventArgs e) { try { LogMessage("🔬 교차점 생성 테스트 시작..."); UpdateStatus("교차점 테스트 중..."); // 테스트 실행 IntersectionTestDebugger.RunIntersectionTest(); LogMessage("✅ 교차점 테스트 완료 - Debug 창을 확인하세요"); UpdateStatus("교차점 테스트 완료"); } catch (Exception ex) { LogMessage($"❌ 교차점 테스트 중 오류: {ex.Message}"); UpdateStatus("교차점 테스트 중 오류 발생"); } } } }