Files
manual_wpf/MainWindow.xaml.cs
2025-08-12 14:33:18 +09:00

2867 lines
129 KiB
C#
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
/// <summary>
/// MessageBox 표시 (자동 모드에서는 로그만 출력)
/// </summary>
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);
}
}
/// <summary>
/// 확인 대화상자 표시 (자동 모드에서는 자동으로 Yes 반환)
/// </summary>
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);
}
}
/// <summary>
/// DWG 파일 처리 - 자동 모드용 (폴더 경로 직접 지정)
/// </summary>
/// <param name="sourceFolderPath">처리할 폴더 경로</param>
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<double>(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;
}
}
/// <summary>
/// DWG 파일 처리 - 기존 메서드
/// </summary>
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<double>(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();
}
/// <summary>
/// PDF 추출 결과를 사용하여 Excel 매핑을 효율적으로 업데이트합니다 (새로운 방식).
/// </summary>
/// <param name="csvFilePath">CSV 결과 파일 경로</param>
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}");
}
}
/// <summary>
/// PDF 추출 결과 JSON 파일을 사용하여 Excel 매핑 시트를 업데이트합니다 (기존 방식 - 사용 안함).
/// </summary>
/// <param name="csvFilePath">CSV 결과 파일 경로</param>
[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}");
}
}
/// <summary>
/// 파일이 다른 프로세스에서 사용 중인지 확인합니다.
/// </summary>
/// <param name="filePath">확인할 파일 경로</param>
/// <returns>사용 중이면 true, 아니면 false</returns>
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;
}
}
/// <summary>
/// 가장 최근의 매핑 데이터 파일을 찾습니다.
/// </summary>
/// <param name="resultDir">검색할 디렉토리</param>
/// <returns>최신 매핑 데이터 파일 경로 또는 null</returns>
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));
}
}
/// <summary>
/// 가장 최근의 PDF 추출 JSON 파일을 찾아 Excel 매핑을 효율적으로 업데이트합니다.
/// </summary>
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;
}
}
/// <summary>
/// JSON 파일의 내용을 미리보기로 보여줍니다.
/// </summary>
/// <param name="jsonFilePath">JSON 파일 경로</param>
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}");
}
}
/// <summary>
/// 자동 처리 버튼 클릭 이벤트
/// </summary>
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;
}
}
/// <summary>
/// DWG 전용 폴더별 추출 버튼 클릭 이벤트
/// </summary>
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;
}
}
/// <summary>
/// DWG 추출 (Height 정렬) 버튼 클릭 이벤트
/// </summary>
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;
}
}
/// <summary>
/// 폴더에서 리프 폴더들(하위 폴더가 없는 폴더)을 재귀적으로 찾습니다.
/// </summary>
/// <param name="rootPath">루트 폴더 경로</param>
/// <returns>리프 폴더 경로 목록</returns>
private List<string> FindLeafFolders(string rootPath)
{
var leafFolders = new List<string>();
try
{
FindLeafFoldersRecursive(rootPath, leafFolders);
}
catch (Exception ex)
{
LogMessage($"❌ 리프 폴더 검색 중 오류: {ex.Message}");
}
return leafFolders;
}
/// <summary>
/// 재귀적으로 리프 폴더를 찾는 헬퍼 메서드
/// </summary>
/// <param name="currentPath">현재 폴더 경로</param>
/// <param name="leafFolders">리프 폴더 목록</param>
private void FindLeafFoldersRecursive(string currentPath, List<string> 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}");
}
}
/// <summary>
/// 리프 폴더들을 자동으로 처리합니다.
/// </summary>
/// <param name="leafFolders">처리할 리프 폴더 목록</param>
/// <param name="resultBaseFolder">결과 저장 기본 폴더</param>
private async Task ProcessLeafFoldersAutomatically(List<string> 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("✅ 자동 처리 완료");
}
/// <summary>
/// 리프 폴더들에 대해 DWG 전용 추출을 수행합니다.
/// </summary>
/// <param name="leafFolders">처리할 리프 폴더 목록</param>
/// <param name="resultBaseFolder">결과 파일 저장 기본 폴더</param>
private async Task ProcessLeafFoldersDwgOnly(List<string> 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 전용 추출 완료");
}
/// <summary>
/// DWG Height 정렬 처리를 위한 리프 폴더 처리
/// </summary>
/// <param name="leafFolders">처리할 리프 폴더 목록</param>
/// <param name="resultBaseFolder">결과 저장 기본 폴더</param>
private async Task ProcessLeafFoldersDwgHeightSort(List<string> 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 정렬 추출 완료");
}
/// <summary>
/// 모든 DWG 파일을 단일 Excel 파일에서 Height 정렬하여 처리합니다.
/// </summary>
/// <param name="allDwgFiles">모든 DWG 파일 정보 (파일경로, 폴더명)</param>
/// <param name="resultFolder">결과 폴더</param>
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<DiagonalLine>(),
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<CellBoundaryInfo>(),
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;
}
}
/// <summary>
/// 지정된 폴더에서 DWG 파일들만 처리합니다.
/// </summary>
/// <param name="sourceFolderPath">처리할 폴더 경로</param>
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;
}
}
/// <summary>
/// DWG 전용 Excel 파일을 목표 경로로 이름 변경
/// </summary>
/// <param name="resultFolder">결과 폴더</param>
/// <param name="leafFolderPath">리프 폴더 경로</param>
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}");
}
}
/// <summary>
/// 지정된 폴더에서 DWG 파일들을 Height 정렬하여 처리합니다.
/// </summary>
/// <param name="sourceFolderPath">처리할 폴더 경로</param>
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;
}
}
/// <summary>
/// DWG Height 정렬 Excel 파일을 목표 경로로 이름 변경
/// </summary>
/// <param name="resultFolder">결과 폴더</param>
/// <param name="leafFolderPath">리프 폴더 경로</param>
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}");
}
}
/// <summary>
/// 최신 Excel 파일을 목표 경로로 이름 변경
/// </summary>
/// <param name="resultFolder">결과 폴더</param>
/// <param name="targetPath">목표 파일 경로</param>
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}");
}
}
/// <summary>
/// 버튼들의 활성화 상태를 설정합니다.
/// </summary>
/// <param name="enabled">활성화 여부</param>
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);
}
/// <summary>
/// Note 추출 기능 테스트 메서드
/// </summary>
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<CellBoundaryInfo>(),
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}");
}
}
}
/// <summary>
/// 빌드 시간을 상태바에 표시합니다.
/// </summary>
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}");
}
}
/// <summary>
/// 교차점 테스트 버튼 클릭 이벤트
/// </summary>
private void BtnTestIntersection_Click(object sender, RoutedEventArgs e)
{
try
{
LogMessage("🔬 교차점 생성 테스트 시작...");
UpdateStatus("교차점 테스트 중...");
// 테스트 실행
IntersectionTestDebugger.RunIntersectionTest();
LogMessage("✅ 교차점 테스트 완료 - Debug 창을 확인하세요");
UpdateStatus("교차점 테스트 완료");
}
catch (Exception ex)
{
LogMessage($"❌ 교차점 테스트 중 오류: {ex.Message}");
UpdateStatus("교차점 테스트 중 오류 발생");
}
}
}
}