diff --git a/Models/DwgDataExtractor.cs b/Models/DwgDataExtractor.cs index c000cc0..eb4c71f 100644 --- a/Models/DwgDataExtractor.cs +++ b/Models/DwgDataExtractor.cs @@ -5,12 +5,13 @@ using System.IO; using System.Linq; using System.Threading; using Teigha.DatabaseServices; +using Teigha.Geometry; using Teigha.Runtime; namespace DwgExtractorManual.Models { /// - /// DWG Ͽ ؽƮ ƼƼ ϴ Ŭ + /// DWG ���Ͽ��� �ؽ�Ʈ ��ƼƼ�� �����ϴ� Ŭ���� /// internal class DwgDataExtractor { @@ -22,7 +23,7 @@ namespace DwgExtractorManual.Models } /// - /// DWG Ͽ ͸ Ͽ ExcelRowData Ʈ ȯ + /// DWG ���Ͽ��� �����͸� �����Ͽ� ExcelRowData ����Ʈ�� ��ȯ /// public DwgExtractionResult ExtractFromDwgFile(string filePath, IProgress? progress = null, CancellationToken cancellationToken = default) { @@ -30,7 +31,7 @@ namespace DwgExtractorManual.Models if (!File.Exists(filePath)) { - Debug.WriteLine($"? ʽϴ: {filePath}"); + Debug.WriteLine($"? ������ �������� �ʽ��ϴ�: {filePath}"); return result; } @@ -84,26 +85,26 @@ namespace DwgExtractorManual.Models } catch (OperationCanceledException) { - Debug.WriteLine("? ۾ ҵǾϴ."); + Debug.WriteLine("? �۾��� ��ҵǾ����ϴ�."); progress?.Report(0); return result; } catch (Teigha.Runtime.Exception ex) { progress?.Report(0); - Debug.WriteLine($"? DWG ó Teigha ߻: {ex.Message}"); + Debug.WriteLine($"? DWG ���� ó�� �� Teigha ���� �߻�: {ex.Message}"); return result; } catch (System.Exception ex) { progress?.Report(0); - Debug.WriteLine($"? Ϲ ߻: {ex.Message}"); + Debug.WriteLine($"? �Ϲ� ���� �߻�: {ex.Message}"); return result; } } /// - /// DWG Ͽ ؽƮ ƼƼ Ͽ Height Բ ȯմϴ. + /// DWG ���Ͽ��� �ؽ�Ʈ ��ƼƼ���� �����Ͽ� Height ������ �Բ� ��ȯ�մϴ�. /// public List ExtractTextEntitiesWithHeight(string filePath) { @@ -127,7 +128,7 @@ namespace DwgExtractorManual.Models { string layerName = GetLayerName(ent.LayerId, tran, database); - // AttributeReference ó + // AttributeReference ó�� if (ent is BlockReference blr) { foreach (ObjectId attId in blr.AttributeCollection) @@ -149,7 +150,7 @@ namespace DwgExtractorManual.Models } } } - // DBText ó + // DBText ó�� else if (ent is DBText dbText) { otherTextEntities.Add(new TextEntityInfo @@ -161,7 +162,7 @@ namespace DwgExtractorManual.Models Text = dbText.TextString }); } - // MText ó + // MText ó�� else if (ent is MText mText) { otherTextEntities.Add(new TextEntityInfo @@ -183,7 +184,7 @@ namespace DwgExtractorManual.Models } catch (System.Exception ex) { - Debug.WriteLine($"? ؽƮ ƼƼ ({Path.GetFileName(filePath)}): {ex.Message}"); + Debug.WriteLine($"? �ؽ�Ʈ ��ƼƼ ���� �� ���� ({Path.GetFileName(filePath)}): {ex.Message}"); } var sortedAttRefEntities = attRefEntities.OrderByDescending(e => e.Height).ToList(); @@ -195,7 +196,7 @@ namespace DwgExtractorManual.Models private void ProcessEntity(Entity ent, Transaction tran, Database database, string layerName, string fileName, DwgExtractionResult result) { - // AttributeDefinition + // AttributeDefinition ���� if (ent is AttributeDefinition attDef) { var titleBlockRow = new TitleBlockRowData @@ -211,10 +212,10 @@ namespace DwgExtractorManual.Models result.TitleBlockRows.Add(titleBlockRow); - // + // ���� ������ ���� AddMappingData(fileName, attDef.Tag, attDef.TextString, result); } - // BlockReference AttributeReference + // BlockReference �� �� ���� AttributeReference ���� else if (ent is BlockReference blr) { foreach (ObjectId attId in blr.AttributeCollection) @@ -236,7 +237,7 @@ namespace DwgExtractorManual.Models result.TitleBlockRows.Add(titleBlockRow); - // + // ���� ������ ���� var aiLabel = fieldMapper.ExpresswayToAilabel(attRef.Tag); if (aiLabel != null) { @@ -246,7 +247,7 @@ namespace DwgExtractorManual.Models } } } - // DBText ƼƼ ( Ʈ) + // DBText ��ƼƼ ���� (���� ��Ʈ) else if (ent is DBText dbText) { var textEntityRow = new TextEntityRowData @@ -260,7 +261,7 @@ namespace DwgExtractorManual.Models result.TextEntityRows.Add(textEntityRow); } - // MText ƼƼ ( Ʈ) + // MText ��ƼƼ ���� (���� ��Ʈ) else if (ent is MText mText) { var textEntityRow = new TextEntityRowData @@ -330,14 +331,607 @@ namespace DwgExtractorManual.Models } catch (System.Exception ex) { - Debug.WriteLine($"Layer ̸ : {ex.Message}"); + Debug.WriteLine($"Layer �̸� �������� ����: {ex.Message}"); return ""; } } + + /// + /// 도면에서 Note와 관련된 텍스트들을 추출합니다. + /// + public List ExtractNotesFromDrawing(string filePath) + { + var noteEntities = new List(); + + try + { + using (var database = new Database(false, true)) + { + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + + 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) + { + var allEntities = btr.Cast().ToList(); + var dbTextIds = new List(); + var polylineIds = new List(); + var lineIds = new List(); + + // 먼저 모든 관련 엔터티들의 ObjectId를 수집 + foreach (ObjectId entId in allEntities) + { + 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 그룹들을 저장할 리스트 (각 그룹은 NOTE 헤더 + 내용들) + var noteGroups = new List>(); + + // 이미 사용된 박스들을 추적 (중복 할당 방지) + var usedBoxes = new HashSet<(Point3d minPoint, Point3d maxPoint)>(); + + // 각 Note에 대해 처리 + foreach (var noteTextId in noteTextIds) + { + using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText) + { + Debug.WriteLine($"[DEBUG] Note 처리 중: '{noteText.TextString}' at {noteText.Position}"); + + // 이 Note에 대한 그룹 생성 + var currentNoteGroup = new List(); + + // Note 우측아래에 있는 박스 찾기 (이미 사용된 박스 제외) + var noteBox = FindNoteBox(tran, noteText, polylineIds, lineIds, usedBoxes); + + if (noteBox.HasValue) + { + Debug.WriteLine($"[DEBUG] Note 박스 발견: {noteBox.Value.minPoint} to {noteBox.Value.maxPoint}"); + + // 사용된 박스로 등록 + usedBoxes.Add(noteBox.Value); + + // 박스 내부의 텍스트들 찾기 + var boxTextIds = FindTextsInNoteBox(tran, noteText, noteBox.Value, dbTextIds); + Debug.WriteLine($"[DEBUG] 박스 내 텍스트: {boxTextIds.Count}개"); + + // Note 자체를 그룹의 첫 번째로 추가 + currentNoteGroup.Add(new NoteEntityInfo + { + Type = "Note", + Layer = GetLayerName(noteText.LayerId, tran, database), + Text = noteText.TextString, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename), + X = noteText.Position.X, + Y = noteText.Position.Y, + SortOrder = 0 // Note는 항상 먼저 + }); + + // 박스 내 텍스트들을 좌표별로 정렬하여 그룹에 추가 + var sortedBoxTexts = GetSortedNoteContents(tran, boxTextIds, database); + currentNoteGroup.AddRange(sortedBoxTexts); + } + else + { + Debug.WriteLine($"[DEBUG] Note '{noteText.TextString}'에 대한 박스를 찾을 수 없음"); + + // 박스가 없어도 Note 헤더는 추가 + currentNoteGroup.Add(new NoteEntityInfo + { + Type = "Note", + Layer = GetLayerName(noteText.LayerId, tran, database), + Text = noteText.TextString, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename), + X = noteText.Position.X, + Y = noteText.Position.Y, + SortOrder = 0 + }); + } + + noteGroups.Add(currentNoteGroup); + } + } + + // Note 그룹들을 Y 좌표별로 정렬 (위에서 아래로) + var sortedNoteGroups = noteGroups + .OrderByDescending(group => group[0].Y) // 각 그룹의 첫 번째 항목(NOTE 헤더)의 Y 좌표로 정렬 + .ToList(); + + // 정렬된 그룹들을 하나의 리스트로 합치기 + foreach (var group in sortedNoteGroups) + { + noteEntities.AddRange(group); + } + } + tran.Commit(); + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Note 추출 중 오류: {ex.Message}"); + } + + Debug.WriteLine($"[DEBUG] 최종 Note 엔티티 정렬 완료: {noteEntities.Count}개"); + + return noteEntities; + } + + /// + /// 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) + { + noteTextIds.Add(textId); + Debug.WriteLine($"[DEBUG] Note 텍스트 발견: '{textContent}' at {dbText.Position}, Color: {dbText.Color}"); + } + } + } + + return noteTextIds; + } + + /// + /// Note 우측아래에 있는 박스를 찾습니다 (교차선 기반 알고리즘). + /// Note position에서 height*2 만큼 아래로 수평선을 그어서 교차하는 Line/Polyline을 찾습니다. + /// + private (Point3d minPoint, Point3d maxPoint)? FindNoteBox( + Transaction tran, DBText noteText, List polylineIds, List lineIds, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes) + { + 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 * 20, searchY, 0); + var searchLineEnd = new Point3d(notePos.X + noteHeight * 100, 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) && !usedBoxes.Contains(box.Value)) + { + Debug.WriteLine($"[DEBUG] 교차하는 Polyline 박스 발견: {box.Value.minPoint} to {box.Value.maxPoint}"); + return box; + } + else if (box.HasValue && usedBoxes.Contains(box.Value)) + { + Debug.WriteLine($"[DEBUG] 박스가 이미 사용됨: {box.Value.minPoint} to {box.Value.maxPoint}"); + } + } + } + } + + // 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 && !usedBoxes.Contains(rectangle.Value)) + { + Debug.WriteLine($"[DEBUG] 교차하는 Line 사각형 발견: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}"); + return rectangle; + } + else if (rectangle.HasValue && usedBoxes.Contains(rectangle.Value)) + { + Debug.WriteLine($"[DEBUG] Line 사각형이 이미 사용됨: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}"); + } + } + + 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 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; + + 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 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) + { + boxTextIds.Add(textId); + Debug.WriteLine($"[DEBUG] 박스 내 텍스트 발견: '{dbText.TextString}' at {textPos}"); + } + } + } + } + + return boxTextIds; + } + + /// + /// Note 박스 내의 텍스트들을 좌표에 따라 정렬합니다 (위에서 아래로, 왼쪽에서 오른쪽으로). + /// + private List GetSortedNoteContents(Transaction tran, List boxTextIds, Database database) + { + var noteContents = new List(); + + // 먼저 모든 텍스트와 좌표 정보를 수집 + var textInfoList = new List<(ObjectId id, double x, double y, string text, string layer)>(); + + foreach (var boxTextId in boxTextIds) + { + using (var boxText = tran.GetObject(boxTextId, OpenMode.ForRead) as DBText) + { + if (boxText != null) + { + var pos = boxText.Position; + textInfoList.Add((boxTextId, pos.X, pos.Y, boxText.TextString, GetLayerName(boxText.LayerId, tran, database))); + } + } + } + + // Y 좌표로 먼저 정렬 (위에서 아래로 = Y 큰 값부터), 그다음 X 좌표로 정렬 (왼쪽에서 오른쪽으로 = X 작은 값부터) + // CAD에서는 Y축이 위로 갈수록 커지므로, 텍스트를 위에서 아래로 읽으려면 Y가 큰 것부터 처리 (큰 Y = 위쪽) + var sortedTexts = textInfoList + .OrderByDescending(t => Math.Round(t.y, 1)) // Y 좌표로 내림차순 (위에서 아래로: 큰 Y부터) + .ThenBy(t => Math.Round(t.x, 1)) // X 좌표로 오름차순 (왼쪽에서 오른쪽으로: 작은 X부터) + .ToList(); + + Debug.WriteLine($"[DEBUG] Note 내용 정렬 결과:"); + int sortOrder = 1; // Note 자체는 0이므로 내용은 1부터 시작 + + foreach (var textInfo in sortedTexts) + { + Debug.WriteLine($"[DEBUG] 순서 {sortOrder}: '{textInfo.text}' at ({textInfo.x:F1}, {textInfo.y:F1})"); + + noteContents.Add(new NoteEntityInfo + { + Type = "NoteContent", + Layer = textInfo.layer, + Text = textInfo.text, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename), + X = textInfo.x, + Y = textInfo.y, + SortOrder = sortOrder + }); + + sortOrder++; + } + + return noteContents; + } } /// - /// DWG Ŭ + /// DWG ���� ����� ��� Ŭ���� /// public class DwgExtractionResult { @@ -358,7 +952,7 @@ namespace DwgExtractorManual.Models } /// - /// Title Block + /// Title Block �� ������ /// public class TitleBlockRowData { @@ -372,7 +966,7 @@ namespace DwgExtractorManual.Models } /// - /// Text Entity + /// Text Entity �� ������ /// public class TextEntityRowData { @@ -384,7 +978,7 @@ namespace DwgExtractorManual.Models } /// - /// ؽƮ ƼƼ Ŭ + /// �ؽ�Ʈ ��ƼƼ ������ ��� Ŭ���� /// public class TextEntityInfo { @@ -394,4 +988,19 @@ namespace DwgExtractorManual.Models public string Tag { get; set; } = ""; public string Text { get; set; } = ""; } + + /// + /// Note 엔티티 정보 클래스 + /// + public class NoteEntityInfo + { + public string Type { get; set; } = ""; + public string Layer { get; set; } = ""; + public string Text { get; set; } = ""; + public string Path { get; set; } = ""; + public string FileName { get; set; } = ""; + public double X { get; set; } = 0; // X 좌표 + public double Y { get; set; } = 0; // Y 좌표 + public int SortOrder { get; set; } = 0; // 정렬 순서 (Note=0, NoteContent=1,2,3...) + } } \ No newline at end of file diff --git a/Models/ExcelDataWriter.cs b/Models/ExcelDataWriter.cs index 829650f..dbe5356 100644 --- a/Models/ExcelDataWriter.cs +++ b/Models/ExcelDataWriter.cs @@ -7,7 +7,7 @@ using Excel = Microsoft.Office.Interop.Excel; namespace DwgExtractorManual.Models { /// - /// Excel Ʈ ͸ ۾ ϴ Ŭ + /// Excel ��Ʈ�� �����͸� ���� �۾��� ����ϴ� Ŭ���� /// internal class ExcelDataWriter { @@ -19,14 +19,14 @@ namespace DwgExtractorManual.Models } /// - /// Title Block ͸ Excel Ʈ + /// Title Block �����͸� Excel ��Ʈ�� ��� /// public void WriteTitleBlockData(List titleBlockRows) { if (excelManager.TitleBlockSheet == null || titleBlockRows == null || titleBlockRows.Count == 0) return; - int currentRow = 2; // + int currentRow = 2; // ��� ���� ����� ���� foreach (var row in titleBlockRows) { @@ -42,14 +42,14 @@ namespace DwgExtractorManual.Models } /// - /// Text Entity ͸ Excel Ʈ + /// Text Entity �����͸� Excel ��Ʈ�� ��� /// public void WriteTextEntityData(List textEntityRows) { if (excelManager.TextEntitiesSheet == null || textEntityRows == null || textEntityRows.Count == 0) return; - int currentRow = 2; // + int currentRow = 2; // ��� ���� ����� ���� foreach (var row in textEntityRows) { @@ -63,7 +63,7 @@ namespace DwgExtractorManual.Models } /// - /// ͸ Excel Ʈ + /// ���� �����͸� Excel ��Ʈ�� ��� /// public void WriteMappingDataToExcel(Dictionary> mappingData) { @@ -72,7 +72,7 @@ namespace DwgExtractorManual.Models if (excelManager.MappingSheet == null || mappingData == null) return; - int currentRow = 2; // + int currentRow = 2; // ��� ���� ����� ���� Debug.WriteLine($"[DEBUG] Writing mapping data to Excel. Total files: {mappingData.Count}"); @@ -95,7 +95,7 @@ namespace DwgExtractorManual.Models try { - // ġ Ʈ 迭 + // ��ġ ������Ʈ�� ���� �迭 ��� object[,] rowData = new object[1, 6]; rowData[0, 0] = fileName; rowData[0, 1] = mapKey; @@ -122,13 +122,13 @@ namespace DwgExtractorManual.Models } catch (System.Exception ex) { - Debug.WriteLine($"? Excel : {ex.Message}"); + Debug.WriteLine($"? ���� ������ Excel ��� �� ����: {ex.Message}"); throw; } } /// - /// Excel Ʈ FileName AILabel ĪǴ ã Pdf_value Ʈ + /// Excel ���� ��Ʈ���� FileName�� AILabel�� ��Ī�Ǵ� ���� ã�� Pdf_value�� ������Ʈ /// public bool UpdateExcelRow(string fileName, string aiLabel, string pdfValue) { @@ -159,13 +159,13 @@ namespace DwgExtractorManual.Models } catch (System.Exception ex) { - Debug.WriteLine($"? Excel Ʈ : {ex.Message}"); + Debug.WriteLine($"? Excel �� ������Ʈ �� ����: {ex.Message}"); return false; } } /// - /// DWG ũ ϰ (PDF ÷ ) + /// DWG ���� ���� ��ũ���� �����ϰ� ���� (PDF �÷� ����) /// public void SaveDwgOnlyMappingWorkbook(Dictionary> mappingData, string resultFolderPath) { @@ -174,26 +174,26 @@ namespace DwgExtractorManual.Models string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string savePath = System.IO.Path.Combine(resultFolderPath, $"{timestamp}_DwgOnly_Mapping.xlsx"); - Debug.WriteLine($"[DEBUG] DWG ũ : {savePath}"); + Debug.WriteLine($"[DEBUG] DWG ���� ���� ��ũ�� ���� ����: {savePath}"); var dwgOnlyWorkbook = excelManager.CreateNewWorkbook(); var dwgOnlyWorksheet = (Excel.Worksheet)dwgOnlyWorkbook.Worksheets[1]; dwgOnlyWorksheet.Name = "DWG Mapping Data"; - // (PDF Value ÷ ) - dwgOnlyWorksheet.Cells[1, 1] = "ϸ"; + // ��� ���� (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 = mappingData.Sum(f => f.Value.Count); if (totalRows > 0) { @@ -227,7 +227,7 @@ namespace DwgExtractorManual.Models dwgOnlyWorksheet.Columns.AutoFit(); excelManager.SaveWorkbookAs(dwgOnlyWorkbook, savePath); - Debug.WriteLine($"? DWG ũ Ϸ: {System.IO.Path.GetFileName(savePath)}"); + Debug.WriteLine($"? DWG ���� ���� ��ũ�� ���� �Ϸ�: {System.IO.Path.GetFileName(savePath)}"); dwgOnlyWorkbook.Close(false); System.GC.Collect(); @@ -235,17 +235,17 @@ namespace DwgExtractorManual.Models } catch (System.Exception ex) { - Debug.WriteLine($"? DWG ũ : {ex.Message}"); + Debug.WriteLine($"? DWG ���� ���� ��ũ�� ���� �� ����: {ex.Message}"); throw; } } /// - /// Height ĵ Excel + /// Height ���ĵ� Excel ���� ���� /// public void WriteHeightSortedData(List textEntities, Excel.Worksheet worksheet, string fileName) { - // + // ��� ���� worksheet.Cells[1, 1] = "Height"; worksheet.Cells[1, 2] = "Type"; worksheet.Cells[1, 3] = "Layer"; @@ -253,16 +253,16 @@ namespace DwgExtractorManual.Models worksheet.Cells[1, 5] = "FileName"; worksheet.Cells[1, 6] = "Text"; - // Ÿ + // ��� ��Ÿ�� ���� var headerRange = worksheet.Range["A1:F1"]; headerRange.Font.Bold = true; headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); - // ʹ ExtractTextEntitiesWithHeight ̹ ĵǾǷ ٽ ʽϴ. + // �����ʹ� ExtractTextEntitiesWithHeight���� �̹� ���ĵǾ����Ƿ� �ٽ� �������� �ʽ��ϴ�. - // Է + // ������ �Է� int row = 2; - foreach (var entity in textEntities) // sortedEntities textEntities + foreach (var entity in textEntities) // sortedEntities�� textEntities�� ���� { worksheet.Cells[row, 1] = entity.Height; worksheet.Cells[row, 2] = entity.Type; @@ -275,5 +275,99 @@ namespace DwgExtractorManual.Models worksheet.Columns.AutoFit(); } + + /// + /// Note 엔티티들을 Excel 워크시트에 기록합니다 (기존 데이터 아래에 추가). + /// + public void WriteNoteEntities(List noteEntities, Excel.Worksheet worksheet, string fileName) + { + if (noteEntities == null || noteEntities.Count == 0) + { + Debug.WriteLine("[DEBUG] Note 엔티티가 없습니다."); + return; + } + + try + { + // 현재 워크시트의 마지막 사용된 행 찾기 + Excel.Range usedRange = null; + int lastRow = 1; + + try + { + usedRange = worksheet.UsedRange; + lastRow = usedRange?.Rows.Count ?? 1; + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] UsedRange 접근 오류, 기본값 사용: {ex.Message}"); + lastRow = 1; + } + + int startRow = lastRow + 2; // 한 줄 띄우고 시작 + Debug.WriteLine($"[DEBUG] Note 데이터 기록 시작: {startRow}행부터 {noteEntities.Count}개 항목"); + + // Note 섹션 헤더 추가 (간단한 방식으로 변경) + try + { + worksheet.Cells[startRow - 1, 1] = "=== Notes ==="; + worksheet.Cells[startRow - 1, 2] = ""; + worksheet.Cells[startRow - 1, 3] = ""; + worksheet.Cells[startRow - 1, 4] = ""; + worksheet.Cells[startRow - 1, 5] = ""; + worksheet.Cells[startRow - 1, 6] = ""; + + // 헤더 스타일 적용 (개별 셀로 처리) + var headerCell = worksheet.Cells[startRow - 1, 1]; + headerCell.Font.Bold = true; + headerCell.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] Note 헤더 작성 오류: {ex.Message}"); + } + + // Note 데이터 입력 (배치 방식으로 성능 향상) + int row = startRow; + try + { + foreach (var noteEntity in noteEntities) + { + worksheet.Cells[row, 1] = 0; // Height는 0으로 설정 + worksheet.Cells[row, 2] = noteEntity.Type ?? ""; + worksheet.Cells[row, 3] = noteEntity.Layer ?? ""; + worksheet.Cells[row, 4] = ""; // Tag는 빈 값 + worksheet.Cells[row, 5] = fileName ?? ""; + worksheet.Cells[row, 6] = noteEntity.Text ?? ""; + + Debug.WriteLine($"[DEBUG] Excel 기록: Row {row}, Order {noteEntity.SortOrder}, Type {noteEntity.Type}, Pos({noteEntity.X:F1},{noteEntity.Y:F1}), Text: '{noteEntity.Text}'"); + row++; + } + + Debug.WriteLine($"[DEBUG] Note 데이터 기록 완료: {row - startRow}개 항목"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] Note 데이터 입력 오류: {ex.Message}"); + Debug.WriteLine($"[DEBUG] 처리된 행: {row - startRow}개"); + } + + // AutoFit 시도 (오류 발생시 무시) + try + { + worksheet.Columns.AutoFit(); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] AutoFit 오류 (무시됨): {ex.Message}"); + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ WriteNoteEntities 전체 오류: {ex.Message}"); + Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + throw; // 상위로 예외 전파 + } + } } } \ No newline at end of file diff --git a/Models/ExportExcel.cs b/Models/ExportExcel.cs index 7149a9b..bc92844 100644 --- a/Models/ExportExcel.cs +++ b/Models/ExportExcel.cs @@ -262,6 +262,14 @@ namespace DwgExtractorManual.Models var textEntities = dwgExtractor.ExtractTextEntitiesWithHeight(filePath); excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); + // Note 엔티티 추출 및 기록 + var noteEntities = dwgExtractor.ExtractNotesFromDrawing(filePath); + if (noteEntities.Count > 0) + { + excelWriter.WriteNoteEntities(noteEntities, worksheet, fileName); + Debug.WriteLine($"[DEBUG] {fileName}: {noteEntities.Count}개 Note 엔티티 추가됨"); + } + Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티"); } catch (System.Exception ex)