From 52fbc1c9676038ab5ec564300d08840a0dac2a43 Mon Sep 17 00:00:00 2001 From: horu2day Date: Wed, 13 Aug 2025 13:57:46 +0900 Subject: [PATCH] =?UTF-8?q?1=EC=B0=A8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Models/DwgDataExtractor.cs | 107 +++++++++++++++++++++++---------- Models/ExcelDataWriter.cs | 118 ++++++++++++++++++++++++++----------- Models/ExportExcel.cs | 15 +++-- 3 files changed, 167 insertions(+), 73 deletions(-) diff --git a/Models/DwgDataExtractor.cs b/Models/DwgDataExtractor.cs index afc8681..a08bfb2 100644 --- a/Models/DwgDataExtractor.cs +++ b/Models/DwgDataExtractor.cs @@ -107,6 +107,14 @@ namespace DwgExtractorManual.Models /// DWG ���Ͽ��� �ؽ�Ʈ ��ƼƼ���� �����Ͽ� Height ������ �Բ� ��ȯ�մϴ�. /// public List ExtractTextEntitiesWithHeight(string filePath) + { + return ExtractTextEntitiesWithHeightExcluding(filePath, new HashSet()); + } + + /// + /// DWG 파일에서 텍스트 엔터티들을 추출하되, 지정된 ObjectId들은 제외합니다. + /// + public List ExtractTextEntitiesWithHeightExcluding(string filePath, HashSet excludeIds) { var attRefEntities = new List(); var otherTextEntities = new List(); @@ -137,15 +145,19 @@ namespace DwgExtractorManual.Models { if (attRef != null) { - var textString = attRef.TextString == null ? "" : attRef.TextString; - attRefEntities.Add(new TextEntityInfo + // 일반 텍스트 추출시 height 3 이하 제외 + if (attRef.Height > 3) { - Height = attRef.Height, - Type = "AttRef", - Layer = layerName, - Tag = attRef.Tag, - Text = textString, - }); + var textString = attRef.TextString == null ? "" : attRef.TextString; + attRefEntities.Add(new TextEntityInfo + { + Height = attRef.Height, + Type = "AttRef", + Layer = layerName, + Tag = attRef.Tag, + Text = textString, + }); + } } } } @@ -153,26 +165,42 @@ namespace DwgExtractorManual.Models // DBText ó�� else if (ent is DBText dbText) { - otherTextEntities.Add(new TextEntityInfo + // �Ϲ� �ؽ�Ʈ ����� height 3 ���� ���� + // Note에서 사용된 텍스트 제외 + if (!excludeIds.Contains(entId)) { - Height = dbText.Height, - Type = "DBText", - Layer = layerName, - Tag = "", - Text = dbText.TextString - }); + if (dbText.Height > 3) + { + otherTextEntities.Add(new TextEntityInfo + { + Height = dbText.Height, + Type = "DBText", + Layer = layerName, + Tag = "", + Text = dbText.TextString + }); + } + } } // MText ó�� else if (ent is MText mText) { - otherTextEntities.Add(new TextEntityInfo + // �Ϲ� �ؽ�Ʈ ����� height 3 ���� ���� + // Note에서 사용된 텍스트 제외 + if (!excludeIds.Contains(entId)) { - Height = mText.Height, - Type = "MText", - Layer = layerName, - Tag = "", - Text = mText.Contents - }); + if (mText.Height > 3) + { + otherTextEntities.Add(new TextEntityInfo + { + Height = mText.Height, + Type = "MText", + Layer = layerName, + Tag = "", + Text = mText.Contents + }); + } + } } } } @@ -404,6 +432,9 @@ namespace DwgExtractorManual.Models Debug.WriteLine($"[DEBUG] Skipping null noteText for ObjectId: {noteTextId}"); continue; } + + // Note 헤더 텍스트를 사용된 텍스트 ID에 추가 + result.UsedTextIds.Add(noteTextId); // 특정 노트만 테스트하기 위한 필터 (디버깅용) // if (noteText == null || !noteText.TextString.Contains("도로용지경계 기준 노트")) // { @@ -460,6 +491,12 @@ namespace DwgExtractorManual.Models // 테이블 외부의 일반 텍스트들을 좌표별로 정렬하여 그룹에 추가 var sortedNonTableTexts = GetSortedNoteContents(tran, nonTableTextIds, database); currentNoteGroup.AddRange(sortedNonTableTexts); + + // 박스 내부 텍스트들을 사용된 텍스트 ID에 추가 + foreach (var textId in nonTableTextIds) + { + result.UsedTextIds.Add(textId); + } } else { @@ -509,6 +546,7 @@ namespace DwgExtractorManual.Models } Debug.WriteLine($"[DEBUG] 최종 Note 엔티티 정렬 완료: {noteEntities.Count}개"); + Debug.WriteLine($"[DEBUG] Note에서 사용된 텍스트 ID 개수: {result.UsedTextIds.Count}개"); result.NoteEntities = noteEntities; result.IntersectionPoints = LastIntersectionPoints.Select(ip => new IntersectionPoint { Position = ip.Position, DirectionBits = ip.DirectionBits, Row = ip.Row, Column = ip.Column }).ToList(); @@ -2146,7 +2184,7 @@ namespace DwgExtractorManual.Models foreach (var cellBoundary in cellBoundaries) { - var textsInCell = new List(); + var textsInCell = new List<(string text, double y)>(); foreach (var textId in allTextIds) { @@ -2177,14 +2215,15 @@ namespace DwgExtractorManual.Models { if (!string.IsNullOrWhiteSpace(textContent)) { - textsInCell.Add(textContent.Trim()); - Debug.WriteLine($"[CELL_TEXT] ✅ {cellBoundary.Label}에 텍스트 추가: '{textContent.Trim()}'"); + textsInCell.Add((textContent.Trim(), textPosition.Y)); + Debug.WriteLine($"[CELL_TEXT] ✅ {cellBoundary.Label}에 텍스트 추가: '{textContent.Trim()}' at Y={textPosition.Y:F1}"); } } } - // 셀 경계에 찾은 텍스트들을 콤마로 연결하여 저장 - cellBoundary.CellText = string.Join(", ", textsInCell); + // Y값이 큰 것부터 정렬 (위에서 아래로)하여 콤마로 연결 + var sortedTexts = textsInCell.OrderByDescending(t => t.y).Select(t => t.text); + cellBoundary.CellText = string.Join(", ", sortedTexts); Debug.WriteLine($"[CELL_TEXT] {cellBoundary.Label} 최종 텍스트: '{cellBoundary.CellText}'"); } @@ -2731,7 +2770,7 @@ namespace DwgExtractorManual.Models foreach (var cell in cells) { - var textsInCell = new List(); + var textsInCell = new List<(string text, double y)>(); foreach (var textId in textIds) { if (assignedIds.Contains(textId)) continue; @@ -2743,16 +2782,19 @@ namespace DwgExtractorManual.Models // Check if the text's alignment point is inside the cell if (IsPointInCell(dbText.Position, cell)) { - textsInCell.Add(dbText.TextString); - cell.CellText = string.Join("\n", textsInCell); + textsInCell.Add((dbText.TextString, dbText.Position.Y)); assignedIds.Add(textId); } } } } - if(textsInCell.Any()) + + // Y값이 큰 것부터 정렬 (위에서 아래로)하여 텍스트 설정 + if (textsInCell.Any()) { - assignedTexts[cell] = textsInCell; + var sortedTexts = textsInCell.OrderByDescending(t => t.y).Select(t => t.text); + cell.CellText = string.Join("\n", sortedTexts); + assignedTexts[cell] = textsInCell.Select(t => t.text).ToList(); } } @@ -2824,6 +2866,7 @@ namespace DwgExtractorManual.Models public List IntersectionPoints { get; set; } = new List(); public List<(Teigha.Geometry.Point3d topLeft, Teigha.Geometry.Point3d bottomRight, string label)> DiagonalLines { get; set; } = new List<(Teigha.Geometry.Point3d, Teigha.Geometry.Point3d, string)>(); public List TableSegments { get; set; } = new List(); + public HashSet UsedTextIds { get; set; } = new HashSet(); // Note에서 사용된 텍스트 ID들 } /// diff --git a/Models/ExcelDataWriter.cs b/Models/ExcelDataWriter.cs index 9ab45a1..1cd32a3 100644 --- a/Models/ExcelDataWriter.cs +++ b/Models/ExcelDataWriter.cs @@ -422,39 +422,53 @@ namespace DwgExtractorManual.Models int maxTableRow = 0; foreach (var cellBoundary in noteEntity.CellBoundaries) { - var (cellRow, cellCol) = ParseRowColFromLabel(cellBoundary.Label); - Debug.WriteLine($"[DEBUG] CellBoundary 처리: {cellBoundary.Label} → Row={cellRow}, Col={cellCol}, Text='{cellBoundary.CellText}'"); + var (sRow, sCol, eRow, eCol) = ParseCellRangeFromLabel(cellBoundary.Label); + Debug.WriteLine($"[DEBUG] CellBoundary 처리: {cellBoundary.Label} → Range=R{sRow}C{sCol}:R{eRow}C{eCol}, Text='{cellBoundary.CellText}'"); - if (cellRow > 0 && cellCol > 0) + if (sRow > 0 && sCol > 0 && eRow > 0 && eCol > 0) { - // Excel에서 테이블 위치 계산: - // R1 → Note의 현재 행 (currentRow) - // R2 → Note의 다음 행 (currentRow + 1) - // C1 → G열(7), C2 → H열(8) - int excelRow = currentRow + (cellRow - 1); // R1=currentRow, R2=currentRow+1, ... - int excelCol = 7 + (cellCol - 1); // C1=G열(7), C2=H열(8), ... + // 병합된 영역의 셀 개수 계산: (eRow - sRow) × (eCol - sCol) + int rowCount = eRow - sRow; // 행 개수 (bottomRight - topLeft) + int colCount = eCol - sCol; // 열 개수 (bottomRight - topLeft) - Debug.WriteLine($"[DEBUG] Excel 위치: {cellBoundary.Label} → Excel[{excelRow},{excelCol}]"); + Debug.WriteLine($"[DEBUG] 병합 영역 크기: {rowCount+1}행 × {colCount+1}열"); - // Excel 범위 체크 (최대 20개 컬럼까지) - if (excelCol <= 26) // Z열까지 + // 병합된 영역의 모든 셀에 텍스트 복사 (topLeft부터 bottomRight-1까지) + for (int r = sRow; r < eRow; r++) // < eRow (bottomRight 제외) { - // CellText가 비어있어도 일단 배치해보기 (디버그용) - var cellValue = string.IsNullOrEmpty(cellBoundary.CellText) ? "[빈셀]" : cellBoundary.CellText; - worksheet.Cells[excelRow, excelCol] = cellValue; - Debug.WriteLine($"[DEBUG] ✅ 셀 배치 완료: {cellBoundary.Label} → Excel[{excelRow},{excelCol}] = '{cellValue}'"); + for (int c = sCol; c < eCol; c++) // < eCol (bottomRight 제외) + { + // Excel에서 테이블 위치 계산: + // R1 → Note의 현재 행 (currentRow) + // R2 → Note의 다음 행 (currentRow + 1) + // C1 → G열(7), C2 → H열(8) + int excelRow = currentRow + (r - 1); // R1=currentRow, R2=currentRow+1, ... + int excelCol = 7 + (c - 1); // C1=G열(7), C2=H열(8), ... + + // Excel 범위 체크 (최대 20개 컬럼까지) + if (excelCol <= 26) // Z열까지 + { + // CellText가 비어있어도 일단 배치해보기 (디버그용) + var cellValue = string.IsNullOrEmpty(cellBoundary.CellText) ? "[빈셀]" : cellBoundary.CellText; + // 텍스트 형식으로 설정하여 "0:0" 같은 값이 시간으로 포맷되지 않도록 함 + var cell = (Excel.Range)worksheet.Cells[excelRow, excelCol]; + cell.NumberFormat = "@"; // 텍스트 형식 + cell.Value = cellValue; + Debug.WriteLine($"[DEBUG] ✅ 셀 복사: R{r}C{c} → Excel[{excelRow},{excelCol}] = '{cellValue}'"); + } + else + { + Debug.WriteLine($"[DEBUG] ❌ Excel 컬럼 범위 초과: {excelCol} > 26"); + } + + // 테이블이 차지하는 최대 행 수 추적 + maxTableRow = Math.Max(maxTableRow, r); + } } - else - { - Debug.WriteLine($"[DEBUG] ❌ Excel 컬럼 범위 초과: {excelCol} > 26"); - } - - // 테이블이 차지하는 최대 행 수 추적 - maxTableRow = Math.Max(maxTableRow, cellRow); } else { - Debug.WriteLine($"[DEBUG] ❌ 잘못된 Row/Col: {cellBoundary.Label} → Row={cellRow}, Col={cellCol}"); + Debug.WriteLine($"[DEBUG] ❌ 잘못된 Range: {cellBoundary.Label} → R{sRow}C{sCol}:R{eRow}C{eCol}"); } } @@ -479,7 +493,10 @@ namespace DwgExtractorManual.Models for (int cellIndex = 0; cellIndex < Math.Min(tableCells.Length, 15); cellIndex++) { var cellValue = tableCells[cellIndex].Trim().Trim('"'); // 따옴표 제거 - worksheet.Cells[currentRow, 7 + cellIndex] = cellValue; // G열(7)부터 시작 + // 텍스트 형식으로 설정하여 "0:0" 같은 값이 시간으로 포맷되지 않도록 함 + var cell = (Excel.Range)worksheet.Cells[currentRow, 7 + cellIndex]; + cell.NumberFormat = "@"; // 텍스트 형식 + cell.Value = cellValue; // G열(7)부터 시작 } // 표의 첫 번째 행이 아니면 새로운 Excel 행 추가 @@ -551,30 +568,59 @@ namespace DwgExtractorManual.Models /// - /// 라벨에서 Row, Col 정보를 파싱합니다. - /// 예: "R1C2" → (1, 2) 또는 "R2C2→R3C4" → (2, 2) (시작 위치 사용) + /// 라벨에서 셀 범위 정보를 파싱합니다. + /// 예: "R1C2" → (1, 2, 1, 2) 또는 "R2C2→R3C4" → (2, 2, 3, 4) /// - private (int row, int col) ParseRowColFromLabel(string label) + private (int sRow, int sCol, int eRow, int eCol) ParseCellRangeFromLabel(string label) { try { if (string.IsNullOrEmpty(label)) - return (0, 0); + return (0, 0, 0, 0); - // "R2C2→R3C4" 형태인 경우 시작 부분만 사용 - var startPart = label; if (label.Contains("→")) { - startPart = label.Split('→')[0]; + // "R2C2→R3C4" 형태 - 범위 파싱 + var parts = label.Split('→'); + if (parts.Length == 2) + { + var (sRow, sCol) = ParseSingleCell(parts[0]); + var (eRow, eCol) = ParseSingleCell(parts[1]); + return (sRow, sCol, eRow, eCol); + } } + else + { + // "R1C2" 형태 - 단일 셀 + var (row, col) = ParseSingleCell(label); + return (row, col, row, col); + } + + return (0, 0, 0, 0); + } + catch + { + return (0, 0, 0, 0); + } + } - var rIndex = startPart.IndexOf('R'); - var cIndex = startPart.IndexOf('C'); + /// + /// 단일 셀 위치를 파싱합니다. (예: "R2C3" → (2, 3)) + /// + private (int row, int col) ParseSingleCell(string cellStr) + { + try + { + if (string.IsNullOrEmpty(cellStr)) + return (0, 0); + + var rIndex = cellStr.IndexOf('R'); + var cIndex = cellStr.IndexOf('C'); if (rIndex >= 0 && cIndex > rIndex) { - var rowStr = startPart.Substring(rIndex + 1, cIndex - rIndex - 1); - var colStr = startPart.Substring(cIndex + 1); + var rowStr = cellStr.Substring(rIndex + 1, cIndex - rIndex - 1); + var colStr = cellStr.Substring(cIndex + 1); if (int.TryParse(rowStr, out int row) && int.TryParse(colStr, out int col)) { diff --git a/Models/ExportExcel.cs b/Models/ExportExcel.cs index 324e01b..fa8191e 100644 --- a/Models/ExportExcel.cs +++ b/Models/ExportExcel.cs @@ -198,7 +198,11 @@ namespace DwgExtractorManual.Models worksheet.Name = excelManager.GetValidSheetName(fileName); firstSheetProcessed = true; - var textEntities = DwgExtractor.ExtractTextEntitiesWithHeight(dwgFile); + // Note 엔티티 먼저 추출 + var noteEntities = DwgExtractor.ExtractNotesFromDrawing(dwgFile); + + // Note에서 사용된 텍스트 제외하고 일반 텍스트 추출 + var textEntities = DwgExtractor.ExtractTextEntitiesWithHeightExcluding(dwgFile, noteEntities.UsedTextIds); excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티"); @@ -263,11 +267,12 @@ namespace DwgExtractorManual.Models worksheet.Name = excelManager.GetValidSheetName(fileName); firstSheetProcessed = true; - var textEntities = DwgExtractor.ExtractTextEntitiesWithHeight(filePath); - excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); - - // Note 엔티티 추출 및 기록 + // Note 엔티티 먼저 추출 var noteEntities = DwgExtractor.ExtractNotesFromDrawing(filePath); + + // Note에서 사용된 텍스트 제외하고 일반 텍스트 추출 + var textEntities = DwgExtractor.ExtractTextEntitiesWithHeightExcluding(filePath, noteEntities.UsedTextIds); + excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); if (noteEntities.NoteEntities.Count > 0) { excelWriter.WriteNoteEntities(noteEntities.NoteEntities, worksheet, fileName);