diff --git a/Models/DwgDataExtractor.cs b/Models/DwgDataExtractor.cs
index 33d3744..8d18d33 100644
--- a/Models/DwgDataExtractor.cs
+++ b/Models/DwgDataExtractor.cs
@@ -506,6 +506,59 @@ namespace DwgExtractorManual.Models
return noteTextIds;
}
+ ///
+ /// 모든 Line과 Polyline 엔터티를 통합된 Line 세그먼트 리스트로 변환합니다.
+ /// Polyline은 구성 Line 세그먼트로 분해됩니다.
+ ///
+ private List GetAllLineSegments(Transaction tran, List polylineIds, List lineIds)
+ {
+ var allLineSegments = new List();
+
+ // Polylines를 Line 세그먼트로 분해하여 추가
+ foreach (var polylineId in polylineIds)
+ {
+ using (var polyline = tran.GetObject(polylineId, OpenMode.ForRead) as Polyline)
+ {
+ if (polyline == null) continue;
+ var explodedEntities = new DBObjectCollection();
+ try
+ {
+ polyline.Explode(explodedEntities);
+ foreach (DBObject obj in explodedEntities)
+ {
+ if (obj is Line line)
+ {
+ allLineSegments.Add(line.Clone() as Line);
+ }
+ obj.Dispose();
+ }
+ }
+ catch (System.Exception ex)
+ {
+ Debug.WriteLine($"[DEBUG] Polyline 분해 중 오류: {ex.Message}");
+ }
+ finally
+ {
+ explodedEntities.Dispose();
+ }
+ }
+ }
+
+ // 기존 Line들을 추가
+ foreach (var lineId in lineIds)
+ {
+ using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line)
+ {
+ if (line != null)
+ {
+ allLineSegments.Add(line.Clone() as Line);
+ }
+ }
+ }
+
+ return allLineSegments;
+ }
+
///
/// Note 텍스트 아래에 있는 콘텐츠 박스를 찾습니다.
/// Note의 정렬점을 기준으로 안정적인 검색 라인을 사용합니다.
@@ -513,83 +566,84 @@ namespace DwgExtractorManual.Models
private (Point3d minPoint, Point3d maxPoint)? FindNoteBox(
Transaction tran, DBText noteText, List polylineIds, List lineIds, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
{
- var allLineSegments = new List();
+ var allLineSegments = GetAllLineSegments(tran, polylineIds, lineIds);
try
{
var notePos = noteText.Position;
var noteHeight = noteText.Height;
- // Polylines를 Line 세그먼트로 분해하여 추가
- foreach (var polylineId in polylineIds)
- {
- using (var polyline = tran.GetObject(polylineId, OpenMode.ForRead) as Polyline)
- {
- if (polyline == null) continue;
- var explodedEntities = new DBObjectCollection();
- try
- {
- polyline.Explode(explodedEntities);
- foreach (DBObject obj in explodedEntities)
- {
- if (obj is Line line)
- {
- allLineSegments.Add(line.Clone() as Line);
- }
- obj.Dispose();
- }
- }
- catch (System.Exception ex)
- {
- Debug.WriteLine($"[DEBUG] Polyline 분해 중 오류: {ex.Message}");
- }
- finally
- {
- explodedEntities.Dispose();
- }
- }
- }
-
- // 기존 Line들을 추가
- foreach (var lineId in lineIds)
- {
- using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line)
- {
- if (line != null)
- {
- allLineSegments.Add(line.Clone() as Line);
- }
- }
- }
-
- double stableX;
+ // Note의 X 좌표 결정 (정렬 방식에 따라)
+ double noteX;
if (noteText.HorizontalMode == TextHorizontalMode.TextLeft)
{
- stableX = noteText.Position.X;
+ noteX = noteText.Position.X;
}
else
{
- stableX = noteText.AlignmentPoint.X;
+ noteX = noteText.AlignmentPoint.X;
}
- double searchY = notePos.Y - (noteHeight * 2);
- double searchWidth = noteHeight * 15;
- var searchLineStart = new Point3d(stableX - noteHeight * 6, searchY, 0); // 왼쪽으로 확장 (중간값)
- var searchLineEnd = new Point3d(stableX + searchWidth, searchY, 0);
- var searchLineEnd = new Point3d(stableX + searchWidth, searchY, 0);
-
- Debug.WriteLine($"[DEBUG] 확장된 통합 검색 라인: ({searchLineStart.X:F2}, {searchLineStart.Y:F2}) to ({searchLineEnd.X:F2}, {searchLineEnd.Y:F2}) for NOTE at ({notePos.X:F2}, {notePos.Y:F2})");
+ Debug.WriteLine($"[DEBUG] 수직 Ray-Casting 시작: NOTE '{noteText.TextString}' at ({noteX:F2}, {notePos.Y:F2})");
- var intersectingLines = FindIntersectingLineSegments(allLineSegments, searchLineStart, searchLineEnd);
- foreach (var startLine in intersectingLines)
+ // 수직 레이 캐스팅을 위한 하향 스캔
+ var horizontalLines = new List<(Line line, double y)>();
+
+ // 모든 라인 세그먼트를 검사하여 수직 레이와 교차하는 수평선들을 찾음
+ foreach (var line in allLineSegments)
{
- var rectangle = TraceRectangleFromLineSegments(allLineSegments, startLine, notePos, noteHeight, usedBoxes);
- if (rectangle.HasValue)
+ // 수평선인지 확인 (Y 좌표가 거의 같음)
+ if (Math.Abs(line.StartPoint.Y - line.EndPoint.Y) < noteHeight * 0.1)
{
- Debug.WriteLine($"[DEBUG] 교차하는 Line/Polyline 조합 사각형 발견: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}");
- return rectangle;
+ double lineY = (line.StartPoint.Y + line.EndPoint.Y) / 2;
+ double lineMinX = Math.Min(line.StartPoint.X, line.EndPoint.X);
+ double lineMaxX = Math.Max(line.StartPoint.X, line.EndPoint.X);
+
+ // 수직 레이가 이 수평선과 교차하는지 확인
+ if (noteX >= lineMinX && noteX <= lineMaxX && lineY < notePos.Y)
+ {
+ horizontalLines.Add((line, lineY));
+ Debug.WriteLine($"[DEBUG] 수평선 발견: Y={lineY:F2}, X범위=({lineMinX:F2}~{lineMaxX:F2})");
+ }
}
}
+ // Y 좌표로 정렬 (위에서 아래로)
+ var sortedHorizontalLines = horizontalLines
+ .OrderByDescending(hl => hl.y)
+ .ToList();
+
+ Debug.WriteLine($"[DEBUG] 정렬된 수평선 개수: {sortedHorizontalLines.Count}");
+
+ // 두 번째 수평선을 콘텐츠 박스의 상단으로 식별 (첫 번째는 NOTE 자체의 경계로 가정)
+ if (sortedHorizontalLines.Count >= 2)
+ {
+ var topLine = sortedHorizontalLines[1].line;
+ Debug.WriteLine($"[DEBUG] 콘텐츠 박스 상단선 선택 (2번째): Y={sortedHorizontalLines[1].y:F2}");
+
+ // 이 상단선으로부터 박스를 추적
+ var rectangle = TraceBoxFromTopLine(allLineSegments, topLine, notePos, noteHeight, usedBoxes);
+ if (rectangle.HasValue)
+ {
+ Debug.WriteLine($"[DEBUG] Ray-Casting으로 박스 발견 (2번째 선): {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}");
+ return rectangle;
+ }
+
+ // 2번째 선으로 실패시 3번째 선 시도 (shadow effect polyline 고려)
+ if (sortedHorizontalLines.Count >= 3)
+ {
+ var fallbackTopLine = sortedHorizontalLines[2].line;
+ Debug.WriteLine($"[DEBUG] 대안 콘텐츠 박스 상단선 선택 (3번째): Y={sortedHorizontalLines[2].y:F2}");
+
+ var fallbackRectangle = TraceBoxFromTopLine(allLineSegments, fallbackTopLine, notePos, noteHeight, usedBoxes);
+ if (fallbackRectangle.HasValue)
+ {
+ Debug.WriteLine($"[DEBUG] Ray-Casting으로 박스 발견 (3번째 선): {fallbackRectangle.Value.minPoint} to {fallbackRectangle.Value.maxPoint}");
+ return fallbackRectangle;
+ }
+ }
+ }
+
+ Debug.WriteLine($"[DEBUG] Ray-Casting으로 적절한 박스를 찾지 못함");
return null;
}
finally
@@ -602,133 +656,158 @@ namespace DwgExtractorManual.Models
}
}
- private List FindIntersectingLineSegments(List allLineSegments, Point3d searchLineStart, Point3d searchLineEnd)
- {
- var intersectingLines = new List();
- foreach (var line in allLineSegments)
- {
- if (DoLinesIntersect(searchLineStart, searchLineEnd, line.StartPoint, line.EndPoint))
- {
- intersectingLines.Add(line);
- }
- }
- return intersectingLines;
- }
-
- private (Point3d minPoint, Point3d maxPoint)? TraceRectangleFromLineSegments(List allLineSegments, Line startLine, Point3d notePos, double noteHeight, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
+ ///
+ /// 식별된 상단선으로부터 지능적으로 박스의 나머지 세 변을 추적합니다.
+ /// 작은 간격에 대해 관용적입니다.
+ ///
+ private (Point3d minPoint, Point3d maxPoint)? TraceBoxFromTopLine(
+ List allLineSegments, Line topLine, Point3d notePos, double noteHeight, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
{
try
{
- double tolerance = noteHeight * 2.0;
- var visitedLines = new HashSet();
- var rectanglePoints = new List();
+ double tolerance = noteHeight * 0.5; // 간격 허용 오차
+
+ Debug.WriteLine($"[DEBUG] 박스 추적 시작: 상단선 ({topLine.StartPoint.X:F2},{topLine.StartPoint.Y:F2}) to ({topLine.EndPoint.X:F2},{topLine.EndPoint.Y:F2})");
- var currentPoint = startLine.StartPoint;
- var nextPoint = startLine.EndPoint;
- rectanglePoints.Add(currentPoint);
- rectanglePoints.Add(nextPoint);
- visitedLines.Add(startLine);
+ // 상단선의 끝점들
+ var topLeft = new Point3d(Math.Min(topLine.StartPoint.X, topLine.EndPoint.X), topLine.StartPoint.Y, 0);
+ var topRight = new Point3d(Math.Max(topLine.StartPoint.X, topLine.EndPoint.X), topLine.StartPoint.Y, 0);
- for (int step = 0; step < 5; step++)
+ // 왼쪽 수직선 찾기
+ Line leftLine = null;
+ foreach (var line in allLineSegments)
{
- var findResult = FindNextConnectedLineSegment(allLineSegments, nextPoint, visitedLines, tolerance);
- if (findResult == null) break;
-
- var nextLine = findResult.Value.line;
- var connectionType = findResult.Value.connectionType;
-
- Point3d newNextPoint = (connectionType == "Start") ? nextLine.EndPoint : nextLine.StartPoint;
-
- rectanglePoints.Add(newNextPoint);
- visitedLines.Add(nextLine);
- nextPoint = newNextPoint;
-
- if (nextPoint.DistanceTo(currentPoint) < tolerance)
+ if (IsVerticalLine(line, noteHeight) &&
+ IsLineConnectedToPoint(line, topLeft, tolerance))
{
+ leftLine = line;
+ Debug.WriteLine($"[DEBUG] 왼쪽 수직선 발견: ({line.StartPoint.X:F2},{line.StartPoint.Y:F2}) to ({line.EndPoint.X:F2},{line.EndPoint.Y:F2})");
break;
}
}
- if (rectanglePoints.Count >= 4)
+ // 오른쪽 수직선 찾기
+ Line rightLine = null;
+ foreach (var line in allLineSegments)
{
- var bounds = CalculateBounds(rectanglePoints);
- if (bounds.HasValue && IsValidNoteBox(bounds.Value, notePos, noteHeight) && !usedBoxes.Contains(bounds.Value))
+ if (IsVerticalLine(line, noteHeight) &&
+ IsLineConnectedToPoint(line, topRight, tolerance))
{
- return bounds;
+ rightLine = line;
+ Debug.WriteLine($"[DEBUG] 오른쪽 수직선 발견: ({line.StartPoint.X:F2},{line.StartPoint.Y:F2}) to ({line.EndPoint.X:F2},{line.EndPoint.Y:F2})");
+ break;
}
}
+
+ if (leftLine == null || rightLine == null)
+ {
+ Debug.WriteLine($"[DEBUG] 수직선을 찾을 수 없음: left={leftLine != null}, right={rightLine != null}");
+ return null;
+ }
+
+ // 하단 끝점들 계산
+ var bottomLeft = GetFarEndPoint(leftLine, topLeft);
+ var bottomRight = GetFarEndPoint(rightLine, topRight);
+
+ // 하단선 찾기
+ Line bottomLine = null;
+ double expectedBottomY = Math.Min(bottomLeft.Y, bottomRight.Y);
+
+ foreach (var line in allLineSegments)
+ {
+ if (IsHorizontalLine(line, noteHeight))
+ {
+ double lineY = (line.StartPoint.Y + line.EndPoint.Y) / 2;
+ if (Math.Abs(lineY - expectedBottomY) < tolerance)
+ {
+ // 하단선이 왼쪽과 오른쪽 끝점을 연결하는지 확인
+ var lineLeft = new Point3d(Math.Min(line.StartPoint.X, line.EndPoint.X), lineY, 0);
+ var lineRight = new Point3d(Math.Max(line.StartPoint.X, line.EndPoint.X), lineY, 0);
+
+ if (Math.Abs(lineLeft.X - bottomLeft.X) < tolerance &&
+ Math.Abs(lineRight.X - bottomRight.X) < tolerance)
+ {
+ bottomLine = line;
+ Debug.WriteLine($"[DEBUG] 하단선 발견: ({line.StartPoint.X:F2},{line.StartPoint.Y:F2}) to ({line.EndPoint.X:F2},{line.EndPoint.Y:F2})");
+ break;
+ }
+ }
+ }
+ }
+
+ if (bottomLine == null)
+ {
+ Debug.WriteLine($"[DEBUG] 하단선을 찾을 수 없음");
+ return null;
+ }
+
+ // 최종 박스 경계 계산
+ var minPoint = new Point3d(
+ Math.Min(topLeft.X, bottomLeft.X),
+ Math.Min(topLeft.Y, bottomLeft.Y),
+ 0
+ );
+ var maxPoint = new Point3d(
+ Math.Max(topRight.X, bottomRight.X),
+ Math.Max(topLeft.Y, bottomLeft.Y),
+ 0
+ );
+
+ var result = (minPoint, maxPoint);
+
+ // 유효성 검사
+ if (IsValidNoteBox(result, notePos, noteHeight) && !usedBoxes.Contains(result))
+ {
+ Debug.WriteLine($"[DEBUG] 유효한 박스 추적 완료: ({minPoint.X:F2},{minPoint.Y:F2}) to ({maxPoint.X:F2},{maxPoint.Y:F2})");
+ return result;
+ }
+
+ Debug.WriteLine($"[DEBUG] 추적된 박스가 유효하지 않음");
return null;
}
catch (System.Exception ex)
{
- Debug.WriteLine($"[DEBUG] 통합 사각형 추적 중 오류: {ex.Message}");
+ Debug.WriteLine($"[DEBUG] 박스 추적 중 오류: {ex.Message}");
return null;
}
}
- private (Line line, string connectionType)? FindNextConnectedLineSegment(List allLineSegments, Point3d currentPoint, HashSet visitedLines, double tolerance)
+ ///
+ /// 선분이 수직선인지 확인합니다.
+ ///
+ private bool IsVerticalLine(Line line, double noteHeight)
{
- Line bestMatch = null;
- string bestConnectionType = null;
- double minDistance = tolerance;
-
- foreach (var line in allLineSegments)
- {
- if (visitedLines.Contains(line)) continue;
-
- double distToStart = currentPoint.DistanceTo(line.StartPoint);
- if (distToStart < minDistance)
- {
- minDistance = distToStart;
- bestMatch = line;
- bestConnectionType = "Start";
- }
-
- double distToEnd = currentPoint.DistanceTo(line.EndPoint);
- if (distToEnd < minDistance)
- {
- minDistance = distToEnd;
- bestMatch = line;
- bestConnectionType = "End";
- }
- }
-
- if (bestMatch != null)
- {
- return (bestMatch, bestConnectionType);
- }
-
- return null;
+ return Math.Abs(line.StartPoint.X - line.EndPoint.X) < noteHeight * 0.1;
}
-
///
- /// 수평선이 Polyline과 교차하는지 확인합니다.
+ /// 선분이 수평선인지 확인합니다.
///
- private bool DoesLineIntersectPolyline(Point3d lineStart, Point3d lineEnd, Polyline polyline)
+ private bool IsHorizontalLine(Line line, double noteHeight)
{
- 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;
- }
+ return Math.Abs(line.StartPoint.Y - line.EndPoint.Y) < noteHeight * 0.1;
}
+ ///
+ /// 선분이 지정된 점에 연결되어 있는지 확인합니다.
+ ///
+ private bool IsLineConnectedToPoint(Line line, Point3d point, double tolerance)
+ {
+ return point.DistanceTo(line.StartPoint) < tolerance || point.DistanceTo(line.EndPoint) < tolerance;
+ }
+
+ ///
+ /// 선분에서 지정된 점으로부터 가장 먼 끝점을 반환합니다.
+ ///
+ private Point3d GetFarEndPoint(Line line, Point3d referencePoint)
+ {
+ double distToStart = referencePoint.DistanceTo(line.StartPoint);
+ double distToEnd = referencePoint.DistanceTo(line.EndPoint);
+
+ return distToStart > distToEnd ? line.StartPoint : line.EndPoint;
+ }
+
+
///
/// 두 선분이 교차하는지 확인합니다.
///
@@ -755,33 +834,6 @@ namespace DwgExtractorManual.Models
}
}
- ///
- /// 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 박스인지 확인합니다.