도면에서 표 추출
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public string? SourceFolderPath { get; set; }
|
||||
public string? DestinationFolderPath { get; set; }
|
||||
public string? LastExportType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
261
Models/CsvDataWriter.cs
Normal file
261
Models/CsvDataWriter.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Note 데이터를 CSV 파일로 출력하는 클래스
|
||||
/// Note Box 안의 일반 텍스트와 테이블 텍스트를 분리하여 CSV로 출력
|
||||
/// </summary>
|
||||
public class CsvDataWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Note 박스 안의 일반 텍스트들을 CSV 파일로 저장
|
||||
/// </summary>
|
||||
public void WriteNoteBoxTextToCsv(List<NoteEntityInfo> noteEntities, string filePath)
|
||||
{
|
||||
if (noteEntities == null || noteEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var csvLines = new List<string>();
|
||||
|
||||
// CSV 헤더 추가
|
||||
csvLines.Add("Type,Layer,Text,X,Y,SortOrder,Path,FileName");
|
||||
|
||||
// Note와 NoteContent 데이터 추출 (테이블 제외)
|
||||
var noteBoxTexts = noteEntities
|
||||
.Where(ne => ne.Type == "Note" || ne.Type == "NoteContent")
|
||||
.OrderBy(ne => ne.SortOrder)
|
||||
.ToList();
|
||||
|
||||
foreach (var noteEntity in noteBoxTexts)
|
||||
{
|
||||
var csvLine = $"{EscapeCsvField(noteEntity.Type)}," +
|
||||
$"{EscapeCsvField(noteEntity.Layer)}," +
|
||||
$"{EscapeCsvField(noteEntity.Text)}," +
|
||||
$"{noteEntity.X:F3}," +
|
||||
$"{noteEntity.Y:F3}," +
|
||||
$"{noteEntity.SortOrder}," +
|
||||
$"{EscapeCsvField(noteEntity.Path)}," +
|
||||
$"{EscapeCsvField(noteEntity.FileName)}";
|
||||
|
||||
csvLines.Add(csvLine);
|
||||
}
|
||||
|
||||
// UTF-8 BOM 포함하여 파일 저장 (Excel에서 한글 깨짐 방지)
|
||||
var utf8WithBom = new UTF8Encoding(true);
|
||||
File.WriteAllLines(filePath, csvLines, utf8WithBom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 박스 안의 테이블 데이터들을 별도 CSV 파일로 저장
|
||||
/// </summary>
|
||||
public void WriteNoteTablesToCsv(List<NoteEntityInfo> noteEntities, string filePath)
|
||||
{
|
||||
if (noteEntities == null || noteEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var allCsvLines = new List<string>();
|
||||
|
||||
// 테이블 데이터가 있는 Note들 추출
|
||||
var notesWithTables = noteEntities
|
||||
.Where(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv))
|
||||
.OrderByDescending(ne => ne.Y) // Y 좌표로 정렬 (위에서 아래로)
|
||||
.ToList();
|
||||
|
||||
foreach (var noteWithTable in notesWithTables)
|
||||
{
|
||||
// Note 정보 헤더 추가
|
||||
allCsvLines.Add($"=== NOTE: {noteWithTable.Text} (at {noteWithTable.X:F1}, {noteWithTable.Y:F1}) ===");
|
||||
allCsvLines.Add(""); // 빈 줄
|
||||
|
||||
// 테이블 CSV 데이터 추가
|
||||
var tableLines = noteWithTable.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
allCsvLines.AddRange(tableLines);
|
||||
|
||||
// Note 간 구분을 위한 빈 줄들
|
||||
allCsvLines.Add("");
|
||||
allCsvLines.Add("");
|
||||
}
|
||||
|
||||
// UTF-8 BOM 포함하여 파일 저장
|
||||
var utf8WithBom = new UTF8Encoding(true);
|
||||
File.WriteAllLines(filePath, allCsvLines, utf8WithBom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 박스와 테이블 데이터를 통합하여 하나의 CSV 파일로 저장
|
||||
/// </summary>
|
||||
public void WriteNoteDataToCombinedCsv(List<NoteEntityInfo> noteEntities, string filePath)
|
||||
{
|
||||
if (noteEntities == null || noteEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var csvLines = new List<string>();
|
||||
|
||||
// CSV 헤더 추가
|
||||
csvLines.Add("Type,Layer,Text,X,Y,SortOrder,TableData,Path,FileName");
|
||||
|
||||
// 모든 Note 관련 데이터를 SortOrder로 정렬
|
||||
var sortedNoteEntities = noteEntities
|
||||
.OrderBy(ne => ne.SortOrder)
|
||||
.ToList();
|
||||
|
||||
foreach (var noteEntity in sortedNoteEntities)
|
||||
{
|
||||
// 테이블 데이터가 있는 경우 이를 별도 필드로 처리
|
||||
var tableData = "";
|
||||
if (noteEntity.Type == "Note" && !string.IsNullOrEmpty(noteEntity.TableCsv))
|
||||
{
|
||||
// 테이블 CSV 데이터를 하나의 필드로 압축 (줄바꿈을 |로 대체)
|
||||
tableData = noteEntity.TableCsv.Replace("\n", "|").Replace("\r", "");
|
||||
}
|
||||
|
||||
var csvLine = $"{EscapeCsvField(noteEntity.Type)}," +
|
||||
$"{EscapeCsvField(noteEntity.Layer)}," +
|
||||
$"{EscapeCsvField(noteEntity.Text)}," +
|
||||
$"{noteEntity.X:F3}," +
|
||||
$"{noteEntity.Y:F3}," +
|
||||
$"{noteEntity.SortOrder}," +
|
||||
$"{EscapeCsvField(tableData)}," +
|
||||
$"{EscapeCsvField(noteEntity.Path)}," +
|
||||
$"{EscapeCsvField(noteEntity.FileName)}";
|
||||
|
||||
csvLines.Add(csvLine);
|
||||
}
|
||||
|
||||
// UTF-8 BOM 포함하여 파일 저장
|
||||
var utf8WithBom = new UTF8Encoding(true);
|
||||
File.WriteAllLines(filePath, csvLines, utf8WithBom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 각 Note별로 개별 CSV 파일 생성 (테이블이 있는 경우)
|
||||
/// </summary>
|
||||
public void WriteIndividualNoteTablesCsv(List<NoteEntityInfo> noteEntities, string baseDirectory)
|
||||
{
|
||||
if (noteEntities == null || noteEntities.Count == 0)
|
||||
return;
|
||||
|
||||
// 디렉토리가 없으면 생성
|
||||
if (!Directory.Exists(baseDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(baseDirectory);
|
||||
}
|
||||
|
||||
var notesWithTables = noteEntities
|
||||
.Where(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv))
|
||||
.OrderByDescending(ne => ne.Y)
|
||||
.ToList();
|
||||
|
||||
int noteIndex = 1;
|
||||
foreach (var noteWithTable in notesWithTables)
|
||||
{
|
||||
// 파일명 생성 (특수문자 제거)
|
||||
var safeNoteText = MakeSafeFileName(noteWithTable.Text);
|
||||
var fileName = $"Note_{noteIndex:D2}_{safeNoteText}.csv";
|
||||
var fullPath = Path.Combine(baseDirectory, fileName);
|
||||
|
||||
// 테이블 CSV 데이터를 파일로 저장
|
||||
var tableLines = noteWithTable.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var utf8WithBom = new UTF8Encoding(true);
|
||||
File.WriteAllLines(fullPath, tableLines, utf8WithBom);
|
||||
|
||||
noteIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV 필드에서 특수문자를 이스케이프 처리
|
||||
/// </summary>
|
||||
private string EscapeCsvField(string field)
|
||||
{
|
||||
if (string.IsNullOrEmpty(field))
|
||||
return "";
|
||||
|
||||
// 쉼표, 따옴표, 줄바꿈이 있으면 따옴표로 감싸기
|
||||
if (field.Contains(",") || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
|
||||
{
|
||||
return "\"" + field.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파일명에 사용할 수 없는 문자들을 제거하여 안전한 파일명 생성
|
||||
/// </summary>
|
||||
private string MakeSafeFileName(string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
return "Unknown";
|
||||
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var safeFileName = fileName;
|
||||
|
||||
foreach (var invalidChar in invalidChars)
|
||||
{
|
||||
safeFileName = safeFileName.Replace(invalidChar, '_');
|
||||
}
|
||||
|
||||
// 길이 제한 (Windows 파일명 제한 고려)
|
||||
if (safeFileName.Length > 50)
|
||||
{
|
||||
safeFileName = safeFileName.Substring(0, 50);
|
||||
}
|
||||
|
||||
return safeFileName.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 데이터 통계 정보를 CSV로 저장
|
||||
/// </summary>
|
||||
public void WriteNoteStatisticsToCsv(List<NoteEntityInfo> noteEntities, string filePath)
|
||||
{
|
||||
if (noteEntities == null || noteEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var csvLines = new List<string>();
|
||||
|
||||
// 통계 헤더
|
||||
csvLines.Add("Statistic,Count,Details");
|
||||
|
||||
// 전체 Note 개수
|
||||
var totalNotes = noteEntities.Count(ne => ne.Type == "Note");
|
||||
csvLines.Add($"Total Notes,{totalNotes},");
|
||||
|
||||
// 테이블이 있는 Note 개수
|
||||
var notesWithTables = noteEntities.Count(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv));
|
||||
csvLines.Add($"Notes with Tables,{notesWithTables},");
|
||||
|
||||
// 일반 텍스트만 있는 Note 개수
|
||||
var notesWithTextOnly = totalNotes - notesWithTables;
|
||||
csvLines.Add($"Notes with Text Only,{notesWithTextOnly},");
|
||||
|
||||
// 전체 Note 콘텐츠 개수
|
||||
var totalNoteContents = noteEntities.Count(ne => ne.Type == "NoteContent");
|
||||
csvLines.Add($"Total Note Contents,{totalNoteContents},");
|
||||
|
||||
// 레이어별 분포
|
||||
csvLines.Add(",,");
|
||||
csvLines.Add("Layer Distribution,,");
|
||||
|
||||
var layerGroups = noteEntities
|
||||
.GroupBy(ne => ne.Layer)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.ToList();
|
||||
|
||||
foreach (var layerGroup in layerGroups)
|
||||
{
|
||||
csvLines.Add($"Layer: {layerGroup.Key},{layerGroup.Count()},");
|
||||
}
|
||||
|
||||
var utf8WithBom = new UTF8Encoding(true);
|
||||
File.WriteAllLines(filePath, csvLines, utf8WithBom);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,65 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 엔터티 데이터를 Excel 시트에 쓰기 (테이블 및 셀 병합 포함)
|
||||
/// </summary>
|
||||
public void WriteNoteEntityData(List<NoteEntityInfo> noteEntityRows)
|
||||
{
|
||||
if (excelManager.NoteEntitiesSheet == null || noteEntityRows == null || noteEntityRows.Count == 0)
|
||||
return;
|
||||
|
||||
int excelRow = 2; // 헤더 다음 행부터 시작
|
||||
|
||||
foreach (var note in noteEntityRows)
|
||||
{
|
||||
// 기본 Note 정보 쓰기
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 1] = note.Type;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 2] = note.Layer;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 3] = note.Text;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 4] = note.X;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 5] = note.Y;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 6] = note.SortOrder;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 8] = note.Path;
|
||||
excelManager.NoteEntitiesSheet.Cells[excelRow, 9] = note.FileName;
|
||||
|
||||
int tableRowCount = 0;
|
||||
if (note.Cells != null && note.Cells.Count > 0)
|
||||
{
|
||||
// 테이블 데이터 처리
|
||||
foreach (var cell in note.Cells)
|
||||
{
|
||||
int startRow = excelRow + cell.Row;
|
||||
int startCol = 7 + cell.Column; // G열부터 시작
|
||||
int endRow = startRow + cell.RowSpan - 1;
|
||||
int endCol = startCol + cell.ColumnSpan - 1;
|
||||
|
||||
Excel.Range cellRange = excelManager.NoteEntitiesSheet.Range[
|
||||
excelManager.NoteEntitiesSheet.Cells[startRow, startCol],
|
||||
excelManager.NoteEntitiesSheet.Cells[endRow, endCol]];
|
||||
|
||||
// 병합 먼저 수행
|
||||
if (cell.RowSpan > 1 || cell.ColumnSpan > 1)
|
||||
{
|
||||
cellRange.Merge();
|
||||
}
|
||||
|
||||
// 값 설정 및 서식 지정
|
||||
cellRange.Value = cell.CellText;
|
||||
cellRange.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter;
|
||||
cellRange.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
|
||||
}
|
||||
|
||||
// 이 테이블이 차지하는 총 행 수를 계산
|
||||
tableRowCount = note.Cells.Max(c => c.Row + c.RowSpan);
|
||||
}
|
||||
|
||||
// 다음 Note를 기록할 위치로 이동
|
||||
// 테이블이 있으면 테이블 높이만큼, 없으면 한 칸만 이동
|
||||
excelRow += (tableRowCount > 0) ? tableRowCount : 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Excel <20><>Ʈ<EFBFBD><C6AE> <20><><EFBFBD>
|
||||
/// </summary>
|
||||
@@ -144,8 +203,8 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
for (int row = 2; row <= lastRow; row++)
|
||||
{
|
||||
var cellFileName = excelManager.MappingSheet.Cells[row, 1]?.Value?.ToString() ?? "";
|
||||
var cellAiLabel = excelManager.MappingSheet.Cells[row, 3]?.Value?.ToString() ?? "";
|
||||
var cellFileName = ((Excel.Range)excelManager.MappingSheet.Cells[row, 1]).Value?.ToString() ?? "";
|
||||
var cellAiLabel = ((Excel.Range)excelManager.MappingSheet.Cells[row, 3]).Value?.ToString() ?? "";
|
||||
|
||||
if (string.Equals(cellFileName.Trim(), fileName.Trim(), StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(cellAiLabel.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||
@@ -278,6 +337,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
/// <summary>
|
||||
/// Note 엔티티들을 Excel 워크시트에 기록합니다 (기존 데이터 아래에 추가).
|
||||
/// CellBoundary 데이터를 사용하여 병합된 셀의 텍스트를 적절히 처리합니다.
|
||||
/// </summary>
|
||||
public void WriteNoteEntities(List<NoteEntityInfo> noteEntities, Excel.Worksheet worksheet, string fileName)
|
||||
{
|
||||
@@ -307,18 +367,28 @@ namespace DwgExtractorManual.Models
|
||||
int startRow = lastRow + 2; // 한 줄 띄우고 시작
|
||||
Debug.WriteLine($"[DEBUG] Note 데이터 기록 시작: {startRow}행부터 {noteEntities.Count}개 항목");
|
||||
|
||||
// Note 섹션 헤더 추가 (간단한 방식으로 변경)
|
||||
// Note 섹션 헤더 추가 (표 컬럼 포함)
|
||||
try
|
||||
{
|
||||
worksheet.Cells[startRow - 1, 1] = "=== Notes ===";
|
||||
worksheet.Cells[startRow - 1, 1] = "=== Notes (with Cell Boundary Tables) ===";
|
||||
worksheet.Cells[startRow - 1, 2] = "";
|
||||
worksheet.Cells[startRow - 1, 3] = "";
|
||||
worksheet.Cells[startRow - 1, 4] = "";
|
||||
worksheet.Cells[startRow - 1, 5] = "";
|
||||
worksheet.Cells[startRow - 1, 6] = "";
|
||||
|
||||
// 표 컬럼 헤더 추가 (G열부터 최대 20개 컬럼)
|
||||
for (int col = 7; col <= 26; col++) // G~Z열 (20개 컬럼)
|
||||
{
|
||||
worksheet.Cells[startRow - 1, col] = $"Table Col {col - 6}";
|
||||
var tableHeaderCell = (Excel.Range)worksheet.Cells[startRow - 1, col];
|
||||
tableHeaderCell.Font.Bold = true;
|
||||
tableHeaderCell.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue);
|
||||
tableHeaderCell.Font.Size = 9; // 작은 폰트로 설정
|
||||
}
|
||||
|
||||
// 헤더 스타일 적용 (개별 셀로 처리)
|
||||
var headerCell = worksheet.Cells[startRow - 1, 1];
|
||||
var headerCell = (Excel.Range)worksheet.Cells[startRow - 1, 1];
|
||||
headerCell.Font.Bold = true;
|
||||
headerCell.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow);
|
||||
}
|
||||
@@ -327,29 +397,130 @@ namespace DwgExtractorManual.Models
|
||||
Debug.WriteLine($"[DEBUG] Note 헤더 작성 오류: {ex.Message}");
|
||||
}
|
||||
|
||||
// Note 데이터 입력 (배치 방식으로 성능 향상)
|
||||
// Note 데이터 입력 (CellBoundary 데이터 사용)
|
||||
int row = startRow;
|
||||
try
|
||||
{
|
||||
foreach (var noteEntity in noteEntities)
|
||||
{
|
||||
// 기본 Note 정보 입력 (F열까지)
|
||||
worksheet.Cells[row, 1] = 0; // Height는 0으로 설정
|
||||
worksheet.Cells[row, 2] = noteEntity.Type ?? "";
|
||||
worksheet.Cells[row, 3] = noteEntity.Layer ?? "";
|
||||
worksheet.Cells[row, 4] = ""; // Tag는 빈 값
|
||||
worksheet.Cells[row, 5] = fileName ?? "";
|
||||
worksheet.Cells[row, 6] = noteEntity.Text ?? "";
|
||||
worksheet.Cells[row, 6] = noteEntity.Text ?? ""; // 일반 텍스트만 (표 데이터 제외)
|
||||
|
||||
int currentRow = row; // 현재 처리 중인 행 번호
|
||||
|
||||
// "NOTE" 타입인 경우 행 배경색 변경
|
||||
// CellBoundary 데이터가 있으면 G열부터 테이블 데이터 처리
|
||||
if (noteEntity.CellBoundaries != null && noteEntity.CellBoundaries.Count > 0)
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] CellBoundary 데이터 처리: Row {row}, 셀 수={noteEntity.CellBoundaries.Count}");
|
||||
|
||||
// CellBoundary의 각 셀을 해당 위치에 직접 배치
|
||||
int maxTableRow = 0;
|
||||
foreach (var cellBoundary in noteEntity.CellBoundaries)
|
||||
{
|
||||
var (cellRow, cellCol) = ParseRowColFromLabel(cellBoundary.Label);
|
||||
Debug.WriteLine($"[DEBUG] CellBoundary 처리: {cellBoundary.Label} → Row={cellRow}, Col={cellCol}, Text='{cellBoundary.CellText}'");
|
||||
|
||||
if (cellRow > 0 && cellCol > 0)
|
||||
{
|
||||
// Excel에서 테이블 위치 계산:
|
||||
// R1 → Note의 현재 행 (currentRow)
|
||||
// R2 → Note의 다음 행 (currentRow + 1)
|
||||
// C1 → G열(7), C2 → H열(8)
|
||||
int excelRow = currentRow + (cellRow - 1); // R1=currentRow, R2=currentRow+1, ...
|
||||
int excelCol = 7 + (cellCol - 1); // C1=G열(7), C2=H열(8), ...
|
||||
|
||||
Debug.WriteLine($"[DEBUG] Excel 위치: {cellBoundary.Label} → Excel[{excelRow},{excelCol}]");
|
||||
|
||||
// Excel 범위 체크 (최대 20개 컬럼까지)
|
||||
if (excelCol <= 26) // Z열까지
|
||||
{
|
||||
// CellText가 비어있어도 일단 배치해보기 (디버그용)
|
||||
var cellValue = string.IsNullOrEmpty(cellBoundary.CellText) ? "[빈셀]" : cellBoundary.CellText;
|
||||
worksheet.Cells[excelRow, excelCol] = cellValue;
|
||||
Debug.WriteLine($"[DEBUG] ✅ 셀 배치 완료: {cellBoundary.Label} → Excel[{excelRow},{excelCol}] = '{cellValue}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] ❌ Excel 컬럼 범위 초과: {excelCol} > 26");
|
||||
}
|
||||
|
||||
// 테이블이 차지하는 최대 행 수 추적
|
||||
maxTableRow = Math.Max(maxTableRow, cellRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] ❌ 잘못된 Row/Col: {cellBoundary.Label} → Row={cellRow}, Col={cellCol}");
|
||||
}
|
||||
}
|
||||
|
||||
// 테이블이 여러 행을 차지하는 경우 currentRow 업데이트
|
||||
if (maxTableRow > 1)
|
||||
{
|
||||
currentRow += (maxTableRow - 1);
|
||||
Debug.WriteLine($"[DEBUG] 테이블 행 수만큼 currentRow 업데이트: +{maxTableRow - 1} → {currentRow}");
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(noteEntity.TableCsv))
|
||||
{
|
||||
// 기존 TableCsv 방식 (백업용)
|
||||
var tableRows = noteEntity.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Debug.WriteLine($"[DEBUG] 기존 표 데이터 처리: Row {row}, 표 행 수={tableRows.Length}");
|
||||
|
||||
for (int tableRowIndex = 0; tableRowIndex < tableRows.Length; tableRowIndex++)
|
||||
{
|
||||
var tableCells = tableRows[tableRowIndex].Split(',');
|
||||
|
||||
// 각 셀을 G열부터 배치 (최대 15개 컬럼까지)
|
||||
for (int cellIndex = 0; cellIndex < Math.Min(tableCells.Length, 15); cellIndex++)
|
||||
{
|
||||
var cellValue = tableCells[cellIndex].Trim().Trim('"'); // 따옴표 제거
|
||||
worksheet.Cells[currentRow, 7 + cellIndex] = cellValue; // G열(7)부터 시작
|
||||
}
|
||||
|
||||
// 표의 첫 번째 행이 아니면 새로운 Excel 행 추가
|
||||
if (tableRowIndex > 0)
|
||||
{
|
||||
currentRow++;
|
||||
// 새로운 행에는 기본 Note 정보 복사 (Type, Layer 등)
|
||||
worksheet.Cells[currentRow, 1] = 0;
|
||||
worksheet.Cells[currentRow, 2] = noteEntity.Type ?? "";
|
||||
worksheet.Cells[currentRow, 3] = noteEntity.Layer ?? "";
|
||||
worksheet.Cells[currentRow, 4] = "";
|
||||
worksheet.Cells[currentRow, 5] = fileName ?? "";
|
||||
worksheet.Cells[currentRow, 6] = "(continued)"; // 연속 표시
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[DEBUG] 표 행 {tableRowIndex + 1}/{tableRows.Length}: Excel Row {currentRow}, 셀 수={tableCells.Length}");
|
||||
}
|
||||
}
|
||||
|
||||
// "NOTE" 타입인 경우 행 배경색 변경 (표 영역 포함)
|
||||
if (noteEntity.Type == "Note")
|
||||
{
|
||||
Excel.Range noteRowRange = worksheet.Range[worksheet.Cells[row, 1], worksheet.Cells[row, 6]];
|
||||
Excel.Range noteRowRange = worksheet.Range[worksheet.Cells[row, 1], worksheet.Cells[currentRow, 26]]; // Z열까지
|
||||
noteRowRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow);
|
||||
noteRowRange.Font.Bold = true;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[DEBUG] Excel 기록: Row {row}, Order {noteEntity.SortOrder}, Type {noteEntity.Type}, Pos({noteEntity.X:F1},{noteEntity.Y:F1}), Text: '{noteEntity.Text}'");
|
||||
row++;
|
||||
Debug.WriteLine($"[DEBUG] Excel 기록: Row {row}~{currentRow}, Order {noteEntity.SortOrder}, Type {noteEntity.Type}, Pos({noteEntity.X:F1},{noteEntity.Y:F1}), Text: '{noteEntity.Text}', HasCellBoundaries: {noteEntity.CellBoundaries?.Count > 0} (Count: {noteEntity.CellBoundaries?.Count ?? 0}), HasTableCsv: {!string.IsNullOrEmpty(noteEntity.TableCsv)}");
|
||||
|
||||
// CellBoundaries 상세 디버그
|
||||
if (noteEntity.CellBoundaries != null && noteEntity.CellBoundaries.Count > 0)
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] CellBoundaries 상세:");
|
||||
foreach (var cb in noteEntity.CellBoundaries.Take(5)) // 처음 5개만 출력
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] {cb.Label}: '{cb.CellText}'");
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 Note는 현재 행의 다음 행부터 시작
|
||||
row = currentRow + 1;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[DEBUG] Note 데이터 기록 완료: {row - startRow}개 항목");
|
||||
@@ -377,5 +548,46 @@ namespace DwgExtractorManual.Models
|
||||
throw; // 상위로 예외 전파
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 라벨에서 Row, Col 정보를 파싱합니다.
|
||||
/// 예: "R1C2" → (1, 2) 또는 "R2C2→R3C4" → (2, 2) (시작 위치 사용)
|
||||
/// </summary>
|
||||
private (int row, int col) ParseRowColFromLabel(string label)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(label))
|
||||
return (0, 0);
|
||||
|
||||
// "R2C2→R3C4" 형태인 경우 시작 부분만 사용
|
||||
var startPart = label;
|
||||
if (label.Contains("→"))
|
||||
{
|
||||
startPart = label.Split('→')[0];
|
||||
}
|
||||
|
||||
var rIndex = startPart.IndexOf('R');
|
||||
var cIndex = startPart.IndexOf('C');
|
||||
|
||||
if (rIndex >= 0 && cIndex > rIndex)
|
||||
{
|
||||
var rowStr = startPart.Substring(rIndex + 1, cIndex - rIndex - 1);
|
||||
var colStr = startPart.Substring(cIndex + 1);
|
||||
|
||||
if (int.TryParse(rowStr, out int row) && int.TryParse(colStr, out int col))
|
||||
{
|
||||
return (row, col);
|
||||
}
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Excel = Microsoft.Office.Interop.Excel;
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Excel COM <20><>ü <20><><EFBFBD><EFBFBD> <20><> <20>⺻ <20>۾<EFBFBD><DBBE><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
|
||||
/// Excel COM <20><>ü <20><><EFBFBD><EFBFBD> <20><> <20>⺻ <20>۾<EFBFBD><DBBE><EFBFBD> <20><><EFBFBD><EFBFBD>ϴ<EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
internal class ExcelManager : IDisposable
|
||||
{
|
||||
@@ -17,10 +17,11 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
public Excel.Worksheet? TitleBlockSheet { get; private set; }
|
||||
public Excel.Worksheet? TextEntitiesSheet { get; private set; }
|
||||
public Excel.Worksheet? NoteEntitiesSheet { get; private set; }
|
||||
public Excel.Worksheet? MappingSheet { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Excel <20><><EFBFBD>ø<EFBFBD><C3B8><EFBFBD><EFBFBD>̼<EFBFBD> <20><> <20><>ũ<EFBFBD><C5A9>Ʈ <20>ʱ<EFBFBD>ȭ
|
||||
/// Excel <20><><EFBFBD>ø<EFBFBD><C3B8><EFBFBD><EFBFBD>̼<EFBFBD> <20><> <20><>ũ<EFBFBD><C5A9>Ʈ <20>ʱ<EFBFBD>ȭ
|
||||
/// </summary>
|
||||
public void InitializeExcel()
|
||||
{
|
||||
@@ -28,21 +29,26 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
var excelApp = new Excel.Application();
|
||||
ExcelApplication = excelApp;
|
||||
ExcelApplication.Visible = false; // WPF<50><46><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ó<><C3B3>
|
||||
ExcelApplication.Visible = false; // WPF<50><46><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ó<><C3B3>
|
||||
Excel.Workbook workbook = excelApp.Workbooks.Add();
|
||||
TitleBlockWorkbook = workbook;
|
||||
|
||||
// Title Block Sheet <20><><EFBFBD><EFBFBD> (<28>⺻ Sheet1)
|
||||
// Title Block Sheet <20><><EFBFBD><EFBFBD> (<28>⺻ Sheet1)
|
||||
TitleBlockSheet = (Excel.Worksheet)workbook.Sheets[1];
|
||||
TitleBlockSheet.Name = "Title Block";
|
||||
SetupTitleBlockHeaders();
|
||||
|
||||
// Text Entities Sheet <20>߰<EFBFBD>
|
||||
// Text Entities Sheet <20>߰<EFBFBD>
|
||||
TextEntitiesSheet = (Excel.Worksheet)workbook.Sheets.Add();
|
||||
TextEntitiesSheet.Name = "Text Entities";
|
||||
SetupTextEntitiesHeaders();
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ϳ<EFBFBD> <20><>ũ<EFBFBD><C5A9> <20><> <20><>Ʈ <20><><EFBFBD><EFBFBD>
|
||||
// Note Entities Sheet <20>߰<EFBFBD>
|
||||
NoteEntitiesSheet = (Excel.Worksheet)workbook.Sheets.Add();
|
||||
NoteEntitiesSheet.Name = "Note Entities";
|
||||
SetupNoteEntitiesHeaders();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ϳ<EFBFBD> <20><>ũ<EFBFBD><C5A9> <20><> <20><>Ʈ <20><><EFBFBD><EFBFBD>
|
||||
MappingWorkbook = excelApp.Workbooks.Add();
|
||||
MappingSheet = (Excel.Worksheet)MappingWorkbook.Sheets[1];
|
||||
MappingSheet.Name = "Mapping Data";
|
||||
@@ -50,14 +56,14 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Excel <20>ʱ<EFBFBD>ȭ <20><> <20><><EFBFBD><EFBFBD> <20><EFBFBD>: {ex.Message}");
|
||||
Debug.WriteLine($"Excel <20>ʱ<EFBFBD>ȭ <20><> <20><><EFBFBD><EFBFBD> <20><EFBFBD>: {ex.Message}");
|
||||
ReleaseExcelObjects();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD> Excel <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>Ʈ<EFBFBD><C6AE> <20><><EFBFBD><EFBFBD>
|
||||
/// <20><><EFBFBD><EFBFBD> Excel <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>Ʈ<EFBFBD><C6AE> <20><><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
public bool OpenExistingFile(string excelFilePath)
|
||||
{
|
||||
@@ -65,7 +71,7 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
if (!File.Exists(excelFilePath))
|
||||
{
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʽ<EFBFBD><CABD>ϴ<EFBFBD>: {excelFilePath}");
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʽ<EFBFBD><CABD>ϴ<EFBFBD>: {excelFilePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,7 +86,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
if (MappingSheet == null)
|
||||
{
|
||||
Debug.WriteLine("? 'Mapping Data' <20><>Ʈ<EFBFBD><C6AE> ã<><C3A3> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>.");
|
||||
Debug.WriteLine("? 'Mapping Data' <20><>Ʈ<EFBFBD><C6AE> ã<><C3A3> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -88,13 +94,13 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD>ο<EFBFBD> <20><>ũ<EFBFBD><C5A9> <20><><EFBFBD><EFBFBD> (Height <20><><EFBFBD>Ŀ<EFBFBD>)
|
||||
/// <20><><EFBFBD>ο<EFBFBD> <20><>ũ<EFBFBD><C5A9> <20><><EFBFBD><EFBFBD> (Height <20><><EFBFBD>Ŀ<EFBFBD>)
|
||||
/// </summary>
|
||||
public Excel.Workbook CreateNewWorkbook()
|
||||
{
|
||||
@@ -106,62 +112,83 @@ namespace DwgExtractorManual.Models
|
||||
return ExcelApplication.Workbooks.Add();
|
||||
}
|
||||
|
||||
// Title Block <20><>Ʈ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
// Title Block <20><>Ʈ <20><><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
private void SetupTitleBlockHeaders()
|
||||
{
|
||||
if (TitleBlockSheet == null) return;
|
||||
|
||||
TitleBlockSheet.Cells[1, 1] = "Type"; // <20><>: AttributeReference, AttributeDefinition
|
||||
TitleBlockSheet.Cells[1, 2] = "Name"; // BlockReference <20≯<EFBFBD> <20>Ǵ<EFBFBD> BlockDefinition <20≯<EFBFBD>
|
||||
TitleBlockSheet.Cells[1, 1] = "Type"; // <20><>: AttributeReference, AttributeDefinition
|
||||
TitleBlockSheet.Cells[1, 2] = "Name"; // BlockReference <20≯<EFBFBD> <20>Ǵ<EFBFBD> BlockDefinition <20≯<EFBFBD>
|
||||
TitleBlockSheet.Cells[1, 3] = "Tag"; // Attribute Tag
|
||||
TitleBlockSheet.Cells[1, 4] = "Prompt"; // Attribute Prompt
|
||||
TitleBlockSheet.Cells[1, 5] = "Value"; // Attribute <20><> (TextString)
|
||||
TitleBlockSheet.Cells[1, 6] = "Path"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD><EFBFBD>
|
||||
TitleBlockSheet.Cells[1, 7] = "FileName"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD>
|
||||
TitleBlockSheet.Cells[1, 5] = "Value"; // Attribute <20><> (TextString)
|
||||
TitleBlockSheet.Cells[1, 6] = "Path"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD>
|
||||
TitleBlockSheet.Cells[1, 7] = "FileName"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD>
|
||||
|
||||
// <20><><EFBFBD><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
// <20><><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
Excel.Range headerRange = TitleBlockSheet.Range["A1:G1"];
|
||||
headerRange.Font.Bold = true;
|
||||
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue);
|
||||
}
|
||||
|
||||
// Text Entities <20><>Ʈ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
// Text Entities <20><>Ʈ <20><><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
private void SetupTextEntitiesHeaders()
|
||||
{
|
||||
if (TextEntitiesSheet == null) return;
|
||||
|
||||
TextEntitiesSheet.Cells[1, 1] = "Type"; // DBText, MText
|
||||
TextEntitiesSheet.Cells[1, 2] = "Layer"; // Layer <20≯<EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 3] = "Text"; // <20><><EFBFBD><EFBFBD> <20>ؽ<EFBFBD>Ʈ <20><><EFBFBD><EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 4] = "Path"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD><EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 5] = "FileName"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 2] = "Layer"; // Layer <20≯<EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 3] = "Text"; // <20><><EFBFBD><EFBFBD> <20>ؽ<EFBFBD>Ʈ <20><><EFBFBD><EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 4] = "Path"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD>
|
||||
TextEntitiesSheet.Cells[1, 5] = "FileName"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD>
|
||||
|
||||
// <20><><EFBFBD><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
// <20><><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
Excel.Range headerRange = TextEntitiesSheet.Range["A1:E1"];
|
||||
headerRange.Font.Bold = true;
|
||||
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGreen);
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>Ʈ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>Ʈ <20><><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
private void SetupMappingHeaders()
|
||||
{
|
||||
if (MappingSheet == null) return;
|
||||
|
||||
MappingSheet.Cells[1, 1] = "FileName"; // <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
|
||||
MappingSheet.Cells[1, 2] = "MapKey"; // <20><><EFBFBD><EFBFBD> Ű
|
||||
MappingSheet.Cells[1, 3] = "AILabel"; // AI <20><><EFBFBD><EFBFBD>
|
||||
MappingSheet.Cells[1, 1] = "FileName"; // <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
|
||||
MappingSheet.Cells[1, 2] = "MapKey"; // <20><><EFBFBD><EFBFBD> Ű
|
||||
MappingSheet.Cells[1, 3] = "AILabel"; // AI <20><>
|
||||
MappingSheet.Cells[1, 4] = "DwgTag"; // DWG Tag
|
||||
MappingSheet.Cells[1, 5] = "Att_value"; // DWG <20><>
|
||||
MappingSheet.Cells[1, 6] = "Pdf_value"; // PDF <20><> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><>)
|
||||
MappingSheet.Cells[1, 5] = "Att_value"; // DWG <20><>
|
||||
MappingSheet.Cells[1, 6] = "Pdf_value"; // PDF <20><> (<28><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><>)
|
||||
|
||||
// <20><><EFBFBD><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
// <20><><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
Excel.Range headerRange = MappingSheet.Range["A1:F1"];
|
||||
headerRange.Font.Bold = true;
|
||||
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow);
|
||||
}
|
||||
|
||||
// Note Entities <20><>Ʈ <20><><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
private void SetupNoteEntitiesHeaders()
|
||||
{
|
||||
if (NoteEntitiesSheet == null) return;
|
||||
|
||||
NoteEntitiesSheet.Cells[1, 1] = "Type"; // Note, NoteContent
|
||||
NoteEntitiesSheet.Cells[1, 2] = "Layer"; // Layer <20≯<EFBFBD>
|
||||
NoteEntitiesSheet.Cells[1, 3] = "Text"; // <20><><EFBFBD><EFBFBD> <20>ؽ<EFBFBD>Ʈ <20><><EFBFBD><EFBFBD>
|
||||
NoteEntitiesSheet.Cells[1, 4] = "X"; // X <20><>ǥ
|
||||
NoteEntitiesSheet.Cells[1, 5] = "Y"; // Y <20><>ǥ
|
||||
NoteEntitiesSheet.Cells[1, 6] = "SortOrder"; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
NoteEntitiesSheet.Cells[1, 7] = "TableCsv"; // <20><><EFBFBD>̺<EFBFBD> CSV <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
NoteEntitiesSheet.Cells[1, 8] = "Path"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD>
|
||||
NoteEntitiesSheet.Cells[1, 9] = "FileName"; // <20><><EFBFBD><EFBFBD> DWG <20><><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD>
|
||||
|
||||
// <20><><EFBFBD> <20><> <20><>Ÿ<EFBFBD><C5B8>
|
||||
Excel.Range headerRange = NoteEntitiesSheet.Range["A1:I1"];
|
||||
headerRange.Font.Bold = true;
|
||||
headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightCoral);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><>ũ<EFBFBD><C5A9> <20><><EFBFBD><EFBFBD>
|
||||
/// <20><>ũ<EFBFBD><C5A9> <20><><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
public bool SaveWorkbook(Excel.Workbook? workbook = null)
|
||||
{
|
||||
@@ -189,13 +216,13 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
Debug.WriteLine($"? Excel <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><>ũ<EFBFBD><C5A9><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>ο<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
/// <20><>ũ<EFBFBD><C5A9><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ο<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
public void SaveWorkbookAs(Excel.Workbook? workbook, string savePath)
|
||||
{
|
||||
@@ -213,14 +240,14 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Excel <20><>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20>ִ<EFBFBD> <20><>ȿ<EFBFBD><C8BF> <20≯<EFBFBD><CCB8><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>մϴ<D5B4>.
|
||||
/// Excel <20><>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20>ִ<EFBFBD> <20><>ȿ<EFBFBD><C8BF> <20≯<EFBFBD><CCB8><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>մϴ<D5B4>.
|
||||
/// </summary>
|
||||
public string GetValidSheetName(string originalName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalName))
|
||||
return "Sheet";
|
||||
|
||||
// Excel <20><>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʴ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
// Excel <20><>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʴ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
string validName = originalName;
|
||||
char[] invalidChars = { '\\', '/', '?', '*', '[', ']', ':' };
|
||||
|
||||
@@ -229,7 +256,7 @@ namespace DwgExtractorManual.Models
|
||||
validName = validName.Replace(c, '_');
|
||||
}
|
||||
|
||||
// 31<33>ڷ<EFBFBD> <20><><EFBFBD><EFBFBD> (Excel <20><>Ʈ<EFBFBD><C6AE> <20>ִ<EFBFBD> <20><><EFBFBD><EFBFBD>)
|
||||
// 31<33>ڷ<EFBFBD> <20><><EFBFBD><EFBFBD> (Excel <20><>Ʈ<EFBFBD><C6AE> <20>ִ<EFBFBD> <20><><EFBFBD><EFBFBD>)
|
||||
if (validName.Length > 31)
|
||||
{
|
||||
validName = validName.Substring(0, 31);
|
||||
@@ -261,6 +288,7 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
ReleaseComObject(TitleBlockSheet);
|
||||
ReleaseComObject(TextEntitiesSheet);
|
||||
ReleaseComObject(NoteEntitiesSheet);
|
||||
ReleaseComObject(MappingSheet);
|
||||
ReleaseComObject(TitleBlockWorkbook);
|
||||
ReleaseComObject(MappingWorkbook);
|
||||
@@ -268,6 +296,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
TitleBlockSheet = null;
|
||||
TextEntitiesSheet = null;
|
||||
NoteEntitiesSheet = null;
|
||||
MappingSheet = null;
|
||||
TitleBlockWorkbook = null;
|
||||
MappingWorkbook = null;
|
||||
@@ -285,7 +314,7 @@ namespace DwgExtractorManual.Models
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,16 +322,16 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("[DEBUG] ExcelManager Dispose <20><><EFBFBD><EFBFBD>");
|
||||
Debug.WriteLine("[DEBUG] ExcelManager Dispose <20><><EFBFBD><EFBFBD>");
|
||||
CloseWorkbooks();
|
||||
ReleaseExcelObjects();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
Debug.WriteLine("[DEBUG] ExcelManager Dispose <20>Ϸ<EFBFBD>");
|
||||
Debug.WriteLine("[DEBUG] ExcelManager Dispose <20>Ϸ<EFBFBD>");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] ExcelManager Dispose <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
Debug.WriteLine($"[DEBUG] ExcelManager Dispose <20><> <20><><EFBFBD><EFBFBD>: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
// 컴포넌트들
|
||||
private readonly ExcelManager excelManager;
|
||||
private readonly DwgDataExtractor dwgExtractor;
|
||||
public readonly DwgDataExtractor DwgExtractor;
|
||||
private readonly JsonDataProcessor jsonProcessor;
|
||||
private readonly ExcelDataWriter excelWriter;
|
||||
private readonly FieldMapper fieldMapper;
|
||||
@@ -29,7 +29,7 @@ namespace DwgExtractorManual.Models
|
||||
private Dictionary<string, Dictionary<string, (string, string, string, string)>> FileToMapkeyToLabelTagValuePdf
|
||||
= new Dictionary<string, Dictionary<string, (string, string, string, string)>>();
|
||||
|
||||
readonly List<string> MapKeys;
|
||||
readonly List<string>? MapKeys;
|
||||
|
||||
/// <summary>
|
||||
/// 생성자: 모든 컴포넌트 초기화
|
||||
@@ -41,7 +41,7 @@ namespace DwgExtractorManual.Models
|
||||
Debug.WriteLine("🔄 FieldMapper 로딩 중: mapping_table_json.json...");
|
||||
fieldMapper = FieldMapper.LoadFromFile("fletimageanalysis/mapping_table_json.json");
|
||||
Debug.WriteLine("✅ FieldMapper 로딩 성공");
|
||||
MapKeys = fieldMapper.GetAllDocAiKeys();
|
||||
MapKeys = fieldMapper.GetAllDocAiKeys() ?? new List<string>();
|
||||
Debug.WriteLine($"📊 총 DocAI 키 개수: {MapKeys?.Count ?? 0}");
|
||||
|
||||
// 매핑 테스트 (디버깅용)
|
||||
@@ -52,7 +52,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
// 컴포넌트들 초기화
|
||||
excelManager = new ExcelManager();
|
||||
dwgExtractor = new DwgDataExtractor(fieldMapper);
|
||||
DwgExtractor = new DwgDataExtractor(fieldMapper);
|
||||
jsonProcessor = new JsonDataProcessor();
|
||||
excelWriter = new ExcelDataWriter(excelManager);
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace DwgExtractorManual.Models
|
||||
try
|
||||
{
|
||||
// DWG 데이터 추출
|
||||
var extractionResult = dwgExtractor.ExtractFromDwgFile(filePath, progress, cancellationToken);
|
||||
var extractionResult = DwgExtractor.ExtractFromDwgFile(filePath, progress, cancellationToken);
|
||||
|
||||
if (extractionResult == null)
|
||||
{
|
||||
@@ -191,14 +191,14 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
try
|
||||
{
|
||||
var worksheet = firstSheetProcessed ?
|
||||
heightSortedWorkbook.Worksheets.Add() :
|
||||
Microsoft.Office.Interop.Excel.Worksheet worksheet = firstSheetProcessed ?
|
||||
(Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets.Add() :
|
||||
(Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
|
||||
worksheet.Name = excelManager.GetValidSheetName(fileName);
|
||||
firstSheetProcessed = true;
|
||||
|
||||
var textEntities = dwgExtractor.ExtractTextEntitiesWithHeight(dwgFile);
|
||||
var textEntities = DwgExtractor.ExtractTextEntitiesWithHeight(dwgFile);
|
||||
excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName);
|
||||
|
||||
Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티");
|
||||
@@ -212,7 +212,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
if (!firstSheetProcessed)
|
||||
{
|
||||
var defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
Microsoft.Office.Interop.Excel.Worksheet defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
defaultSheet.Name = "No_DWG_Files";
|
||||
defaultSheet.Cells[1, 1] = "No DWG files found in this folder";
|
||||
}
|
||||
@@ -238,6 +238,10 @@ namespace DwgExtractorManual.Models
|
||||
{
|
||||
Debug.WriteLine($"[DEBUG] 단일 Excel 파일로 Height 정렬 생성 시작: {allDwgFiles.Count}개 파일");
|
||||
|
||||
// 시각화 데이터 초기화
|
||||
MainWindow.ClearVisualizationData();
|
||||
Debug.WriteLine("[VISUALIZATION] 시각화 데이터 초기화 완료");
|
||||
|
||||
var heightSortedWorkbook = excelManager.CreateNewWorkbook();
|
||||
bool firstSheetProcessed = false;
|
||||
|
||||
@@ -252,22 +256,22 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
try
|
||||
{
|
||||
var worksheet = firstSheetProcessed ?
|
||||
heightSortedWorkbook.Worksheets.Add() :
|
||||
Microsoft.Office.Interop.Excel.Worksheet worksheet = firstSheetProcessed ?
|
||||
(Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets.Add() :
|
||||
(Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
|
||||
worksheet.Name = excelManager.GetValidSheetName(fileName);
|
||||
firstSheetProcessed = true;
|
||||
|
||||
var textEntities = dwgExtractor.ExtractTextEntitiesWithHeight(filePath);
|
||||
var textEntities = DwgExtractor.ExtractTextEntitiesWithHeight(filePath);
|
||||
excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName);
|
||||
|
||||
// Note 엔티티 추출 및 기록
|
||||
var noteEntities = dwgExtractor.ExtractNotesFromDrawing(filePath);
|
||||
if (noteEntities.Count > 0)
|
||||
var noteEntities = DwgExtractor.ExtractNotesFromDrawing(filePath);
|
||||
if (noteEntities.NoteEntities.Count > 0)
|
||||
{
|
||||
excelWriter.WriteNoteEntities(noteEntities, worksheet, fileName);
|
||||
Debug.WriteLine($"[DEBUG] {fileName}: {noteEntities.Count}개 Note 엔티티 추가됨");
|
||||
excelWriter.WriteNoteEntities(noteEntities.NoteEntities, worksheet, fileName);
|
||||
Debug.WriteLine($"[DEBUG] {fileName}: {noteEntities.NoteEntities.Count}개 Note 엔티티 추가됨");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티");
|
||||
@@ -281,7 +285,7 @@ namespace DwgExtractorManual.Models
|
||||
|
||||
if (!firstSheetProcessed)
|
||||
{
|
||||
var defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
Microsoft.Office.Interop.Excel.Worksheet defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1];
|
||||
defaultSheet.Name = "No_DWG_Files";
|
||||
defaultSheet.Cells[1, 1] = "No DWG files found in any folder";
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
267
Models/IntersectionTestDebugger.cs
Normal file
267
Models/IntersectionTestDebugger.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Teigha.Geometry;
|
||||
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 교차점 생성 및 셀 추출 로직을 테스트하고 디버깅하는 클래스
|
||||
/// </summary>
|
||||
public class IntersectionTestDebugger
|
||||
{
|
||||
/// <summary>
|
||||
/// 간단한 테스트 테이블을 만들어서 교차점과 셀 생성을 테스트합니다.
|
||||
/// </summary>
|
||||
public static void RunIntersectionTest()
|
||||
{
|
||||
Debug.WriteLine("=== 교차점 및 셀 생성 테스트 시작 ===");
|
||||
|
||||
try
|
||||
{
|
||||
// 테스트용 테이블 생성 (3x4 그리드)
|
||||
var testSegments = CreateTestTable();
|
||||
Debug.WriteLine($"테스트 선분 개수: {testSegments.Count}");
|
||||
|
||||
// DwgDataExtractor 인스턴스 생성 (실제 코드와 동일)
|
||||
var mappingData = new MappingTableData();
|
||||
var fieldMapper = new FieldMapper(mappingData);
|
||||
var extractor = new DwgDataExtractor(fieldMapper);
|
||||
|
||||
// 교차점 찾기 테스트
|
||||
var intersections = FindTestIntersections(testSegments, extractor);
|
||||
Debug.WriteLine($"발견된 교차점 개수: {intersections.Count}");
|
||||
|
||||
// 각 교차점의 DirectionBits 출력
|
||||
for (int i = 0; i < intersections.Count; i++)
|
||||
{
|
||||
var intersection = intersections[i];
|
||||
Debug.WriteLine($"교차점 {i}: ({intersection.Position.X:F1}, {intersection.Position.Y:F1}) - DirectionBits: {intersection.DirectionBits} - R{intersection.Row}C{intersection.Column}");
|
||||
|
||||
// topLeft/bottomRight 검증
|
||||
bool isTopLeft = extractor.IsValidTopLeft(intersection.DirectionBits);
|
||||
bool isBottomRight = extractor.IsValidBottomRight(intersection.DirectionBits);
|
||||
Debug.WriteLine($" IsTopLeft: {isTopLeft}, IsBottomRight: {isBottomRight}");
|
||||
}
|
||||
|
||||
Debug.WriteLine("=== 테스트 완료 ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"테스트 중 오류 발생: {ex.Message}");
|
||||
Debug.WriteLine(ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 테스트용 3x4 테이블 선분들을 생성합니다.
|
||||
/// </summary>
|
||||
private static List<(Point3d start, Point3d end, bool isHorizontal)> CreateTestTable()
|
||||
{
|
||||
var segments = new List<(Point3d start, Point3d end, bool isHorizontal)>();
|
||||
|
||||
// 수평선들 (4개 - 0, 10, 20, 30 Y좌표)
|
||||
for (int i = 0; i <= 3; i++)
|
||||
{
|
||||
double y = i * 10.0;
|
||||
segments.Add((new Point3d(0, y, 0), new Point3d(40, y, 0), true));
|
||||
}
|
||||
|
||||
// 수직선들 (5개 - 0, 10, 20, 30, 40 X좌표)
|
||||
for (int i = 0; i <= 4; i++)
|
||||
{
|
||||
double x = i * 10.0;
|
||||
segments.Add((new Point3d(x, 0, 0), new Point3d(x, 30, 0), false));
|
||||
}
|
||||
|
||||
Debug.WriteLine($"생성된 테스트 테이블: 수평선 4개, 수직선 5개");
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 테스트 선분들로부터 교차점을 찾습니다.
|
||||
/// </summary>
|
||||
private static List<IntersectionPoint> FindTestIntersections(List<(Point3d start, Point3d end, bool isHorizontal)> segments, DwgDataExtractor extractor)
|
||||
{
|
||||
var intersections = new List<IntersectionPoint>();
|
||||
double tolerance = 0.1;
|
||||
|
||||
var horizontalSegments = segments.Where(s => s.isHorizontal).ToList();
|
||||
var verticalSegments = segments.Where(s => !s.isHorizontal).ToList();
|
||||
|
||||
foreach (var hSeg in horizontalSegments)
|
||||
{
|
||||
foreach (var vSeg in verticalSegments)
|
||||
{
|
||||
// 교차점 계산
|
||||
double intersectX = vSeg.start.X;
|
||||
double intersectY = hSeg.start.Y;
|
||||
var intersectPoint = new Point3d(intersectX, intersectY, 0);
|
||||
|
||||
// 교차점이 두 선분의 범위 내에 있는지 확인
|
||||
bool onHorizontal = intersectX >= Math.Min(hSeg.start.X, hSeg.end.X) - tolerance &&
|
||||
intersectX <= Math.Max(hSeg.start.X, hSeg.end.X) + tolerance;
|
||||
|
||||
bool onVertical = intersectY >= Math.Min(vSeg.start.Y, vSeg.end.Y) - tolerance &&
|
||||
intersectY <= Math.Max(vSeg.start.Y, vSeg.end.Y) + tolerance;
|
||||
|
||||
if (onHorizontal && onVertical)
|
||||
{
|
||||
// DirectionBits 계산
|
||||
int directionBits = CalculateDirectionBits(intersectPoint, segments, tolerance);
|
||||
|
||||
// Row, Column 계산 (1-based)
|
||||
int row = (int)Math.Round(intersectY / 10.0) + 1;
|
||||
int column = (int)Math.Round(intersectX / 10.0) + 1;
|
||||
|
||||
var intersection = new IntersectionPoint
|
||||
{
|
||||
Position = intersectPoint,
|
||||
DirectionBits = directionBits,
|
||||
Row = row,
|
||||
Column = column
|
||||
};
|
||||
|
||||
intersections.Add(intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersections;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 점에서의 DirectionBits를 계산합니다.
|
||||
/// </summary>
|
||||
private static int CalculateDirectionBits(Point3d point, List<(Point3d start, Point3d end, bool isHorizontal)> segments, double tolerance)
|
||||
{
|
||||
int bits = 0;
|
||||
// Right: 1, Up: 2, Left: 4, Down: 8
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment.isHorizontal)
|
||||
{
|
||||
// 수평선에서 점이 선분 위에 있는지 확인
|
||||
if (Math.Abs(point.Y - segment.start.Y) < tolerance &&
|
||||
point.X >= Math.Min(segment.start.X, segment.end.X) - tolerance &&
|
||||
point.X <= Math.Max(segment.start.X, segment.end.X) + tolerance)
|
||||
{
|
||||
// 오른쪽으로 선분이 있는지 확인
|
||||
if (Math.Max(segment.start.X, segment.end.X) > point.X + tolerance)
|
||||
bits |= 1; // Right
|
||||
|
||||
// 왼쪽으로 선분이 있는지 확인
|
||||
if (Math.Min(segment.start.X, segment.end.X) < point.X - tolerance)
|
||||
bits |= 4; // Left
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 수직선에서 점이 선분 위에 있는지 확인
|
||||
if (Math.Abs(point.X - segment.start.X) < tolerance &&
|
||||
point.Y >= Math.Min(segment.start.Y, segment.end.Y) - tolerance &&
|
||||
point.Y <= Math.Max(segment.start.Y, segment.end.Y) + tolerance)
|
||||
{
|
||||
// 위쪽으로 선분이 있는지 확인
|
||||
if (Math.Max(segment.start.Y, segment.end.Y) > point.Y + tolerance)
|
||||
bits |= 2; // Up
|
||||
|
||||
// 아래쪽으로 선분이 있는지 확인
|
||||
if (Math.Min(segment.start.Y, segment.end.Y) < point.Y - tolerance)
|
||||
bits |= 8; // Down
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차점들로부터 셀을 추출합니다.
|
||||
/// </summary>
|
||||
private static List<TableCell> ExtractTestCells(List<IntersectionPoint> intersections,
|
||||
List<(Point3d start, Point3d end, bool isHorizontal)> segments,
|
||||
DwgDataExtractor extractor)
|
||||
{
|
||||
var cells = new List<TableCell>();
|
||||
double tolerance = 0.1;
|
||||
|
||||
// topLeft 후보들을 찾아서 각각에 대해 bottomRight를 찾기
|
||||
var topLeftCandidates = intersections.Where(i => extractor.IsValidTopLeft(i.DirectionBits)).ToList();
|
||||
Debug.WriteLine($"TopLeft 후보 개수: {topLeftCandidates.Count}");
|
||||
|
||||
foreach (var topLeft in topLeftCandidates)
|
||||
{
|
||||
Debug.WriteLine($"\nTopLeft 후보 R{topLeft.Row}C{topLeft.Column} 처리 중...");
|
||||
|
||||
// bottomRight 찾기 (실제 코드와 동일한 방식)
|
||||
var bottomRight = FindBottomRightForTest(topLeft, intersections, extractor);
|
||||
|
||||
if (bottomRight != null)
|
||||
{
|
||||
Debug.WriteLine($" BottomRight 발견: R{bottomRight.Row}C{bottomRight.Column}");
|
||||
|
||||
// 셀 생성
|
||||
var cell = new TableCell
|
||||
{
|
||||
MinPoint = new Point3d(topLeft.Position.X, bottomRight.Position.Y, 0),
|
||||
MaxPoint = new Point3d(bottomRight.Position.X, topLeft.Position.Y, 0),
|
||||
Row = topLeft.Row,
|
||||
Column = topLeft.Column,
|
||||
CellText = $"R{topLeft.Row}C{topLeft.Column}"
|
||||
};
|
||||
|
||||
cells.Add(cell);
|
||||
Debug.WriteLine($" 셀 생성 완료: ({cell.MinPoint.X:F1},{cell.MinPoint.Y:F1}) to ({cell.MaxPoint.X:F1},{cell.MaxPoint.Y:F1})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($" BottomRight을 찾지 못함");
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 테스트용 bottomRight 찾기 메서드
|
||||
/// </summary>
|
||||
private static IntersectionPoint FindBottomRightForTest(IntersectionPoint topLeft,
|
||||
List<IntersectionPoint> intersections,
|
||||
DwgDataExtractor extractor)
|
||||
{
|
||||
// 교차점들을 Row/Column으로 딕셔너리 구성
|
||||
var intersectionLookup = intersections
|
||||
.Where(i => i.Row > 0 && i.Column > 0)
|
||||
.GroupBy(i => i.Row)
|
||||
.ToDictionary(g => g.Key, g => g.ToDictionary(i => i.Column, i => i));
|
||||
|
||||
// topLeft에서 시작하여 bottomRight 찾기
|
||||
int maxRow = intersectionLookup.Keys.Any() ? intersectionLookup.Keys.Max() : topLeft.Row;
|
||||
|
||||
for (int targetRow = topLeft.Row + 1; targetRow <= maxRow + 2; targetRow++)
|
||||
{
|
||||
if (!intersectionLookup.ContainsKey(targetRow)) continue;
|
||||
|
||||
var rowIntersections = intersectionLookup[targetRow];
|
||||
var availableColumns = rowIntersections.Keys.Where(col => col >= topLeft.Column).OrderBy(col => col);
|
||||
|
||||
foreach (int targetColumn in availableColumns)
|
||||
{
|
||||
var candidate = rowIntersections[targetColumn];
|
||||
|
||||
if (extractor.IsValidBottomRight(candidate.DirectionBits) ||
|
||||
(targetRow == maxRow && targetColumn == intersectionLookup.Values.SelectMany(row => row.Keys).Max()))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
177
Models/NoteExtractionTester.cs
Normal file
177
Models/NoteExtractionTester.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Note 박스 텍스트와 테이블 추출 기능을 테스트하는 클래스
|
||||
/// </summary>
|
||||
public class NoteExtractionTester
|
||||
{
|
||||
/// <summary>
|
||||
/// DWG 파일에서 Note 데이터를 추출하고 CSV로 저장하는 전체 테스트
|
||||
/// </summary>
|
||||
public static void TestNoteExtractionAndCsvExport(string dwgFilePath, string outputDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("=== Note 추출 및 CSV 내보내기 테스트 시작 ===");
|
||||
|
||||
// 출력 디렉토리가 없으면 생성
|
||||
if (!Directory.Exists(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
Debug.WriteLine($"출력 디렉토리 생성: {outputDirectory}");
|
||||
}
|
||||
|
||||
// 1. Teigha 서비스 초기화
|
||||
Debug.WriteLine("1. Teigha 서비스 초기화 중...");
|
||||
TeighaServicesManager.Instance.AcquireServices();
|
||||
|
||||
// 2. DwgDataExtractor 인스턴스 생성
|
||||
Debug.WriteLine("2. DwgDataExtractor 인스턴스 생성...");
|
||||
var mappingData = new MappingTableData();
|
||||
var fieldMapper = new FieldMapper(mappingData);
|
||||
var extractor = new DwgDataExtractor(fieldMapper);
|
||||
|
||||
// 3. Note 데이터 추출
|
||||
Debug.WriteLine($"3. DWG 파일에서 Note 추출 중: {Path.GetFileName(dwgFilePath)}");
|
||||
var noteEntities = extractor.ExtractNotesFromDrawing(dwgFilePath);
|
||||
|
||||
Debug.WriteLine($" 추출된 Note 엔터티 수: {noteEntities.NoteEntities.Count}");
|
||||
|
||||
// 4. Note 데이터 분석
|
||||
AnalyzeNoteData(noteEntities.NoteEntities);
|
||||
|
||||
// 5. CSV 내보내기
|
||||
Debug.WriteLine("5. CSV 파일 생성 중...");
|
||||
var csvWriter = new CsvDataWriter();
|
||||
var baseFileName = Path.GetFileNameWithoutExtension(dwgFilePath);
|
||||
|
||||
// 5-1. Note 박스 텍스트만 CSV로 저장
|
||||
var noteTextCsvPath = Path.Combine(outputDirectory, $"{baseFileName}_note_texts.csv");
|
||||
csvWriter.WriteNoteBoxTextToCsv(noteEntities.NoteEntities, noteTextCsvPath);
|
||||
Debug.WriteLine($" Note 텍스트 CSV 저장: {noteTextCsvPath}");
|
||||
|
||||
// 5-2. Note 테이블 데이터만 CSV로 저장
|
||||
var noteTableCsvPath = Path.Combine(outputDirectory, $"{baseFileName}_note_tables.csv");
|
||||
csvWriter.WriteNoteTablesToCsv(noteEntities.NoteEntities, noteTableCsvPath);
|
||||
Debug.WriteLine($" Note 테이블 CSV 저장: {noteTableCsvPath}");
|
||||
|
||||
// 5-3. 통합 CSV 저장
|
||||
var combinedCsvPath = Path.Combine(outputDirectory, $"{baseFileName}_note_combined.csv");
|
||||
csvWriter.WriteNoteDataToCombinedCsv(noteEntities.NoteEntities, combinedCsvPath);
|
||||
Debug.WriteLine($" 통합 CSV 저장: {combinedCsvPath}");
|
||||
|
||||
// 5-4. 개별 테이블 CSV 저장
|
||||
var individualTablesDir = Path.Combine(outputDirectory, $"{baseFileName}_individual_tables");
|
||||
csvWriter.WriteIndividualNoteTablesCsv(noteEntities.NoteEntities, individualTablesDir);
|
||||
Debug.WriteLine($" 개별 테이블 CSV 저장: {individualTablesDir}");
|
||||
|
||||
// 5-5. 통계 정보 CSV 저장
|
||||
var statisticsCsvPath = Path.Combine(outputDirectory, $"{baseFileName}_note_statistics.csv");
|
||||
csvWriter.WriteNoteStatisticsToCsv(noteEntities.NoteEntities, statisticsCsvPath);
|
||||
Debug.WriteLine($" 통계 정보 CSV 저장: {statisticsCsvPath}");
|
||||
|
||||
Debug.WriteLine("=== Note 추출 및 CSV 내보내기 테스트 완료 ===");
|
||||
Debug.WriteLine($"모든 결과 파일이 저장되었습니다: {outputDirectory}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"❌ 테스트 중 오류 발생: {ex.Message}");
|
||||
Debug.WriteLine($"스택 트레이스: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Teigha 서비스 정리
|
||||
try
|
||||
{
|
||||
TeighaServicesManager.Instance.ForceDisposeServices();
|
||||
Debug.WriteLine("Teigha 서비스 정리 완료");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Teigha 서비스 정리 중 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 추출된 Note 데이터를 분석하여 로그로 출력
|
||||
/// </summary>
|
||||
private static void AnalyzeNoteData(List<NoteEntityInfo> noteEntities)
|
||||
{
|
||||
Debug.WriteLine("=== Note 데이터 분석 ===");
|
||||
|
||||
var noteHeaders = noteEntities.Where(ne => ne.Type == "Note").ToList();
|
||||
var noteContents = noteEntities.Where(ne => ne.Type == "NoteContent").ToList();
|
||||
var notesWithTables = noteEntities.Where(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv)).ToList();
|
||||
|
||||
Debug.WriteLine($"전체 Note 헤더 수: {noteHeaders.Count}");
|
||||
Debug.WriteLine($"전체 Note 콘텐츠 수: {noteContents.Count}");
|
||||
Debug.WriteLine($"테이블이 있는 Note 수: {notesWithTables.Count}");
|
||||
|
||||
// 각 Note 분석
|
||||
foreach (var note in noteHeaders)
|
||||
{
|
||||
Debug.WriteLine($"");
|
||||
Debug.WriteLine($"Note: '{note.Text}' at ({note.X:F1}, {note.Y:F1})");
|
||||
|
||||
if (!string.IsNullOrEmpty(note.TableCsv))
|
||||
{
|
||||
var tableLines = note.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Debug.WriteLine($" 테이블 데이터: {tableLines.Length}행");
|
||||
|
||||
// 테이블 내용 일부 출력
|
||||
for (int i = 0; i < Math.Min(3, tableLines.Length); i++)
|
||||
{
|
||||
var line = tableLines[i];
|
||||
if (line.Length > 50) line = line.Substring(0, 50) + "...";
|
||||
Debug.WriteLine($" 행 {i + 1}: {line}");
|
||||
}
|
||||
|
||||
if (tableLines.Length > 3)
|
||||
{
|
||||
Debug.WriteLine($" ... 및 {tableLines.Length - 3}개 행 더");
|
||||
}
|
||||
}
|
||||
|
||||
// 이 Note와 연관된 NoteContent들
|
||||
var relatedContents = noteContents.Where(nc =>
|
||||
Math.Abs(nc.Y - note.Y) < 50 && // Y 좌표가 비슷한 범위 (Note 아래)
|
||||
nc.Y < note.Y) // Note보다 아래쪽
|
||||
.OrderBy(nc => nc.SortOrder)
|
||||
.ToList();
|
||||
|
||||
if (relatedContents.Count > 0)
|
||||
{
|
||||
Debug.WriteLine($" 관련 콘텐츠: {relatedContents.Count}개");
|
||||
foreach (var content in relatedContents.Take(3))
|
||||
{
|
||||
Debug.WriteLine($" '{content.Text}' at ({content.X:F1}, {content.Y:F1})");
|
||||
}
|
||||
if (relatedContents.Count > 3)
|
||||
{
|
||||
Debug.WriteLine($" ... 및 {relatedContents.Count - 3}개 더");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 레이어별 분포
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("레이어별 분포:");
|
||||
var layerGroups = noteEntities.GroupBy(ne => ne.Layer).OrderByDescending(g => g.Count());
|
||||
foreach (var layerGroup in layerGroups)
|
||||
{
|
||||
Debug.WriteLine($" {layerGroup.Key}: {layerGroup.Count()}개");
|
||||
}
|
||||
|
||||
Debug.WriteLine("=== Note 데이터 분석 완료 ===");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
public static class SettingsManager
|
||||
{
|
||||
private static readonly string SettingsFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
|
||||
|
||||
public static void SaveSettings(AppSettings settings)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(settings, Formatting.Indented);
|
||||
File.WriteAllText(SettingsFilePath, json);
|
||||
}
|
||||
|
||||
public static AppSettings? LoadSettings()
|
||||
{
|
||||
if (!File.Exists(SettingsFilePath))
|
||||
{ return null; }
|
||||
|
||||
string json = File.ReadAllText(SettingsFilePath);
|
||||
return JsonConvert.DeserializeObject<AppSettings>(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace DwgExtractorManual.Models
|
||||
/// </summary>
|
||||
internal sealed class SqlDatas : IDisposable
|
||||
{
|
||||
Services appServices; // ODA 제품 활성화용 (managed by singleton)
|
||||
Services? appServices; // ODA 제품 활성화용 (managed by singleton)
|
||||
readonly string connectionString = "Host=localhost;Database=postgres;Username=postgres;Password=Qwer1234";
|
||||
|
||||
void InitializeTeighaServices()
|
||||
@@ -143,8 +143,8 @@ namespace DwgExtractorManual.Models
|
||||
cmd.Parameters.AddWithValue("Type", "DBText");
|
||||
cmd.Parameters.AddWithValue("Layer", layerName);
|
||||
cmd.Parameters.AddWithValue("Text", dbText.TextString ?? "");
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename);
|
||||
cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename));
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename ?? "");
|
||||
cmd.Parameters.AddWithValue("FileName", string.IsNullOrEmpty(database.Filename) ? "" : Path.GetFileName(database.Filename));
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -162,8 +162,8 @@ namespace DwgExtractorManual.Models
|
||||
cmd.Parameters.AddWithValue("Type", "MText");
|
||||
cmd.Parameters.AddWithValue("Layer", layerName);
|
||||
cmd.Parameters.AddWithValue("Text", mText.Contents ?? "");
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename);
|
||||
cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename));
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename ?? "");
|
||||
cmd.Parameters.AddWithValue("FileName", string.IsNullOrEmpty(database.Filename) ? "" : Path.GetFileName(database.Filename));
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -231,8 +231,8 @@ namespace DwgExtractorManual.Models
|
||||
else
|
||||
cmd.Parameters.AddWithValue("Value", tString);
|
||||
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename);
|
||||
cmd.Parameters.AddWithValue("FileName", Path.GetFileName(database.Filename));
|
||||
cmd.Parameters.AddWithValue("Path", database.Filename ?? "");
|
||||
cmd.Parameters.AddWithValue("FileName", string.IsNullOrEmpty(database.Filename) ? "" : Path.GetFileName(database.Filename));
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
115
Models/TableCellVisualizationData.cs
Normal file
115
Models/TableCellVisualizationData.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Teigha.Geometry;
|
||||
|
||||
namespace DwgExtractorManual.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 테이블 셀 시각화를 위한 데이터 클래스
|
||||
/// </summary>
|
||||
public class TableCellVisualizationData
|
||||
{
|
||||
public string FileName { get; set; } = "";
|
||||
public string NoteText { get; set; } = "";
|
||||
public List<CellBounds> Cells { get; set; } = new List<CellBounds>();
|
||||
public List<SegmentInfo> TableSegments { get; set; } = new List<SegmentInfo>();
|
||||
public List<TextInfo> TextEntities { get; set; } = new List<TextInfo>();
|
||||
public List<IntersectionInfo> IntersectionPoints { get; set; } = new List<IntersectionInfo>(); // 교차점 정보 추가
|
||||
public List<DiagonalLine> DiagonalLines { get; set; } = new List<DiagonalLine>(); // 셀 대각선 정보 추가
|
||||
public List<CellBoundaryInfo> CellBoundaries { get; set; } = new List<CellBoundaryInfo>(); // 정확한 셀 경계 정보 추가
|
||||
public (double minX, double minY, double maxX, double maxY) NoteBounds { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 셀 경계 정보
|
||||
/// </summary>
|
||||
public class CellBounds
|
||||
{
|
||||
public double MinX { get; set; }
|
||||
public double MinY { get; set; }
|
||||
public double MaxX { get; set; }
|
||||
public double MaxY { get; set; }
|
||||
public int Row { get; set; }
|
||||
public int Column { get; set; }
|
||||
public string Text { get; set; } = "";
|
||||
public bool IsValid { get; set; } = true;
|
||||
|
||||
public double Width => MaxX - MinX;
|
||||
public double Height => MaxY - MinY;
|
||||
public double CenterX => (MinX + MaxX) / 2;
|
||||
public double CenterY => (MinY + MaxY) / 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선분 정보
|
||||
/// </summary>
|
||||
public class SegmentInfo
|
||||
{
|
||||
public double StartX { get; set; }
|
||||
public double StartY { get; set; }
|
||||
public double EndX { get; set; }
|
||||
public double EndY { get; set; }
|
||||
public bool IsHorizontal { get; set; }
|
||||
public string Color { get; set; } = "Black";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 정보
|
||||
/// </summary>
|
||||
public class TextInfo
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public string Text { get; set; } = "";
|
||||
public bool IsInTable { get; set; }
|
||||
public string Color { get; set; } = "Blue";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차점 정보
|
||||
/// </summary>
|
||||
public class IntersectionInfo
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public int DirectionBits { get; set; } // 비트 플래그 숫자
|
||||
public int Row { get; set; } // Row 번호
|
||||
public int Column { get; set; } // Column 번호
|
||||
public bool IsTopLeft { get; set; } // topLeft 후보인지
|
||||
public bool IsBottomRight { get; set; } // bottomRight 후보인지
|
||||
public string Color { get; set; } = "Red";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대각선 정보 (셀 디버깅용)
|
||||
/// </summary>
|
||||
public class DiagonalLine
|
||||
{
|
||||
public double StartX { get; set; }
|
||||
public double StartY { get; set; }
|
||||
public double EndX { get; set; }
|
||||
public double EndY { get; set; }
|
||||
public string Color { get; set; } = "Green";
|
||||
public string Label { get; set; } = ""; // 디버깅 라벨
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 정확한 셀 경계 정보 (4개 모서리 좌표)
|
||||
/// </summary>
|
||||
public class CellBoundaryInfo
|
||||
{
|
||||
public double TopLeftX { get; set; }
|
||||
public double TopLeftY { get; set; }
|
||||
public double TopRightX { get; set; }
|
||||
public double TopRightY { get; set; }
|
||||
public double BottomLeftX { get; set; }
|
||||
public double BottomLeftY { get; set; }
|
||||
public double BottomRightX { get; set; }
|
||||
public double BottomRightY { get; set; }
|
||||
public string Label { get; set; } = "";
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public string Color { get; set; } = "DarkBlue";
|
||||
public string CellText { get; set; } = ""; // 셀 내 텍스트 내용
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ namespace DwgExtractorManual.Models
|
||||
public sealed class TeighaServicesManager
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static TeighaServicesManager _instance = null;
|
||||
private static Services _services = null;
|
||||
private static TeighaServicesManager? _instance = null;
|
||||
private static Services? _services = null;
|
||||
private static int _referenceCount = 0;
|
||||
private static bool _isActivated = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user