1059 lines
46 KiB
C#
1059 lines
46 KiB
C#
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>
|
||
/// 모든 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의 정렬점을 기준으로 안정적인 검색 라인을 사용합니다.
|
||
/// </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 = GetAllLineSegments(tran, polylineIds, lineIds);
|
||
try
|
||
{
|
||
var notePos = noteText.Position;
|
||
var noteHeight = noteText.Height;
|
||
|
||
// Note의 X 좌표 결정 (정렬 방식에 따라)
|
||
double noteX;
|
||
if (noteText.HorizontalMode == TextHorizontalMode.TextLeft)
|
||
{
|
||
noteX = noteText.Position.X;
|
||
}
|
||
else
|
||
{
|
||
noteX = noteText.AlignmentPoint.X;
|
||
}
|
||
|
||
Debug.WriteLine($"[DEBUG] 수직 Ray-Casting 시작: NOTE '{noteText.TextString}' at ({noteX:F2}, {notePos.Y:F2})");
|
||
|
||
// 수직 레이 캐스팅을 위한 하향 스캔
|
||
var horizontalLines = new List<(Line line, double y)>();
|
||
|
||
// 모든 라인 세그먼트를 검사하여 수직 레이와 교차하는 수평선들을 찾음
|
||
foreach (var line in allLineSegments)
|
||
{
|
||
// 수평선인지 확인 (Y 좌표가 거의 같음)
|
||
if (Math.Abs(line.StartPoint.Y - line.EndPoint.Y) < noteHeight * 0.1)
|
||
{
|
||
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
|
||
{
|
||
// 생성된 모든 Line 객체들을 Dispose
|
||
foreach (var line in allLineSegments)
|
||
{
|
||
line.Dispose();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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 * 0.5; // 간격 허용 오차
|
||
|
||
Debug.WriteLine($"[DEBUG] 박스 추적 시작: 상단선 ({topLine.StartPoint.X:F2},{topLine.StartPoint.Y:F2}) to ({topLine.EndPoint.X:F2},{topLine.EndPoint.Y:F2})");
|
||
|
||
// 상단선의 끝점들
|
||
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);
|
||
|
||
// 왼쪽 수직선 찾기
|
||
Line leftLine = null;
|
||
foreach (var line in allLineSegments)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
// 오른쪽 수직선 찾기
|
||
Line rightLine = null;
|
||
foreach (var line in allLineSegments)
|
||
{
|
||
if (IsVerticalLine(line, noteHeight) &&
|
||
IsLineConnectedToPoint(line, topRight, tolerance))
|
||
{
|
||
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}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 선분이 수직선인지 확인합니다.
|
||
/// </summary>
|
||
private bool IsVerticalLine(Line line, double noteHeight)
|
||
{
|
||
return Math.Abs(line.StartPoint.X - line.EndPoint.X) < noteHeight * 0.1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 선분이 수평선인지 확인합니다.
|
||
/// </summary>
|
||
private bool IsHorizontalLine(Line line, double noteHeight)
|
||
{
|
||
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>
|
||
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>
|
||
/// 박스가 유효한 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...)
|
||
}
|
||
} |