2867 lines
129 KiB
C#
2867 lines
129 KiB
C#
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("교차점 테스트 중 오류 발생");
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|