using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Teigha.DatabaseServices;
using Teigha.Geometry;
using Teigha.Runtime;
namespace DwgExtractorManual.Models
{
///
/// DWG ���Ͽ��� �ؽ�Ʈ ��ƼƼ�� �����ϴ� Ŭ����
///
internal class DwgDataExtractor
{
private readonly FieldMapper fieldMapper;
public DwgDataExtractor(FieldMapper fieldMapper)
{
this.fieldMapper = fieldMapper ?? throw new ArgumentNullException(nameof(fieldMapper));
}
///
/// DWG ���Ͽ��� ������ �����Ͽ� ExcelRowData ����Ʈ�� ��ȯ
///
public DwgExtractionResult ExtractFromDwgFile(string filePath, IProgress? progress = null, CancellationToken cancellationToken = default)
{
var result = new DwgExtractionResult();
if (!File.Exists(filePath))
{
Debug.WriteLine($"? ������ �������� �ʽ��ϴ�: {filePath}");
return result;
}
try
{
progress?.Report(0);
cancellationToken.ThrowIfCancellationRequested();
using (var database = new Database(false, true))
{
database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null);
progress?.Report(10);
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)
{
int totalEntities = btr.Cast().Count();
int processedCount = 0;
var fileName = Path.GetFileNameWithoutExtension(database.Filename);
if (string.IsNullOrEmpty(fileName))
{
fileName = "Unknown_File";
}
foreach (ObjectId entId in btr)
{
cancellationToken.ThrowIfCancellationRequested();
using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity)
{
string layerName = GetLayerName(ent.LayerId, tran, database);
ProcessEntity(ent, tran, database, layerName, fileName, result);
}
processedCount++;
double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0;
progress?.Report(Math.Min(currentProgress, 90.0));
}
}
tran.Commit();
}
}
progress?.Report(100);
return result;
}
catch (OperationCanceledException)
{
Debug.WriteLine("? �۾��� ��ҵǾ����ϴ�.");
progress?.Report(0);
return result;
}
catch (Teigha.Runtime.Exception ex)
{
progress?.Report(0);
Debug.WriteLine($"? DWG ���� ó�� �� Teigha ���� ��: {ex.Message}");
return result;
}
catch (System.Exception ex)
{
progress?.Report(0);
Debug.WriteLine($"? �Ϲ� ���� ��: {ex.Message}");
return result;
}
}
///
/// DWG ���Ͽ��� �ؽ�Ʈ ��ƼƼ���� �����Ͽ� Height ������ �Բ� ��ȯ�մϴ�.
///
public List ExtractTextEntitiesWithHeight(string filePath)
{
var attRefEntities = new List();
var otherTextEntities = 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)
{
foreach (ObjectId entId in btr)
{
using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity)
{
string layerName = GetLayerName(ent.LayerId, tran, database);
// AttributeReference ó��
if (ent is BlockReference blr)
{
foreach (ObjectId attId in blr.AttributeCollection)
{
using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference)
{
if (attRef != null)
{
var textString = attRef.TextString == null ? "" : attRef.TextString;
attRefEntities.Add(new TextEntityInfo
{
Height = attRef.Height,
Type = "AttRef",
Layer = layerName,
Tag = attRef.Tag,
Text = textString,
});
}
}
}
}
// DBText ó��
else if (ent is DBText dbText)
{
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 = mText.Height,
Type = "MText",
Layer = layerName,
Tag = "",
Text = mText.Contents
});
}
}
}
}
tran.Commit();
}
}
}
catch (System.Exception ex)
{
Debug.WriteLine($"? �ؽ�Ʈ ��ƼƼ ���� �� ���� ({Path.GetFileName(filePath)}): {ex.Message}");
}
var sortedAttRefEntities = attRefEntities.OrderByDescending(e => e.Height).ToList();
var sortedOtherTextEntities = otherTextEntities.OrderByDescending(e => e.Height).ToList();
sortedAttRefEntities.AddRange(sortedOtherTextEntities);
return sortedAttRefEntities;
}
private void ProcessEntity(Entity ent, Transaction tran, Database database, string layerName, string fileName, DwgExtractionResult result)
{
// AttributeDefinition ����
if (ent is AttributeDefinition attDef)
{
var titleBlockRow = new TitleBlockRowData
{
Type = attDef.GetType().Name,
Name = attDef.BlockName,
Tag = attDef.Tag,
Prompt = attDef.Prompt,
Value = attDef.TextString,
Path = database.Filename,
FileName = Path.GetFileName(database.Filename)
};
result.TitleBlockRows.Add(titleBlockRow);
// ���� ������ ����
AddMappingData(fileName, attDef.Tag, attDef.TextString, result);
}
// BlockReference �� �� ���� AttributeReference ����
else if (ent is BlockReference blr)
{
foreach (ObjectId attId in blr.AttributeCollection)
{
using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference)
{
if (attRef != null && attRef.TextString.Trim() != "")
{
var titleBlockRow = new TitleBlockRowData
{
Type = attRef.GetType().Name,
Name = blr.Name,
Tag = attRef.Tag,
Prompt = GetPromptFromAttributeReference(tran, blr, attRef.Tag),
Value = attRef.TextString,
Path = database.Filename,
FileName = Path.GetFileName(database.Filename)
};
result.TitleBlockRows.Add(titleBlockRow);
// ���� ������ ����
var aiLabel = fieldMapper.ExpresswayToAilabel(attRef.Tag);
if (aiLabel != null)
{
AddMappingData(fileName, attRef.Tag, attRef.TextString, result);
}
}
}
}
}
// DBText ��ƼƼ ���� (���� ��Ʈ)
else if (ent is DBText dbText)
{
var textEntityRow = new TextEntityRowData
{
Type = "DBText",
Layer = layerName,
Text = dbText.TextString,
Path = database.Filename,
FileName = Path.GetFileName(database.Filename)
};
result.TextEntityRows.Add(textEntityRow);
}
// MText ��ƼƼ ���� (���� ��Ʈ)
else if (ent is MText mText)
{
var textEntityRow = new TextEntityRowData
{
Type = "MText",
Layer = layerName,
Text = mText.Contents,
Path = database.Filename,
FileName = Path.GetFileName(database.Filename)
};
result.TextEntityRows.Add(textEntityRow);
}
}
private void AddMappingData(string fileName, string tag, string attValue, DwgExtractionResult result)
{
var aiLabel = fieldMapper.ExpresswayToAilabel(tag);
var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel);
if (!string.IsNullOrEmpty(aiLabel))
{
var finalMapKey = mapKey ?? aiLabel;
result.AddMappingData(fileName, finalMapKey, aiLabel, tag, attValue, "");
}
else
{
var finalMapKey = mapKey ?? tag;
if (!string.IsNullOrEmpty(finalMapKey))
{
result.AddMappingData(fileName, finalMapKey, tag, tag, attValue, "");
}
}
}
private 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;
}
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 "";
}
}
///
/// 도면에서 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의 정렬점을 기준으로 안정적인 검색 라인을 사용합니다.
///
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;
double stableX;
if (noteText.HorizontalMode == TextHorizontalMode.TextLeft)
{
stableX = noteText.Position.X;
}
else
{
stableX = noteText.AlignmentPoint.X;
}
// 검색 라인 정의: 안정적인 X좌표를 기준으로 하되, 약간의 오차를 허용하는 너비
double searchY = notePos.Y - (noteHeight * 2);
double searchWidth = noteHeight * 15;
var searchLineStart = new Point3d(stableX - noteHeight * 2, 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}) with stableX={stableX:F2}");
// 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;
}
}
}
}
// 2. Line들과 교차하여 사각형 찾기
var intersectingLines = FindIntersectingLines(tran, lineIds, searchLineStart, searchLineEnd);
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;
}
}
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
{
double tolerance = noteHeight * 2.0; // 갭 허용 오차를 Note 높이에 비례하여 설정
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:F2}, {currentPoint.Y:F2}) -> ({nextPoint.X:F2}, {nextPoint.Y:F2})");
for (int step = 0; step < 5; step++) // 최대 5번의 연결 시도
{
var findResult = FindNextConnectedLine(tran, lineIds, nextPoint, visitedLines, tolerance);
if (findResult == null) break;
var nextLineId = findResult.Value.lineId;
var connectionType = findResult.Value.connectionType;
using (var nextLine = tran.GetObject(nextLineId, OpenMode.ForRead) as Line)
{
if (nextLine == null) break;
Point3d newNextPoint;
if (connectionType == "Start")
{
newNextPoint = nextLine.EndPoint;
}
else
{
newNextPoint = nextLine.StartPoint;
}
rectanglePoints.Add(newNextPoint);
visitedLines.Add(nextLineId);
nextPoint = newNextPoint;
Debug.WriteLine($"[DEBUG] 다음 Line 추가 (갭 허용): {nextLine.StartPoint} -> {nextLine.EndPoint}");
if (nextPoint.DistanceTo(currentPoint) < tolerance)
{
Debug.WriteLine("[DEBUG] 사각형 완성됨 (갭 허용)");
break;
}
}
}
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 lineId, string connectionType)? FindNextConnectedLine(Transaction tran, List lineIds, Point3d currentPoint, HashSet visitedLines, double tolerance)
{
ObjectId? bestMatchId = null;
string? bestConnectionType = null;
double minDistance = 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;
double distToStart = currentPoint.DistanceTo(line.StartPoint);
if (distToStart < minDistance)
{
minDistance = distToStart;
bestMatchId = lineId;
bestConnectionType = "Start";
}
double distToEnd = currentPoint.DistanceTo(line.EndPoint);
if (distToEnd < minDistance)
{
minDistance = distToEnd;
bestMatchId = lineId;
bestConnectionType = "End";
}
}
}
if (bestMatchId.HasValue)
{
return (bestMatchId.Value, bestConnectionType);
}
return 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 ���� ����� ��� Ŭ����
///
public class DwgExtractionResult
{
public List TitleBlockRows { get; set; } = new List();
public List TextEntityRows { get; set; } = new List();
public Dictionary> FileToMapkeyToLabelTagValuePdf { get; set; }
= new Dictionary>();
public void AddMappingData(string fileName, string mapKey, string aiLabel, string dwgTag, string dwgValue, string pdfValue)
{
if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName))
{
FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary();
}
FileToMapkeyToLabelTagValuePdf[fileName][mapKey] = (aiLabel, dwgTag, dwgValue, pdfValue);
}
}
///
/// Title Block �� ������
///
public class TitleBlockRowData
{
public string Type { get; set; } = "";
public string Name { get; set; } = "";
public string Tag { get; set; } = "";
public string Prompt { get; set; } = "";
public string Value { get; set; } = "";
public string Path { get; set; } = "";
public string FileName { get; set; } = "";
}
///
/// Text Entity �� ������
///
public class TextEntityRowData
{
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 class TextEntityInfo
{
public double Height { get; set; }
public string Type { get; set; } = "";
public string Layer { get; set; } = "";
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...)
}
}