diff --git a/Models/DwgDataExtractor.cs b/Models/DwgDataExtractor.cs index eb4c71f..a8a42b9 100644 --- a/Models/DwgDataExtractor.cs +++ b/Models/DwgDataExtractor.cs @@ -516,10 +516,10 @@ namespace DwgExtractorManual.Models var notePos = noteText.Position; var noteHeight = noteText.Height; - // Note position에서 height * 2 만큼 아래로 수평선 정의 (더 넓은 범위로 검색) + // Note position에서 height * 2 만큼 아래로 수평선 정의 double searchY = notePos.Y - (noteHeight * 2); - var searchLineStart = new Point3d(notePos.X - noteHeight * 20, searchY, 0); - var searchLineEnd = new Point3d(notePos.X + noteHeight * 100, searchY, 0); + var searchLineStart = new Point3d(notePos.X - noteHeight * 10, searchY, 0); + var searchLineEnd = new Point3d(notePos.X + noteHeight * 50, searchY, 0); Debug.WriteLine($"[DEBUG] 교차 검색선: ({searchLineStart.X}, {searchLineStart.Y}) to ({searchLineEnd.X}, {searchLineEnd.Y})"); @@ -568,6 +568,7 @@ namespace DwgExtractorManual.Models return null; } + /// /// 수평선이 Polyline과 교차하는지 확인합니다. /// @@ -667,16 +668,11 @@ namespace DwgExtractorManual.Models if (boxWidth < noteHeight || boxHeight < noteHeight) return false; if (boxWidth > noteHeight * 100 || boxHeight > noteHeight * 100) return false; - // Note 위치와 적절한 거리에 있는지 확인 (더 유연한 검증) + // Note 위치와 적절한 거리에 있는지 확인 + double distanceX = Math.Abs(box.minPoint.X - notePos.X); double distanceY = Math.Abs(box.maxPoint.Y - notePos.Y); - - // Y 거리만 확인 (X 거리는 제거 - 오른쪽에 있는 note box도 허용) - if (distanceY > noteHeight * 15) return false; - - // Note가 박스의 일정 범위 내에 있는지 확인 (X 범위를 더 넓게) - bool isWithinXRange = (notePos.X >= box.minPoint.X - noteHeight * 100) && - (notePos.X <= box.maxPoint.X + noteHeight * 20); - if (!isWithinXRange) return false; + + if (distanceX > noteHeight * 50 || distanceY > noteHeight * 10) return false; return true; } diff --git a/Models/ExportExcel_old.cs b/Models/ExportExcel_old.cs new file mode 100644 index 0000000..b28b721 --- /dev/null +++ b/Models/ExportExcel_old.cs @@ -0,0 +1,2382 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +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; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DwgExtractorManual.Models +{ + /// + /// DWG 파일에서 Excel로 데이터 내보내기 클래스 + /// AttributeReference, AttributeDefinition, DBText, MText 추출 지원 + /// + internal class ExportExcel_old : IDisposable + { + // ODA 서비스 객체 (managed by singleton) + 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 Excel.Workbook mappingWorkbook; // 매핑 데이터용 워크북 + private Excel.Worksheet mappingSheet; // 매핑 데이터용 시트 + + readonly List MapKeys; + + + // 각 시트의 현재 행 번호 + private int titleBlockCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 + private int textEntitiesCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 + private int mappingDataCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 + + // 매핑 데이터 저장용 딕셔너리 - (AILabel, DwgTag, DwgValue, PdfValue) + private Dictionary> FileToMapkeyToLabelTagValuePdf = new Dictionary>(); + + private FieldMapper fieldMapper; + // 생성자: ODA 및 Excel 초기화 + public ExportExcel_old() + { + try + { + Debug.WriteLine("🔄 FieldMapper 로딩 중: mapping_table_json.json..."); + fieldMapper = FieldMapper.LoadFromFile("fletimageanalysis/mapping_table_json.json"); + Debug.WriteLine("✅ FieldMapper 로딩 성공"); + MapKeys = fieldMapper.GetAllDocAiKeys(); + Debug.WriteLine($"📊 총 DocAI 키 개수: {MapKeys?.Count ?? 0}"); + + // 매핑 테스트 (디버깅용) + TestFieldMapper(); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ FieldMapper 로딩 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + + Debug.WriteLine("🔄 ODA 초기화 중..."); + InitializeTeighaServices(); + Debug.WriteLine("🔄 Excel 초기화 중..."); + InitializeExcel(); + } + + // 필드 매퍼 테스트 + private void TestFieldMapper() + { + Debug.WriteLine("[DEBUG] Testing field mapper..."); + + // 몇 가지 알려진 expressway 필드로 테스트 + var testFields = new[] { "TD_DNAME_MAIN", "TD_DWGNO", "TD_DWGCODE", "TB_MTITIL"}; + + foreach (var field in testFields) + { + var aiLabel = fieldMapper.ExpresswayToAilabel(field); + var docAiKey = fieldMapper.AilabelToDocAiKey(aiLabel); + Debug.WriteLine($"[DEBUG] Field: {field} -> AILabel: {aiLabel} -> DocAiKey: {docAiKey}"); + } + } + + // Teigha 서비스 초기화 (싱글톤 사용) + private void InitializeTeighaServices() + { + try + { + Debug.WriteLine("[DEBUG] TeighaServicesManager를 통한 Services 획득 중..."); + appServices = TeighaServicesManager.Instance.AcquireServices(); + Debug.WriteLine($"[DEBUG] Services 획득 성공. Reference Count: {TeighaServicesManager.Instance.ReferenceCount}"); + } + catch (Teigha.Runtime.Exception ex) + { + Debug.WriteLine($"[DEBUG] Teigha Services 초기화 실패: {ex.Message}"); + throw; + } + } + + // Excel 애플리케이션 및 워크시트 초기화 + private void InitializeExcel() + { + try + { + var excelApp = new Excel.Application(); + excelApplication = excelApp; + excelApplication.Visible = false; // WPF에서는 숨김 처리 + + + // 매핑 데이터용 워크북 및 시트 생성 + mappingWorkbook = excelApp.Workbooks.Add(); + mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets[1]; + mappingSheet.Name = "Mapping Data"; + SetupMappingHeaders(); + + + //Excel.Workbook workbook = excelApp.Workbooks.Add(); + //workbook1 = workbook; + + // Title Block Sheet 설정 (기본 Sheet1) + + titleBlockSheet = (Excel.Worksheet)mappingWorkbook.Sheets.Add(); + titleBlockSheet = (Excel.Worksheet)mappingWorkbook.Sheets[2]; + titleBlockSheet.Name = "Title Block"; + SetupTitleBlockHeaders(); + + // Text Entities Sheet 추가 + textEntitiesSheet = (Excel.Worksheet)mappingWorkbook.Sheets.Add(); + textEntitiesSheet = (Excel.Worksheet)mappingWorkbook.Sheets[3]; + textEntitiesSheet.Name = "Text Entities"; + SetupTextEntitiesHeaders(); + + + + + + + } + catch (System.Exception ex) + { + Debug.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); + } + + // 매핑 데이터 시트 헤더 설정 + private void SetupMappingHeaders() + { + mappingSheet.Cells[1, 1] = "FileName"; // 파일 이름 + mappingSheet.Cells[1, 2] = "MapKey"; // 매핑 키 + mappingSheet.Cells[1, 3] = "AILabel"; // AI 라벨 + mappingSheet.Cells[1, 4] = "DwgTag"; // DWG Tag + mappingSheet.Cells[1, 5] = "Att_value"; // DWG 값 + mappingSheet.Cells[1, 6] = "Pdf_value"; // PDF 값 (현재는 빈 값) + + // 헤더 행 스타일 + Excel.Range headerRange = mappingSheet.Range["A1:F1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); + } + + /// + /// 단일 DWG 파일에서 AttributeReference/AttributeDefinition 데이터를 추출하여 + /// 초기화된 Excel 워크시트에 추가합니다. + /// + /// 처리할 DWG 파일 경로 + /// 진행 상태 보고를 위한 IProgress 객체 + /// 작업 취소를 위한 CancellationToken + /// 성공 시 true, 실패 시 false 반환 + public bool ExportDwgToExcel(string filePath, IProgress progress = null, CancellationToken cancellationToken = default, bool isExportNote =false) + { + Debug.WriteLine($"[DEBUG] ExportDwgToExcel 시작: {filePath}"); + + if (excelApplication == null) + { + Debug.WriteLine("❌ Excel이 초기화되지 않았습니다."); + return false; + } + + if (!File.Exists(filePath)) + { + Debug.WriteLine($"❌ 파일이 존재하지 않습니다: {filePath}"); + return false; + } + + try + { + Debug.WriteLine("[DEBUG] 진행률 0% 보고"); + progress?.Report(0); + cancellationToken.ThrowIfCancellationRequested(); + + Debug.WriteLine("[DEBUG] ODA Database 객체 생성 중..."); + // ODA Database 객체 생성 및 DWG 파일 읽기 + using (var database = new Database(false, true)) + { + Debug.WriteLine($"[DEBUG] DWG 파일 읽기 시도: {filePath}"); + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + Debug.WriteLine("[DEBUG] DWG 파일 읽기 성공"); + + cancellationToken.ThrowIfCancellationRequested(); + Debug.WriteLine("[DEBUG] 진행률 10% 보고"); + progress?.Report(10); + + Debug.WriteLine("[DEBUG] 트랜잭션 시작 중..."); + using (var tran = database.TransactionManager.StartTransaction()) + { + Debug.WriteLine("[DEBUG] BlockTable 접근 중..."); + var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; + Debug.WriteLine("[DEBUG] ModelSpace 접근 중..."); + using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) + { + int totalEntities = btr.Cast().Count(); + Debug.WriteLine($"[DEBUG] 총 엔티티 수: {totalEntities}"); + 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); + var fileName = Path.GetFileNameWithoutExtension(database.Filename); + + // 파일명 유효성 검사 + if (string.IsNullOrEmpty(fileName)) + { + fileName = "Unknown_File"; + Debug.WriteLine($"[DEBUG] Using default filename: {fileName}"); + } + + // 파일별 매핑 딕셔너리 초기화 + if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName)) + { + FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary(); + } + + // 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++; + + var tag = attDef.Tag; + var aiLabel = fieldMapper.ExpresswayToAilabel(tag); + var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel); + var attValue = attDef.TextString; + Debug.WriteLine($"[DEBUG] AttributeDefinition - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}"); + + // 매핑 데이터 저장 + if (!string.IsNullOrEmpty(aiLabel)) + { + // mapKey가 null이면 aiLabel을 mapKey로 사용 + var finalMapKey = mapKey ?? aiLabel; + FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, ""); + Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})"); + } + else + { + // aiLabel이 null이면 tag를 사용하여 저장 + var finalMapKey = mapKey ?? tag; + if (!string.IsNullOrEmpty(finalMapKey)) + { + FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, ""); + Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})"); + } + else + { + Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeDefinition"); + } + } + } + // 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++; + + var tag = attRef.Tag; + var aiLabel = fieldMapper.ExpresswayToAilabel(tag); + if (aiLabel == null) continue; + var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel); + var attValue = attRef.TextString; + + Debug.WriteLine($"[DEBUG] AttributeReference - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}"); + + // 매핑 데이터 저장 + if (!string.IsNullOrEmpty(aiLabel)) + { + // mapKey가 null이면 aiLabel을 mapKey로 사용 + var finalMapKey = mapKey ?? aiLabel; + FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, ""); + Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})"); + } + else + { + // aiLabel이 null이면 tag를 사용하여 저장 + var finalMapKey = mapKey ?? tag; + if (!string.IsNullOrEmpty(finalMapKey)) + { + FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, ""); + Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})"); + } + else + { + Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeReference"); + } + } + } + } + } + } + // 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++; + } + } + + // write values in new excel file with FileToMapkeyToLabelTag + + processedCount++; + double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0; + progress?.Report(Math.Min(currentProgress, 90.0)); + } + + if (isExportNote) + { + Debug.WriteLine("[DEBUG] Note 추출 시작..."); + ExtractNotesFromDrawing(tran, btr, database, progress, cancellationToken); + Debug.WriteLine("[DEBUG] Note 추출 완료"); + } + } + tran.Commit(); + } + + Debug.WriteLine($"[DEBUG] Transaction committed. Total mapping entries collected: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); + + // 매핑 데이터를 Excel 시트에 기록 + Debug.WriteLine("[DEBUG] 매핑 데이터를 Excel에 기록 중..."); + WriteMappingDataToExcel(); + Debug.WriteLine("[DEBUG] 매핑 데이터 Excel 기록 완료"); + } + + progress?.Report(100); + return true; + } + catch (OperationCanceledException) + { + Debug.WriteLine("❌ 작업이 취소되었습니다."); + progress?.Report(0); + return false; + } + catch (Teigha.Runtime.Exception ex) + { + progress?.Report(0); + Debug.WriteLine($"❌ DWG 파일 처리 중 Teigha 오류 발생:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" ErrorStatus: {ex.ErrorStatus}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + return false; + } + catch (System.Exception ex) + { + progress?.Report(0); + Debug.WriteLine($"❌ 일반 오류 발생:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + return false; + } + } + /// + /// 도면에서 Note와 관련된 텍스트들을 추출합니다. + /// + private void ExtractNotesFromDrawing(Transaction tran, BlockTableRecord btr, Database database, + IProgress progress, CancellationToken cancellationToken) + { + try + { + var allEntities = btr.Cast().ToList(); + var dbTextIds = new List(); + var polylineIds = new List(); + var lineIds = new List(); + + // 먼저 모든 관련 엔터티들의 ObjectId를 수집 + foreach (ObjectId entId in allEntities) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) + { + if (ent is DBText) + { + dbTextIds.Add(entId); + } + else if (ent is Polyline) + { + polylineIds.Add(entId); + } + else if (ent is Line) + { + lineIds.Add(entId); + } + } + } + + Debug.WriteLine($"[DEBUG] 수집된 엔터티: DBText={dbTextIds.Count}, Polyline={polylineIds.Count}, Line={lineIds.Count}"); + + // Note 텍스트들을 찾기 + var noteTextIds = FindNoteTexts(tran, dbTextIds); + Debug.WriteLine($"[DEBUG] 발견된 Note 텍스트: {noteTextIds.Count}개"); + + // 각 Note에 대해 처리 + foreach (var noteTextId in noteTextIds) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText) + { + Debug.WriteLine($"[DEBUG] Note 처리 중: '{noteText.TextString}' at {noteText.Position}"); + + // Note 우측아래에 있는 박스 찾기 + var noteBox = FindNoteBox(tran, noteText, polylineIds, lineIds); + + if (noteBox.HasValue) + { + Debug.WriteLine($"[DEBUG] Note 박스 발견: {noteBox.Value.minPoint} to {noteBox.Value.maxPoint}"); + + // 박스 내부의 텍스트들 찾기 + var boxTextIds = FindTextsInNoteBox(tran, noteText, noteBox.Value, dbTextIds); + Debug.WriteLine($"[DEBUG] 박스 내 텍스트: {boxTextIds.Count}개"); + + // 결과를 Excel에 기록 + WriteNoteDataToExcel(tran, noteTextId, boxTextIds, database); + } + else + { + Debug.WriteLine($"[DEBUG] Note '{noteText.TextString}'에 대한 박스를 찾을 수 없음"); + } + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Note 추출 중 오류: {ex.Message}"); + throw; + } + } + /// + /// DBText 중에서 "Note"가 포함된 텍스트들을 찾습니다. + /// + private List FindNoteTexts(Transaction tran, List dbTextIds) + { + var noteTextIds = new List(); + + foreach (var textId in dbTextIds) + { + using (var dbText = tran.GetObject(textId, OpenMode.ForRead) as DBText) + { + if (dbText == null) continue; + + string textContent = dbText.TextString?.Trim() ?? ""; + + // 대소문자 구분없이 "Note"가 포함되어 있는지 확인 + if (textContent.IndexOf("Note", StringComparison.OrdinalIgnoreCase) >= 0) + { + // 색상 확인 (마젠타색인지) + //if (IsNoteColor(dbText)) + //{ + noteTextIds.Add(textId); + Debug.WriteLine($"[DEBUG] Note 텍스트 발견: '{textContent}' at {dbText.Position}, Color: {dbText.Color}"); + //} + } + } + } + + return noteTextIds; + } + /// + /// DBText 중에서 "Note"가 포함된 텍스트들을 찾습니다. + /// + /// + /// Note 우측아래에 있는 박스를 찾습니다 (교차선 기반 알고리즘). + /// Note position에서 height*2 만큼 아래로 수평선을 그어서 교차하는 Line/Polyline을 찾습니다. + /// + private (Point3d minPoint, Point3d maxPoint)? FindNoteBox( + Transaction tran, DBText noteText, List polylineIds, List lineIds) + { + var notePos = noteText.Position; + var noteHeight = noteText.Height; + + // Note position에서 height * 2 만큼 아래로 수평선 정의 + double searchY = notePos.Y - (noteHeight * 2); + var searchLineStart = new Point3d(notePos.X - noteHeight * 10, searchY, 0); + var searchLineEnd = new Point3d(notePos.X + noteHeight * 50, searchY, 0); + + Debug.WriteLine($"[DEBUG] 교차 검색선: ({searchLineStart.X}, {searchLineStart.Y}) to ({searchLineEnd.X}, {searchLineEnd.Y})"); + + // 1. Polyline과 교차하는지 확인 + foreach (var polylineId in polylineIds) + { + using (var polyline = tran.GetObject(polylineId, OpenMode.ForRead) as Polyline) + { + if (polyline == null) continue; + + // 교차점이 있는지 확인 + if (DoesLineIntersectPolyline(searchLineStart, searchLineEnd, polyline)) + { + var box = GetPolylineBounds(polyline); + if (box.HasValue && IsValidNoteBox(box.Value, notePos, noteHeight)) + { + Debug.WriteLine($"[DEBUG] 교차하는 Polyline 박스 발견: {box.Value.minPoint} to {box.Value.maxPoint}"); + return box; + } + } + } + } + + // 2. Line들과 교차하여 닫힌 사각형 찾기 + var intersectingLines = FindIntersectingLines(tran, lineIds, searchLineStart, searchLineEnd); + Debug.WriteLine($"[DEBUG] 교차하는 Line 수: {intersectingLines.Count}"); + + foreach (var startLineId in intersectingLines) + { + var rectangle = TraceRectangleFromLine(tran, lineIds, startLineId, notePos, noteHeight); + if (rectangle.HasValue) + { + Debug.WriteLine($"[DEBUG] 교차하는 Line 사각형 발견: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}"); + return rectangle; + } + } + + return null; + } + /// + /// 수평선이 Polyline과 교차하는지 확인합니다. + /// + private bool DoesLineIntersectPolyline(Point3d lineStart, Point3d lineEnd, Polyline polyline) + { + try + { + for (int i = 0; i < polyline.NumberOfVertices; i++) + { + int nextIndex = (i + 1) % polyline.NumberOfVertices; + var segStart = polyline.GetPoint3dAt(i); + var segEnd = polyline.GetPoint3dAt(nextIndex); + + // 수평선과 폴리라인 세그먼트의 교차점 확인 + if (DoLinesIntersect(lineStart, lineEnd, segStart, segEnd)) + { + return true; + } + } + return false; + } + catch + { + return false; + } + } + /// + /// 두 선분이 교차하는지 확인합니다. + /// + private bool DoLinesIntersect(Point3d line1Start, Point3d line1End, Point3d line2Start, Point3d line2End) + { + try + { + double x1 = line1Start.X, y1 = line1Start.Y; + double x2 = line1End.X, y2 = line1End.Y; + double x3 = line2Start.X, y3 = line2Start.Y; + double x4 = line2End.X, y4 = line2End.Y; + + double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + if (Math.Abs(denom) < 1e-10) return false; // 평행선 + + double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; + double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; + + return t >= 0 && t <= 1 && u >= 0 && u <= 1; + } + catch + { + return false; + } + } + /// + /// Polyline의 경계 상자를 계산합니다. + /// + private (Point3d minPoint, Point3d maxPoint)? GetPolylineBounds(Polyline polyline) + { + try + { + if (polyline.NumberOfVertices < 3) return null; + + var vertices = new List(); + for (int i = 0; i < polyline.NumberOfVertices; i++) + { + vertices.Add(polyline.GetPoint3dAt(i)); + } + + double minX = vertices.Min(v => v.X); + double maxX = vertices.Max(v => v.X); + double minY = vertices.Min(v => v.Y); + double maxY = vertices.Max(v => v.Y); + + return (new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); + } + catch + { + return null; + } + } + + /// + /// 박스가 유효한 Note 박스인지 확인합니다. + /// + private bool IsValidNoteBox((Point3d minPoint, Point3d maxPoint) box, Point3d notePos, double noteHeight) + { + try + { + // 박스가 Note 아래쪽에 있는지 확인 + if (box.maxPoint.Y >= notePos.Y) return false; + + // 박스 크기가 적절한지 확인 + double boxWidth = box.maxPoint.X - box.minPoint.X; + double boxHeight = box.maxPoint.Y - box.minPoint.Y; + + // 너무 작거나 큰 박스는 제외 + if (boxWidth < noteHeight || boxHeight < noteHeight) return false; + if (boxWidth > noteHeight * 100 || boxHeight > noteHeight * 100) return false; + + // Note 위치와 적절한 거리에 있는지 확인 + double distanceX = Math.Abs(box.minPoint.X - notePos.X); + double distanceY = Math.Abs(box.maxPoint.Y - notePos.Y); + + if (distanceX > noteHeight * 50 || distanceY > noteHeight * 10) return false; + + return true; + } + catch + { + return false; + } + } + + /// + /// 수평선과 교차하는 Line들을 찾습니다. + /// + private List FindIntersectingLines(Transaction tran, List lineIds, Point3d searchLineStart, Point3d searchLineEnd) + { + var intersectingLines = new List(); + + foreach (var lineId in lineIds) + { + using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line) + { + if (line == null) continue; + + if (DoLinesIntersect(searchLineStart, searchLineEnd, line.StartPoint, line.EndPoint)) + { + intersectingLines.Add(lineId); + Debug.WriteLine($"[DEBUG] 교차 Line 발견: ({line.StartPoint.X}, {line.StartPoint.Y}) to ({line.EndPoint.X}, {line.EndPoint.Y})"); + } + } + } + + return intersectingLines; + } + + /// + /// Line에서 시작하여 반시계방향으로 사각형을 추적합니다. + /// + private (Point3d minPoint, Point3d maxPoint)? TraceRectangleFromLine(Transaction tran, List lineIds, ObjectId startLineId, Point3d notePos, double noteHeight) + { + try + { + const double tolerance = 2.0; // 좌표 오차 허용 범위 + var visitedLines = new HashSet(); + var rectanglePoints = new List(); + + using (var startLine = tran.GetObject(startLineId, OpenMode.ForRead) as Line) + { + if (startLine == null) return null; + + var currentPoint = startLine.StartPoint; + var nextPoint = startLine.EndPoint; + rectanglePoints.Add(currentPoint); + rectanglePoints.Add(nextPoint); + visitedLines.Add(startLineId); + + Debug.WriteLine($"[DEBUG] 사각형 추적 시작: ({currentPoint.X}, {currentPoint.Y}) -> ({nextPoint.X}, {nextPoint.Y})"); + + // 최대 4개의 Line으로 사각형 완성 시도 + for (int step = 0; step < 3; step++) // 시작 Line 제외하고 3개 더 찾기 + { + var nextLineId = FindNextConnectedLine(tran, lineIds, nextPoint, visitedLines, tolerance); + if (nextLineId == ObjectId.Null) break; + + using (var nextLine = tran.GetObject(nextLineId, OpenMode.ForRead) as Line) + { + if (nextLine == null) break; + + // 현재 점과 가까운 쪽을 시작점으로 설정 + Point3d lineStart, lineEnd; + if (nextPoint.DistanceTo(nextLine.StartPoint) < nextPoint.DistanceTo(nextLine.EndPoint)) + { + lineStart = nextLine.StartPoint; + lineEnd = nextLine.EndPoint; + } + else + { + lineStart = nextLine.EndPoint; + lineEnd = nextLine.StartPoint; + } + + rectanglePoints.Add(lineEnd); + visitedLines.Add(nextLineId); + nextPoint = lineEnd; + + Debug.WriteLine($"[DEBUG] 다음 Line 추가: ({lineStart.X}, {lineStart.Y}) -> ({lineEnd.X}, {lineEnd.Y})"); + + // 시작점으로 돌아왔는지 확인 (사각형 완성) + if (nextPoint.DistanceTo(currentPoint) < tolerance) + { + Debug.WriteLine("[DEBUG] 사각형 완성됨"); + break; + } + } + } + + // 4개 이상의 점이 있고 닫힌 형태인지 확인 + if (rectanglePoints.Count >= 4) + { + var bounds = CalculateBounds(rectanglePoints); + if (bounds.HasValue && IsValidNoteBox(bounds.Value, notePos, noteHeight)) + { + return bounds; + } + } + } + + return null; + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] 사각형 추적 중 오류: {ex.Message}"); + return null; + } + } + + /// + /// 현재 점에서 연결된 다음 Line을 찾습니다. + /// + private ObjectId FindNextConnectedLine(Transaction tran, List lineIds, Point3d currentPoint, HashSet visitedLines, double tolerance) + { + foreach (var lineId in lineIds) + { + if (visitedLines.Contains(lineId)) continue; + + using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line) + { + if (line == null) continue; + + // 현재 점과 연결되어 있는지 확인 + if (currentPoint.DistanceTo(line.StartPoint) < tolerance || + currentPoint.DistanceTo(line.EndPoint) < tolerance) + { + return lineId; + } + } + } + + return ObjectId.Null; + } + + /// + /// 점들의 경계 상자를 계산합니다. + /// + private (Point3d minPoint, Point3d maxPoint)? CalculateBounds(List points) + { + try + { + if (points.Count < 3) return null; + + double minX = points.Min(p => p.X); + double maxX = points.Max(p => p.X); + double minY = points.Min(p => p.Y); + double maxY = points.Max(p => p.Y); + + return (new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); + } + catch + { + return null; + } + } + /// + /// Note 텍스트인지 색상으로 확인합니다 (마젠타색). + /// + private bool IsNoteColor(DBText text) + { + try + { + // AutoCAD에서 마젠타는 ColorIndex 6번 + if (text.Color.ColorIndex == 6) // Magenta + return true; + + // RGB 값으로도 확인 (마젠타: R=255, G=0, B=255) + if (text.Color.Red == 255 && text.Color.Green == 0 && text.Color.Blue == 255) + return true; + + return false; + } + catch + { + return false; + } + } + + /// + /// Note 우측아래에 있는 박스를 찾습니다 (Polyline 또는 Line 4개). + /// + + + /// + /// Polyline이 Note 박스인지 확인하고 경계를 반환합니다. + /// + private (Point3d minPoint, Point3d maxPoint)? GetPolylineBox(Polyline polyline, Point3d notePos, double noteHeight) + { + try + { + if (polyline.NumberOfVertices < 4) return null; + + var vertices = new List(); + for (int i = 0; i < polyline.NumberOfVertices; i++) + { + vertices.Add(polyline.GetPoint3dAt(i)); + } + + // 경계 계산 + double minX = vertices.Min(v => v.X); + double maxX = vertices.Max(v => v.X); + double minY = vertices.Min(v => v.Y); + double maxY = vertices.Max(v => v.Y); + + var minPoint = new Point3d(minX, minY, 0); + var maxPoint = new Point3d(maxX, maxY, 0); + + // Note 우측아래에 있는지 확인 + if (minX > notePos.X && maxY < notePos.Y) + { + // 박스가 너무 크거나 작지 않은지 확인 + double boxWidth = maxX - minX; + double boxHeight = maxY - minY; + + if (boxWidth > noteHeight && boxHeight > noteHeight && + boxWidth < noteHeight * 50 && boxHeight < noteHeight * 50) + { + return (minPoint, maxPoint); + } + } + + return null; + } + catch + { + return null; + } + } + + /// + /// Line들로부터 사각형 박스를 찾습니다. + /// + private (Point3d minPoint, Point3d maxPoint)? FindRectangleFromLines( + Transaction tran, List lineIds, Point3d notePos, double noteHeight) + { + try + { + // Note 우측아래 영역의 Line들만 필터링 + var candidateLineIds = new List(); + + foreach (var lineId in lineIds) + { + using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line) + { + if (line == null) continue; + + var startPoint = line.StartPoint; + var endPoint = line.EndPoint; + + // Note 우측아래에 있는 Line인지 확인 + if ((startPoint.X > notePos.X && startPoint.Y < notePos.Y) || + (endPoint.X > notePos.X && endPoint.Y < notePos.Y)) + { + candidateLineIds.Add(lineId); + } + } + } + + Debug.WriteLine($"[DEBUG] 후보 Line 수: {candidateLineIds.Count}"); + + // 4개의 Line으로 사각형을 구성할 수 있는지 확인 + var rectangles = FindRectanglesFromLines(tran, candidateLineIds, noteHeight); + + foreach (var rect in rectangles) + { + // Note 우측아래에 있는 사각형인지 확인 + if (rect.minPoint.X > notePos.X && rect.maxPoint.Y < notePos.Y) + { + return rect; + } + } + + return null; + } + catch + { + return null; + } + } + + /// + /// Line들로부터 가능한 사각형들을 찾습니다. + /// + private List<(Point3d minPoint, Point3d maxPoint)> FindRectanglesFromLines( + Transaction tran, List lineIds, double noteHeight) + { + var rectangles = new List<(Point3d minPoint, Point3d maxPoint)>(); + + try + { + // 모든 Line의 끝점들을 수집 + var points = new HashSet(); + var lineData = new List<(Point3d start, Point3d end)>(); + + foreach (var lineId in lineIds) + { + using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line) + { + if (line == null) continue; + + points.Add(line.StartPoint); + points.Add(line.EndPoint); + lineData.Add((line.StartPoint, line.EndPoint)); + } + } + + var pointList = points.ToList(); + + // 4개 점으로 사각형을 만들 수 있는지 확인 + for (int i = 0; i < pointList.Count - 3; i++) + { + for (int j = i + 1; j < pointList.Count - 2; j++) + { + for (int k = j + 1; k < pointList.Count - 1; k++) + { + for (int l = k + 1; l < pointList.Count; l++) + { + var rect = TryFormRectangle(new[] { pointList[i], pointList[j], pointList[k], pointList[l] }, lineData); + if (rect.HasValue) + { + rectangles.Add(rect.Value); + } + } + } + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] 사각형 찾기 중 오류: {ex.Message}"); + } + + return rectangles; + } + + /// + /// 4개의 점이 사각형을 형성하는지 확인합니다. + /// + private (Point3d minPoint, Point3d maxPoint)? TryFormRectangle(Point3d[] points, List<(Point3d start, Point3d end)> lineData) + { + try + { + if (points.Length != 4) return null; + + // 점들을 정렬하여 사각형인지 확인 + var sortedPoints = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToArray(); + + double minX = sortedPoints.Min(p => p.X); + double maxX = sortedPoints.Max(p => p.X); + double minY = sortedPoints.Min(p => p.Y); + double maxY = sortedPoints.Max(p => p.Y); + + // 실제 사각형의 4개 모서리가 Line으로 연결되어 있는지 확인 + var expectedEdges = new[] + { + (new Point3d(minX, minY, 0), new Point3d(maxX, minY, 0)), // 아래 + (new Point3d(maxX, minY, 0), new Point3d(maxX, maxY, 0)), // 오른쪽 + (new Point3d(maxX, maxY, 0), new Point3d(minX, maxY, 0)), // 위 + (new Point3d(minX, maxY, 0), new Point3d(minX, minY, 0)) // 왼쪽 + }; + + int foundEdges = 0; + foreach (var edge in expectedEdges) + { + if (HasLine(lineData, edge.Item1, edge.Item2)) + { + foundEdges++; + } + } + + // 4개 모서리가 모두 있으면 사각형 + if (foundEdges >= 3) // 완전하지 않을 수도 있으므로 3개 이상 + { + return (new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); + } + + return null; + } + catch + { + return null; + } + } + + /// + /// 두 점을 연결하는 Line이 있는지 확인합니다. + /// + private bool HasLine(List<(Point3d start, Point3d end)> lineData, Point3d point1, Point3d point2) + { + const double tolerance = 0.1; // 허용 오차 + + foreach (var line in lineData) + { + // 순방향 확인 + if (point1.DistanceTo(line.start) < tolerance && point2.DistanceTo(line.end) < tolerance) + return true; + + // 역방향 확인 + if (point1.DistanceTo(line.end) < tolerance && point2.DistanceTo(line.start) < tolerance) + return true; + } + + return false; + } + + /// + /// Note 박스 내부의 텍스트들을 찾습니다. + /// + private List FindTextsInNoteBox( + Transaction tran, DBText noteText, (Point3d minPoint, Point3d maxPoint) noteBox, List allTextIds) + { + var boxTextIds = new List(); + var noteHeight = noteText.Height; + + foreach (var textId in allTextIds) + { + // Note 자신은 제외 + if (textId == noteText.ObjectId) continue; + + using (var dbText = tran.GetObject(textId, OpenMode.ForRead) as DBText) + { + if (dbText == null) continue; + + var textPos = dbText.Position; + + // 박스 내부에 있는지 확인 + if (textPos.X >= noteBox.minPoint.X && textPos.X <= noteBox.maxPoint.X && + textPos.Y >= noteBox.minPoint.Y && textPos.Y <= noteBox.maxPoint.Y) + { + // Note의 height보다 작거나 같은지 확인 + if (dbText.Height <= noteHeight) + { + // 색상 확인 (그린색인지) + //if (IsBoxTextColor(dbText)) + //{ + boxTextIds.Add(textId); + Debug.WriteLine($"[DEBUG] 박스 내 텍스트 발견: '{dbText.TextString}' at {textPos}"); + //} + } + } + } + } + + return boxTextIds; + } + + /// + /// 박스 내 텍스트 색상인지 확인합니다 (그린색). + /// + private bool IsBoxTextColor(DBText text) + { + try + { + // AutoCAD에서 그린은 ColorIndex 3번 + if (text.Color.ColorIndex == 3) // Green + return true; + + // RGB 값으로도 확인 (그린: R=0, G=255, B=0) + if (text.Color.Red == 0 && text.Color.Green == 255 && text.Color.Blue == 0) + return true; + + return false; + } + catch + { + return false; + } + } + + /// + /// Note 데이터를 Excel에 기록합니다. + /// + private void WriteNoteDataToExcel( + Transaction tran, ObjectId noteTextId, List boxTextIds, Database database) + { + try + { + // Note 정보 기록 + using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText) + { + if (noteText != null) + { + textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "Note"; // Type + textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = GetLayerName(noteText.LayerId, tran, database); // Layer + textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = noteText.TextString; // Text + textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path + textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName + textEntitiesCurrentRow++; + } + } + + // 박스 내 텍스트들 기록 + foreach (var boxTextId in boxTextIds) + { + using (var boxText = tran.GetObject(boxTextId, OpenMode.ForRead) as DBText) + { + if (boxText != null) + { + textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "NoteContent"; // Type + textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = GetLayerName(boxText.LayerId, tran, database); // Layer + textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = boxText.TextString; // Text + textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path + textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName + textEntitiesCurrentRow++; + } + } + } + + using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText) + { + if (noteText != null) + { + Debug.WriteLine($"[DEBUG] Note 데이터 Excel 기록 완료: Note='{noteText.TextString}', Content={boxTextIds.Count}개"); + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Note 데이터 Excel 기록 중 오류: {ex.Message}"); + } + } + // 매핑 데이터를 Excel 시트에 기록 + private void WriteMappingDataToExcel() + { + try + { + int currentRow = 2; // 헤더 다음 행부터 시작 + + Debug.WriteLine($"[DEBUG] Writing mapping data to Excel. Total files: {FileToMapkeyToLabelTagValuePdf.Count}"); + Debug.WriteLine($"[DEBUG] 시작 행: {currentRow}"); + + foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) + { + string fileName = fileEntry.Key; + var mappingData = fileEntry.Value; + + Debug.WriteLine($"[DEBUG] Processing file: {fileName}, entries: {mappingData.Count}"); + + foreach (var mapEntry in mappingData) + { + string mapKey = mapEntry.Key; + (string aiLabel, string dwgTag, string attValue, string pdfValue) = mapEntry.Value; + + // null 값 방지 + if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(mapKey)) + { + Debug.WriteLine($"[DEBUG] Skipping entry with null/empty values: fileName={fileName}, mapKey={mapKey}"); + continue; + } + + Debug.WriteLine($"[DEBUG] Writing row {currentRow}: {fileName} | {mapKey} | {aiLabel} | {dwgTag} | PDF: {pdfValue}"); + + try + { + // 배치 업데이트를 위한 배열 사용 + object[,] rowData = new object[1, 6]; + rowData[0, 0] = fileName; // FileName + rowData[0, 1] = mapKey; // MapKey + rowData[0, 2] = aiLabel ?? ""; // AILabel + rowData[0, 3] = dwgTag ?? ""; // DwgTag + rowData[0, 4] = attValue ?? ""; // DwgValue (Att_value) + rowData[0, 5] = pdfValue ?? ""; // PdfValue (Pdf_value) + + Excel.Range range = mappingSheet.Range[mappingSheet.Cells[currentRow, 1], mappingSheet.Cells[currentRow, 6]]; + range.Value = rowData; + + Debug.WriteLine($"[DEBUG] Row {currentRow} written successfully"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Error writing row {currentRow}: {ex.Message}"); + } + + currentRow++; + } + } + + Debug.WriteLine($"[DEBUG] Mapping data written to Excel. Total rows: {currentRow - 2}"); + Debug.WriteLine($"[DEBUG] 최종 행 번호: {currentRow}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 매핑 데이터 Excel 기록 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; // 상위 메서드로 예외 전파 + } + } + + /// + /// 기존 Excel 파일을 열어 JSON 파일의 PDF 분석 결과로 매핑 시트를 업데이트합니다. + /// + /// 기존 Excel 파일 경로 + /// PDF 분석 결과 JSON 파일 경로 + /// 성공 시 true, 실패 시 false + public bool UpdateExistingExcelWithJson(string excelFilePath, string jsonFilePath) + { + try + { + Debug.WriteLine($"[DEBUG] 기존 Excel 파일 업데이트 시작: {excelFilePath}"); + + if (!File.Exists(excelFilePath)) + { + Debug.WriteLine($"❌ Excel 파일이 존재하지 않습니다: {excelFilePath}"); + return false; + } + + if (!File.Exists(jsonFilePath)) + { + Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); + return false; + } + + Debug.WriteLine($"[DEBUG] Excel 애플리케이션 초기화 중..."); + + // 기존 Excel 파일 열기 + if (excelApplication == null) + { + excelApplication = new Excel.Application(); + excelApplication.Visible = false; + Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨"); + } + + Debug.WriteLine($"[DEBUG] Excel 파일 열기 시도: {excelFilePath}"); + mappingWorkbook = excelApplication.Workbooks.Open(excelFilePath); + Debug.WriteLine("[DEBUG] Excel 파일 열기 성공"); + + Debug.WriteLine("[DEBUG] 'Mapping Data' 시트 찾는 중..."); + mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets["Mapping Data"]; + + if (mappingSheet == null) + { + Debug.WriteLine("❌ 'Mapping Data' 시트를 찾을 수 없습니다."); + // 사용 가능한 시트 목록 출력 + Debug.WriteLine("사용 가능한 시트:"); + foreach (Excel.Worksheet sheet in mappingWorkbook.Sheets) + { + Debug.WriteLine($" - {sheet.Name}"); + } + return false; + } + + Debug.WriteLine("✅ 기존 Excel 파일 열기 성공"); + + // JSON에서 PDF 값 업데이트 + Debug.WriteLine("[DEBUG] JSON 파싱 및 업데이트 시작"); + bool result = UpdateMappingSheetFromJson(jsonFilePath); + + Debug.WriteLine($"[DEBUG] 업데이트 결과: {result}"); + return result; + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 기존 Excel 파일 업데이트 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + return false; + } + } + + /// + /// JSON 파일에서 PDF 분석 결과를 읽어 Excel 매핑 시트의 Pdf_value 컬럼을 업데이트합니다. + /// + /// PDF 분석 결과 JSON 파일 경로 + /// 성공 시 true, 실패 시 false + public bool UpdateMappingSheetFromJson(string jsonFilePath) + { + try + { + Debug.WriteLine($"[DEBUG] JSON 파일에서 PDF 값 업데이트 시작: {jsonFilePath}"); + + if (!File.Exists(jsonFilePath)) + { + Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); + return false; + } + + if (mappingSheet == null) + { + Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다."); + return false; + } + + // JSON 파일 읽기 및 정리 + string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8); + Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes"); + + // JSON 내용 정리 (주석 제거 등) + jsonContent = CleanJsonContent(jsonContent); + + JObject jsonData; + try + { + jsonData = JObject.Parse(jsonContent); + } + catch (Newtonsoft.Json.JsonReaderException jsonEx) + { + Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); + Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); + Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); + throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}"); + } + + var results = jsonData["results"] as JArray; + if (results == null) + { + Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다."); + Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}"); + return false; + } + + int updatedCount = 0; + int totalEntries = 0; + + foreach (JObject result in results) + { + var fileInfo = result["file_info"]; + var pdfAnalysis = result["pdf_analysis"]; + + if (fileInfo == null || pdfAnalysis == null) continue; + + string fileName = fileInfo["name"]?.ToString(); + if (string.IsNullOrEmpty(fileName)) continue; + + // 파일 확장자 제거 + string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); + Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}"); + + // PDF 분석 결과의 각 필드 처리 + foreach (var property in pdfAnalysis.Cast()) + { + string aiLabel = property.Name; // 예: "설계공구_Station_col1" + var valueObj = property.Value as JObject; + + if (valueObj == null) continue; + + string pdfValue = valueObj["value"]?.ToString(); + if (string.IsNullOrEmpty(pdfValue)) continue; + + totalEntries++; + Debug.WriteLine($"[DEBUG] Searching for match: FileName={fileNameWithoutExt}, AILabel={aiLabel}, Value={pdfValue}"); + + // Excel 시트에서 매칭되는 행 찾기 및 업데이트 + if (UpdateExcelRow(fileNameWithoutExt, aiLabel, pdfValue)) + { + updatedCount++; + Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}"); + } + else + { + Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}"); + } + } + } + + Debug.WriteLine($"[DEBUG] PDF 값 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨"); + return true; + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ JSON에서 PDF 값 업데이트 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); + if (ex.InnerException != null) + { + Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); + } + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + return false; + } + } + + /// + /// Excel 매핑 시트에서 FileName과 AILabel이 매칭되는 행을 찾아 Pdf_value를 업데이트합니다. + /// + /// 파일명 (확장자 제외) + /// AI 라벨 (예: "설계공구_Station_col1") + /// PDF에서 추출된 값 + /// 업데이트 성공 시 true + private bool UpdateExcelRow(string fileName, string aiLabel, string pdfValue) + { + try + { + // Excel 시트의 마지막 사용된 행 찾기 + Excel.Range usedRange = mappingSheet.UsedRange; + if (usedRange == null) return false; + + int lastRow = usedRange.Rows.Count; + + // 2행부터 검색 (1행은 헤더) + for (int row = 2; row <= lastRow; row++) + { + // Column 1: FileName, Column 3: AILabel 확인 + var cellFileName = mappingSheet.Cells[row, 1]?.Value?.ToString() ?? ""; + var cellAiLabel = mappingSheet.Cells[row, 3]?.Value?.ToString() ?? ""; + + Debug.WriteLine($"[DEBUG] Row {row}: FileName='{cellFileName}', AILabel='{cellAiLabel}'"); + + // 매칭 확인 (대소문자 구분 없이) + if (string.Equals(cellFileName.Trim(), fileName.Trim(), StringComparison.OrdinalIgnoreCase) && + string.Equals(cellAiLabel.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)) + { + // Column 6: Pdf_value에 값 기록 + mappingSheet.Cells[row, 6] = pdfValue; + Debug.WriteLine($"[DEBUG] Updated row {row}, column 6 with value: {pdfValue}"); + return true; + } + } + + return false; // 매칭되는 행을 찾지 못함 + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Excel 행 업데이트 중 오류: {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 워크북을 저장합니다. + /// + /// 성공 시 true, 실패 시 false + public bool SaveExcel() + { + try + { + if (mappingWorkbook != null) + { + mappingWorkbook.Save(); + Debug.WriteLine("✅ Excel 파일 저장 완료"); + return true; + } + + if (workbook1 != null) + { + workbook1.Save(); + Debug.WriteLine("✅ Excel 파일 저장 완료"); + return true; + } + + Debug.WriteLine("❌ 저장할 워크북이 없습니다."); + return false; + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Excel 파일 저장 중 오류: {ex.Message}"); + return false; + } + } + + /// + /// 매핑 워크북만 저장하고 닫습니다 (완전한 매핑용). + /// + /// 저장할 파일 경로 + public void SaveMappingWorkbookOnly(string savePath) + { + try + { + Debug.WriteLine($"[DEBUG] 매핑 워크북 저장 시작: {savePath}"); + + if (mappingWorkbook == null) + { + Debug.WriteLine("❌ 매핑 워크북이 초기화되지 않았습니다."); + return; + } + + string directory = Path.GetDirectoryName(savePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // 매핑 워크북만 저장 (Excel 2007+ 형식으로) + mappingWorkbook.SaveAs(savePath, + FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, + AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); + + Debug.WriteLine($"✅ 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 매핑 워크북 저장 중 오류: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// DWG 전용 매핑 워크북을 생성하고 저장합니다 (PDF 컬럼 제외). + /// + /// 결과 파일 저장 폴더 경로 + public void SaveDwgOnlyMappingWorkbook(string resultFolderPath) + { + try + { + string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + string savePath = Path.Combine(resultFolderPath, $"{timestamp}_DwgOnly_Mapping.xlsx"); + + Debug.WriteLine($"[DEBUG] DWG 전용 매핑 워크북 생성 시작: {savePath}"); + + // Excel 애플리케이션 초기화 확인 + if (excelApplication == null) + { + excelApplication = new Excel.Application(); + excelApplication.Visible = false; + Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨 (DWG 전용)"); + } + + // DWG 전용 워크북 생성 + var dwgOnlyWorkbook = excelApplication.Workbooks.Add(); + var dwgOnlyWorksheet = (Excel.Worksheet)dwgOnlyWorkbook.Worksheets[1]; + dwgOnlyWorksheet.Name = "DWG Mapping Data"; + + // 헤더 생성 (PDF Value 컬럼 제외) + dwgOnlyWorksheet.Cells[1, 1] = "파일명"; + dwgOnlyWorksheet.Cells[1, 2] = "Map Key"; + dwgOnlyWorksheet.Cells[1, 3] = "AI Label"; + dwgOnlyWorksheet.Cells[1, 4] = "DWG Tag"; + dwgOnlyWorksheet.Cells[1, 5] = "DWG Value"; + + // 헤더 스타일 적용 + var headerRange = dwgOnlyWorksheet.Range["A1:E1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray); + headerRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous; + + // 데이터 입력 (배치 처리로 성능 향상) + int totalRows = FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count); + if (totalRows > 0) + { + object[,] data = new object[totalRows, 5]; + int row = 0; + + foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) + { + string fileName = fileEntry.Key; + foreach (var mapEntry in fileEntry.Value) + { + string mapKey = mapEntry.Key; + var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value; + + data[row, 0] = fileName; + data[row, 1] = mapKey; + data[row, 2] = aiLabel; + data[row, 3] = dwgTag; + data[row, 4] = dwgValue; + + row++; + } + } + + // 한 번에 모든 데이터 입력 + Excel.Range dataRange = dwgOnlyWorksheet.Range[ + dwgOnlyWorksheet.Cells[2, 1], + dwgOnlyWorksheet.Cells[totalRows + 1, 5]]; + dataRange.Value = data; + } + + // 컬럼 자동 크기 조정 + dwgOnlyWorksheet.Columns.AutoFit(); + + // 파일 저장 + string directory = Path.GetDirectoryName(savePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + dwgOnlyWorkbook.SaveAs(savePath, + FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, + AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); + + Debug.WriteLine($"✅ DWG 전용 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}"); + + // 워크북 정리 + dwgOnlyWorkbook.Close(false); + ReleaseComObject(dwgOnlyWorksheet); + ReleaseComObject(dwgOnlyWorkbook); + + // 가비지 컬렉션 강제 실행으로 메모리 해제 + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ DWG 전용 매핑 워크북 저장 중 오류: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// 현재 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); + + // 매핑 데이터 워크북도 저장 + if (mappingWorkbook != null) + { + string mappingPath = Path.Combine(directory, Path.GetFileNameWithoutExtension(savePath) + "_Mapping.xlsx"); + mappingWorkbook.SaveAs(mappingPath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"Excel 파일 저장 중 오류 발생: {ex.Message}"); + } + finally + { + CloseExcelObjects(); + } + } + + /// + /// Excel 객체들을 닫습니다 (저장하지 않음). + /// + public void CloseExcelObjectsWithoutSaving() + { + try + { + Debug.WriteLine("[DEBUG] Excel 객체 정리 시작"); + + if (workbook1 != null) + { + try { workbook1.Close(false); } + catch { } + } + if (mappingWorkbook != null) + { + try { mappingWorkbook.Close(false); } + catch { } + } + if (excelApplication != null) + { + try { excelApplication.Quit(); } + catch { } + } + + ReleaseExcelObjects(); + + Debug.WriteLine("✅ Excel 객체 정리 완료"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Excel 객체 정리 중 오류: {ex.Message}"); + } + } + + private void CloseExcelObjects() + { + if (workbook1 != null) + { + try { workbook1.Close(false); } + catch { } + } + if (mappingWorkbook != null) + { + try { mappingWorkbook.Close(false); } + catch { } + } + if (excelApplication != null) + { + try { excelApplication.Quit(); } + catch { } + } + + ReleaseExcelObjects(); + } + + private void ReleaseExcelObjects() + { + ReleaseComObject(titleBlockSheet); + ReleaseComObject(textEntitiesSheet); + ReleaseComObject(mappingSheet); + ReleaseComObject(workbook1); + ReleaseComObject(mappingWorkbook); + ReleaseComObject(excelApplication); + + titleBlockSheet = null; + textEntitiesSheet = null; + mappingSheet = null; + workbook1 = null; + mappingWorkbook = 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) + { + Debug.WriteLine($"Layer 이름 가져오기 오류: {ex.Message}"); + return ""; + } + } + + /// + /// 매핑 딕셔너리를 JSON 파일로 저장합니다. + /// + /// 저장할 JSON 파일 경로 + public void SaveMappingDictionary(string filePath) + { + try + { + Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 저장 시작: {filePath}"); + + // 딕셔너리를 직렬화 가능한 형태로 변환 + var serializableData = new Dictionary>(); + + foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) + { + var fileData = new Dictionary(); + foreach (var mapEntry in fileEntry.Value) + { + fileData[mapEntry.Key] = new + { + AILabel = mapEntry.Value.Item1, + DwgTag = mapEntry.Value.Item2, + DwgValue = mapEntry.Value.Item3, + PdfValue = mapEntry.Value.Item4 + }; + } + serializableData[fileEntry.Key] = fileData; + } + + // JSON으로 직렬화 + string jsonContent = JsonConvert.SerializeObject(serializableData, Formatting.Indented); + File.WriteAllText(filePath, jsonContent, System.Text.Encoding.UTF8); + + Debug.WriteLine($"✅ 매핑 딕셔너리 저장 완료: {Path.GetFileName(filePath)}"); + Debug.WriteLine($"📊 저장된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}"); + + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 매핑 딕셔너리 저장 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// JSON 파일에서 매핑 딕셔너리를 로드합니다. + /// + /// 로드할 JSON 파일 경로 + public void LoadMappingDictionary(string filePath) + { + try + { + Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 로드 시작: {filePath}"); + + if (!File.Exists(filePath)) + { + Debug.WriteLine($"⚠️ 매핑 파일이 존재하지 않습니다: {filePath}"); + return; + } + + string jsonContent = File.ReadAllText(filePath, System.Text.Encoding.UTF8); + Debug.WriteLine($"[DEBUG] JSON 내용 길이: {jsonContent.Length}"); + + // JSON 내용 정리 (주석 제거 등) + jsonContent = CleanJsonContent(jsonContent); + + Dictionary> deserializedData; + try + { + deserializedData = JsonConvert.DeserializeObject>>(jsonContent); + } + catch (Newtonsoft.Json.JsonReaderException jsonEx) + { + Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); + Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); + Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); + throw new System.Exception($"매핑 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {filePath}"); + } + + // 새로운 딕셔너리 초기화 + FileToMapkeyToLabelTagValuePdf.Clear(); + Debug.WriteLine("[DEBUG] 기존 딕셔너리 초기화됨"); + + if (deserializedData != null) + { + Debug.WriteLine($"[DEBUG] 역직렬화된 파일 수: {deserializedData.Count}"); + + foreach (var fileEntry in deserializedData) + { + Debug.WriteLine($"[DEBUG] 파일 처리 중: {fileEntry.Key}"); + var fileData = new Dictionary(); + + foreach (var mapEntry in fileEntry.Value) + { + var valueObj = mapEntry.Value; + string aiLabel = valueObj["AILabel"]?.ToString() ?? ""; + string dwgTag = valueObj["DwgTag"]?.ToString() ?? ""; + string dwgValue = valueObj["DwgValue"]?.ToString() ?? ""; + string pdfValue = valueObj["PdfValue"]?.ToString() ?? ""; + + fileData[mapEntry.Key] = (aiLabel, dwgTag, dwgValue, pdfValue); + Debug.WriteLine($"[DEBUG] 항목 로드: {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, PDF:{pdfValue}"); + } + + FileToMapkeyToLabelTagValuePdf[fileEntry.Key] = fileData; + Debug.WriteLine($"[DEBUG] 파일 {fileEntry.Key}: {fileData.Count}개 항목 로드됨"); + } + } + + Debug.WriteLine($"✅ 매핑 딕셔너리 로드 완료"); + Debug.WriteLine($"📊 로드된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}"); + Debug.WriteLine($"📊 총 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); + + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 매핑 딕셔너리 로드 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// 매핑 딕셔너리 데이터를 Excel 시트에 기록합니다 (완전한 매핑용). + /// + public void WriteCompleteMapping() + { + try + { + Debug.WriteLine("[DEBUG] WriteCompleteMapping 시작"); + Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 항목 수: {FileToMapkeyToLabelTagValuePdf.Count}"); + + if (FileToMapkeyToLabelTagValuePdf.Count == 0) + { + Debug.WriteLine("⚠️ 매핑 딕셔너리가 비어있습니다."); + return; + } + + if (mappingSheet == null) + { + Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다."); + return; + } + + Debug.WriteLine($"[DEBUG] 매핑 시트 이름: {mappingSheet.Name}"); + Debug.WriteLine($"[DEBUG] 전체 데이터 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); + + // 샘플 데이터 출력 (처음 3개) + int sampleCount = 0; + foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf.Take(2)) + { + Debug.WriteLine($"[DEBUG] 파일: {fileEntry.Key}"); + foreach (var mapEntry in fileEntry.Value.Take(3)) + { + var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value; + Debug.WriteLine($"[DEBUG] {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, DWGVAL:{dwgValue}, PDF:{pdfValue}"); + sampleCount++; + } + } + + // 기존 WriteMappingDataToExcel 메서드 호출 + WriteMappingDataToExcel(); + + Debug.WriteLine("✅ WriteCompleteMapping 완료"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ WriteCompleteMapping 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// PDF 분석 결과 JSON으로 매핑 딕셔너리의 PdfValue를 업데이트합니다. + /// + /// PDF 분석 결과 JSON 파일 경로 + public void UpdateWithPdfData(string jsonFilePath) + { + try + { + Debug.WriteLine($"[DEBUG] PDF 데이터로 매핑 딕셔너리 업데이트 시작: {jsonFilePath}"); + + if (!File.Exists(jsonFilePath)) + { + Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); + return; + } + + // JSON 파일 읽기 및 정리 + string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8); + Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes"); + + // JSON 내용 정리 (주석 제거 등) + jsonContent = CleanJsonContent(jsonContent); + + JObject jsonData; + try + { + jsonData = JObject.Parse(jsonContent); + } + catch (Newtonsoft.Json.JsonReaderException jsonEx) + { + Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); + Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); + Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); + throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}"); + } + + var results = jsonData["results"] as JArray; + if (results == null) + { + Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다."); + Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}"); + return; + } + + int updatedCount = 0; + int totalEntries = 0; + + foreach (JObject result in results) + { + var fileInfo = result["file_info"]; + var pdfAnalysis = result["pdf_analysis"]; + + if (fileInfo == null || pdfAnalysis == null) continue; + + string fileName = fileInfo["name"]?.ToString(); + if (string.IsNullOrEmpty(fileName)) continue; + + // 파일 확장자 제거 + string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); + Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}"); + + // 해당 파일의 매핑 데이터 확인 + if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileNameWithoutExt)) + { + Debug.WriteLine($"⚠️ 매핑 데이터에 파일이 없습니다: {fileNameWithoutExt}"); + continue; + } + + // PDF 분석 결과의 각 필드 처리 + foreach (var property in pdfAnalysis.Cast()) + { + string aiLabel = property.Name; // 예: "설계공구_Station_col1" + var valueObj = property.Value as JObject; + + if (valueObj == null) continue; + + string pdfValue = valueObj["value"]?.ToString(); + if (string.IsNullOrEmpty(pdfValue)) continue; + + totalEntries++; + + // 매핑 딕셔너리에서 해당 항목 찾기 + var fileData = FileToMapkeyToLabelTagValuePdf[fileNameWithoutExt]; + + // AILabel로 매칭 찾기 + var matchingEntry = fileData.FirstOrDefault(kvp => + string.Equals(kvp.Value.Item1.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(matchingEntry.Key)) + { + // 기존 값 유지하면서 PdfValue만 업데이트 + var existingValue = matchingEntry.Value; + fileData[matchingEntry.Key] = (existingValue.Item1, existingValue.Item2, existingValue.Item3, pdfValue); + updatedCount++; + + Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}"); + } + else + { + Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}"); + } + } + } + + Debug.WriteLine($"[DEBUG] PDF 데이터 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨"); + + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ PDF 데이터 업데이트 중 오류:"); + Debug.WriteLine($" 메시지: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; + } + } + + /// + /// 폴더 간 처리를 위해 누적된 데이터를 정리합니다. + /// + public void ClearAccumulatedData() + { + try + { + Debug.WriteLine("[DEBUG] 누적 데이터 정리 시작"); + + FileToMapkeyToLabelTagValuePdf.Clear(); + + titleBlockCurrentRow = 2; + textEntitiesCurrentRow = 2; + mappingDataCurrentRow = 2; + + Debug.WriteLine("✅ 누적 데이터 정리 완료"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 누적 데이터 정리 중 오류: {ex.Message}"); + } + } + + /// + /// JSON 내용을 정리하여 파싱 가능한 상태로 만듭니다. + /// 주석 제거 및 기타 무효한 문자 처리 + /// + /// 원본 JSON 내용 + /// 정리된 JSON 내용 + private string CleanJsonContent(string jsonContent) + { + if (string.IsNullOrEmpty(jsonContent)) + return jsonContent; + + try + { + // 줄별로 처리하여 주석 제거 + var lines = jsonContent.Split('\n'); + var cleanedLines = new List(); + + bool inMultiLineComment = false; + + foreach (string line in lines) + { + string processedLine = line; + + // 멀티라인 주석 처리 (/* */) + if (inMultiLineComment) + { + int endIndex = processedLine.IndexOf("*/"); + if (endIndex >= 0) + { + processedLine = processedLine.Substring(endIndex + 2); + inMultiLineComment = false; + } + else + { + continue; // 전체 라인이 주석 + } + } + + // 멀티라인 주석 시작 확인 + int multiLineStart = processedLine.IndexOf("/*"); + if (multiLineStart >= 0) + { + int multiLineEnd = processedLine.IndexOf("*/", multiLineStart + 2); + if (multiLineEnd >= 0) + { + // 같은 라인에서 시작하고 끝나는 주석 + processedLine = processedLine.Substring(0, multiLineStart) + + processedLine.Substring(multiLineEnd + 2); + } + else + { + // 멀티라인 주석 시작 + processedLine = processedLine.Substring(0, multiLineStart); + inMultiLineComment = true; + } + } + + // 싱글라인 주석 제거 (//) - 문자열 내부의 //는 제외 + bool inString = false; + bool escaped = false; + int commentIndex = -1; + + for (int i = 0; i < processedLine.Length - 1; i++) + { + char current = processedLine[i]; + char next = processedLine[i + 1]; + + if (escaped) + { + escaped = false; + continue; + } + + if (current == '\\') + { + escaped = true; + continue; + } + + if (current == '"') + { + inString = !inString; + continue; + } + + if (!inString && current == '/' && next == '/') + { + commentIndex = i; + break; + } + } + + if (commentIndex >= 0) + { + processedLine = processedLine.Substring(0, commentIndex); + } + + // 빈 라인이 아니면 추가 + if (!string.IsNullOrWhiteSpace(processedLine)) + { + cleanedLines.Add(processedLine); + } + } + + string result = string.Join("\n", cleanedLines); + Debug.WriteLine($"[DEBUG] JSON 정리 완료: {jsonContent.Length} -> {result.Length} bytes"); + return result; + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ JSON 정리 중 오류: {ex.Message}"); + // 정리 실패시 원본 반환 + return jsonContent; + } + } + + public void Dispose() + { + try + { + Debug.WriteLine("[DEBUG] ExportExcel Dispose 시작"); + + if (excelApplication != null) + { + Debug.WriteLine("[DEBUG] Excel 객체 정리 중..."); + CloseExcelObjects(); + } + + if (appServices != null) + { + Debug.WriteLine("[DEBUG] Teigha Services 해제 중..."); + try + { + TeighaServicesManager.Instance.ReleaseServices(); + Debug.WriteLine($"[DEBUG] Teigha Services 해제 완료. Remaining ref count: {TeighaServicesManager.Instance.ReferenceCount}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] Teigha Services 해제 중 오류 (무시됨): {ex.Message}"); + } + finally + { + appServices = null; + } + } + + Debug.WriteLine("[DEBUG] ExportExcel Dispose 완료"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] ExportExcel Dispose 중 전역 오류: {ex.Message}"); + // Disposal 오류는 로그만 남기고 계속 진행 + } + } + } +} diff --git a/notedetectproblem.txt b/notedetectproblem.txt new file mode 100644 index 0000000..d9ff578 --- /dev/null +++ b/notedetectproblem.txt @@ -0,0 +1,134 @@ + NOTE Detection Algorithm Context Report + + Problem Summary + + Successfully integrated NOTE extraction from ExportExcel_note.cs into the new modular architecture, but encountering + issues where only some NOTEs are being detected and finding their content boxes. + + Current Status + + ✅ FIXED: No note content issue - reverted to original working cross-line intersection algorithm from ExportExcel_old.cs + + 🔄 ONGOING: Not detecting all NOTEs (missing notes 2 and 4 from a 4-note layout) + + Architecture Overview + + Key Files and Components + + - Main Entry Point: Models/ExportExcel.cs:265-271 - calls note extraction in ExportAllDwgToExcelHeightSorted + - Core Algorithm: Models/DwgDataExtractor.cs:342-480 - ExtractNotesFromDrawing method + - Note Box Detection: Models/DwgDataExtractor.cs:513-569 - FindNoteBox method + - Excel Output: Models/ExcelDataWriter.cs:282-371 - WriteNoteEntities method + + Current Algorithm Flow + + 1. Collection Phase: Gather all DBText, Polyline, and Line entities + 2. NOTE Detection: Find DBText containing "NOTE" (case-insensitive) + 3. Box Finding: For each NOTE, use cross-line intersection to find content box below + 4. Content Extraction: Find text entities within detected boxes + 5. Sorting & Grouping: Sort by coordinates (Y descending, X ascending) and group NOTE+content + 6. Excel Output: Write to Excel with NOTE followed immediately by its content + + Current Working Algorithm (Reverted from ExportExcel_old.cs) + + FindNoteBox Method (DwgDataExtractor.cs:513-569) + + // Draws horizontal search line below NOTE position + double searchY = notePos.Y - (noteHeight * 2); + var searchLineStart = new Point3d(notePos.X - noteHeight * 10, searchY, 0); + var searchLineEnd = new Point3d(notePos.X + noteHeight * 50, searchY, 0); + + // 1. Check Polyline intersections + // 2. Check Line intersections and trace rectangles + // 3. Use usedBoxes HashSet to prevent duplicate assignment + + IsValidNoteBox Validation (DwgDataExtractor.cs:1005-1032) + + // Simple validation criteria: + // - Box must be below NOTE (box.maxPoint.Y < notePos.Y) + // - Size constraints: noteHeight < width/height < noteHeight * 100 + // - Distance constraints: X distance < noteHeight * 50, Y distance < noteHeight * 10 + + Known Issues from Previous Sessions + + Issue 1: 1/1/3/3 Duplicate Content (PREVIOUSLY FIXED) + + Problem: Multiple NOTEs finding the same large spanning polyline + Root Cause: Box detection finding one large polyline spanning multiple note areas + Solution Applied: Used usedBoxes HashSet to prevent duplicate assignment + + Issue 2: Reverse Note Ordering (PREVIOUSLY FIXED) + + Problem: Notes written in reverse order + Solution Applied: Sort by Y descending (bigger Y = top), then X ascending + + Issue 3: Wrong Note Grouping (PREVIOUSLY FIXED) + + Problem: All NOTEs grouped first, then all content + Solution Applied: Group each NOTE immediately with its content + + Issue 4: Missing NOTEs 2 and 4 (CURRENT ISSUE) + + Problem: In a 4-note layout arranged as 1-2 (top row) and 3-4 (bottom row), only notes 1 and 3 are detected + Possible Causes: + - Search line positioning not intersecting with notes 2 and 4's content boxes + - Box validation criteria too restrictive for right-side notes + - Geometric relationship between NOTE position and content box differs for right-side notes + + Debug Information Available + + Last Known Debug Output (5 NOTEs detected but no content found) + + [DEBUG] Note 텍스트 발견: 'NOTE' at (57.0572050838764,348.6990318186563,0) + [DEBUG] Note 텍스트 발견: 'NOTE' at (471.6194660633719,501.3393888589908,0) + [DEBUG] Note 텍스트 발견: 'NOTE' at (444.9503218738628,174.19527687737536,0) + [DEBUG] Note 텍스트 발견: 'NOTE' at (602.7327260134425,174.43523739278135,0) + [DEBUG] Note 텍스트 발견: 'NOTE' at (635.5065816693041,502.83938885945645,0) + + Reference Image + + - noteExample.png shows expected layout with numbered sections 1-7 in Korean text + - Shows box-structured layout where each NOTE should have corresponding content below + + Key Coordinate Analysis + + From debug logs, NOTEs at similar Y coordinates appear to be in pairs: + - Top Row: (444.95, 174.20) and (602.73, 174.44) - Y≈174 + - Middle Row: (471.62, 501.34) and (635.51, 502.84) - Y≈502 + - Single: (57.06, 348.70) - Y≈349 + + Pattern suggests left-right pairing where right-side NOTEs might need different search strategies. + + Investigation Areas for Next Session + + Priority 1: Search Line Geometry + + - Analyze why horizontal search lines from right-side NOTEs don't intersect content boxes + - Consider adjusting search line direction/positioning for right-side notes + - Debug actual intersection results for missing NOTEs + + Priority 2: Box Validation Criteria + + - Review IsValidNoteBox distance calculations for right-side NOTEs + - Consider if content boxes for right-side NOTEs have different geometric relationships + + Priority 3: Coordinate Pattern Analysis + + - Investigate why NOTEs at (602.73, 174.44) and (635.51, 502.84) aren't finding content + - Compare successful vs failed NOTE positions and their content box relationships + + Quick Start Commands for Next Session + + 1. Run existing code to see current NOTE detection results + 2. Add detailed debug logging to FindNoteBox for specific coordinates: (602.73, 174.44) and (635.51, 502.84) + 3. Analyze intersection results and box validation for these specific NOTEs + 4. Consider geometric adjustments for right-side NOTE detection + + Code State + + - Current implementation in Models/DwgDataExtractor.cs uses proven cross-line intersection algorithm + - usedBoxes tracking prevents duplicate assignment + - NOTE+content grouping and Y-coordinate sorting working correctly + - Excel output formatting functional + + The foundation is solid; focus should be on geometric refinements for complete NOTE detection coverage. \ No newline at end of file