raycasting note content box detect

This commit is contained in:
2025-07-31 11:23:46 +09:00
parent 107eab90fa
commit 3abb3c07ce

View File

@@ -506,6 +506,59 @@ namespace DwgExtractorManual.Models
return noteTextIds;
}
/// <summary>
/// 모든 Line과 Polyline 엔터티를 통합된 Line 세그먼트 리스트로 변환합니다.
/// Polyline은 구성 Line 세그먼트로 분해됩니다.
/// </summary>
private List<Line> GetAllLineSegments(Transaction tran, List<ObjectId> polylineIds, List<ObjectId> lineIds)
{
var allLineSegments = new List<Line>();
// 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;
}
/// <summary>
/// Note 텍스트 아래에 있는 콘텐츠 박스를 찾습니다.
/// Note의 정렬점을 기준으로 안정적인 검색 라인을 사용합니다.
@@ -513,83 +566,84 @@ namespace DwgExtractorManual.Models
private (Point3d minPoint, Point3d maxPoint)? FindNoteBox(
Transaction tran, DBText noteText, List<ObjectId> polylineIds, List<ObjectId> lineIds, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
{
var allLineSegments = new List<Line>();
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<Line> FindIntersectingLineSegments(List<Line> allLineSegments, Point3d searchLineStart, Point3d searchLineEnd)
{
var intersectingLines = new List<Line>();
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<Line> allLineSegments, Line startLine, Point3d notePos, double noteHeight, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
/// <summary>
/// 식별된 상단선으로부터 지능적으로 박스의 나머지 세 변을 추적합니다.
/// 작은 간격에 대해 관용적입니다.
/// </summary>
private (Point3d minPoint, Point3d maxPoint)? TraceBoxFromTopLine(
List<Line> allLineSegments, Line topLine, Point3d notePos, double noteHeight, HashSet<(Point3d minPoint, Point3d maxPoint)> usedBoxes)
{
try
{
double tolerance = noteHeight * 2.0;
var visitedLines = new HashSet<Line>();
var rectanglePoints = new List<Point3d>();
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<Line> allLineSegments, Point3d currentPoint, HashSet<Line> visitedLines, double tolerance)
/// <summary>
/// 선분이 수직선인지 확인합니다.
/// </summary>
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;
}
/// <summary>
/// 수평선이 Polyline과 교차하는지 확인합니다.
/// 선분이 수평선인지 확인합니다.
/// </summary>
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;
}
/// <summary>
/// 선분이 지정된 점에 연결되어 있는지 확인합니다.
/// </summary>
private bool IsLineConnectedToPoint(Line line, Point3d point, double tolerance)
{
return point.DistanceTo(line.StartPoint) < tolerance || point.DistanceTo(line.EndPoint) < tolerance;
}
/// <summary>
/// 선분에서 지정된 점으로부터 가장 먼 끝점을 반환합니다.
/// </summary>
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;
}
/// <summary>
/// 두 선분이 교차하는지 확인합니다.
/// </summary>
@@ -755,33 +834,6 @@ namespace DwgExtractorManual.Models
}
}
/// <summary>
/// Polyline의 경계 상자를 계산합니다.
/// </summary>
private (Point3d minPoint, Point3d maxPoint)? GetPolylineBounds(Polyline polyline)
{
try
{
if (polyline.NumberOfVertices < 3) return null;
var vertices = new List<Point3d>();
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;
}
}
/// <summary>
/// 박스가 유효한 Note 박스인지 확인합니다.