2383 lines
103 KiB
C#
2383 lines
103 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices; // COM 객체 해제를 위해 필요
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Teigha.DatabaseServices;
|
|
using Teigha.Geometry;
|
|
using Teigha.Runtime;
|
|
using Excel = Microsoft.Office.Interop.Excel;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace DwgExtractorManual.Models
|
|
{
|
|
/// <summary>
|
|
/// DWG 파일에서 Excel로 데이터 내보내기 클래스
|
|
/// AttributeReference, AttributeDefinition, DBText, MText 추출 지원
|
|
/// </summary>
|
|
internal class ExportExcel_old : IDisposable
|
|
{
|
|
// ODA 서비스 객체 (managed by singleton)
|
|
private Services appServices;
|
|
|
|
// Excel COM 객체들
|
|
private Excel.Application excelApplication;
|
|
private Excel.Workbook workbook1;
|
|
private Excel.Worksheet titleBlockSheet; // Title Block용 시트
|
|
private Excel.Worksheet textEntitiesSheet; // Text Entities용 시트
|
|
private Excel.Workbook mappingWorkbook; // 매핑 데이터용 워크북
|
|
private Excel.Worksheet mappingSheet; // 매핑 데이터용 시트
|
|
|
|
readonly List<string> MapKeys;
|
|
|
|
|
|
// 각 시트의 현재 행 번호
|
|
private int titleBlockCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작
|
|
private int textEntitiesCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작
|
|
private int mappingDataCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작
|
|
|
|
// 매핑 데이터 저장용 딕셔너리 - (AILabel, DwgTag, DwgValue, PdfValue)
|
|
private Dictionary<string, Dictionary<string, (string, string, string, string)>> FileToMapkeyToLabelTagValuePdf = new Dictionary<string, Dictionary<string, (string, string, string, string)>>();
|
|
|
|
private FieldMapper fieldMapper;
|
|
// 생성자: ODA 및 Excel 초기화
|
|
public ExportExcel_old()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("🔄 FieldMapper 로딩 중: mapping_table_json.json...");
|
|
fieldMapper = FieldMapper.LoadFromFile("fletimageanalysis/mapping_table_json.json");
|
|
Debug.WriteLine("✅ FieldMapper 로딩 성공");
|
|
MapKeys = fieldMapper.GetAllDocAiKeys();
|
|
Debug.WriteLine($"📊 총 DocAI 키 개수: {MapKeys?.Count ?? 0}");
|
|
|
|
// 매핑 테스트 (디버깅용)
|
|
TestFieldMapper();
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ FieldMapper 로딩 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 예외 타입: {ex.GetType().Name}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
|
|
Debug.WriteLine("🔄 ODA 초기화 중...");
|
|
InitializeTeighaServices();
|
|
Debug.WriteLine("🔄 Excel 초기화 중...");
|
|
InitializeExcel();
|
|
}
|
|
|
|
// 필드 매퍼 테스트
|
|
private void TestFieldMapper()
|
|
{
|
|
Debug.WriteLine("[DEBUG] Testing field mapper...");
|
|
|
|
// 몇 가지 알려진 expressway 필드로 테스트
|
|
var testFields = new[] { "TD_DNAME_MAIN", "TD_DWGNO", "TD_DWGCODE", "TB_MTITIL"};
|
|
|
|
foreach (var field in testFields)
|
|
{
|
|
var aiLabel = fieldMapper.ExpresswayToAilabel(field);
|
|
var docAiKey = fieldMapper.AilabelToDocAiKey(aiLabel);
|
|
Debug.WriteLine($"[DEBUG] Field: {field} -> AILabel: {aiLabel} -> DocAiKey: {docAiKey}");
|
|
}
|
|
}
|
|
|
|
// Teigha 서비스 초기화 (싱글톤 사용)
|
|
private void InitializeTeighaServices()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] TeighaServicesManager를 통한 Services 획득 중...");
|
|
appServices = TeighaServicesManager.Instance.AcquireServices();
|
|
Debug.WriteLine($"[DEBUG] Services 획득 성공. Reference Count: {TeighaServicesManager.Instance.ReferenceCount}");
|
|
}
|
|
catch (Teigha.Runtime.Exception ex)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Teigha Services 초기화 실패: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Excel 애플리케이션 및 워크시트 초기화
|
|
private void InitializeExcel()
|
|
{
|
|
try
|
|
{
|
|
var excelApp = new Excel.Application();
|
|
excelApplication = excelApp;
|
|
excelApplication.Visible = false; // WPF에서는 숨김 처리
|
|
|
|
|
|
// 매핑 데이터용 워크북 및 시트 생성
|
|
mappingWorkbook = excelApp.Workbooks.Add();
|
|
mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets[1];
|
|
mappingSheet.Name = "Mapping Data";
|
|
SetupMappingHeaders();
|
|
|
|
|
|
//Excel.Workbook workbook = excelApp.Workbooks.Add();
|
|
//workbook1 = workbook;
|
|
|
|
// Title Block Sheet 설정 (기본 Sheet1)
|
|
|
|
titleBlockSheet = (Excel.Worksheet)mappingWorkbook.Sheets.Add();
|
|
titleBlockSheet = (Excel.Worksheet)mappingWorkbook.Sheets[2];
|
|
titleBlockSheet.Name = "Title Block";
|
|
SetupTitleBlockHeaders();
|
|
|
|
// Text Entities Sheet 추가
|
|
textEntitiesSheet = (Excel.Worksheet)mappingWorkbook.Sheets.Add();
|
|
textEntitiesSheet = (Excel.Worksheet)mappingWorkbook.Sheets[3];
|
|
textEntitiesSheet.Name = "Text Entities";
|
|
SetupTextEntitiesHeaders();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"Excel 초기화 중 오류 발생: {ex.Message}");
|
|
ReleaseExcelObjects();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Title Block 시트 헤더 설정
|
|
private void SetupTitleBlockHeaders()
|
|
{
|
|
titleBlockSheet.Cells[1, 1] = "Type"; // 예: AttributeReference, AttributeDefinition
|
|
titleBlockSheet.Cells[1, 2] = "Name"; // BlockReference 이름 또는 BlockDefinition 이름
|
|
titleBlockSheet.Cells[1, 3] = "Tag"; // Attribute Tag
|
|
titleBlockSheet.Cells[1, 4] = "Prompt"; // Attribute Prompt
|
|
titleBlockSheet.Cells[1, 5] = "Value"; // Attribute 값 (TextString)
|
|
titleBlockSheet.Cells[1, 6] = "Path"; // 원본 DWG 파일 전체 경로
|
|
titleBlockSheet.Cells[1, 7] = "FileName"; // 원본 DWG 파일 이름만
|
|
|
|
// 헤더 행 스타일
|
|
Excel.Range headerRange = titleBlockSheet.Range["A1:G1"];
|
|
headerRange.Font.Bold = true;
|
|
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue);
|
|
}
|
|
|
|
// Text Entities 시트 헤더 설정
|
|
private void SetupTextEntitiesHeaders()
|
|
{
|
|
textEntitiesSheet.Cells[1, 1] = "Type"; // DBText, MText
|
|
textEntitiesSheet.Cells[1, 2] = "Layer"; // Layer 이름
|
|
textEntitiesSheet.Cells[1, 3] = "Text"; // 실제 텍스트 내용
|
|
textEntitiesSheet.Cells[1, 4] = "Path"; // 원본 DWG 파일 전체 경로
|
|
textEntitiesSheet.Cells[1, 5] = "FileName"; // 원본 DWG 파일 이름만
|
|
|
|
// 헤더 행 스타일
|
|
Excel.Range headerRange = textEntitiesSheet.Range["A1:E1"];
|
|
headerRange.Font.Bold = true;
|
|
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGreen);
|
|
}
|
|
|
|
// 매핑 데이터 시트 헤더 설정
|
|
private void SetupMappingHeaders()
|
|
{
|
|
mappingSheet.Cells[1, 1] = "FileName"; // 파일 이름
|
|
mappingSheet.Cells[1, 2] = "MapKey"; // 매핑 키
|
|
mappingSheet.Cells[1, 3] = "AILabel"; // AI 라벨
|
|
mappingSheet.Cells[1, 4] = "DwgTag"; // DWG Tag
|
|
mappingSheet.Cells[1, 5] = "Att_value"; // DWG 값
|
|
mappingSheet.Cells[1, 6] = "Pdf_value"; // PDF 값 (현재는 빈 값)
|
|
|
|
// 헤더 행 스타일
|
|
Excel.Range headerRange = mappingSheet.Range["A1:F1"];
|
|
headerRange.Font.Bold = true;
|
|
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단일 DWG 파일에서 AttributeReference/AttributeDefinition 데이터를 추출하여
|
|
/// 초기화된 Excel 워크시트에 추가합니다.
|
|
/// </summary>
|
|
/// <param name="filePath">처리할 DWG 파일 경로</param>
|
|
/// <param name="progress">진행 상태 보고를 위한 IProgress 객체</param>
|
|
/// <param name="cancellationToken">작업 취소를 위한 CancellationToken</param>
|
|
/// <returns>성공 시 true, 실패 시 false 반환</returns>
|
|
public bool ExportDwgToExcel(string filePath, IProgress<double> progress = null, CancellationToken cancellationToken = default, bool isExportNote =false)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] ExportDwgToExcel 시작: {filePath}");
|
|
|
|
if (excelApplication == null)
|
|
{
|
|
Debug.WriteLine("❌ Excel이 초기화되지 않았습니다.");
|
|
return false;
|
|
}
|
|
|
|
if (!File.Exists(filePath))
|
|
{
|
|
Debug.WriteLine($"❌ 파일이 존재하지 않습니다: {filePath}");
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] 진행률 0% 보고");
|
|
progress?.Report(0);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
Debug.WriteLine("[DEBUG] ODA Database 객체 생성 중...");
|
|
// ODA Database 객체 생성 및 DWG 파일 읽기
|
|
using (var database = new Database(false, true))
|
|
{
|
|
Debug.WriteLine($"[DEBUG] DWG 파일 읽기 시도: {filePath}");
|
|
database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null);
|
|
Debug.WriteLine("[DEBUG] DWG 파일 읽기 성공");
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
Debug.WriteLine("[DEBUG] 진행률 10% 보고");
|
|
progress?.Report(10);
|
|
|
|
Debug.WriteLine("[DEBUG] 트랜잭션 시작 중...");
|
|
using (var tran = database.TransactionManager.StartTransaction())
|
|
{
|
|
Debug.WriteLine("[DEBUG] BlockTable 접근 중...");
|
|
var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;
|
|
Debug.WriteLine("[DEBUG] ModelSpace 접근 중...");
|
|
using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord)
|
|
{
|
|
int totalEntities = btr.Cast<ObjectId>().Count();
|
|
Debug.WriteLine($"[DEBUG] 총 엔티티 수: {totalEntities}");
|
|
int processedCount = 0;
|
|
|
|
foreach (ObjectId entId in btr)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity)
|
|
{
|
|
// Layer 이름 가져오기 (공통)
|
|
string layerName = GetLayerName(ent.LayerId, tran, database);
|
|
var fileName = Path.GetFileNameWithoutExtension(database.Filename);
|
|
|
|
// 파일명 유효성 검사
|
|
if (string.IsNullOrEmpty(fileName))
|
|
{
|
|
fileName = "Unknown_File";
|
|
Debug.WriteLine($"[DEBUG] Using default filename: {fileName}");
|
|
}
|
|
|
|
// 파일별 매핑 딕셔너리 초기화
|
|
if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName))
|
|
{
|
|
FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary<string, (string, string, string, string)>();
|
|
}
|
|
|
|
// AttributeDefinition 추출
|
|
if (ent is AttributeDefinition attDef)
|
|
{
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attDef.GetType().Name;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 2] = attDef.BlockName;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attDef.Tag;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 4] = attDef.Prompt;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attDef.TextString;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename);
|
|
titleBlockCurrentRow++;
|
|
|
|
var tag = attDef.Tag;
|
|
var aiLabel = fieldMapper.ExpresswayToAilabel(tag);
|
|
var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel);
|
|
var attValue = attDef.TextString;
|
|
Debug.WriteLine($"[DEBUG] AttributeDefinition - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}");
|
|
|
|
// 매핑 데이터 저장
|
|
if (!string.IsNullOrEmpty(aiLabel))
|
|
{
|
|
// mapKey가 null이면 aiLabel을 mapKey로 사용
|
|
var finalMapKey = mapKey ?? aiLabel;
|
|
FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, "");
|
|
Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})");
|
|
}
|
|
else
|
|
{
|
|
// aiLabel이 null이면 tag를 사용하여 저장
|
|
var finalMapKey = mapKey ?? tag;
|
|
if (!string.IsNullOrEmpty(finalMapKey))
|
|
{
|
|
FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, "");
|
|
Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})");
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeDefinition");
|
|
}
|
|
}
|
|
}
|
|
// BlockReference 및 그 안의 AttributeReference 추출
|
|
else if (ent is BlockReference blr)
|
|
{
|
|
foreach (ObjectId attId in blr.AttributeCollection)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference)
|
|
{
|
|
if (attRef != null && attRef.TextString.Trim() != "")
|
|
{
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attRef.GetType().Name;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 2] = blr.Name;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attRef.Tag;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 4] = GetPromptFromAttributeReference(tran, blr, attRef.Tag);
|
|
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attRef.TextString;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename;
|
|
titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename);
|
|
titleBlockCurrentRow++;
|
|
|
|
var tag = attRef.Tag;
|
|
var aiLabel = fieldMapper.ExpresswayToAilabel(tag);
|
|
if (aiLabel == null) continue;
|
|
var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel);
|
|
var attValue = attRef.TextString;
|
|
|
|
Debug.WriteLine($"[DEBUG] AttributeReference - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}");
|
|
|
|
// 매핑 데이터 저장
|
|
if (!string.IsNullOrEmpty(aiLabel))
|
|
{
|
|
// mapKey가 null이면 aiLabel을 mapKey로 사용
|
|
var finalMapKey = mapKey ?? aiLabel;
|
|
FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, "");
|
|
Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})");
|
|
}
|
|
else
|
|
{
|
|
// aiLabel이 null이면 tag를 사용하여 저장
|
|
var finalMapKey = mapKey ?? tag;
|
|
if (!string.IsNullOrEmpty(finalMapKey))
|
|
{
|
|
FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, "");
|
|
Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})");
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeReference");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// DBText 엔티티 추출 (별도 시트)
|
|
else if (ent is DBText dbText)
|
|
{
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "DBText"; // Type
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = dbText.TextString; // Text
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName
|
|
textEntitiesCurrentRow++;
|
|
}
|
|
// MText 엔티티 추출 (별도 시트)
|
|
else if (ent is MText mText)
|
|
{
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "MText"; // Type
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = mText.Contents; // Text
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName
|
|
textEntitiesCurrentRow++;
|
|
}
|
|
}
|
|
|
|
// write values in new excel file with FileToMapkeyToLabelTag
|
|
|
|
processedCount++;
|
|
double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0;
|
|
progress?.Report(Math.Min(currentProgress, 90.0));
|
|
}
|
|
|
|
if (isExportNote)
|
|
{
|
|
Debug.WriteLine("[DEBUG] Note 추출 시작...");
|
|
ExtractNotesFromDrawing(tran, btr, database, progress, cancellationToken);
|
|
Debug.WriteLine("[DEBUG] Note 추출 완료");
|
|
}
|
|
}
|
|
tran.Commit();
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] Transaction committed. Total mapping entries collected: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}");
|
|
|
|
// 매핑 데이터를 Excel 시트에 기록
|
|
Debug.WriteLine("[DEBUG] 매핑 데이터를 Excel에 기록 중...");
|
|
WriteMappingDataToExcel();
|
|
Debug.WriteLine("[DEBUG] 매핑 데이터 Excel 기록 완료");
|
|
}
|
|
|
|
progress?.Report(100);
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.WriteLine("❌ 작업이 취소되었습니다.");
|
|
progress?.Report(0);
|
|
return false;
|
|
}
|
|
catch (Teigha.Runtime.Exception ex)
|
|
{
|
|
progress?.Report(0);
|
|
Debug.WriteLine($"❌ DWG 파일 처리 중 Teigha 오류 발생:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" ErrorStatus: {ex.ErrorStatus}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
return false;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
progress?.Report(0);
|
|
Debug.WriteLine($"❌ 일반 오류 발생:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 예외 타입: {ex.GetType().Name}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
return false;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 도면에서 Note와 관련된 텍스트들을 추출합니다.
|
|
/// </summary>
|
|
private void ExtractNotesFromDrawing(Transaction tran, BlockTableRecord btr, Database database,
|
|
IProgress<double> progress, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
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)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
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에 대해 처리
|
|
foreach (var noteTextId in noteTextIds)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Note 처리 중: '{noteText.TextString}' at {noteText.Position}");
|
|
|
|
// Note 우측아래에 있는 박스 찾기
|
|
var noteBox = FindNoteBox(tran, noteText, polylineIds, lineIds);
|
|
|
|
if (noteBox.HasValue)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Note 박스 발견: {noteBox.Value.minPoint} to {noteBox.Value.maxPoint}");
|
|
|
|
// 박스 내부의 텍스트들 찾기
|
|
var boxTextIds = FindTextsInNoteBox(tran, noteText, noteBox.Value, dbTextIds);
|
|
Debug.WriteLine($"[DEBUG] 박스 내 텍스트: {boxTextIds.Count}개");
|
|
|
|
// 결과를 Excel에 기록
|
|
WriteNoteDataToExcel(tran, noteTextId, boxTextIds, database);
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Note '{noteText.TextString}'에 대한 박스를 찾을 수 없음");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Note 추출 중 오류: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
/// <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)
|
|
{
|
|
// 색상 확인 (마젠타색인지)
|
|
//if (IsNoteColor(dbText))
|
|
//{
|
|
noteTextIds.Add(textId);
|
|
Debug.WriteLine($"[DEBUG] Note 텍스트 발견: '{textContent}' at {dbText.Position}, Color: {dbText.Color}");
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
return noteTextIds;
|
|
}
|
|
/// <summary>
|
|
/// DBText 중에서 "Note"가 포함된 텍스트들을 찾습니다.
|
|
/// </summary>
|
|
/// <summary>
|
|
/// Note 우측아래에 있는 박스를 찾습니다 (교차선 기반 알고리즘).
|
|
/// Note position에서 height*2 만큼 아래로 수평선을 그어서 교차하는 Line/Polyline을 찾습니다.
|
|
/// </summary>
|
|
private (Point3d minPoint, Point3d maxPoint)? FindNoteBox(
|
|
Transaction tran, DBText noteText, List<ObjectId> polylineIds, List<ObjectId> lineIds)
|
|
{
|
|
var notePos = noteText.Position;
|
|
var noteHeight = noteText.Height;
|
|
|
|
// Note position에서 height * 2 만큼 아래로 수평선 정의
|
|
double searchY = notePos.Y - (noteHeight * 2);
|
|
var searchLineStart = new Point3d(notePos.X - noteHeight * 10, searchY, 0);
|
|
var searchLineEnd = new Point3d(notePos.X + noteHeight * 50, searchY, 0);
|
|
|
|
Debug.WriteLine($"[DEBUG] 교차 검색선: ({searchLineStart.X}, {searchLineStart.Y}) to ({searchLineEnd.X}, {searchLineEnd.Y})");
|
|
|
|
// 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))
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 교차하는 Polyline 박스 발견: {box.Value.minPoint} to {box.Value.maxPoint}");
|
|
return box;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Line들과 교차하여 닫힌 사각형 찾기
|
|
var intersectingLines = FindIntersectingLines(tran, lineIds, searchLineStart, searchLineEnd);
|
|
Debug.WriteLine($"[DEBUG] 교차하는 Line 수: {intersectingLines.Count}");
|
|
|
|
foreach (var startLineId in intersectingLines)
|
|
{
|
|
var rectangle = TraceRectangleFromLine(tran, lineIds, startLineId, notePos, noteHeight);
|
|
if (rectangle.HasValue)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 교차하는 Line 사각형 발견: {rectangle.Value.minPoint} to {rectangle.Value.maxPoint}");
|
|
return rectangle;
|
|
}
|
|
}
|
|
|
|
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>
|
|
/// 수평선과 교차하는 Line들을 찾습니다.
|
|
/// </summary>
|
|
private List<ObjectId> FindIntersectingLines(Transaction tran, List<ObjectId> lineIds, Point3d searchLineStart, Point3d searchLineEnd)
|
|
{
|
|
var intersectingLines = new List<ObjectId>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Line에서 시작하여 반시계방향으로 사각형을 추적합니다.
|
|
/// </summary>
|
|
private (Point3d minPoint, Point3d maxPoint)? TraceRectangleFromLine(Transaction tran, List<ObjectId> lineIds, ObjectId startLineId, Point3d notePos, double noteHeight)
|
|
{
|
|
try
|
|
{
|
|
const double tolerance = 2.0; // 좌표 오차 허용 범위
|
|
var visitedLines = new HashSet<ObjectId>();
|
|
var rectanglePoints = new List<Point3d>();
|
|
|
|
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}, {currentPoint.Y}) -> ({nextPoint.X}, {nextPoint.Y})");
|
|
|
|
// 최대 4개의 Line으로 사각형 완성 시도
|
|
for (int step = 0; step < 3; step++) // 시작 Line 제외하고 3개 더 찾기
|
|
{
|
|
var nextLineId = FindNextConnectedLine(tran, lineIds, nextPoint, visitedLines, tolerance);
|
|
if (nextLineId == ObjectId.Null) break;
|
|
|
|
using (var nextLine = tran.GetObject(nextLineId, OpenMode.ForRead) as Line)
|
|
{
|
|
if (nextLine == null) break;
|
|
|
|
// 현재 점과 가까운 쪽을 시작점으로 설정
|
|
Point3d lineStart, lineEnd;
|
|
if (nextPoint.DistanceTo(nextLine.StartPoint) < nextPoint.DistanceTo(nextLine.EndPoint))
|
|
{
|
|
lineStart = nextLine.StartPoint;
|
|
lineEnd = nextLine.EndPoint;
|
|
}
|
|
else
|
|
{
|
|
lineStart = nextLine.EndPoint;
|
|
lineEnd = nextLine.StartPoint;
|
|
}
|
|
|
|
rectanglePoints.Add(lineEnd);
|
|
visitedLines.Add(nextLineId);
|
|
nextPoint = lineEnd;
|
|
|
|
Debug.WriteLine($"[DEBUG] 다음 Line 추가: ({lineStart.X}, {lineStart.Y}) -> ({lineEnd.X}, {lineEnd.Y})");
|
|
|
|
// 시작점으로 돌아왔는지 확인 (사각형 완성)
|
|
if (nextPoint.DistanceTo(currentPoint) < tolerance)
|
|
{
|
|
Debug.WriteLine("[DEBUG] 사각형 완성됨");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4개 이상의 점이 있고 닫힌 형태인지 확인
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 점에서 연결된 다음 Line을 찾습니다.
|
|
/// </summary>
|
|
private ObjectId FindNextConnectedLine(Transaction tran, List<ObjectId> lineIds, Point3d currentPoint, HashSet<ObjectId> visitedLines, double 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;
|
|
|
|
// 현재 점과 연결되어 있는지 확인
|
|
if (currentPoint.DistanceTo(line.StartPoint) < tolerance ||
|
|
currentPoint.DistanceTo(line.EndPoint) < tolerance)
|
|
{
|
|
return lineId;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ObjectId.Null;
|
|
}
|
|
|
|
/// <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 bool IsNoteColor(DBText text)
|
|
{
|
|
try
|
|
{
|
|
// AutoCAD에서 마젠타는 ColorIndex 6번
|
|
if (text.Color.ColorIndex == 6) // Magenta
|
|
return true;
|
|
|
|
// RGB 값으로도 확인 (마젠타: R=255, G=0, B=255)
|
|
if (text.Color.Red == 255 && text.Color.Green == 0 && text.Color.Blue == 255)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Note 우측아래에 있는 박스를 찾습니다 (Polyline 또는 Line 4개).
|
|
/// </summary>
|
|
|
|
|
|
/// <summary>
|
|
/// Polyline이 Note 박스인지 확인하고 경계를 반환합니다.
|
|
/// </summary>
|
|
private (Point3d minPoint, Point3d maxPoint)? GetPolylineBox(Polyline polyline, Point3d notePos, double noteHeight)
|
|
{
|
|
try
|
|
{
|
|
if (polyline.NumberOfVertices < 4) 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);
|
|
|
|
var minPoint = new Point3d(minX, minY, 0);
|
|
var maxPoint = new Point3d(maxX, maxY, 0);
|
|
|
|
// Note 우측아래에 있는지 확인
|
|
if (minX > notePos.X && maxY < notePos.Y)
|
|
{
|
|
// 박스가 너무 크거나 작지 않은지 확인
|
|
double boxWidth = maxX - minX;
|
|
double boxHeight = maxY - minY;
|
|
|
|
if (boxWidth > noteHeight && boxHeight > noteHeight &&
|
|
boxWidth < noteHeight * 50 && boxHeight < noteHeight * 50)
|
|
{
|
|
return (minPoint, maxPoint);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Line들로부터 사각형 박스를 찾습니다.
|
|
/// </summary>
|
|
private (Point3d minPoint, Point3d maxPoint)? FindRectangleFromLines(
|
|
Transaction tran, List<ObjectId> lineIds, Point3d notePos, double noteHeight)
|
|
{
|
|
try
|
|
{
|
|
// Note 우측아래 영역의 Line들만 필터링
|
|
var candidateLineIds = new List<ObjectId>();
|
|
|
|
foreach (var lineId in lineIds)
|
|
{
|
|
using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line)
|
|
{
|
|
if (line == null) continue;
|
|
|
|
var startPoint = line.StartPoint;
|
|
var endPoint = line.EndPoint;
|
|
|
|
// Note 우측아래에 있는 Line인지 확인
|
|
if ((startPoint.X > notePos.X && startPoint.Y < notePos.Y) ||
|
|
(endPoint.X > notePos.X && endPoint.Y < notePos.Y))
|
|
{
|
|
candidateLineIds.Add(lineId);
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] 후보 Line 수: {candidateLineIds.Count}");
|
|
|
|
// 4개의 Line으로 사각형을 구성할 수 있는지 확인
|
|
var rectangles = FindRectanglesFromLines(tran, candidateLineIds, noteHeight);
|
|
|
|
foreach (var rect in rectangles)
|
|
{
|
|
// Note 우측아래에 있는 사각형인지 확인
|
|
if (rect.minPoint.X > notePos.X && rect.maxPoint.Y < notePos.Y)
|
|
{
|
|
return rect;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Line들로부터 가능한 사각형들을 찾습니다.
|
|
/// </summary>
|
|
private List<(Point3d minPoint, Point3d maxPoint)> FindRectanglesFromLines(
|
|
Transaction tran, List<ObjectId> lineIds, double noteHeight)
|
|
{
|
|
var rectangles = new List<(Point3d minPoint, Point3d maxPoint)>();
|
|
|
|
try
|
|
{
|
|
// 모든 Line의 끝점들을 수집
|
|
var points = new HashSet<Point3d>();
|
|
var lineData = new List<(Point3d start, Point3d end)>();
|
|
|
|
foreach (var lineId in lineIds)
|
|
{
|
|
using (var line = tran.GetObject(lineId, OpenMode.ForRead) as Line)
|
|
{
|
|
if (line == null) continue;
|
|
|
|
points.Add(line.StartPoint);
|
|
points.Add(line.EndPoint);
|
|
lineData.Add((line.StartPoint, line.EndPoint));
|
|
}
|
|
}
|
|
|
|
var pointList = points.ToList();
|
|
|
|
// 4개 점으로 사각형을 만들 수 있는지 확인
|
|
for (int i = 0; i < pointList.Count - 3; i++)
|
|
{
|
|
for (int j = i + 1; j < pointList.Count - 2; j++)
|
|
{
|
|
for (int k = j + 1; k < pointList.Count - 1; k++)
|
|
{
|
|
for (int l = k + 1; l < pointList.Count; l++)
|
|
{
|
|
var rect = TryFormRectangle(new[] { pointList[i], pointList[j], pointList[k], pointList[l] }, lineData);
|
|
if (rect.HasValue)
|
|
{
|
|
rectangles.Add(rect.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 사각형 찾기 중 오류: {ex.Message}");
|
|
}
|
|
|
|
return rectangles;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 4개의 점이 사각형을 형성하는지 확인합니다.
|
|
/// </summary>
|
|
private (Point3d minPoint, Point3d maxPoint)? TryFormRectangle(Point3d[] points, List<(Point3d start, Point3d end)> lineData)
|
|
{
|
|
try
|
|
{
|
|
if (points.Length != 4) return null;
|
|
|
|
// 점들을 정렬하여 사각형인지 확인
|
|
var sortedPoints = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToArray();
|
|
|
|
double minX = sortedPoints.Min(p => p.X);
|
|
double maxX = sortedPoints.Max(p => p.X);
|
|
double minY = sortedPoints.Min(p => p.Y);
|
|
double maxY = sortedPoints.Max(p => p.Y);
|
|
|
|
// 실제 사각형의 4개 모서리가 Line으로 연결되어 있는지 확인
|
|
var expectedEdges = new[]
|
|
{
|
|
(new Point3d(minX, minY, 0), new Point3d(maxX, minY, 0)), // 아래
|
|
(new Point3d(maxX, minY, 0), new Point3d(maxX, maxY, 0)), // 오른쪽
|
|
(new Point3d(maxX, maxY, 0), new Point3d(minX, maxY, 0)), // 위
|
|
(new Point3d(minX, maxY, 0), new Point3d(minX, minY, 0)) // 왼쪽
|
|
};
|
|
|
|
int foundEdges = 0;
|
|
foreach (var edge in expectedEdges)
|
|
{
|
|
if (HasLine(lineData, edge.Item1, edge.Item2))
|
|
{
|
|
foundEdges++;
|
|
}
|
|
}
|
|
|
|
// 4개 모서리가 모두 있으면 사각형
|
|
if (foundEdges >= 3) // 완전하지 않을 수도 있으므로 3개 이상
|
|
{
|
|
return (new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 두 점을 연결하는 Line이 있는지 확인합니다.
|
|
/// </summary>
|
|
private bool HasLine(List<(Point3d start, Point3d end)> lineData, Point3d point1, Point3d point2)
|
|
{
|
|
const double tolerance = 0.1; // 허용 오차
|
|
|
|
foreach (var line in lineData)
|
|
{
|
|
// 순방향 확인
|
|
if (point1.DistanceTo(line.start) < tolerance && point2.DistanceTo(line.end) < tolerance)
|
|
return true;
|
|
|
|
// 역방향 확인
|
|
if (point1.DistanceTo(line.end) < tolerance && point2.DistanceTo(line.start) < tolerance)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
// 색상 확인 (그린색인지)
|
|
//if (IsBoxTextColor(dbText))
|
|
//{
|
|
boxTextIds.Add(textId);
|
|
Debug.WriteLine($"[DEBUG] 박스 내 텍스트 발견: '{dbText.TextString}' at {textPos}");
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return boxTextIds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 박스 내 텍스트 색상인지 확인합니다 (그린색).
|
|
/// </summary>
|
|
private bool IsBoxTextColor(DBText text)
|
|
{
|
|
try
|
|
{
|
|
// AutoCAD에서 그린은 ColorIndex 3번
|
|
if (text.Color.ColorIndex == 3) // Green
|
|
return true;
|
|
|
|
// RGB 값으로도 확인 (그린: R=0, G=255, B=0)
|
|
if (text.Color.Red == 0 && text.Color.Green == 255 && text.Color.Blue == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Note 데이터를 Excel에 기록합니다.
|
|
/// </summary>
|
|
private void WriteNoteDataToExcel(
|
|
Transaction tran, ObjectId noteTextId, List<ObjectId> boxTextIds, Database database)
|
|
{
|
|
try
|
|
{
|
|
// Note 정보 기록
|
|
using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText)
|
|
{
|
|
if (noteText != null)
|
|
{
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "Note"; // Type
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = GetLayerName(noteText.LayerId, tran, database); // Layer
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = noteText.TextString; // Text
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName
|
|
textEntitiesCurrentRow++;
|
|
}
|
|
}
|
|
|
|
// 박스 내 텍스트들 기록
|
|
foreach (var boxTextId in boxTextIds)
|
|
{
|
|
using (var boxText = tran.GetObject(boxTextId, OpenMode.ForRead) as DBText)
|
|
{
|
|
if (boxText != null)
|
|
{
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "NoteContent"; // Type
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = GetLayerName(boxText.LayerId, tran, database); // Layer
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = boxText.TextString; // Text
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path
|
|
textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName
|
|
textEntitiesCurrentRow++;
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var noteText = tran.GetObject(noteTextId, OpenMode.ForRead) as DBText)
|
|
{
|
|
if (noteText != null)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Note 데이터 Excel 기록 완료: Note='{noteText.TextString}', Content={boxTextIds.Count}개");
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Note 데이터 Excel 기록 중 오류: {ex.Message}");
|
|
}
|
|
}
|
|
// 매핑 데이터를 Excel 시트에 기록
|
|
private void WriteMappingDataToExcel()
|
|
{
|
|
try
|
|
{
|
|
int currentRow = 2; // 헤더 다음 행부터 시작
|
|
|
|
Debug.WriteLine($"[DEBUG] Writing mapping data to Excel. Total files: {FileToMapkeyToLabelTagValuePdf.Count}");
|
|
Debug.WriteLine($"[DEBUG] 시작 행: {currentRow}");
|
|
|
|
foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf)
|
|
{
|
|
string fileName = fileEntry.Key;
|
|
var mappingData = fileEntry.Value;
|
|
|
|
Debug.WriteLine($"[DEBUG] Processing file: {fileName}, entries: {mappingData.Count}");
|
|
|
|
foreach (var mapEntry in mappingData)
|
|
{
|
|
string mapKey = mapEntry.Key;
|
|
(string aiLabel, string dwgTag, string attValue, string pdfValue) = mapEntry.Value;
|
|
|
|
// null 값 방지
|
|
if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(mapKey))
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Skipping entry with null/empty values: fileName={fileName}, mapKey={mapKey}");
|
|
continue;
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] Writing row {currentRow}: {fileName} | {mapKey} | {aiLabel} | {dwgTag} | PDF: {pdfValue}");
|
|
|
|
try
|
|
{
|
|
// 배치 업데이트를 위한 배열 사용
|
|
object[,] rowData = new object[1, 6];
|
|
rowData[0, 0] = fileName; // FileName
|
|
rowData[0, 1] = mapKey; // MapKey
|
|
rowData[0, 2] = aiLabel ?? ""; // AILabel
|
|
rowData[0, 3] = dwgTag ?? ""; // DwgTag
|
|
rowData[0, 4] = attValue ?? ""; // DwgValue (Att_value)
|
|
rowData[0, 5] = pdfValue ?? ""; // PdfValue (Pdf_value)
|
|
|
|
Excel.Range range = mappingSheet.Range[mappingSheet.Cells[currentRow, 1], mappingSheet.Cells[currentRow, 6]];
|
|
range.Value = rowData;
|
|
|
|
Debug.WriteLine($"[DEBUG] Row {currentRow} written successfully");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Error writing row {currentRow}: {ex.Message}");
|
|
}
|
|
|
|
currentRow++;
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] Mapping data written to Excel. Total rows: {currentRow - 2}");
|
|
Debug.WriteLine($"[DEBUG] 최종 행 번호: {currentRow}");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 매핑 데이터 Excel 기록 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 예외 타입: {ex.GetType().Name}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw; // 상위 메서드로 예외 전파
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기존 Excel 파일을 열어 JSON 파일의 PDF 분석 결과로 매핑 시트를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="excelFilePath">기존 Excel 파일 경로</param>
|
|
/// <param name="jsonFilePath">PDF 분석 결과 JSON 파일 경로</param>
|
|
/// <returns>성공 시 true, 실패 시 false</returns>
|
|
public bool UpdateExistingExcelWithJson(string excelFilePath, string jsonFilePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 기존 Excel 파일 업데이트 시작: {excelFilePath}");
|
|
|
|
if (!File.Exists(excelFilePath))
|
|
{
|
|
Debug.WriteLine($"❌ Excel 파일이 존재하지 않습니다: {excelFilePath}");
|
|
return false;
|
|
}
|
|
|
|
if (!File.Exists(jsonFilePath))
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}");
|
|
return false;
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] Excel 애플리케이션 초기화 중...");
|
|
|
|
// 기존 Excel 파일 열기
|
|
if (excelApplication == null)
|
|
{
|
|
excelApplication = new Excel.Application();
|
|
excelApplication.Visible = false;
|
|
Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨");
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] Excel 파일 열기 시도: {excelFilePath}");
|
|
mappingWorkbook = excelApplication.Workbooks.Open(excelFilePath);
|
|
Debug.WriteLine("[DEBUG] Excel 파일 열기 성공");
|
|
|
|
Debug.WriteLine("[DEBUG] 'Mapping Data' 시트 찾는 중...");
|
|
mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets["Mapping Data"];
|
|
|
|
if (mappingSheet == null)
|
|
{
|
|
Debug.WriteLine("❌ 'Mapping Data' 시트를 찾을 수 없습니다.");
|
|
// 사용 가능한 시트 목록 출력
|
|
Debug.WriteLine("사용 가능한 시트:");
|
|
foreach (Excel.Worksheet sheet in mappingWorkbook.Sheets)
|
|
{
|
|
Debug.WriteLine($" - {sheet.Name}");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Debug.WriteLine("✅ 기존 Excel 파일 열기 성공");
|
|
|
|
// JSON에서 PDF 값 업데이트
|
|
Debug.WriteLine("[DEBUG] JSON 파싱 및 업데이트 시작");
|
|
bool result = UpdateMappingSheetFromJson(jsonFilePath);
|
|
|
|
Debug.WriteLine($"[DEBUG] 업데이트 결과: {result}");
|
|
return result;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 기존 Excel 파일 업데이트 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 예외 타입: {ex.GetType().Name}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 파일에서 PDF 분석 결과를 읽어 Excel 매핑 시트의 Pdf_value 컬럼을 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="jsonFilePath">PDF 분석 결과 JSON 파일 경로</param>
|
|
/// <returns>성공 시 true, 실패 시 false</returns>
|
|
public bool UpdateMappingSheetFromJson(string jsonFilePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] JSON 파일에서 PDF 값 업데이트 시작: {jsonFilePath}");
|
|
|
|
if (!File.Exists(jsonFilePath))
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}");
|
|
return false;
|
|
}
|
|
|
|
if (mappingSheet == null)
|
|
{
|
|
Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다.");
|
|
return false;
|
|
}
|
|
|
|
// JSON 파일 읽기 및 정리
|
|
string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8);
|
|
Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes");
|
|
|
|
// JSON 내용 정리 (주석 제거 등)
|
|
jsonContent = CleanJsonContent(jsonContent);
|
|
|
|
JObject jsonData;
|
|
try
|
|
{
|
|
jsonData = JObject.Parse(jsonContent);
|
|
}
|
|
catch (Newtonsoft.Json.JsonReaderException jsonEx)
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}");
|
|
Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):");
|
|
Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent);
|
|
throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}");
|
|
}
|
|
|
|
var results = jsonData["results"] as JArray;
|
|
if (results == null)
|
|
{
|
|
Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다.");
|
|
Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}");
|
|
return false;
|
|
}
|
|
|
|
int updatedCount = 0;
|
|
int totalEntries = 0;
|
|
|
|
foreach (JObject result in results)
|
|
{
|
|
var fileInfo = result["file_info"];
|
|
var pdfAnalysis = result["pdf_analysis"];
|
|
|
|
if (fileInfo == null || pdfAnalysis == null) continue;
|
|
|
|
string fileName = fileInfo["name"]?.ToString();
|
|
if (string.IsNullOrEmpty(fileName)) continue;
|
|
|
|
// 파일 확장자 제거
|
|
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
|
Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}");
|
|
|
|
// PDF 분석 결과의 각 필드 처리
|
|
foreach (var property in pdfAnalysis.Cast<JProperty>())
|
|
{
|
|
string aiLabel = property.Name; // 예: "설계공구_Station_col1"
|
|
var valueObj = property.Value as JObject;
|
|
|
|
if (valueObj == null) continue;
|
|
|
|
string pdfValue = valueObj["value"]?.ToString();
|
|
if (string.IsNullOrEmpty(pdfValue)) continue;
|
|
|
|
totalEntries++;
|
|
Debug.WriteLine($"[DEBUG] Searching for match: FileName={fileNameWithoutExt}, AILabel={aiLabel}, Value={pdfValue}");
|
|
|
|
// Excel 시트에서 매칭되는 행 찾기 및 업데이트
|
|
if (UpdateExcelRow(fileNameWithoutExt, aiLabel, pdfValue))
|
|
{
|
|
updatedCount++;
|
|
Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}");
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] PDF 값 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨");
|
|
return true;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ JSON에서 PDF 값 업데이트 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 예외 타입: {ex.GetType().Name}");
|
|
if (ex.InnerException != null)
|
|
{
|
|
Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}");
|
|
}
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excel 매핑 시트에서 FileName과 AILabel이 매칭되는 행을 찾아 Pdf_value를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="fileName">파일명 (확장자 제외)</param>
|
|
/// <param name="aiLabel">AI 라벨 (예: "설계공구_Station_col1")</param>
|
|
/// <param name="pdfValue">PDF에서 추출된 값</param>
|
|
/// <returns>업데이트 성공 시 true</returns>
|
|
private bool UpdateExcelRow(string fileName, string aiLabel, string pdfValue)
|
|
{
|
|
try
|
|
{
|
|
// Excel 시트의 마지막 사용된 행 찾기
|
|
Excel.Range usedRange = mappingSheet.UsedRange;
|
|
if (usedRange == null) return false;
|
|
|
|
int lastRow = usedRange.Rows.Count;
|
|
|
|
// 2행부터 검색 (1행은 헤더)
|
|
for (int row = 2; row <= lastRow; row++)
|
|
{
|
|
// Column 1: FileName, Column 3: AILabel 확인
|
|
var cellFileName = mappingSheet.Cells[row, 1]?.Value?.ToString() ?? "";
|
|
var cellAiLabel = mappingSheet.Cells[row, 3]?.Value?.ToString() ?? "";
|
|
|
|
Debug.WriteLine($"[DEBUG] Row {row}: FileName='{cellFileName}', AILabel='{cellAiLabel}'");
|
|
|
|
// 매칭 확인 (대소문자 구분 없이)
|
|
if (string.Equals(cellFileName.Trim(), fileName.Trim(), StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(cellAiLabel.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Column 6: Pdf_value에 값 기록
|
|
mappingSheet.Cells[row, 6] = pdfValue;
|
|
Debug.WriteLine($"[DEBUG] Updated row {row}, column 6 with value: {pdfValue}");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false; // 매칭되는 행을 찾지 못함
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Excel 행 업데이트 중 오류: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Paste the helper function from above here
|
|
public 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 열려있는 Excel 워크북을 저장합니다.
|
|
/// </summary>
|
|
/// <returns>성공 시 true, 실패 시 false</returns>
|
|
public bool SaveExcel()
|
|
{
|
|
try
|
|
{
|
|
if (mappingWorkbook != null)
|
|
{
|
|
mappingWorkbook.Save();
|
|
Debug.WriteLine("✅ Excel 파일 저장 완료");
|
|
return true;
|
|
}
|
|
|
|
if (workbook1 != null)
|
|
{
|
|
workbook1.Save();
|
|
Debug.WriteLine("✅ Excel 파일 저장 완료");
|
|
return true;
|
|
}
|
|
|
|
Debug.WriteLine("❌ 저장할 워크북이 없습니다.");
|
|
return false;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Excel 파일 저장 중 오류: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 매핑 워크북만 저장하고 닫습니다 (완전한 매핑용).
|
|
/// </summary>
|
|
/// <param name="savePath">저장할 파일 경로</param>
|
|
public void SaveMappingWorkbookOnly(string savePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 매핑 워크북 저장 시작: {savePath}");
|
|
|
|
if (mappingWorkbook == null)
|
|
{
|
|
Debug.WriteLine("❌ 매핑 워크북이 초기화되지 않았습니다.");
|
|
return;
|
|
}
|
|
|
|
string directory = Path.GetDirectoryName(savePath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
// 매핑 워크북만 저장 (Excel 2007+ 형식으로)
|
|
mappingWorkbook.SaveAs(savePath,
|
|
FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook,
|
|
AccessMode: Excel.XlSaveAsAccessMode.xlNoChange);
|
|
|
|
Debug.WriteLine($"✅ 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 매핑 워크북 저장 중 오류: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// DWG 전용 매핑 워크북을 생성하고 저장합니다 (PDF 컬럼 제외).
|
|
/// </summary>
|
|
/// <param name="resultFolderPath">결과 파일 저장 폴더 경로</param>
|
|
public void SaveDwgOnlyMappingWorkbook(string resultFolderPath)
|
|
{
|
|
try
|
|
{
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
string savePath = Path.Combine(resultFolderPath, $"{timestamp}_DwgOnly_Mapping.xlsx");
|
|
|
|
Debug.WriteLine($"[DEBUG] DWG 전용 매핑 워크북 생성 시작: {savePath}");
|
|
|
|
// Excel 애플리케이션 초기화 확인
|
|
if (excelApplication == null)
|
|
{
|
|
excelApplication = new Excel.Application();
|
|
excelApplication.Visible = false;
|
|
Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨 (DWG 전용)");
|
|
}
|
|
|
|
// DWG 전용 워크북 생성
|
|
var dwgOnlyWorkbook = excelApplication.Workbooks.Add();
|
|
var dwgOnlyWorksheet = (Excel.Worksheet)dwgOnlyWorkbook.Worksheets[1];
|
|
dwgOnlyWorksheet.Name = "DWG Mapping Data";
|
|
|
|
// 헤더 생성 (PDF Value 컬럼 제외)
|
|
dwgOnlyWorksheet.Cells[1, 1] = "파일명";
|
|
dwgOnlyWorksheet.Cells[1, 2] = "Map Key";
|
|
dwgOnlyWorksheet.Cells[1, 3] = "AI Label";
|
|
dwgOnlyWorksheet.Cells[1, 4] = "DWG Tag";
|
|
dwgOnlyWorksheet.Cells[1, 5] = "DWG Value";
|
|
|
|
// 헤더 스타일 적용
|
|
var headerRange = dwgOnlyWorksheet.Range["A1:E1"];
|
|
headerRange.Font.Bold = true;
|
|
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray);
|
|
headerRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
|
|
|
|
// 데이터 입력 (배치 처리로 성능 향상)
|
|
int totalRows = FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count);
|
|
if (totalRows > 0)
|
|
{
|
|
object[,] data = new object[totalRows, 5];
|
|
int row = 0;
|
|
|
|
foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf)
|
|
{
|
|
string fileName = fileEntry.Key;
|
|
foreach (var mapEntry in fileEntry.Value)
|
|
{
|
|
string mapKey = mapEntry.Key;
|
|
var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value;
|
|
|
|
data[row, 0] = fileName;
|
|
data[row, 1] = mapKey;
|
|
data[row, 2] = aiLabel;
|
|
data[row, 3] = dwgTag;
|
|
data[row, 4] = dwgValue;
|
|
|
|
row++;
|
|
}
|
|
}
|
|
|
|
// 한 번에 모든 데이터 입력
|
|
Excel.Range dataRange = dwgOnlyWorksheet.Range[
|
|
dwgOnlyWorksheet.Cells[2, 1],
|
|
dwgOnlyWorksheet.Cells[totalRows + 1, 5]];
|
|
dataRange.Value = data;
|
|
}
|
|
|
|
// 컬럼 자동 크기 조정
|
|
dwgOnlyWorksheet.Columns.AutoFit();
|
|
|
|
// 파일 저장
|
|
string directory = Path.GetDirectoryName(savePath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
dwgOnlyWorkbook.SaveAs(savePath,
|
|
FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook,
|
|
AccessMode: Excel.XlSaveAsAccessMode.xlNoChange);
|
|
|
|
Debug.WriteLine($"✅ DWG 전용 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}");
|
|
|
|
// 워크북 정리
|
|
dwgOnlyWorkbook.Close(false);
|
|
ReleaseComObject(dwgOnlyWorksheet);
|
|
ReleaseComObject(dwgOnlyWorkbook);
|
|
|
|
// 가비지 컬렉션 강제 실행으로 메모리 해제
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ DWG 전용 매핑 워크북 저장 중 오류: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 Excel 워크북을 지정된 경로에 저장하고 Excel 애플리케이션을 종료합니다.
|
|
/// </summary>
|
|
/// <param name="savePath">Excel 파일을 저장할 전체 경로</param>
|
|
public void SaveAndCloseExcel(string savePath)
|
|
{
|
|
if (workbook1 == null) return;
|
|
|
|
try
|
|
{
|
|
string directory = Path.GetDirectoryName(savePath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
workbook1.SaveAs(savePath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange);
|
|
|
|
// 매핑 데이터 워크북도 저장
|
|
if (mappingWorkbook != null)
|
|
{
|
|
string mappingPath = Path.Combine(directory, Path.GetFileNameWithoutExtension(savePath) + "_Mapping.xlsx");
|
|
mappingWorkbook.SaveAs(mappingPath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange);
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"Excel 파일 저장 중 오류 발생: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
CloseExcelObjects();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excel 객체들을 닫습니다 (저장하지 않음).
|
|
/// </summary>
|
|
public void CloseExcelObjectsWithoutSaving()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] Excel 객체 정리 시작");
|
|
|
|
if (workbook1 != null)
|
|
{
|
|
try { workbook1.Close(false); }
|
|
catch { }
|
|
}
|
|
if (mappingWorkbook != null)
|
|
{
|
|
try { mappingWorkbook.Close(false); }
|
|
catch { }
|
|
}
|
|
if (excelApplication != null)
|
|
{
|
|
try { excelApplication.Quit(); }
|
|
catch { }
|
|
}
|
|
|
|
ReleaseExcelObjects();
|
|
|
|
Debug.WriteLine("✅ Excel 객체 정리 완료");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ Excel 객체 정리 중 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void CloseExcelObjects()
|
|
{
|
|
if (workbook1 != null)
|
|
{
|
|
try { workbook1.Close(false); }
|
|
catch { }
|
|
}
|
|
if (mappingWorkbook != null)
|
|
{
|
|
try { mappingWorkbook.Close(false); }
|
|
catch { }
|
|
}
|
|
if (excelApplication != null)
|
|
{
|
|
try { excelApplication.Quit(); }
|
|
catch { }
|
|
}
|
|
|
|
ReleaseExcelObjects();
|
|
}
|
|
|
|
private void ReleaseExcelObjects()
|
|
{
|
|
ReleaseComObject(titleBlockSheet);
|
|
ReleaseComObject(textEntitiesSheet);
|
|
ReleaseComObject(mappingSheet);
|
|
ReleaseComObject(workbook1);
|
|
ReleaseComObject(mappingWorkbook);
|
|
ReleaseComObject(excelApplication);
|
|
|
|
titleBlockSheet = null;
|
|
textEntitiesSheet = null;
|
|
mappingSheet = null;
|
|
workbook1 = null;
|
|
mappingWorkbook = null;
|
|
excelApplication = null;
|
|
}
|
|
|
|
private void ReleaseComObject(object obj)
|
|
{
|
|
try
|
|
{
|
|
if (obj != null && Marshal.IsComObject(obj))
|
|
{
|
|
Marshal.ReleaseComObject(obj);
|
|
}
|
|
}
|
|
catch (System.Exception)
|
|
{
|
|
// 해제 중 오류 발생 시 무시
|
|
}
|
|
finally
|
|
{
|
|
obj = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Layer ID로부터 Layer 이름을 가져옵니다.
|
|
/// </summary>
|
|
/// <param name="layerId">Layer ObjectId</param>
|
|
/// <param name="transaction">현재 트랜잭션</param>
|
|
/// <param name="database">데이터베이스 객체</param>
|
|
/// <returns>Layer 이름 또는 빈 문자열</returns>
|
|
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 "";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 매핑 딕셔너리를 JSON 파일로 저장합니다.
|
|
/// </summary>
|
|
/// <param name="filePath">저장할 JSON 파일 경로</param>
|
|
public void SaveMappingDictionary(string filePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 저장 시작: {filePath}");
|
|
|
|
// 딕셔너리를 직렬화 가능한 형태로 변환
|
|
var serializableData = new Dictionary<string, Dictionary<string, object>>();
|
|
|
|
foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf)
|
|
{
|
|
var fileData = new Dictionary<string, object>();
|
|
foreach (var mapEntry in fileEntry.Value)
|
|
{
|
|
fileData[mapEntry.Key] = new
|
|
{
|
|
AILabel = mapEntry.Value.Item1,
|
|
DwgTag = mapEntry.Value.Item2,
|
|
DwgValue = mapEntry.Value.Item3,
|
|
PdfValue = mapEntry.Value.Item4
|
|
};
|
|
}
|
|
serializableData[fileEntry.Key] = fileData;
|
|
}
|
|
|
|
// JSON으로 직렬화
|
|
string jsonContent = JsonConvert.SerializeObject(serializableData, Formatting.Indented);
|
|
File.WriteAllText(filePath, jsonContent, System.Text.Encoding.UTF8);
|
|
|
|
Debug.WriteLine($"✅ 매핑 딕셔너리 저장 완료: {Path.GetFileName(filePath)}");
|
|
Debug.WriteLine($"📊 저장된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}");
|
|
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 매핑 딕셔너리 저장 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 파일에서 매핑 딕셔너리를 로드합니다.
|
|
/// </summary>
|
|
/// <param name="filePath">로드할 JSON 파일 경로</param>
|
|
public void LoadMappingDictionary(string filePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 로드 시작: {filePath}");
|
|
|
|
if (!File.Exists(filePath))
|
|
{
|
|
Debug.WriteLine($"⚠️ 매핑 파일이 존재하지 않습니다: {filePath}");
|
|
return;
|
|
}
|
|
|
|
string jsonContent = File.ReadAllText(filePath, System.Text.Encoding.UTF8);
|
|
Debug.WriteLine($"[DEBUG] JSON 내용 길이: {jsonContent.Length}");
|
|
|
|
// JSON 내용 정리 (주석 제거 등)
|
|
jsonContent = CleanJsonContent(jsonContent);
|
|
|
|
Dictionary<string, Dictionary<string, JObject>> deserializedData;
|
|
try
|
|
{
|
|
deserializedData = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, JObject>>>(jsonContent);
|
|
}
|
|
catch (Newtonsoft.Json.JsonReaderException jsonEx)
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}");
|
|
Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):");
|
|
Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent);
|
|
throw new System.Exception($"매핑 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {filePath}");
|
|
}
|
|
|
|
// 새로운 딕셔너리 초기화
|
|
FileToMapkeyToLabelTagValuePdf.Clear();
|
|
Debug.WriteLine("[DEBUG] 기존 딕셔너리 초기화됨");
|
|
|
|
if (deserializedData != null)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 역직렬화된 파일 수: {deserializedData.Count}");
|
|
|
|
foreach (var fileEntry in deserializedData)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 파일 처리 중: {fileEntry.Key}");
|
|
var fileData = new Dictionary<string, (string, string, string, string)>();
|
|
|
|
foreach (var mapEntry in fileEntry.Value)
|
|
{
|
|
var valueObj = mapEntry.Value;
|
|
string aiLabel = valueObj["AILabel"]?.ToString() ?? "";
|
|
string dwgTag = valueObj["DwgTag"]?.ToString() ?? "";
|
|
string dwgValue = valueObj["DwgValue"]?.ToString() ?? "";
|
|
string pdfValue = valueObj["PdfValue"]?.ToString() ?? "";
|
|
|
|
fileData[mapEntry.Key] = (aiLabel, dwgTag, dwgValue, pdfValue);
|
|
Debug.WriteLine($"[DEBUG] 항목 로드: {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, PDF:{pdfValue}");
|
|
}
|
|
|
|
FileToMapkeyToLabelTagValuePdf[fileEntry.Key] = fileData;
|
|
Debug.WriteLine($"[DEBUG] 파일 {fileEntry.Key}: {fileData.Count}개 항목 로드됨");
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine($"✅ 매핑 딕셔너리 로드 완료");
|
|
Debug.WriteLine($"📊 로드된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}");
|
|
Debug.WriteLine($"📊 총 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}");
|
|
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 매핑 딕셔너리 로드 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 매핑 딕셔너리 데이터를 Excel 시트에 기록합니다 (완전한 매핑용).
|
|
/// </summary>
|
|
public void WriteCompleteMapping()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] WriteCompleteMapping 시작");
|
|
Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 항목 수: {FileToMapkeyToLabelTagValuePdf.Count}");
|
|
|
|
if (FileToMapkeyToLabelTagValuePdf.Count == 0)
|
|
{
|
|
Debug.WriteLine("⚠️ 매핑 딕셔너리가 비어있습니다.");
|
|
return;
|
|
}
|
|
|
|
if (mappingSheet == null)
|
|
{
|
|
Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다.");
|
|
return;
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] 매핑 시트 이름: {mappingSheet.Name}");
|
|
Debug.WriteLine($"[DEBUG] 전체 데이터 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}");
|
|
|
|
// 샘플 데이터 출력 (처음 3개)
|
|
int sampleCount = 0;
|
|
foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf.Take(2))
|
|
{
|
|
Debug.WriteLine($"[DEBUG] 파일: {fileEntry.Key}");
|
|
foreach (var mapEntry in fileEntry.Value.Take(3))
|
|
{
|
|
var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value;
|
|
Debug.WriteLine($"[DEBUG] {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, DWGVAL:{dwgValue}, PDF:{pdfValue}");
|
|
sampleCount++;
|
|
}
|
|
}
|
|
|
|
// 기존 WriteMappingDataToExcel 메서드 호출
|
|
WriteMappingDataToExcel();
|
|
|
|
Debug.WriteLine("✅ WriteCompleteMapping 완료");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ WriteCompleteMapping 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// PDF 분석 결과 JSON으로 매핑 딕셔너리의 PdfValue를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="jsonFilePath">PDF 분석 결과 JSON 파일 경로</param>
|
|
public void UpdateWithPdfData(string jsonFilePath)
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine($"[DEBUG] PDF 데이터로 매핑 딕셔너리 업데이트 시작: {jsonFilePath}");
|
|
|
|
if (!File.Exists(jsonFilePath))
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}");
|
|
return;
|
|
}
|
|
|
|
// JSON 파일 읽기 및 정리
|
|
string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8);
|
|
Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes");
|
|
|
|
// JSON 내용 정리 (주석 제거 등)
|
|
jsonContent = CleanJsonContent(jsonContent);
|
|
|
|
JObject jsonData;
|
|
try
|
|
{
|
|
jsonData = JObject.Parse(jsonContent);
|
|
}
|
|
catch (Newtonsoft.Json.JsonReaderException jsonEx)
|
|
{
|
|
Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}");
|
|
Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):");
|
|
Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent);
|
|
throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}");
|
|
}
|
|
|
|
var results = jsonData["results"] as JArray;
|
|
if (results == null)
|
|
{
|
|
Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다.");
|
|
Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}");
|
|
return;
|
|
}
|
|
|
|
int updatedCount = 0;
|
|
int totalEntries = 0;
|
|
|
|
foreach (JObject result in results)
|
|
{
|
|
var fileInfo = result["file_info"];
|
|
var pdfAnalysis = result["pdf_analysis"];
|
|
|
|
if (fileInfo == null || pdfAnalysis == null) continue;
|
|
|
|
string fileName = fileInfo["name"]?.ToString();
|
|
if (string.IsNullOrEmpty(fileName)) continue;
|
|
|
|
// 파일 확장자 제거
|
|
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
|
Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}");
|
|
|
|
// 해당 파일의 매핑 데이터 확인
|
|
if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileNameWithoutExt))
|
|
{
|
|
Debug.WriteLine($"⚠️ 매핑 데이터에 파일이 없습니다: {fileNameWithoutExt}");
|
|
continue;
|
|
}
|
|
|
|
// PDF 분석 결과의 각 필드 처리
|
|
foreach (var property in pdfAnalysis.Cast<JProperty>())
|
|
{
|
|
string aiLabel = property.Name; // 예: "설계공구_Station_col1"
|
|
var valueObj = property.Value as JObject;
|
|
|
|
if (valueObj == null) continue;
|
|
|
|
string pdfValue = valueObj["value"]?.ToString();
|
|
if (string.IsNullOrEmpty(pdfValue)) continue;
|
|
|
|
totalEntries++;
|
|
|
|
// 매핑 딕셔너리에서 해당 항목 찾기
|
|
var fileData = FileToMapkeyToLabelTagValuePdf[fileNameWithoutExt];
|
|
|
|
// AILabel로 매칭 찾기
|
|
var matchingEntry = fileData.FirstOrDefault(kvp =>
|
|
string.Equals(kvp.Value.Item1.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (!string.IsNullOrEmpty(matchingEntry.Key))
|
|
{
|
|
// 기존 값 유지하면서 PdfValue만 업데이트
|
|
var existingValue = matchingEntry.Value;
|
|
fileData[matchingEntry.Key] = (existingValue.Item1, existingValue.Item2, existingValue.Item3, pdfValue);
|
|
updatedCount++;
|
|
|
|
Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}");
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine($"[DEBUG] PDF 데이터 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨");
|
|
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ PDF 데이터 업데이트 중 오류:");
|
|
Debug.WriteLine($" 메시지: {ex.Message}");
|
|
Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 폴더 간 처리를 위해 누적된 데이터를 정리합니다.
|
|
/// </summary>
|
|
public void ClearAccumulatedData()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] 누적 데이터 정리 시작");
|
|
|
|
FileToMapkeyToLabelTagValuePdf.Clear();
|
|
|
|
titleBlockCurrentRow = 2;
|
|
textEntitiesCurrentRow = 2;
|
|
mappingDataCurrentRow = 2;
|
|
|
|
Debug.WriteLine("✅ 누적 데이터 정리 완료");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ 누적 데이터 정리 중 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 내용을 정리하여 파싱 가능한 상태로 만듭니다.
|
|
/// 주석 제거 및 기타 무효한 문자 처리
|
|
/// </summary>
|
|
/// <param name="jsonContent">원본 JSON 내용</param>
|
|
/// <returns>정리된 JSON 내용</returns>
|
|
private string CleanJsonContent(string jsonContent)
|
|
{
|
|
if (string.IsNullOrEmpty(jsonContent))
|
|
return jsonContent;
|
|
|
|
try
|
|
{
|
|
// 줄별로 처리하여 주석 제거
|
|
var lines = jsonContent.Split('\n');
|
|
var cleanedLines = new List<string>();
|
|
|
|
bool inMultiLineComment = false;
|
|
|
|
foreach (string line in lines)
|
|
{
|
|
string processedLine = line;
|
|
|
|
// 멀티라인 주석 처리 (/* */)
|
|
if (inMultiLineComment)
|
|
{
|
|
int endIndex = processedLine.IndexOf("*/");
|
|
if (endIndex >= 0)
|
|
{
|
|
processedLine = processedLine.Substring(endIndex + 2);
|
|
inMultiLineComment = false;
|
|
}
|
|
else
|
|
{
|
|
continue; // 전체 라인이 주석
|
|
}
|
|
}
|
|
|
|
// 멀티라인 주석 시작 확인
|
|
int multiLineStart = processedLine.IndexOf("/*");
|
|
if (multiLineStart >= 0)
|
|
{
|
|
int multiLineEnd = processedLine.IndexOf("*/", multiLineStart + 2);
|
|
if (multiLineEnd >= 0)
|
|
{
|
|
// 같은 라인에서 시작하고 끝나는 주석
|
|
processedLine = processedLine.Substring(0, multiLineStart) +
|
|
processedLine.Substring(multiLineEnd + 2);
|
|
}
|
|
else
|
|
{
|
|
// 멀티라인 주석 시작
|
|
processedLine = processedLine.Substring(0, multiLineStart);
|
|
inMultiLineComment = true;
|
|
}
|
|
}
|
|
|
|
// 싱글라인 주석 제거 (//) - 문자열 내부의 //는 제외
|
|
bool inString = false;
|
|
bool escaped = false;
|
|
int commentIndex = -1;
|
|
|
|
for (int i = 0; i < processedLine.Length - 1; i++)
|
|
{
|
|
char current = processedLine[i];
|
|
char next = processedLine[i + 1];
|
|
|
|
if (escaped)
|
|
{
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
|
|
if (current == '\\')
|
|
{
|
|
escaped = true;
|
|
continue;
|
|
}
|
|
|
|
if (current == '"')
|
|
{
|
|
inString = !inString;
|
|
continue;
|
|
}
|
|
|
|
if (!inString && current == '/' && next == '/')
|
|
{
|
|
commentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (commentIndex >= 0)
|
|
{
|
|
processedLine = processedLine.Substring(0, commentIndex);
|
|
}
|
|
|
|
// 빈 라인이 아니면 추가
|
|
if (!string.IsNullOrWhiteSpace(processedLine))
|
|
{
|
|
cleanedLines.Add(processedLine);
|
|
}
|
|
}
|
|
|
|
string result = string.Join("\n", cleanedLines);
|
|
Debug.WriteLine($"[DEBUG] JSON 정리 완료: {jsonContent.Length} -> {result.Length} bytes");
|
|
return result;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"❌ JSON 정리 중 오류: {ex.Message}");
|
|
// 정리 실패시 원본 반환
|
|
return jsonContent;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
Debug.WriteLine("[DEBUG] ExportExcel Dispose 시작");
|
|
|
|
if (excelApplication != null)
|
|
{
|
|
Debug.WriteLine("[DEBUG] Excel 객체 정리 중...");
|
|
CloseExcelObjects();
|
|
}
|
|
|
|
if (appServices != null)
|
|
{
|
|
Debug.WriteLine("[DEBUG] Teigha Services 해제 중...");
|
|
try
|
|
{
|
|
TeighaServicesManager.Instance.ReleaseServices();
|
|
Debug.WriteLine($"[DEBUG] Teigha Services 해제 완료. Remaining ref count: {TeighaServicesManager.Instance.ReferenceCount}");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] Teigha Services 해제 중 오류 (무시됨): {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
appServices = null;
|
|
}
|
|
}
|
|
|
|
Debug.WriteLine("[DEBUG] ExportExcel Dispose 완료");
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.WriteLine($"[DEBUG] ExportExcel Dispose 중 전역 오류: {ex.Message}");
|
|
// Disposal 오류는 로그만 남기고 계속 진행
|
|
}
|
|
}
|
|
}
|
|
}
|