Files
manual_wpf/Models/DwgDataExtractor.cs
2025-07-31 10:03:48 +09:00

1007 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// DWG <20><><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD> <20>ؽ<EFBFBD>Ʈ <20><>ƼƼ<C6BC><C6BC> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
/// </summary>
internal class DwgDataExtractor
{
private readonly FieldMapper fieldMapper;
public DwgDataExtractor(FieldMapper fieldMapper)
{
this.fieldMapper = fieldMapper ?? throw new ArgumentNullException(nameof(fieldMapper));
}
/// <summary>
/// DWG <20><><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> ExcelRowData <20><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE> <20><>ȯ
/// </summary>
public DwgExtractionResult ExtractFromDwgFile(string filePath, IProgress<double>? progress = null, CancellationToken cancellationToken = default)
{
var result = new DwgExtractionResult();
if (!File.Exists(filePath))
{
Debug.WriteLine($"? <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʽ<EFBFBD><CABD>ϴ<EFBFBD>: {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<ObjectId>().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("? <20>۾<EFBFBD><DBBE><EFBFBD> <20><>ҵǾ<D2B5><C7BE><EFBFBD><EFBFBD>ϴ<EFBFBD>.");
progress?.Report(0);
return result;
}
catch (Teigha.Runtime.Exception ex)
{
progress?.Report(0);
Debug.WriteLine($"? DWG <20><><EFBFBD><EFBFBD> ó<><C3B3> <20><> Teigha <20><><EFBFBD><EFBFBD> <20>߻<EFBFBD>: {ex.Message}");
return result;
}
catch (System.Exception ex)
{
progress?.Report(0);
Debug.WriteLine($"? <20>Ϲ<EFBFBD> <20><><EFBFBD><EFBFBD> <20>߻<EFBFBD>: {ex.Message}");
return result;
}
}
/// <summary>
/// DWG <20><><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD> <20>ؽ<EFBFBD>Ʈ <20><>ƼƼ<C6BC><C6BC><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> Height <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>Բ<EFBFBD> <20><>ȯ<EFBFBD>մϴ<D5B4>.
/// </summary>
public List<TextEntityInfo> ExtractTextEntitiesWithHeight(string filePath)
{
var attRefEntities = new List<TextEntityInfo>();
var otherTextEntities = new List<TextEntityInfo>();
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 ó<><C3B3>
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 ó<><C3B3>
else if (ent is DBText dbText)
{
otherTextEntities.Add(new TextEntityInfo
{
Height = dbText.Height,
Type = "DBText",
Layer = layerName,
Tag = "",
Text = dbText.TextString
});
}
// MText ó<><C3B3>
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($"? <20>ؽ<EFBFBD>Ʈ <20><>ƼƼ <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> ({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 <20><><EFBFBD><EFBFBD>
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);
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
AddMappingData(fileName, attDef.Tag, attDef.TextString, result);
}
// BlockReference <20><> <20><> <20><><EFBFBD><EFBFBD> AttributeReference <20><><EFBFBD><EFBFBD>
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);
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
var aiLabel = fieldMapper.ExpresswayToAilabel(attRef.Tag);
if (aiLabel != null)
{
AddMappingData(fileName, attRef.Tag, attRef.TextString, result);
}
}
}
}
}
// DBText <20><>ƼƼ <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> <20><>Ʈ)
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 <20><>ƼƼ <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> <20><>Ʈ)
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 <20≯<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: {ex.Message}");
return "";
}
}
/// <summary>
/// 도면에서 Note와 관련된 텍스트들을 추출합니다.
/// </summary>
public List<NoteEntityInfo> ExtractNotesFromDrawing(string filePath)
{
var noteEntities = new List<NoteEntityInfo>();
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<ObjectId>().ToList();
var dbTextIds = new List<ObjectId>();
var polylineIds = new List<ObjectId>();
var lineIds = new List<ObjectId>();
// 먼저 모든 관련 엔터티들의 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<List<NoteEntityInfo>>();
// 이미 사용된 박스들을 추적 (중복 할당 방지)
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<NoteEntityInfo>();
// 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;
}
/// <summary>
/// DBText 중에서 "Note"가 포함된 텍스트들을 찾습니다.
/// </summary>
private List<ObjectId> FindNoteTexts(Transaction tran, List<ObjectId> dbTextIds)
{
var noteTextIds = new List<ObjectId>();
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;
}
/// <summary>
/// Note 텍스트 아래에 있는 콘텐츠 박스를 찾습니다.
/// Note의 정렬점을 기준으로 안정적인 검색 라인을 사용합니다.
/// </summary>
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>();
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;
if (noteText.HorizontalMode == TextHorizontalMode.TextLeft)
{
stableX = noteText.Position.X;
}
else
{
stableX = 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})");
var intersectingLines = FindIntersectingLineSegments(allLineSegments, searchLineStart, searchLineEnd);
foreach (var startLine in intersectingLines)
{
var rectangle = TraceRectangleFromLineSegments(allLineSegments, startLine, notePos, noteHeight, usedBoxes);
if (rectangle.HasValue)
{
Debug.WriteLine($"[DEBUG] 교차하는 Line/Polyline 조합 사각형 발견: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}");
return rectangle;
}
}
return null;
}
finally
{
// 생성된 모든 Line 객체들을 Dispose
foreach (var line in allLineSegments)
{
line.Dispose();
}
}
}
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)
{
try
{
double tolerance = noteHeight * 2.0;
var visitedLines = new HashSet<Line>();
var rectanglePoints = new List<Point3d>();
var currentPoint = startLine.StartPoint;
var nextPoint = startLine.EndPoint;
rectanglePoints.Add(currentPoint);
rectanglePoints.Add(nextPoint);
visitedLines.Add(startLine);
for (int step = 0; step < 5; step++)
{
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)
{
break;
}
}
if (rectanglePoints.Count >= 4)
{
var bounds = CalculateBounds(rectanglePoints);
if (bounds.HasValue && IsValidNoteBox(bounds.Value, notePos, noteHeight) && !usedBoxes.Contains(bounds.Value))
{
return bounds;
}
}
return null;
}
catch (System.Exception ex)
{
Debug.WriteLine($"[DEBUG] 통합 사각형 추적 중 오류: {ex.Message}");
return null;
}
}
private (Line line, string connectionType)? FindNextConnectedLineSegment(List<Line> allLineSegments, Point3d currentPoint, HashSet<Line> visitedLines, double tolerance)
{
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;
}
/// <summary>
/// 수평선이 Polyline과 교차하는지 확인합니다.
/// </summary>
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;
}
}
/// <summary>
/// 두 선분이 교차하는지 확인합니다.
/// </summary>
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;
}
}
/// <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 박스인지 확인합니다.
/// </summary>
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;
}
}
/// <summary>
/// 점들의 경계 상자를 계산합니다.
/// </summary>
private (Point3d minPoint, Point3d maxPoint)? CalculateBounds(List<Point3d> 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;
}
}
/// <summary>
/// Note 박스 내부의 텍스트들을 찾습니다.
/// </summary>
private List<ObjectId> FindTextsInNoteBox(
Transaction tran, DBText noteText, (Point3d minPoint, Point3d maxPoint) noteBox, List<ObjectId> allTextIds)
{
var boxTextIds = new List<ObjectId>();
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;
}
/// <summary>
/// Note 박스 내의 텍스트들을 좌표에 따라 정렬합니다 (위에서 아래로, 왼쪽에서 오른쪽으로).
/// </summary>
private List<NoteEntityInfo> GetSortedNoteContents(Transaction tran, List<ObjectId> boxTextIds, Database database)
{
var noteContents = new List<NoteEntityInfo>();
// 먼저 모든 텍스트와 좌표 정보를 수집
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;
}
}
/// <summary>
/// DWG <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
/// </summary>
public class DwgExtractionResult
{
public List<TitleBlockRowData> TitleBlockRows { get; set; } = new List<TitleBlockRowData>();
public List<TextEntityRowData> TextEntityRows { get; set; } = new List<TextEntityRowData>();
public Dictionary<string, Dictionary<string, (string, string, string, string)>> FileToMapkeyToLabelTagValuePdf { get; set; }
= new Dictionary<string, Dictionary<string, (string, string, string, string)>>();
public void AddMappingData(string fileName, string mapKey, string aiLabel, string dwgTag, string dwgValue, string pdfValue)
{
if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName))
{
FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary<string, (string, string, string, string)>();
}
FileToMapkeyToLabelTagValuePdf[fileName][mapKey] = (aiLabel, dwgTag, dwgValue, pdfValue);
}
}
/// <summary>
/// Title Block <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
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; } = "";
}
/// <summary>
/// Text Entity <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
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; } = "";
}
/// <summary>
/// <20>ؽ<EFBFBD>Ʈ <20><>ƼƼ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
/// </summary>
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; } = "";
}
/// <summary>
/// Note 엔티티 정보 클래스
/// </summary>
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...)
}
}