commit c8c6c67ba9448d31b106266addeeb285a2c9a943 Author: 김민성 Date: Wed Jul 16 17:40:50 2025 +0900 first commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..f6561d6 --- /dev/null +++ b/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..7154f6a --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,8 @@ +using System.Windows; + +namespace DwgExtractorManual +{ + public partial class App : System.Windows.Application + { + } +} diff --git a/DwgExtractorManual.csproj b/DwgExtractorManual.csproj new file mode 100644 index 0000000..503a2ac --- /dev/null +++ b/DwgExtractorManual.csproj @@ -0,0 +1,33 @@ + + + WinExe + net8.0-windows + true + True + enable + enable + + + + 9 + 1 + 00020813-0000-0000-c000-000000000046 + 0 + tlbimp + false + true + + + + + + + + + + + ..\..\..\..\GitNet8\trunk\DLL\Teigha\vc16_amd64dll_23.12SP2\TD_Mgd_23.12_16.dll + + + + diff --git a/DwgExtractorManual.sln b/DwgExtractorManual.sln new file mode 100644 index 0000000..faa6405 --- /dev/null +++ b/DwgExtractorManual.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DwgExtractorManual", "DwgExtractorManual.csproj", "{78FC2A43-62AA-8BAC-19B6-AD33161987E1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {78FC2A43-62AA-8BAC-19B6-AD33161987E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78FC2A43-62AA-8BAC-19B6-AD33161987E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78FC2A43-62AA-8BAC-19B6-AD33161987E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78FC2A43-62AA-8BAC-19B6-AD33161987E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {12A4DCBD-21DE-42F7-8BAA-BB213CC25F0D} + EndGlobalSection +EndGlobal diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..92c4843 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs new file mode 100644 index 0000000..d317268 --- /dev/null +++ b/MainWindow.xaml.cs @@ -0,0 +1,453 @@ +using System.IO; +using System.Windows; +using System.Diagnostics; +using System.Windows.Threading; +using DwgExtractorManual.Models; + +namespace DwgExtractorManual +{ + public partial class MainWindow : Window + { + private DispatcherTimer _timer; + private ExportExcel? _exportExcel; + private SqlDatas? _sqlDatas; + + public MainWindow() + { + InitializeComponent(); + InitializeDefaultPaths(); + InitializeTimer(); + LogMessage("🚀 DWG 정보 추출기가 시작되었습니다."); + } + + private void InitializeDefaultPaths() + { + // 기본 경로 설정 - 실제 환경에 맞게 수정 + txtSourceFolder.Text = @"D:\MyProjects\AI_TaskForce\AI_도면_dwg_pdf\대산당진_2공구_02도서성과품_01_설계도면\002_토공\01_본선\01_평면 및 종단면도"; + txtResultFolder.Text = @"D:\MyProjects\AI_TaskForce\AI_도면_dwg_pdf\대산당진_2공구_02도서성과품_01_설계도면"; + + // 경로가 존재하지 않으면 기본값으로 설정 + 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) => 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 files = targetDir.GetFiles("*.dwg", SearchOption.AllDirectories); + + txtFileCount.Text = $"파일: {files.Length}개"; + + if (files.Length > 0) + { + LogMessage($"✅ 총 {files.Length}개의 DWG 파일을 발견했습니다."); + + // 처음 몇 개 파일명 로깅 + int showCount = Math.Min(5, files.Length); + for (int i = 0; i < showCount; i++) + { + LogMessage($" 📄 {files[i].Name}"); + } + if (files.Length > 5) + { + LogMessage($" ... 외 {files.Length - 5}개 파일"); + } + } + else + { + LogMessage("⚠️ 선택한 폴더에 DWG 파일이 없습니다."); + } + } + catch (Exception ex) + { + LogMessage($"❌ 폴더 검사 중 오류: {ex.Message}"); + } + } + + private async void BtnExtract_Click(object sender, RoutedEventArgs e) + { + // 입력 유효성 검사 + if (string.IsNullOrEmpty(txtSourceFolder.Text) || !Directory.Exists(txtSourceFolder.Text)) + { + System.Windows.MessageBox.Show("유효한 변환할 폴더를 선택하세요.", "오류", + MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + if (string.IsNullOrEmpty(txtResultFolder.Text) || !Directory.Exists(txtResultFolder.Text)) + { + System.Windows.MessageBox.Show("유효한 결과 저장 폴더를 선택하세요.", "오류", + MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + // 데이터베이스 모드일 때 연결 테스트 + if (rbDatabase.IsChecked == true) + { + LogMessage("🔍 데이터베이스 연결을 확인합니다..."); + try + { + using (var testSql = new SqlDatas()) + { + if (!testSql.TestConnection()) + { + System.Windows.MessageBox.Show( + "PostgreSQL 데이터베이스에 연결할 수 없습니다.\n\n" + + "연결 설정을 확인하고 데이터베이스 서버가 실행 중인지 확인하세요.", + "데이터베이스 연결 오류", + MessageBoxButton.OK, MessageBoxImage.Error); + LogMessage("❌ 데이터베이스 연결 실패"); + return; + } + LogMessage("✅ 데이터베이스 연결 성공"); + } + } + catch (Exception ex) + { + System.Windows.MessageBox.Show( + $"데이터베이스 연결 테스트 중 오류가 발생했습니다:\n\n{ex.Message}", + "오류", MessageBoxButton.OK, MessageBoxImage.Error); + LogMessage($"❌ 데이터베이스 연결 오류: {ex.Message}"); + return; + } + } + + // UI 비활성화 + btnExtract.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("오류가 발생했습니다."); + System.Windows.MessageBox.Show($"작업 중 오류가 발생했습니다:\n\n{ex.Message}", + "오류", MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + // UI 활성화 + btnExtract.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)); // ✅ 정상 작동 + } + } + + 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 파일이 없습니다."); + System.Windows.MessageBox.Show("선택한 폴더에 DWG 파일이 없습니다.", + "정보", MessageBoxButton.OK, MessageBoxImage.Information); + return; + } + + UpdateStatus($"총 {files.Length}개의 DWG 파일을 처리합니다..."); + LogMessage($"📊 처리할 파일 수: {files.Length}개"); + + bool isExcelMode = rbExcel.IsChecked == true; + string outputMode = isExcelMode ? "Excel" : "PostgreSQL 데이터베이스"; + + LogMessage($"📋 출력 모드: {outputMode}"); + LogMessage($"📂 소스 폴더: {txtSourceFolder.Text}"); + LogMessage($"💾 결과 폴더: {txtResultFolder.Text}"); + + if (isExcelMode) + { + await ProcessExcelExport(files, stopwatch); + } + else + { + await ProcessDatabaseExport(files, stopwatch); + } + } + + private async Task ProcessExcelExport(FileInfo[] files, Stopwatch totalStopwatch) + { + LogMessage("📊 Excel 내보내기 모드로 시작합니다..."); + LogMessage("📝 Excel 애플리케이션을 초기화합니다..."); + + var successCount = 0; + var failureCount = 0; + var totalProcessingTime = 0.0; + + try + { + // ExportExcel 클래스 초기화 + _exportExcel = new ExportExcel(); + LogMessage("✅ Excel 애플리케이션 초기화 완료"); + + for (int i = 0; i < files.Length; i++) + { + var file = files[i]; + var fileStopwatch = Stopwatch.StartNew(); + + UpdateStatus($"처리 중: {file.Name} ({i + 1}/{files.Length})"); + LogMessage($"🔄 [{i + 1}/{files.Length}] 처리 중: {file.Name}"); + + try + { + // 실제 DWG 처리 로직 + var progress = new Progress(value => + { + // 파일별 진행률을 전체 진행률에 반영 + var overallProgress = ((i * 100.0 + value) / files.Length); + progressBar.Value = overallProgress; + }); + + bool success = _exportExcel.ExportDwgToExcel(file.FullName, progress); + + fileStopwatch.Stop(); + + if (success) + { + successCount++; + totalProcessingTime += fileStopwatch.ElapsedMilliseconds; + LogMessage($"✅ {file.Name} 처리 완료 ({fileStopwatch.ElapsedMilliseconds}ms)"); + } + else + { + failureCount++; + LogMessage($"❌ {file.Name} 처리 실패 ({fileStopwatch.ElapsedMilliseconds}ms)"); + } + } + catch (Exception ex) + { + failureCount++; + fileStopwatch.Stop(); + LogMessage($"💥 {file.Name} 처리 중 예외 발생: {ex.Message}"); + } + + // 진행률 업데이트 + progressBar.Value = ((i + 1) * 100.0) / files.Length; + + // UI 응답성을 위한 짧은 지연 + await Task.Delay(10); + } + + // Excel 파일 저장 + var excelFileName = Path.Combine(txtResultFolder.Text, + $"{DateTime.Now:yyyyMMdd_HHmmss}_DwgToExcel.xlsx"); + + LogMessage("💾 Excel 파일을 저장합니다..."); + _exportExcel.SaveAndCloseExcel(excelFileName); + LogMessage($"✅ Excel 파일 저장 완료: {Path.GetFileName(excelFileName)}"); + } + 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"); + + System.Windows.MessageBox.Show( + $"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"); + + System.Windows.MessageBox.Show( + $"데이터베이스 내보내기가 완료되었습니다!\n\n" + + $"✅ 성공: {successCount}개\n" + + $"❌ 실패: {failureCount}개\n" + + $"⏱️ 총 소요시간: {totalStopwatch.Elapsed:mm\\:ss}\n\n" + + $"🗄️ PostgreSQL 데이터베이스에 저장됨", + "완료", MessageBoxButton.OK, MessageBoxImage.Information); + } + + 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(); + } + + protected override void OnClosed(EventArgs e) + { + _timer?.Stop(); + _exportExcel?.Dispose(); + _sqlDatas?.Dispose(); + base.OnClosed(e); + } + } +} diff --git a/Models/ExportExcel.cs b/Models/ExportExcel.cs new file mode 100644 index 0000000..8a0b0cf --- /dev/null +++ b/Models/ExportExcel.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; // COM 객체 해제를 위해 필요 +using System.Threading; +using System.Threading.Tasks; +using Teigha.DatabaseServices; +using Teigha.Geometry; +using Teigha.Runtime; +using Excel = Microsoft.Office.Interop.Excel; + +namespace DwgExtractorManual.Models +{ + /// + /// DWG 파일에서 Excel로 데이터 내보내기 클래스 + /// AttributeReference, AttributeDefinition, DBText, MText 추출 지원 + /// + internal class ExportExcel : IDisposable + { + // ODA 서비스 객체 + private Services appServices; + + // Excel COM 객체들 + private Excel.Application excelApplication; + private Excel.Workbook workbook1; + private Excel.Worksheet titleBlockSheet; // Title Block용 시트 + private Excel.Worksheet textEntitiesSheet; // Text Entities용 시트 + + // 각 시트의 현재 행 번호 + private int titleBlockCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 + private int textEntitiesCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 + + // 생성자: ODA 및 Excel 초기화 + public ExportExcel() + { + ActivateAndInitializeODA(); + InitializeExcel(); + } + + // ODA 제품 활성화 및 초기화 + private void ActivateAndInitializeODA() + { + var userInfo = "c2FtYW4gZW5naW5lZXJpbmc="; + var userSignature = "F0kuQTmtVpHtvl/TgaFVGE92/YqGmYR9SLoXckEjnOk8NoAQh7Sg6GQruVC04JqD4C/IipxJYqpqvMfMc2auRMG+cAJCKqKUE2djIMdkUdb+C5IVx2c97fcK5ba3n8DDvB54Upokajl+6j12yD8h8MKGOR3Z3zysObeXD62bFpQgp00GCYTqlxEZtTIRjHIPAfJfix8Y0jtXWWYyVJ3LYOu86as5xtx+hY1aakpYIJiQk/6pGd84qSn/9K1w8nxR7UrFzieDeQ/xM58BHSD4u/ZxVJwvv6Uy10tsdBFBTvfJMAFp05Y7yeyeCNr100tA3iOfmWoXAVRHfxnkPfiYR54aK04QI+R6OGkI+yd1oR5BtmN6BdDt3z8KYK5EpFGJGiJIGoUy5PvkYdJ2VV6xe9JWBiIJuI/tDn1Y+uyTQFA9qaDHnOURriXsRGfy8reDPf1eejybSJxWKkpilG6RXhq3xHlCkjZzh1Q45S+xYXNGatcWMm9nkn20M8Ke5JEVaI9w/p2GE36CHRtRQbt8kfPmsbWNXJCFr4svHW2MPbCKWoyn5XEyMWBnuAKi74zvczB13DKjf29SqSIgF5k/hwy2QrgvnaKzY1k8bw8w2/k0vJXcS3GKOB/ZYDle1tf/lkAD1HtnF9zE18TiXhVnqwAVjwg4ui1RPLn/LMs6b5Y="; + Services.odActivate(userInfo, userSignature); + appServices = new Services(); + } + + // Excel 애플리케이션 및 워크시트 초기화 + private void InitializeExcel() + { + try + { + var excelApp = new Excel.Application(); + excelApplication = excelApp; + excelApplication.Visible = false; // WPF에서는 숨김 처리 + Excel.Workbook workbook = excelApp.Workbooks.Add(); + workbook1 = workbook; + + // Title Block Sheet 설정 (기본 Sheet1) + titleBlockSheet = (Excel.Worksheet)workbook.Sheets[1]; + titleBlockSheet.Name = "Title Block"; + SetupTitleBlockHeaders(); + + // Text Entities Sheet 추가 + textEntitiesSheet = (Excel.Worksheet)workbook.Sheets.Add(); + textEntitiesSheet.Name = "Text Entities"; + SetupTextEntitiesHeaders(); + } + catch (System.Exception ex) + { + Console.WriteLine($"Excel 초기화 중 오류 발생: {ex.Message}"); + ReleaseExcelObjects(); + throw; + } + } + + // Title Block 시트 헤더 설정 + private void SetupTitleBlockHeaders() + { + titleBlockSheet.Cells[1, 1] = "Type"; // 예: AttributeReference, AttributeDefinition + titleBlockSheet.Cells[1, 2] = "Name"; // BlockReference 이름 또는 BlockDefinition 이름 + titleBlockSheet.Cells[1, 3] = "Tag"; // Attribute Tag + titleBlockSheet.Cells[1, 4] = "Prompt"; // Attribute Prompt + titleBlockSheet.Cells[1, 5] = "Value"; // Attribute 값 (TextString) + titleBlockSheet.Cells[1, 6] = "Path"; // 원본 DWG 파일 전체 경로 + titleBlockSheet.Cells[1, 7] = "FileName"; // 원본 DWG 파일 이름만 + + // 헤더 행 스타일 + Excel.Range headerRange = titleBlockSheet.Range["A1:G1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); + } + + // Text Entities 시트 헤더 설정 + private void SetupTextEntitiesHeaders() + { + textEntitiesSheet.Cells[1, 1] = "Type"; // DBText, MText + textEntitiesSheet.Cells[1, 2] = "Layer"; // Layer 이름 + textEntitiesSheet.Cells[1, 3] = "Text"; // 실제 텍스트 내용 + textEntitiesSheet.Cells[1, 4] = "Path"; // 원본 DWG 파일 전체 경로 + textEntitiesSheet.Cells[1, 5] = "FileName"; // 원본 DWG 파일 이름만 + + // 헤더 행 스타일 + Excel.Range headerRange = textEntitiesSheet.Range["A1:E1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGreen); + } + + /// + /// 단일 DWG 파일에서 AttributeReference/AttributeDefinition 데이터를 추출하여 + /// 초기화된 Excel 워크시트에 추가합니다. + /// + /// 처리할 DWG 파일 경로 + /// 진행 상태 보고를 위한 IProgress 객체 + /// 작업 취소를 위한 CancellationToken + /// 성공 시 true, 실패 시 false 반환 + public bool ExportDwgToExcel(string filePath, IProgress progress = null, CancellationToken cancellationToken = default) + { + if (excelApplication == null) + { + Console.WriteLine("Excel이 초기화되지 않았습니다."); + return false; + } + + try + { + progress?.Report(0); + cancellationToken.ThrowIfCancellationRequested(); + + // ODA Database 객체 생성 및 DWG 파일 읽기 + using (var database = new Database(false, true)) + { + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + + cancellationToken.ThrowIfCancellationRequested(); + progress?.Report(10); + + using (var tran = database.TransactionManager.StartTransaction()) + { + var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; + using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) + { + int totalEntities = btr.Cast().Count(); + int processedCount = 0; + + foreach (ObjectId entId in btr) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) + { + // Layer 이름 가져오기 (공통) + string layerName = GetLayerName(ent.LayerId, tran, database); + + // AttributeDefinition 추출 + if (ent is AttributeDefinition attDef) + { + titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attDef.GetType().Name; + titleBlockSheet.Cells[titleBlockCurrentRow, 2] = attDef.BlockName; + titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attDef.Tag; + + + titleBlockSheet.Cells[titleBlockCurrentRow, 4] = attDef.Prompt; + titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attDef.TextString; + titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename; + titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename); + titleBlockCurrentRow++; + } + // BlockReference 및 그 안의 AttributeReference 추출 + else if (ent is BlockReference blr) + { + foreach (ObjectId attId in blr.AttributeCollection) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) + { + if (attRef != null && attRef.TextString.Trim() !="") + { + titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attRef.GetType().Name; + titleBlockSheet.Cells[titleBlockCurrentRow, 2] = blr.Name; + titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attRef.Tag; + titleBlockSheet.Cells[titleBlockCurrentRow, 4] = GetPromptFromAttributeReference(tran, blr, attRef.Tag); + + titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attRef.TextString; + titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename; + titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename); + titleBlockCurrentRow++; + } + } + } + } + // DBText 엔티티 추출 (별도 시트) + else if (ent is DBText dbText) + { + textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "DBText"; // Type + textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer + textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = dbText.TextString; // Text + textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path + textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName + textEntitiesCurrentRow++; + } + // MText 엔티티 추출 (별도 시트) + else if (ent is MText mText) + { + textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "MText"; // Type + textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer + textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = mText.Contents; // Text + textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path + textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName + textEntitiesCurrentRow++; + } + } + + processedCount++; + double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0; + progress?.Report(Math.Min(currentProgress, 90.0)); + } + } + + tran.Commit(); + } + } + + progress?.Report(100); + return true; + } + catch (OperationCanceledException) + { + progress?.Report(0); + return false; + } + catch (Teigha.Runtime.Exception ex) + { + progress?.Report(0); + Console.WriteLine($"DWG 파일 처리 중 오류 발생: {ex.Message}"); + return false; + } + catch (System.Exception ex) + { + progress?.Report(0); + Console.WriteLine($"일반 오류 발생: {ex.Message}"); + return false; + } + } + // Paste the helper function from above here + public string GetPromptFromAttributeReference(Transaction tr, BlockReference blockref, string tag) + { + + string prompt = null; + + + BlockTableRecord blockDef = tr.GetObject(blockref.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; + if (blockDef == null) return null; + + foreach (ObjectId objId in blockDef) + { + AttributeDefinition attDef = tr.GetObject(objId, OpenMode.ForRead) as AttributeDefinition; + if (attDef != null) + { + if (attDef.Tag.Equals(tag, System.StringComparison.OrdinalIgnoreCase)) + { + prompt = attDef.Prompt; + break; + } + } + } + + + return prompt; + } + /// + /// 현재 Excel 워크북을 지정된 경로에 저장하고 Excel 애플리케이션을 종료합니다. + /// + /// Excel 파일을 저장할 전체 경로 + public void SaveAndCloseExcel(string savePath) + { + if (workbook1 == null) return; + + try + { + string directory = Path.GetDirectoryName(savePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + workbook1.SaveAs(savePath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); + } + catch (System.Exception ex) + { + Console.WriteLine($"Excel 파일 저장 중 오류 발생: {ex.Message}"); + } + finally + { + CloseExcelObjects(); + } + } + + private void CloseExcelObjects() + { + if (workbook1 != null) + { + try { workbook1.Close(false); } + catch { } + } + if (excelApplication != null) + { + try { excelApplication.Quit(); } + catch { } + } + + ReleaseExcelObjects(); + } + + private void ReleaseExcelObjects() + { + ReleaseComObject(titleBlockSheet); + ReleaseComObject(textEntitiesSheet); + ReleaseComObject(workbook1); + ReleaseComObject(excelApplication); + + titleBlockSheet = null; + textEntitiesSheet = null; + workbook1 = null; + excelApplication = null; + } + + private void ReleaseComObject(object obj) + { + try + { + if (obj != null && Marshal.IsComObject(obj)) + { + Marshal.ReleaseComObject(obj); + } + } + catch (System.Exception) + { + // 해제 중 오류 발생 시 무시 + } + finally + { + obj = null; + } + } + + /// + /// Layer ID로부터 Layer 이름을 가져옵니다. + /// + /// Layer ObjectId + /// 현재 트랜잭션 + /// 데이터베이스 객체 + /// Layer 이름 또는 빈 문자열 + private string GetLayerName(ObjectId layerId, Transaction transaction, Database database) + { + try + { + using (var layerTableRecord = transaction.GetObject(layerId, OpenMode.ForRead) as LayerTableRecord) + { + return layerTableRecord?.Name ?? ""; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"Layer 이름 가져오기 오류: {ex.Message}"); + return ""; + } + } + + public void Dispose() + { + if (excelApplication != null) + { + CloseExcelObjects(); + } + + if (appServices != null) + { + appServices.Dispose(); + appServices = null; + } + } + } +} diff --git a/Models/SqlDatas.cs b/Models/SqlDatas.cs new file mode 100644 index 0000000..085ef34 --- /dev/null +++ b/Models/SqlDatas.cs @@ -0,0 +1,309 @@ +using Microsoft.Office.Interop.Excel; +using Npgsql; +using System.Diagnostics; +using Teigha.DatabaseServices; +using Teigha.Runtime; +using System.Collections.Generic; +using System.IO; +using System; + +namespace DwgExtractorManual.Models +{ + /// + /// DWG 파일에서 PostgreSQL 데이터베이스로 데이터 내보내기 클래스 + /// AttributeReference, AttributeDefinition, DBText, MText 추출 지원 + /// + internal sealed class SqlDatas : IDisposable + { + Services appServices; // ODA 제품 활성화용 + readonly string connectionString = "Host=localhost;Database=postgres;Username=postgres;Password=Qwer1234"; + + void ActivateAndInitializeODA() + { + var userInfo = "c2FtYW4gZW5naW5lZXJpbmc="; + var userSignature = "F0kuQTmtVpHtvl/TgaFVGE92/YqGmYR9SLoXckEjnOk8NoAQh7Sg6GQruVC04JqD4C/IipxJYqpqvMfMc2auRMG+cAJCKqKUE2djIMdkUdb+C5IVx2c97fcK5ba3n8DDvB54Upokajl+6j12yD8h8MKGOR3Z3zysObeXD62bFpQgp00GCYTqlxEZtTIRjHIPAfJfix8Y0jtXWWYyVJ3LYOu86as5xtx+hY1aakpYIJiQk/6pGd84qSn/9K1w8nxR7UrFzieDeQ/xM58BHSD4u/ZxVJwvv6Uy10tsdBFBTvfJMAFp05Y7yeyeCNr100tA3iOfmWoXAVRHfxnkPfiYR54aK04QI+R6OGkI+yd1oR5BtmN6BdDt3z8KYK5EpFGJGiJIGoUy5PvkYdJ2VV6xe9JWBiIJuI/tDn1Y+uyTQFA9qaDHnOURriXsRGfy8reDPf1eejybSJxWKkpilG6RXhq3xHlCkjZzh1Q45S+xYXNGatcWMm9nkn20M8Ke5JEVaI9w/p2GE36CHRtRQbt8kfPmsbWNXJCFr4svHW2MPbCKWoyn5XEyMWBnuAKi74zvczB13DKjf29SqSIgF5k/hwy2QrgvnaKzY1k8bw8w2/k0vJXcS3GKOB/ZYDle1tf/lkAD1HtnF9zE18TiXhVnqwAVjwg4ui1RPLn/LMs6b5Y="; + Services.odActivate(userInfo, userSignature); + appServices = new Services(); + } + + void CreateTables() + { + try + { + using (var conn = new NpgsqlConnection(connectionString)) + { + conn.Open(); + + // AttributeReferences 테이블 생성 (Title Block용) + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = conn; + cmd.CommandText = @" + CREATE TABLE IF NOT EXISTS AttributeReferences ( + Type VARCHAR(100), + Name VARCHAR(255), + Tag VARCHAR(255), + Prompt VARCHAR(255), + Value TEXT, + Path TEXT, + FileName TEXT, + CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + cmd.ExecuteNonQuery(); + } + + // TextEntities 테이블 생성 (DBText/MText용) + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = conn; + cmd.CommandText = @" + CREATE TABLE IF NOT EXISTS TextEntities ( + Type VARCHAR(50), + Layer VARCHAR(255), + Text TEXT, + Path TEXT, + FileName TEXT, + CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + cmd.ExecuteNonQuery(); + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"테이블 생성 중 오류: {ex.Message}"); + throw; + } + } + + public SqlDatas() + { + ActivateAndInitializeODA(); + CreateTables(); + } + + /// + /// DWG 파일 데이터를 데이터베이스에 저장 + /// + /// DWG 파일 경로 + /// 실패시 true, 성공시 false (기존 콘솔 버전과 호환성) + public bool DwgToDB(string filePath) + { + var result = false; + using (var database = new Database(false, true)) + { + try + { + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + + using (var conn = new NpgsqlConnection(connectionString)) + { + conn.Open(); + + using (var tran = database.TransactionManager.StartTransaction()) + { + // Document Information 추출 + DatabaseSummaryInfo summaryInfo = database.SummaryInfo; + string documentInfo = $"Title: {summaryInfo.Title}, Subject: {summaryInfo.Subject}, Author: {summaryInfo.Author}"; + Dictionary dicTagPrompt = new Dictionary(); + + // Block Table 접근 + var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; + using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) + { + foreach (ObjectId entId in btr) + { + using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) + { + // Layer 이름 가져오기 (공통) + string layerName = GetLayerName(ent.LayerId, tran, database); + + // AttributeDefinition 처리는 생략 (필요시 추가) + if (ent is AttributeDefinition attDef) + { + // 현재는 AttributeDefinition 처리하지 않음 + } + // DBText 엔티티 처리 (별도 테이블) + else if (ent is DBText dbText) + { + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = conn; + cmd.CommandText = @" + INSERT INTO TextEntities (Type, Layer, Text, Path, FileName) + VALUES (@Type, @Layer, @Text, @Path, @FileName)"; + + cmd.Parameters.AddWithValue("Type", "DBText"); + cmd.Parameters.AddWithValue("Layer", layerName); + cmd.Parameters.AddWithValue("Text", dbText.TextString ?? ""); + cmd.Parameters.AddWithValue("Path", database.Filename); + cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename)); + + cmd.ExecuteNonQuery(); + } + } + // MText 엔티티 처리 (별도 테이블) + else if (ent is MText mText) + { + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = conn; + cmd.CommandText = @" + INSERT INTO TextEntities (Type, Layer, Text, Path, FileName) + VALUES (@Type, @Layer, @Text, @Path, @FileName)"; + + cmd.Parameters.AddWithValue("Type", "MText"); + cmd.Parameters.AddWithValue("Layer", layerName); + cmd.Parameters.AddWithValue("Text", mText.Contents ?? ""); + cmd.Parameters.AddWithValue("Path", database.Filename); + cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename)); + + cmd.ExecuteNonQuery(); + } + } + else if (ent is BlockReference blr) + { + foreach (ObjectId attId in blr.AttributeCollection) + { + using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) + { + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = conn; + cmd.CommandText = @" + INSERT INTO AttributeReferences (Type, Name, Tag, Prompt, Value, Path, FileName) + VALUES (@Type, @Name, @Tag, @Prompt, @Value, @Path, @FileName)"; + + var typeFullName = attRef.GetType().ToString(); + var typeName = typeFullName.Substring(typeFullName.LastIndexOf('.') + 1); + var blkName = blr.Name; + var attTag = attRef.Tag; + string prompt = ""; + string tString = ""; + + if (!bt.Has(blkName)) + { + continue; + } + + if (dicTagPrompt.ContainsKey(attTag)) + { + prompt = dicTagPrompt[attTag]; + } + else + { + // 블록 정의 찾기 + ObjectId blockDefId = bt[blkName]; + BlockTableRecord blkDef = (BlockTableRecord)tran.GetObject(blockDefId, OpenMode.ForRead); + + // AttributeDefinition 순회 및 태그 일치 확인 + foreach (ObjectId objId in blkDef) + { + DBObject dbObj = tran.GetObject(objId, OpenMode.ForRead); + + if (dbObj is AttributeDefinition adef) + { + if (adef.Tag.ToUpper() == attTag.ToUpper()) + { + prompt = adef.Prompt; + dicTagPrompt.Add(attTag, prompt); + tString = adef.TextString; + break; + } + } + } + } + + cmd.Parameters.AddWithValue("Type", typeName); + cmd.Parameters.AddWithValue("Name", blr.Name); + cmd.Parameters.AddWithValue("Tag", attTag); + cmd.Parameters.AddWithValue("Prompt", prompt); + + if (!string.IsNullOrEmpty(attRef.TextString)) + cmd.Parameters.AddWithValue("Value", attRef.TextString); + else + cmd.Parameters.AddWithValue("Value", tString); + + cmd.Parameters.AddWithValue("Path", database.Filename); + cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename)); + + cmd.ExecuteNonQuery(); + } + } + } + } + } + } + } + + tran.Commit(); + } + } + } + catch (System.Exception ex) + { + result = true; // 실패시 true 반환 (기존 버전과 호환성) + Debug.WriteLine($"DWG 파일 처리 중 오류: {ex.Message}"); + } + } + + return result; + } + + /// + /// 데이터베이스 연결 테스트 + /// + /// 연결 성공 여부 + public bool TestConnection() + { + try + { + using (var conn = new NpgsqlConnection(connectionString)) + { + conn.Open(); + return true; + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"데이터베이스 연결 테스트 실패: {ex.Message}"); + return false; + } + } + + /// + /// Layer ID로부터 Layer 이름을 가져옵니다. + /// + /// Layer ObjectId + /// 현재 트랜잭션 + /// 데이터베이스 객체 + /// Layer 이름 또는 빈 문자열 + private string GetLayerName(ObjectId layerId, Transaction transaction, Database database) + { + try + { + using (var layerTableRecord = transaction.GetObject(layerId, OpenMode.ForRead) as LayerTableRecord) + { + return layerTableRecord?.Name ?? ""; + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"Layer 이름 가져오기 오류: {ex.Message}"); + return ""; + } + } + + /// + /// 리소스 해제 + /// + public void Dispose() + { + if (appServices != null) + { + appServices.Dispose(); + appServices = null; + } + } + } +} diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..19d85b7 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..c603998 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMajor" + } +}