도면에서 표 추출
This commit is contained in:
20
.claude/settings.local.json
Normal file
20
.claude/settings.local.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dotnet build)",
|
||||
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" DwgExtractorManual.csproj -p:Configuration=Debug -p:Platform=x64)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(where msbuild)",
|
||||
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\MSBuild\\Current\\Bin\\amd64\\MSBuild.exe\" DwgExtractorManual.csproj -p:Configuration=Debug -p:Platform=x64)",
|
||||
"Bash(dotnet run:*)",
|
||||
"Bash(echo $HOME)",
|
||||
"Bash(find:*)",
|
||||
"Bash(dotnet clean:*)",
|
||||
"Bash(dotnet build:*)",
|
||||
"Bash(taskkill:*)",
|
||||
"Bash(wmic process where ProcessId=17428 delete:*)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"contextFileName": "AGENTS.md"
|
||||
}
|
||||
85
AGENTS.md
Normal file
85
AGENTS.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a C# WPF application that extracts data from DWG (AutoCAD) files and processes them using AI analysis. The application has two main components:
|
||||
|
||||
1. **C# WPF Application** (`DwgExtractorManual`) - Main GUI application for DWG processing
|
||||
2. **Python Analysis Module** (`fletimageanalysis`) - AI-powered document analysis using Gemini API
|
||||
|
||||
## Build and Development Commands
|
||||
|
||||
### C# Application
|
||||
```bash
|
||||
# Build the application
|
||||
dotnet build
|
||||
|
||||
# Run the application
|
||||
dotnet run
|
||||
|
||||
# Clean build artifacts
|
||||
dotnet clean
|
||||
|
||||
# Publish for deployment
|
||||
dotnet publish -c Release
|
||||
```
|
||||
|
||||
### Python Module Setup
|
||||
```bash
|
||||
# Run the cleanup and setup script (Windows)
|
||||
cleanup_and_setup.bat
|
||||
|
||||
# Or manually setup Python environment
|
||||
cd fletimageanalysis
|
||||
python -m venv venv
|
||||
call venv\Scripts\activate.bat
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Python CLI Usage
|
||||
```bash
|
||||
# Batch process files via CLI
|
||||
cd fletimageanalysis
|
||||
python batch_cli.py --files "file1.pdf,file2.dxf" --schema "한국도로공사" --concurrent 3 --output "results.csv"
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### C# Component Structure
|
||||
- **MainWindow.xaml.cs** - Main WPF window and UI logic
|
||||
- **Models/DwgDataExtractor.cs** - Core DWG file processing using Teigha SDK
|
||||
- **Models/ExcelDataWriter.cs** - Excel output generation using Office Interop
|
||||
- **Models/TeighaServicesManager.cs** - Singleton manager for Teigha SDK lifecycle
|
||||
- **Models/FieldMapper.cs** - Maps extracted data to target formats
|
||||
- **Models/SettingsManager.cs** - Application configuration management
|
||||
|
||||
### Python Component Structure
|
||||
- **batch_cli.py** - Command-line interface for batch processing
|
||||
- **multi_file_processor.py** - Orchestrates multi-file processing workflows
|
||||
- **gemini_analyzer.py** - AI analysis using Google Gemini API
|
||||
- **pdf_processor.py** - PDF document processing
|
||||
- **dxf_processor.py** - DXF file processing
|
||||
- **csv_exporter.py** - CSV output generation
|
||||
|
||||
### Key Dependencies
|
||||
- **Teigha SDK** - DWG file reading and CAD entity processing (requires DLL files in specific path)
|
||||
- **Microsoft Office Interop** - Excel file generation
|
||||
- **Npgsql** - PostgreSQL database connectivity
|
||||
- **Google Gemini API** - AI-powered document analysis
|
||||
- **PyMuPDF** - PDF processing in Python component
|
||||
|
||||
## Current Development Focus
|
||||
|
||||
The project is undergoing a **Note Detection Refactor** (see `NoteDetectionRefactor.md`):
|
||||
- Replacing fragile "horizontal search line" algorithm in `DwgDataExtractor.cs`
|
||||
- Implementing robust "vertical ray-casting" approach for NOTE content box detection
|
||||
- Key methods being refactored: `FindNoteBox`, `GetAllLineSegments`, `TraceBoxFromTopLine`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Teigha DLLs must be present in the specified path for DWG processing to work
|
||||
- The Python module requires Google Gemini API key configuration
|
||||
- Excel output uses COM Interop and requires Microsoft Office installation
|
||||
- The application supports both manual GUI operation and automated batch processing via CLI
|
||||
14
App.config
Normal file
14
App.config
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="office"
|
||||
publicKeyToken="71e9bce111e9429c"
|
||||
culture="neutral" />
|
||||
<bindingRedirect oldVersion="15.0.0.0"
|
||||
newVersion="16.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
144
Controls/ZoomBorder.cs
Normal file
144
Controls/ZoomBorder.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace DwgExtractorManual.Controls
|
||||
{
|
||||
public class ZoomBorder : Border
|
||||
{
|
||||
private UIElement? child = null;
|
||||
private Point origin;
|
||||
private Point start;
|
||||
|
||||
private TranslateTransform GetTranslateTransform(UIElement element)
|
||||
{
|
||||
return (TranslateTransform)((TransformGroup)element.RenderTransform)
|
||||
.Children.First(tr => tr is TranslateTransform);
|
||||
}
|
||||
|
||||
private ScaleTransform GetScaleTransform(UIElement element)
|
||||
{
|
||||
return (ScaleTransform)((TransformGroup)element.RenderTransform)
|
||||
.Children.First(tr => tr is ScaleTransform);
|
||||
}
|
||||
|
||||
public override UIElement Child
|
||||
{
|
||||
get { return base.Child; }
|
||||
set
|
||||
{
|
||||
if (value != null && value != this.Child)
|
||||
this.Initialize(value);
|
||||
base.Child = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(UIElement element)
|
||||
{
|
||||
this.child = element;
|
||||
if (child != null)
|
||||
{
|
||||
TransformGroup group = new TransformGroup();
|
||||
ScaleTransform st = new ScaleTransform();
|
||||
group.Children.Add(st);
|
||||
TranslateTransform tt = new TranslateTransform();
|
||||
group.Children.Add(tt);
|
||||
child.RenderTransform = group;
|
||||
child.RenderTransformOrigin = new Point(0.0, 0.0);
|
||||
this.MouseWheel += child_MouseWheel;
|
||||
this.MouseLeftButtonDown += child_MouseLeftButtonDown;
|
||||
this.MouseLeftButtonUp += child_MouseLeftButtonUp;
|
||||
this.MouseMove += child_MouseMove;
|
||||
this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
|
||||
child_PreviewMouseRightButtonDown);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
// reset zoom
|
||||
var st = GetScaleTransform(child);
|
||||
st.ScaleX = 1.0;
|
||||
st.ScaleY = 1.0;
|
||||
|
||||
// reset pan
|
||||
var tt = GetTranslateTransform(child);
|
||||
tt.X = 0.0;
|
||||
tt.Y = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private void child_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
var st = GetScaleTransform(child);
|
||||
var tt = GetTranslateTransform(child);
|
||||
|
||||
double zoom = e.Delta > 0 ? .2 : -.2;
|
||||
if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))
|
||||
return;
|
||||
|
||||
Point relative = e.GetPosition(child);
|
||||
double absoluteX;
|
||||
double absoluteY;
|
||||
|
||||
absoluteX = relative.X * st.ScaleX + tt.X;
|
||||
absoluteY = relative.Y * st.ScaleY + tt.Y;
|
||||
|
||||
st.ScaleX += zoom;
|
||||
st.ScaleY += zoom;
|
||||
|
||||
tt.X = absoluteX - relative.X * st.ScaleX;
|
||||
tt.Y = absoluteY - relative.Y * st.ScaleY;
|
||||
}
|
||||
}
|
||||
|
||||
private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
var tt = GetTranslateTransform(child);
|
||||
start = e.GetPosition(this);
|
||||
origin = new Point(tt.X, tt.Y);
|
||||
this.Cursor = Cursors.Hand;
|
||||
child.CaptureMouse();
|
||||
}
|
||||
}
|
||||
|
||||
private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.ReleaseMouseCapture();
|
||||
this.Cursor = Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
|
||||
void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
private void child_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
if (child.IsMouseCaptured)
|
||||
{
|
||||
var tt = GetTranslateTransform(child);
|
||||
Vector v = start - e.GetPosition(this);
|
||||
tt.X = origin.X - v.X;
|
||||
tt.Y = origin.Y - v.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,30 +6,17 @@
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<COMReference Include="Microsoft.Office.Interop.Excel">
|
||||
<VersionMinor>9</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>00020813-0000-0000-c000-000000000046</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql" Version="9.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TD_Mgd_23.12_16">
|
||||
<HintPath>D:\dev_Net8_git\trunk\DLL\Teigha\vc16_amd64dll_23.12SP2\TD_Mgd_23.12_16.dll</HintPath>
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy all Teigha DLLs including native dependencies -->
|
||||
@@ -39,51 +26,33 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Define fletimageanalysis files as content to be copied -->
|
||||
<!-- Separate JSON files and other files for different copy behaviors -->
|
||||
|
||||
<ItemGroup>
|
||||
<!-- JSON files - always copy as new -->
|
||||
<FletImageAnalysisJsonFiles Include="fletimageanalysis\**\*.json" />
|
||||
|
||||
<!-- Other files - incremental copy -->
|
||||
<FletImageAnalysisOtherFiles Include="fletimageanalysis\**\*" Exclude="fletimageanalysis\**\*.json;fletimageanalysis\**\*.pyc;fletimageanalysis\**\__pycache__\**;fletimageanalysis\**\*.tmp;fletimageanalysis\**\*.log;fletimageanalysis\**\*.git\**" />
|
||||
<Reference Include="TD_Mgd_23.12_16">
|
||||
<HintPath>..\..\..\GitNet8\trunk\DLL\Teigha\vc16_amd64dll_23.12SP2\TD_Mgd_23.12_16.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Enhanced copy target that handles both incremental updates and missing folder scenarios -->
|
||||
<Target Name="CopyFletImageAnalysisFolder" AfterTargets="Build">
|
||||
|
||||
<!-- Always show what we're doing -->
|
||||
<Message Text="Copying fletimageanalysis folder contents..." Importance="normal" />
|
||||
<Message Text="JSON files to copy: @(FletImageAnalysisJsonFiles->Count())" Importance="normal" />
|
||||
<Message Text="Other files to copy: @(FletImageAnalysisOtherFiles->Count())" Importance="normal" />
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Office.Interop.Excel">
|
||||
<HintPath>C:\Program Files (x86)\Microsoft Office\Office16\DCF\Microsoft.Office.Interop.Excel.dll</HintPath>
|
||||
<EmbedInteropTypes>false</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="office">
|
||||
<HintPath>C:\Program Files (x86)\Microsoft Office\Office16\DCF\office.dll</HintPath>
|
||||
<EmbedInteropTypes>false</EmbedInteropTypes>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy JSON files - ALWAYS as new (SkipUnchangedFiles=false) -->
|
||||
<Copy SourceFiles="@(FletImageAnalysisJsonFiles)" DestinationFiles="@(FletImageAnalysisJsonFiles->'$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
|
||||
|
||||
<Message Text="JSON files copied (always as new): @(FletImageAnalysisJsonFiles->Count())" Importance="high" />
|
||||
<ItemGroup>
|
||||
<Content Include="fletimageanalysis\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>fletimageanalysis\%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy other files - incrementally (SkipUnchangedFiles=true) -->
|
||||
<Copy SourceFiles="@(FletImageAnalysisOtherFiles)" DestinationFiles="@(FletImageAnalysisOtherFiles->'$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
|
||||
|
||||
<Message Text="Other files copied (incremental): @(FletImageAnalysisOtherFiles->Count())" Importance="normal" />
|
||||
|
||||
<!-- Verify critical JSON file exists after copy -->
|
||||
<Error Condition="!Exists('$(OutDir)fletimageanalysis\mapping_table_json.json')" Text="Critical file missing after copy: mapping_table_json.json" />
|
||||
|
||||
<Message Text="fletimageanalysis folder copy completed successfully." Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<!-- Additional target to ensure folder is copied during publish -->
|
||||
<Target Name="CopyFletImageAnalysisFolderOnPublish" AfterTargets="Publish">
|
||||
<Message Text="Copying fletimageanalysis folder for publish..." Importance="high" />
|
||||
|
||||
<!-- Copy JSON files - always as new -->
|
||||
<Copy SourceFiles="@(FletImageAnalysisJsonFiles)" DestinationFiles="@(FletImageAnalysisJsonFiles->'$(PublishDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
|
||||
|
||||
<!-- Copy other files - always as new for publish -->
|
||||
<Copy SourceFiles="@(FletImageAnalysisOtherFiles)" DestinationFiles="@(FletImageAnalysisOtherFiles->'$(PublishDir)%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
|
||||
|
||||
<Message Text="fletimageanalysis folder publish copy completed." Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
173
IntersectionTestConsole.cs
Normal file
173
IntersectionTestConsole.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Diagnostics;
|
||||
//using System.Linq;
|
||||
//using Teigha.Geometry;
|
||||
//using DwgExtractorManual.Models;
|
||||
|
||||
//namespace DwgExtractorManual
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// 콘솔에서 실행할 수 있는 교차점 테스트 프로그램
|
||||
// /// </summary>
|
||||
// class IntersectionTestConsole
|
||||
// {
|
||||
// static void Main(string[] args)
|
||||
// {
|
||||
// Console.WriteLine("=== 교차점 테스트 프로그램 시작 ===");
|
||||
|
||||
// try
|
||||
// {
|
||||
// RunSimpleIntersectionTest();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($"오류 발생: {ex.Message}");
|
||||
// Console.WriteLine(ex.StackTrace);
|
||||
// }
|
||||
|
||||
// Console.WriteLine("테스트 완료. 아무 키나 누르세요...");
|
||||
// Console.ReadKey();
|
||||
// }
|
||||
|
||||
// static void RunSimpleIntersectionTest()
|
||||
// {
|
||||
// Console.WriteLine("테스트 시작: 3x4 그리드 생성");
|
||||
|
||||
// // 간단한 3x4 테이블 시뮬레이션 (실제 DWG 없이)
|
||||
// var intersections = new List<IntersectionPoint>();
|
||||
|
||||
// // 수동으로 교차점 생성 (3행 x 4열 = 12개 교차점)
|
||||
// for (int row = 1; row <= 4; row++) // 4개 행
|
||||
// {
|
||||
// for (int col = 1; col <= 5; col++) // 5개 열
|
||||
// {
|
||||
// double x = (col - 1) * 10.0; // 0, 10, 20, 30, 40
|
||||
// double y = (row - 1) * 10.0; // 0, 10, 20, 30
|
||||
|
||||
// int directionBits = CalculateDirectionBits(row, col, 4, 5);
|
||||
|
||||
// var intersection = new IntersectionPoint
|
||||
// {
|
||||
// Position = new Point3d(x, y, 0),
|
||||
// DirectionBits = directionBits,
|
||||
// Row = row,
|
||||
// Column = col
|
||||
// };
|
||||
|
||||
// intersections.Add(intersection);
|
||||
// Console.WriteLine($"교차점 R{row}C{col}: ({x:F0},{y:F0}) - DirectionBits: {directionBits}");
|
||||
// }
|
||||
// }
|
||||
|
||||
// Console.WriteLine($"\n총 {intersections.Count}개 교차점 생성됨");
|
||||
|
||||
// // DirectionBits 검증
|
||||
// TestDirectionBitsValidation(intersections);
|
||||
|
||||
// // 셀 추출 시뮬레이션
|
||||
// TestCellExtraction(intersections);
|
||||
// }
|
||||
|
||||
// static int CalculateDirectionBits(int row, int col, int maxRow, int maxCol)
|
||||
// {
|
||||
// int bits = 0;
|
||||
|
||||
// // Right: 1 - 오른쪽에 더 많은 열이 있으면
|
||||
// if (col < maxCol) bits |= 1;
|
||||
|
||||
// // Up: 2 - 위쪽에 더 많은 행이 있으면
|
||||
// if (row < maxRow) bits |= 2;
|
||||
|
||||
// // Left: 4 - 왼쪽에 열이 있으면
|
||||
// if (col > 1) bits |= 4;
|
||||
|
||||
// // Down: 8 - 아래쪽에 행이 있으면
|
||||
// if (row > 1) bits |= 8;
|
||||
|
||||
// return bits;
|
||||
// }
|
||||
|
||||
// static void TestDirectionBitsValidation(List<IntersectionPoint> intersections)
|
||||
// {
|
||||
// Console.WriteLine("\n=== DirectionBits 검증 ===");
|
||||
|
||||
// var mappingData = new MappingTableData();
|
||||
// var fieldMapper = new FieldMapper(mappingData);
|
||||
// var extractor = new DwgDataExtractor(fieldMapper);
|
||||
|
||||
// foreach (var intersection in intersections)
|
||||
// {
|
||||
// bool isTopLeft = extractor.IsValidTopLeft(intersection.DirectionBits);
|
||||
// bool isBottomRight = extractor.IsValidBottomRight(intersection.DirectionBits);
|
||||
|
||||
// Console.WriteLine($"R{intersection.Row}C{intersection.Column} (bits: {intersection.DirectionBits:D2}) - " +
|
||||
// $"TopLeft: {isTopLeft}, BottomRight: {isBottomRight}");
|
||||
// }
|
||||
// }
|
||||
|
||||
// static void TestCellExtraction(List<IntersectionPoint> intersections)
|
||||
// {
|
||||
// Console.WriteLine("\n=== 셀 추출 테스트 ===");
|
||||
|
||||
// var mappingData = new MappingTableData();
|
||||
// var fieldMapper = new FieldMapper(mappingData);
|
||||
// var extractor = new DwgDataExtractor(fieldMapper);
|
||||
|
||||
// // topLeft 후보들 찾기
|
||||
// var topLeftCandidates = intersections.Where(i => extractor.IsValidTopLeft(i.DirectionBits)).ToList();
|
||||
// Console.WriteLine($"TopLeft 후보: {topLeftCandidates.Count}개");
|
||||
|
||||
// foreach (var topLeft in topLeftCandidates)
|
||||
// {
|
||||
// Console.WriteLine($"\nTopLeft R{topLeft.Row}C{topLeft.Column} 처리 중...");
|
||||
|
||||
// // bottomRight 찾기 시뮬레이션
|
||||
// var bottomRight = FindBottomRightSimulation(topLeft, intersections, extractor);
|
||||
|
||||
// if (bottomRight != null)
|
||||
// {
|
||||
// Console.WriteLine($" -> BottomRight 발견: R{bottomRight.Row}C{bottomRight.Column}");
|
||||
// Console.WriteLine($" 셀 생성: ({topLeft.Position.X:F0},{bottomRight.Position.Y:F0}) to ({bottomRight.Position.X:F0},{topLeft.Position.Y:F0})");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Console.WriteLine(" -> BottomRight을 찾지 못함");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// static IntersectionPoint? FindBottomRightSimulation(IntersectionPoint topLeft, List<IntersectionPoint> intersections, DwgDataExtractor extractor)
|
||||
// {
|
||||
// // 교차점들을 Row/Column으로 딕셔너리 구성
|
||||
// var intersectionLookup = intersections
|
||||
// .GroupBy(i => i.Row)
|
||||
// .ToDictionary(g => g.Key, g => g.ToDictionary(i => i.Column, i => i));
|
||||
|
||||
// int maxRow = intersectionLookup.Keys.Max();
|
||||
// int maxColumn = intersectionLookup.Values.SelectMany(row => row.Keys).Max();
|
||||
|
||||
// for (int targetRow = topLeft.Row + 1; targetRow <= maxRow + 1; 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];
|
||||
|
||||
// // bottomRight 검증 또는 테이블 경계 조건
|
||||
// if (extractor.IsValidBottomRight(candidate.DirectionBits) ||
|
||||
// (targetRow == maxRow && targetColumn == maxColumn))
|
||||
// {
|
||||
// return candidate;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
82
MainWindow.Visualization.cs
Normal file
82
MainWindow.Visualization.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using DwgExtractorManual.Models;
|
||||
using DwgExtractorManual.Views;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace DwgExtractorManual
|
||||
{
|
||||
/// <summary>
|
||||
/// MainWindow의 시각화 관련 기능을 담당하는 partial class
|
||||
/// </summary>
|
||||
public partial class MainWindow
|
||||
{
|
||||
// 시각화 데이터 저장
|
||||
private static List<TableCellVisualizationData> _visualizationDataCache = new List<TableCellVisualizationData>();
|
||||
|
||||
/// <summary>
|
||||
/// 테이블 셀 시각화 데이터를 저장합니다.
|
||||
/// </summary>
|
||||
public static void SaveVisualizationData(TableCellVisualizationData data)
|
||||
{
|
||||
_visualizationDataCache.Add(data);
|
||||
Debug.WriteLine($"[VISUALIZATION] 시각화 데이터 저장: {data.FileName}, 셀 수: {data.Cells.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 저장된 시각화 데이터를 가져옵니다.
|
||||
/// </summary>
|
||||
public static List<TableCellVisualizationData> GetVisualizationData()
|
||||
{
|
||||
Debug.WriteLine($"[VISUALIZATION] 시각화 데이터 조회: {_visualizationDataCache.Count}개 항목");
|
||||
return new List<TableCellVisualizationData>(_visualizationDataCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시각화 데이터를 초기화합니다.
|
||||
/// </summary>
|
||||
public static void ClearVisualizationData()
|
||||
{
|
||||
Debug.WriteLine($"[VISUALIZATION] 시각화 데이터 초기화 (기존 {_visualizationDataCache.Count}개 항목 삭제)");
|
||||
_visualizationDataCache.Clear();
|
||||
Debug.WriteLine("[VISUALIZATION] 시각화 데이터 초기화 완료");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 테이블 셀 시각화 창을 엽니다.
|
||||
/// </summary>
|
||||
private void BtnVisualizeCells_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage("🎨 테이블 셀 시각화 창을 여는 중...");
|
||||
|
||||
var visualizationData = GetVisualizationData();
|
||||
LogMessage($"[DEBUG] 조회된 시각화 데이터: {visualizationData.Count}개");
|
||||
|
||||
if (visualizationData.Count == 0)
|
||||
{
|
||||
MessageBox.Show("시각화할 데이터가 없습니다.\n먼저 'DWG추출(Height정렬)' 버튼을 눌러 데이터를 추출해주세요.",
|
||||
"데이터 없음", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
LogMessage("⚠️ 시각화할 데이터가 없습니다. 먼저 추출을 진행해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
var visualizationWindow = new TableCellVisualizationWindow(visualizationData);
|
||||
visualizationWindow.Owner = this;
|
||||
visualizationWindow.Show();
|
||||
|
||||
LogMessage($"✅ 시각화 창 열기 완료 - {visualizationData.Count}개 파일 데이터");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"❌ 시각화 창 열기 중 오류: {ex.Message}");
|
||||
MessageBox.Show($"시각화 창을 여는 중 오류가 발생했습니다:\n{ex.Message}",
|
||||
"오류", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,6 +210,40 @@
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
<Button x:Name="btnVisualizeCells"
|
||||
Content="🎨 셀 시각화" Width="150" Height="45"
|
||||
Margin="5,0"
|
||||
Click="BtnVisualizeCells_Click" FontSize="14" FontWeight="Bold"
|
||||
Background="#9B59B6" Foreground="White"
|
||||
BorderThickness="0">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#9B59B6"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#8E44AD"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
<Button x:Name="btnTestIntersection"
|
||||
Content="🔬 교차점 테스트" Width="150" Height="45"
|
||||
Margin="5,0"
|
||||
Click="BtnTestIntersection_Click" FontSize="14" FontWeight="Bold"
|
||||
Background="#E74C3C" Foreground="White"
|
||||
BorderThickness="0">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#E74C3C"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#C0392B"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
@@ -234,6 +268,8 @@
|
||||
<TextBlock x:Name="txtStatusBar" Text="DWG 정보 추출기 v1.0 - 준비됨"/>
|
||||
<Separator Margin="10,0"/>
|
||||
<TextBlock x:Name="txtFileCount" Text="파일: 0개"/>
|
||||
<Separator Margin="10,0"/>
|
||||
<TextBlock x:Name="txtBuildTime" Text="빌드: 로딩중..." FontSize="11" Foreground="LightGray"/>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace DwgExtractorManual
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private DispatcherTimer _timer;
|
||||
private DispatcherTimer? _timer;
|
||||
private ExportExcel? _exportExcel;
|
||||
private SqlDatas? _sqlDatas;
|
||||
// 자동 처리 모드 플래그
|
||||
@@ -26,16 +26,76 @@ namespace DwgExtractorManual
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeDefaultPaths();
|
||||
InitializeTimer();
|
||||
LoadSettings();
|
||||
SetBuildTime();
|
||||
|
||||
// 앱 종료 시 Teigha 리소스 정리
|
||||
this.Closed += MainWindow_Closed;
|
||||
|
||||
LogMessage("🚀 DWG 정보 추출기가 시작되었습니다.");
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object sender, EventArgs e)
|
||||
private void LoadSettings()
|
||||
{
|
||||
LogMessage("⚙️ 설정을 불러옵니다...");
|
||||
var settings = SettingsManager.LoadSettings();
|
||||
if (settings != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(settings.SourceFolderPath) && Directory.Exists(settings.SourceFolderPath))
|
||||
{
|
||||
txtSourceFolder.Text = settings.SourceFolderPath;
|
||||
LogMessage($"📂 저장된 소스 폴더: {settings.SourceFolderPath}");
|
||||
CheckDwgFiles(settings.SourceFolderPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage($"⚠️ 저장된 소스 폴더를 찾을 수 없습니다: {settings.SourceFolderPath}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.DestinationFolderPath) && Directory.Exists(settings.DestinationFolderPath))
|
||||
{
|
||||
txtResultFolder.Text = settings.DestinationFolderPath;
|
||||
LogMessage($"💾 저장된 결과 폴더: {settings.DestinationFolderPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage($"⚠️ 저장된 결과 폴더를 찾을 수 없습니다: {settings.DestinationFolderPath}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.LastExportType))
|
||||
{
|
||||
if (settings.LastExportType == "Excel")
|
||||
{
|
||||
rbExcel.IsChecked = true;
|
||||
}
|
||||
else if (settings.LastExportType == "Database")
|
||||
{
|
||||
rbDatabase.IsChecked = true;
|
||||
}
|
||||
LogMessage($"📋 저장된 출력 형식: {settings.LastExportType}");
|
||||
}
|
||||
LogMessage("✅ 설정 불러오기 완료.");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage("ℹ️ 저장된 설정 파일이 없습니다. 기본값으로 시작합니다.");
|
||||
InitializeDefaultPaths(); // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
LogMessage("⚙️ 현재 설정을 저장합니다...");
|
||||
var settings = new AppSettings
|
||||
{
|
||||
SourceFolderPath = txtSourceFolder.Text,
|
||||
DestinationFolderPath = txtResultFolder.Text,
|
||||
LastExportType = rbExcel.IsChecked == true ? "Excel" : "Database"
|
||||
};
|
||||
SettingsManager.SaveSettings(settings);
|
||||
LogMessage("✅ 설정 저장 완료.");
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -70,7 +130,13 @@ namespace DwgExtractorManual
|
||||
{
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||
_timer.Tick += (s, e) => txtTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
_timer.Tick += (s, e) =>
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
txtTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
};
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
@@ -176,6 +242,9 @@ namespace DwgExtractorManual
|
||||
|
||||
private async void BtnExtract_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
// 입력 유효성 검사
|
||||
if (string.IsNullOrEmpty(txtSourceFolder.Text) || !Directory.Exists(txtSourceFolder.Text))
|
||||
{
|
||||
@@ -278,7 +347,7 @@ namespace DwgExtractorManual
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Windows.MessageBox.Show(message, title, button, image);
|
||||
System.Windows.MessageBox.Show(this, message, title, button, image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +363,7 @@ namespace DwgExtractorManual
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.Windows.MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
return System.Windows.MessageBox.Show(this, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,12 +464,11 @@ namespace DwgExtractorManual
|
||||
|
||||
LogMessage("💾 Excel 파일과 매핑 데이터를 저장합니다...");
|
||||
|
||||
// 매핑 딕셔너리를 JSON 파일로 저장 (PDF 데이터 병합용)
|
||||
_exportExcel.SaveMappingDictionary(mappingDataFile);
|
||||
_exportExcel?.SaveMappingDictionary(mappingDataFile);
|
||||
LogMessage($"✅ 매핑 데이터 저장 완료: {Path.GetFileName(mappingDataFile)}");
|
||||
|
||||
// Excel 파일 저장
|
||||
_exportExcel.SaveAndCloseExcel(excelFileName);
|
||||
_exportExcel?.SaveAndCloseExcel(excelFileName);
|
||||
LogMessage($"✅ Excel 파일 저장 완료: {Path.GetFileName(excelFileName)}");
|
||||
|
||||
var elapsed = stopwatch.Elapsed;
|
||||
@@ -461,7 +529,7 @@ namespace DwgExtractorManual
|
||||
try
|
||||
{
|
||||
// 실제 DWG 처리 로직 (DwgToDB는 실패시 true 반환)
|
||||
bool failed = _sqlDatas.DwgToDB(file.FullName);
|
||||
bool failed = _sqlDatas?.DwgToDB(file.FullName) ?? true;
|
||||
bool success = !failed;
|
||||
|
||||
fileStopwatch.Stop();
|
||||
@@ -777,6 +845,9 @@ namespace DwgExtractorManual
|
||||
|
||||
private async void BtnPdfExtract_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
// 입력 유효성 검사
|
||||
if (string.IsNullOrEmpty(txtSourceFolder.Text) || !Directory.Exists(txtSourceFolder.Text))
|
||||
{
|
||||
@@ -1092,7 +1163,12 @@ namespace DwgExtractorManual
|
||||
LogMessage($"📄 JSON 파일 확인됨: {Path.GetFileName(jsonFilePath)}");
|
||||
|
||||
// 최신 매핑 데이터 파일 찾기
|
||||
string resultDir = Path.GetDirectoryName(csvFilePath) ?? txtResultFolder.Text;
|
||||
string? resultDir = Path.GetDirectoryName(csvFilePath);
|
||||
if (string.IsNullOrEmpty(resultDir))
|
||||
{
|
||||
LogMessage("⚠️ 결과 디렉터리를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
var mappingDataFiles = Directory.GetFiles(resultDir, "*_mapping_data.json", SearchOption.TopDirectoryOnly)
|
||||
.OrderByDescending(f => File.GetCreationTime(f))
|
||||
.ToArray();
|
||||
@@ -1168,7 +1244,12 @@ namespace DwgExtractorManual
|
||||
LogMessage($"📄 JSON 파일 확인됨: {Path.GetFileName(jsonFilePath)}");
|
||||
|
||||
// 기존 Excel 매핑 파일 검색 (임시 파일 제외)
|
||||
string resultDir = Path.GetDirectoryName(csvFilePath) ?? txtResultFolder.Text;
|
||||
string? resultDir = Path.GetDirectoryName(csvFilePath);
|
||||
if (string.IsNullOrEmpty(resultDir))
|
||||
{
|
||||
LogMessage("⚠️ 결과 디렉터리를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
var allExcelFiles = Directory.GetFiles(resultDir, "*_Mapping.xlsx", SearchOption.TopDirectoryOnly);
|
||||
|
||||
// 임시 파일(~$로 시작하는 파일) 필터링
|
||||
@@ -1296,6 +1377,9 @@ namespace DwgExtractorManual
|
||||
|
||||
private async void BtnMerge_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
// 입력 유효성 검사
|
||||
if (string.IsNullOrEmpty(txtResultFolder.Text) || !Directory.Exists(txtResultFolder.Text))
|
||||
{
|
||||
@@ -1560,6 +1644,9 @@ namespace DwgExtractorManual
|
||||
/// </summary>
|
||||
private async void BtnAuto_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
try
|
||||
{
|
||||
// 입력 검증
|
||||
@@ -1656,6 +1743,9 @@ namespace DwgExtractorManual
|
||||
/// </summary>
|
||||
private async void BtnDwgOnly_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
try
|
||||
{
|
||||
// 경로 검증
|
||||
@@ -1738,6 +1828,13 @@ namespace DwgExtractorManual
|
||||
/// </summary>
|
||||
private async void BtnDwgHeightSort_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
// 시각화 데이터 초기화
|
||||
ClearVisualizationData();
|
||||
LogMessage("🧹 시각화 데이터 초기화 완료");
|
||||
|
||||
try
|
||||
{
|
||||
// 경로 검증
|
||||
@@ -2057,8 +2154,8 @@ namespace DwgExtractorManual
|
||||
|
||||
if (allDwgFiles.Count > 0)
|
||||
{
|
||||
// 단일 Excel 파일에 모든 DWG 파일 처리
|
||||
LogMessage("📏 단일 Height 정렬 Excel 파일 생성 중...");
|
||||
// Height 정렬 Excel 파일 생성 (Note 데이터 포함)
|
||||
LogMessage("📏 Height 정렬 Excel 파일 생성 중 (Note 표 데이터 포함)...");
|
||||
await ProcessAllFilesDwgHeightSort(allDwgFiles, resultBaseFolder);
|
||||
LogMessage("✅ Height 정렬 Excel 파일 생성 완료");
|
||||
}
|
||||
@@ -2109,7 +2206,91 @@ namespace DwgExtractorManual
|
||||
string savePath = Path.Combine(resultFolder, $"{timestamp}_AllDWG_HeightSorted.xlsx");
|
||||
|
||||
exportExcel.ExportAllDwgToExcelHeightSorted(allDwgFiles, savePath);
|
||||
|
||||
|
||||
// 시각화 데이터 캐시 초기화
|
||||
MainWindow.ClearVisualizationData();
|
||||
LogMessage("[DEBUG] 시각화 데이터 캐시 초기화 완료.");
|
||||
|
||||
foreach (var (filePath, folderName) in allDwgFiles)
|
||||
{
|
||||
LogMessage($"[DEBUG] DWG 파일에서 Note 추출 시작: {Path.GetFileName(filePath)}");
|
||||
var noteExtractionResult = exportExcel.DwgExtractor.ExtractNotesFromDrawing(filePath);
|
||||
var noteEntities = noteExtractionResult.NoteEntities;
|
||||
LogMessage($"[DEBUG] 추출된 Note 엔티티 수: {noteEntities.Count}");
|
||||
LogMessage($"[DEBUG] 추출된 IntersectionPoints 수: {noteExtractionResult.IntersectionPoints.Count}");
|
||||
LogMessage($"[DEBUG] 추출된 DiagonalLines 수: {noteExtractionResult.DiagonalLines.Count}");
|
||||
LogMessage($"[DEBUG] 추출된 TableSegments 수: {noteExtractionResult.TableSegments.Count}");
|
||||
|
||||
if (noteEntities.Any())
|
||||
{
|
||||
// 테이블이 있는 Note만 가시화 데이터 생성 (최소 4개 셀 이상)
|
||||
var notesWithTables = noteEntities.Where(ne =>
|
||||
ne.Type == "Note" &&
|
||||
ne.Cells != null &&
|
||||
ne.Cells.Count >= 4 && // 최소 4개 셀이 있어야 테이블로 인정
|
||||
ne.TableSegments != null &&
|
||||
ne.TableSegments.Count >= 4).ToList(); // 최소 4개 선분이 있어야 함
|
||||
|
||||
LogMessage($"[DEBUG] 테이블이 있는 Note: {notesWithTables.Count}개");
|
||||
|
||||
foreach (var noteWithTable in notesWithTables)
|
||||
{
|
||||
var visualizationData = new TableCellVisualizationData
|
||||
{
|
||||
FileName = $"{Path.GetFileName(filePath)} - {noteWithTable.Text}",
|
||||
NoteText = noteWithTable.Text,
|
||||
NoteBounds = (
|
||||
noteWithTable.Cells.Min(c => c.MinPoint.X),
|
||||
noteWithTable.Cells.Min(c => c.MinPoint.Y),
|
||||
noteWithTable.Cells.Max(c => c.MaxPoint.X),
|
||||
noteWithTable.Cells.Max(c => c.MaxPoint.Y)
|
||||
),
|
||||
Cells = noteWithTable.Cells.Select(tc => new CellBounds
|
||||
{
|
||||
MinX = tc.MinPoint.X, MinY = tc.MinPoint.Y, MaxX = tc.MaxPoint.X, MaxY = tc.MaxPoint.Y,
|
||||
Row = tc.Row, Column = tc.Column, Text = tc.CellText
|
||||
}).ToList(),
|
||||
TableSegments = noteWithTable.TableSegments.Select(ts => new SegmentInfo
|
||||
{
|
||||
StartX = ts.StartX, StartY = ts.StartY,
|
||||
EndX = ts.EndX, EndY = ts.EndY,
|
||||
IsHorizontal = ts.IsHorizontal
|
||||
}).ToList(),
|
||||
IntersectionPoints = noteWithTable.IntersectionPoints,
|
||||
DiagonalLines = noteWithTable.DiagonalLines ?? new List<DiagonalLine>(),
|
||||
CellBoundaries = noteWithTable.CellBoundaries?.Select(cb => new CellBoundaryInfo
|
||||
{
|
||||
TopLeftX = cb.TopLeft.X, TopLeftY = cb.TopLeft.Y,
|
||||
TopRightX = cb.TopRight.X, TopRightY = cb.TopRight.Y,
|
||||
BottomLeftX = cb.BottomLeft.X, BottomLeftY = cb.BottomLeft.Y,
|
||||
BottomRightX = cb.BottomRight.X, BottomRightY = cb.BottomRight.Y,
|
||||
Label = cb.Label, Width = cb.Width, Height = cb.Height,
|
||||
CellText = cb.CellText ?? ""
|
||||
}).ToList() ?? new List<CellBoundaryInfo>(),
|
||||
TextEntities = noteEntities.Where(ne => ne.Type == "NoteContent").Select(ne => new TextInfo
|
||||
{
|
||||
X = ne.X, Y = ne.Y, Text = ne.Text,
|
||||
IsInTable = noteWithTable.Cells.Any(cell =>
|
||||
ne.X >= cell.MinPoint.X && ne.X <= cell.MaxPoint.X &&
|
||||
ne.Y >= cell.MinPoint.Y && ne.Y <= cell.MaxPoint.Y)
|
||||
}).ToList()
|
||||
};
|
||||
MainWindow.SaveVisualizationData(visualizationData);
|
||||
LogMessage($"[DEBUG] 테이블 Note 시각화 데이터 추가: {visualizationData.FileName} (셀: {visualizationData.Cells.Count}개)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage($"[DEBUG] Note 엔티티가 없어 시각화 데이터를 생성하지 않습니다: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage($"[DEBUG] 총 {allDwgFiles.Count}개 파일의 시각화 데이터 저장 완료.");
|
||||
|
||||
// 최종 시각화 데이터 확인
|
||||
var finalVisualizationData = MainWindow.GetVisualizationData();
|
||||
LogMessage($"[DEBUG] 최종 저장된 시각화 데이터: {finalVisualizationData.Count}개 항목");
|
||||
|
||||
LogMessage("✅ Height 정렬 Excel 파일 생성 완료");
|
||||
LogMessage($"📁 저장된 파일: {Path.GetFileName(savePath)}");
|
||||
}
|
||||
@@ -2388,5 +2569,298 @@ namespace DwgExtractorManual
|
||||
_sqlDatas?.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 추출 기능 테스트 메서드
|
||||
/// </summary>
|
||||
private async Task TestNoteExtraction()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage("🧪 === Note 추출 기능 테스트 시작 ===");
|
||||
|
||||
string sourceFolder = txtSourceFolder.Text;
|
||||
string resultFolder = txtResultFolder.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(sourceFolder) || !Directory.Exists(sourceFolder))
|
||||
{
|
||||
LogMessage("❌ 소스 폴더가 선택되지 않았거나 존재하지 않습니다.");
|
||||
UpdateStatus("소스 폴더를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(resultFolder))
|
||||
{
|
||||
LogMessage("❌ 결과 폴더가 선택되지 않았습니다.");
|
||||
UpdateStatus("결과 폴더를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 결과 폴더가 없으면 생성
|
||||
if (!Directory.Exists(resultFolder))
|
||||
{
|
||||
Directory.CreateDirectory(resultFolder);
|
||||
LogMessage($"📁 결과 폴더 생성: {resultFolder}");
|
||||
}
|
||||
|
||||
// DWG 파일 찾기
|
||||
var dwgFiles = Directory.GetFiles(sourceFolder, "*.dwg", SearchOption.AllDirectories);
|
||||
LogMessage($"📊 발견된 DWG 파일 수: {dwgFiles.Length}개");
|
||||
|
||||
if (dwgFiles.Length == 0)
|
||||
{
|
||||
LogMessage("⚠️ DWG 파일이 없습니다.");
|
||||
UpdateStatus("DWG 파일이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateStatus("🔧 Note 추출 중...");
|
||||
progressBar.Maximum = dwgFiles.Length;
|
||||
progressBar.Value = 0;
|
||||
|
||||
// Teigha 서비스 초기화
|
||||
LogMessage("🔧 Teigha 서비스 초기화 중...");
|
||||
TeighaServicesManager.Instance.AcquireServices();
|
||||
|
||||
// 간단한 빈 매핑 데이터로 FieldMapper 생성 (테스트용)
|
||||
var mappingData = new MappingTableData();
|
||||
var fieldMapper = new FieldMapper(mappingData);
|
||||
var extractor = new DwgDataExtractor(fieldMapper);
|
||||
var csvWriter = new CsvDataWriter();
|
||||
|
||||
int processedCount = 0;
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
|
||||
// 각 DWG 파일에 대해 Note 추출 테스트
|
||||
foreach (string dwgFile in dwgFiles.Take(3)) // 처음 3개 파일만 테스트
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(dwgFile);
|
||||
LogMessage($"🔍 [{processedCount + 1}/{Math.Min(3, dwgFiles.Length)}] Note 추출 중: {fileName}");
|
||||
|
||||
// Note 데이터 추출
|
||||
var noteExtractionResult = extractor.ExtractNotesFromDrawing(dwgFile);
|
||||
var noteEntities = noteExtractionResult.NoteEntities;
|
||||
|
||||
// 테이블이 있는 Note만 시각화 데이터 저장
|
||||
if (noteEntities.Any())
|
||||
{
|
||||
var notesWithTables = noteEntities.Where(ne =>
|
||||
ne.Type == "Note" &&
|
||||
ne.Cells != null &&
|
||||
ne.Cells.Count >= 4 && // 최소 4개 셀이 있어야 테이블로 인정
|
||||
ne.TableSegments != null &&
|
||||
ne.TableSegments.Count >= 4).ToList(); // 최소 4개 선분이 있어야 함
|
||||
|
||||
LogMessage($" 테이블이 있는 Note: {notesWithTables.Count}개");
|
||||
|
||||
foreach (var noteWithTable in notesWithTables)
|
||||
{
|
||||
var visualizationData = new TableCellVisualizationData
|
||||
{
|
||||
FileName = $"{Path.GetFileName(dwgFile)} - {noteWithTable.Text}",
|
||||
NoteText = noteWithTable.Text,
|
||||
NoteBounds = (
|
||||
noteWithTable.Cells.Min(c => c.MinPoint.X),
|
||||
noteWithTable.Cells.Min(c => c.MinPoint.Y),
|
||||
noteWithTable.Cells.Max(c => c.MaxPoint.X),
|
||||
noteWithTable.Cells.Max(c => c.MaxPoint.Y)
|
||||
),
|
||||
Cells = noteWithTable.Cells.Select(tc => new CellBounds
|
||||
{
|
||||
MinX = tc.MinPoint.X, MinY = tc.MinPoint.Y, MaxX = tc.MaxPoint.X, MaxY = tc.MaxPoint.Y,
|
||||
Row = tc.Row, Column = tc.Column, Text = tc.CellText
|
||||
}).ToList(),
|
||||
TableSegments = noteWithTable.TableSegments.Select(ts => new SegmentInfo
|
||||
{
|
||||
StartX = ts.StartX, StartY = ts.StartY,
|
||||
EndX = ts.EndX, EndY = ts.EndY,
|
||||
IsHorizontal = ts.IsHorizontal
|
||||
}).ToList(),
|
||||
IntersectionPoints = noteWithTable.IntersectionPoints,
|
||||
DiagonalLines = noteExtractionResult.DiagonalLines.Where(dl =>
|
||||
noteWithTable.Cells.Any(cell =>
|
||||
(dl.Item1.X >= cell.MinPoint.X && dl.Item1.X <= cell.MaxPoint.X &&
|
||||
dl.Item1.Y >= cell.MinPoint.Y && dl.Item1.Y <= cell.MaxPoint.Y) ||
|
||||
(dl.Item2.X >= cell.MinPoint.X && dl.Item2.X <= cell.MaxPoint.X &&
|
||||
dl.Item2.Y >= cell.MinPoint.Y && dl.Item2.Y <= cell.MaxPoint.Y))).Select(dl => new DiagonalLine
|
||||
{
|
||||
StartX = dl.Item1.X, StartY = dl.Item1.Y, EndX = dl.Item2.X, EndY = dl.Item2.Y, Label = dl.Item3
|
||||
}).ToList(),
|
||||
CellBoundaries = noteWithTable.CellBoundaries?.Select(cb => new CellBoundaryInfo
|
||||
{
|
||||
TopLeftX = cb.TopLeft.X, TopLeftY = cb.TopLeft.Y,
|
||||
TopRightX = cb.TopRight.X, TopRightY = cb.TopRight.Y,
|
||||
BottomLeftX = cb.BottomLeft.X, BottomLeftY = cb.BottomLeft.Y,
|
||||
BottomRightX = cb.BottomRight.X, BottomRightY = cb.BottomRight.Y,
|
||||
Label = cb.Label, Width = cb.Width, Height = cb.Height,
|
||||
CellText = cb.CellText ?? ""
|
||||
}).ToList() ?? new List<CellBoundaryInfo>(),
|
||||
TextEntities = noteEntities.Where(ne => ne.Type == "NoteContent").Select(ne => new TextInfo
|
||||
{
|
||||
X = ne.X, Y = ne.Y, Text = ne.Text,
|
||||
IsInTable = noteWithTable.Cells.Any(cell =>
|
||||
ne.X >= cell.MinPoint.X && ne.X <= cell.MaxPoint.X &&
|
||||
ne.Y >= cell.MinPoint.Y && ne.Y <= cell.MaxPoint.Y)
|
||||
}).ToList()
|
||||
};
|
||||
MainWindow.SaveVisualizationData(visualizationData);
|
||||
LogMessage($" ✅ 테이블 Note 시각화 데이터 저장: {visualizationData.FileName} (셀: {visualizationData.Cells.Count}개)");
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage($" 추출된 엔터티: {noteEntities.Count}개");
|
||||
|
||||
var noteCount = noteEntities.Count(ne => ne.Type == "Note");
|
||||
var contentCount = noteEntities.Count(ne => ne.Type == "NoteContent");
|
||||
var tableCount = noteEntities.Count(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv));
|
||||
|
||||
LogMessage($" - Note 헤더: {noteCount}개");
|
||||
LogMessage($" - Note 콘텐츠: {contentCount}개");
|
||||
LogMessage($" - 테이블 포함 Note: {tableCount}개");
|
||||
|
||||
// CSV 파일 생성
|
||||
if (noteEntities.Count > 0)
|
||||
{
|
||||
// Note 박스 텍스트 CSV
|
||||
var noteTextCsvPath = Path.Combine(resultFolder, $"{fileName}_note_texts.csv");
|
||||
csvWriter.WriteNoteBoxTextToCsv(noteEntities, noteTextCsvPath);
|
||||
LogMessage($" ✅ Note 텍스트 CSV 저장: {Path.GetFileName(noteTextCsvPath)}");
|
||||
|
||||
// Note 테이블 CSV
|
||||
var noteTableCsvPath = Path.Combine(resultFolder, $"{fileName}_note_tables.csv");
|
||||
csvWriter.WriteNoteTablesToCsv(noteEntities, noteTableCsvPath);
|
||||
LogMessage($" ✅ Note 테이블 CSV 저장: {Path.GetFileName(noteTableCsvPath)}");
|
||||
|
||||
// 통합 CSV
|
||||
var combinedCsvPath = Path.Combine(resultFolder, $"{fileName}_note_combined.csv");
|
||||
csvWriter.WriteNoteDataToCombinedCsv(noteEntities, combinedCsvPath);
|
||||
LogMessage($" ✅ 통합 CSV 저장: {Path.GetFileName(combinedCsvPath)}");
|
||||
|
||||
// 개별 테이블 CSV
|
||||
if (tableCount > 0)
|
||||
{
|
||||
var individualTablesDir = Path.Combine(resultFolder, $"{fileName}_individual_tables");
|
||||
csvWriter.WriteIndividualNoteTablesCsv(noteEntities, individualTablesDir);
|
||||
LogMessage($" ✅ 개별 테이블 CSV 저장: {Path.GetFileName(individualTablesDir)}");
|
||||
}
|
||||
|
||||
// 통계 CSV
|
||||
var statisticsCsvPath = Path.Combine(resultFolder, $"{fileName}_note_statistics.csv");
|
||||
csvWriter.WriteNoteStatisticsToCsv(noteEntities, statisticsCsvPath);
|
||||
LogMessage($" ✅ 통계 CSV 저장: {Path.GetFileName(statisticsCsvPath)}");
|
||||
|
||||
// 첫 번째 테이블이 있는 Note의 내용 출력 (디버깅용)
|
||||
var firstTableNote = noteEntities.FirstOrDefault(ne => ne.Type == "Note" && !string.IsNullOrEmpty(ne.TableCsv));
|
||||
if (firstTableNote != null)
|
||||
{
|
||||
LogMessage($" 📋 첫 번째 테이블 Note: '{firstTableNote.Text}'");
|
||||
var tableLines = firstTableNote.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
LogMessage($" 📋 테이블 행 수: {tableLines.Length}");
|
||||
for (int i = 0; i < Math.Min(3, tableLines.Length); i++)
|
||||
{
|
||||
var line = tableLines[i];
|
||||
if (line.Length > 60) line = line.Substring(0, 60) + "...";
|
||||
LogMessage($" 행 {i + 1}: {line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
successCount++;
|
||||
LogMessage($" ✅ 성공");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failureCount++;
|
||||
LogMessage($" ❌ 실패: {ex.Message}");
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
progressBar.Value = processedCount;
|
||||
|
||||
// UI 업데이트를 위한 지연
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
LogMessage($"🧪 === Note 추출 테스트 완료 ===");
|
||||
LogMessage($"📊 처리 결과: 성공 {successCount}개, 실패 {failureCount}개");
|
||||
LogMessage($"💾 결과 파일들이 저장되었습니다: {resultFolder}");
|
||||
|
||||
UpdateStatus($"Note 추출 테스트 완료: 성공 {successCount}개, 실패 {failureCount}개");
|
||||
|
||||
// 결과 폴더 열기
|
||||
if (successCount > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer.exe", resultFolder);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"❌ Note 추출 테스트 중 오류: {ex.Message}");
|
||||
UpdateStatus("Note 추출 테스트 중 오류 발생");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
TeighaServicesManager.Instance.ForceDisposeServices();
|
||||
LogMessage("🔄 Teigha 서비스 정리 완료");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"⚠️ Teigha 서비스 정리 중 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 빌드 시간을 상태바에 표시합니다.
|
||||
/// </summary>
|
||||
private void SetBuildTime()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 현재 실행 파일의 빌드 시간을 가져옵니다
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var buildDate = System.IO.File.GetLastWriteTime(assembly.Location);
|
||||
txtBuildTime.Text = $"빌드: {buildDate:yyyy-MM-dd HH:mm}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
txtBuildTime.Text = "빌드: 알 수 없음";
|
||||
LogMessage($"⚠️ 빌드 시간 조회 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차점 테스트 버튼 클릭 이벤트
|
||||
/// </summary>
|
||||
private void BtnTestIntersection_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage("🔬 교차점 생성 테스트 시작...");
|
||||
UpdateStatus("교차점 테스트 중...");
|
||||
|
||||
// 테스트 실행
|
||||
IntersectionTestDebugger.RunIntersectionTest();
|
||||
|
||||
LogMessage("✅ 교차점 테스트 완료 - Debug 창을 확인하세요");
|
||||
UpdateStatus("교차점 테스트 완료");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"❌ 교차점 테스트 중 오류: {ex.Message}");
|
||||
UpdateStatus("교차점 테스트 중 오류 발생");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
109
Views/TableCellVisualizationWindow.xaml
Normal file
109
Views/TableCellVisualizationWindow.xaml
Normal file
@@ -0,0 +1,109 @@
|
||||
<Window x:Class="DwgExtractorManual.Views.TableCellVisualizationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:DwgExtractorManual.Controls"
|
||||
Title="테이블 셀 시각화" Height="800" Width="1200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinHeight="600" MinWidth="800">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<Border Grid.Row="0" Background="#34495E" Padding="15">
|
||||
<StackPanel>
|
||||
<TextBlock Text="테이블 셀 시각화"
|
||||
FontSize="20" FontWeight="Bold"
|
||||
Foreground="White" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="추출된 테이블 셀들의 경계를 시각적으로 확인할 수 있습니다"
|
||||
FontSize="12" Foreground="LightGray"
|
||||
HorizontalAlignment="Center" Margin="0,5,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 메인 영역 -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="250"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 좌측 패널: 파일 목록 및 설정 -->
|
||||
<Border Grid.Column="0" Background="#ECF0F1" BorderBrush="#BDC3C7" BorderThickness="0,0,1,0">
|
||||
<StackPanel Margin="10">
|
||||
<TextBlock Text="파일 목록" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||
|
||||
<ListBox x:Name="lstFiles" Height="200"
|
||||
SelectionChanged="LstFiles_SelectionChanged"
|
||||
Background="White" BorderBrush="#BDC3C7">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding FileName}" FontWeight="Bold" FontSize="12"/>
|
||||
<TextBlock Text="{Binding NoteText}" FontSize="10" Foreground="Gray" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock FontSize="10" Foreground="DarkBlue">
|
||||
<Run Text="셀: "/>
|
||||
<Run Text="{Binding Path=Cells.Count, Mode=OneWay}"/>
|
||||
<Run Text="개"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Separator Margin="0,10"/>
|
||||
|
||||
<TextBlock Text="표시 옵션" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||
|
||||
<CheckBox x:Name="chkShowCells" Content="셀 경계 표시 (기존)" IsChecked="False"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowCellBoundaries" Content="정확한 셀 경계 표시" IsChecked="True" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowSegments" Content="선분 표시" IsChecked="True" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowTexts" Content="텍스트 표시" IsChecked="True" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowIntersections" Content="교차점 표시" IsChecked="True" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowDiagonals" Content="셀 대각선 표시" IsChecked="True" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
<CheckBox x:Name="chkShowNoteBounds" Content="Note 경계 표시" IsChecked="False" Margin="0,5,0,0"
|
||||
Checked="RefreshVisualization" Unchecked="RefreshVisualization"/>
|
||||
|
||||
<Separator Margin="0,10"/>
|
||||
|
||||
<TextBlock Text="확대/축소" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||
<Button x:Name="btnZoomFit" Content="초기화 (마우스 우클릭)" Click="BtnZoomFit_Click" Margin="0,2"/>
|
||||
|
||||
<Separator Margin="0,10"/>
|
||||
|
||||
<TextBlock x:Name="txtInfo" Text="파일을 선택하세요" FontSize="11"
|
||||
Foreground="DarkGray" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 우측 패널: 시각화 영역 -->
|
||||
<Border Grid.Column="1" Background="White">
|
||||
<controls:ZoomBorder x:Name="svViewer" ClipToBounds="True">
|
||||
<Canvas x:Name="cnvVisualization" Background="White"
|
||||
Width="800" Height="600"
|
||||
MouseMove="CnvVisualization_MouseMove"/>
|
||||
</controls:ZoomBorder>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 상태바 -->
|
||||
<StatusBar Grid.Row="2" Background="#95A5A6">
|
||||
<StatusBarItem>
|
||||
<TextBlock x:Name="txtStatus" Text="준비됨"/>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock x:Name="txtMousePos" Text="마우스: (0, 0)"/>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
493
Views/TableCellVisualizationWindow.xaml.cs
Normal file
493
Views/TableCellVisualizationWindow.xaml.cs
Normal file
@@ -0,0 +1,493 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using DwgExtractorManual.Models;
|
||||
using Brushes = System.Windows.Media.Brushes;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using Point = System.Windows.Point;
|
||||
using Rectangle = System.Windows.Shapes.Rectangle;
|
||||
|
||||
namespace DwgExtractorManual.Views
|
||||
{
|
||||
public partial class TableCellVisualizationWindow : Window
|
||||
{
|
||||
private List<TableCellVisualizationData> _visualizationData;
|
||||
private TableCellVisualizationData? _currentData;
|
||||
private double _scale = 1.0;
|
||||
private const double MARGIN = 50;
|
||||
|
||||
public TableCellVisualizationWindow(List<TableCellVisualizationData> visualizationData)
|
||||
{
|
||||
InitializeComponent();
|
||||
_visualizationData = visualizationData;
|
||||
LoadFileList();
|
||||
}
|
||||
|
||||
private void LoadFileList()
|
||||
{
|
||||
lstFiles.ItemsSource = _visualizationData;
|
||||
if (_visualizationData.Count > 0)
|
||||
{
|
||||
lstFiles.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void LstFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (lstFiles.SelectedItem is TableCellVisualizationData selectedData)
|
||||
{
|
||||
_currentData = selectedData;
|
||||
RefreshVisualization();
|
||||
UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfo()
|
||||
{
|
||||
if (_currentData == null)
|
||||
{
|
||||
txtInfo.Text = "파일을 선택하세요";
|
||||
txtStatus.Text = "준비됨";
|
||||
return;
|
||||
}
|
||||
|
||||
txtInfo.Text = $"파일: {_currentData.FileName}\n" +
|
||||
$"Note: {_currentData.NoteText}\n" +
|
||||
$"셀 수: {_currentData.Cells.Count}\n" +
|
||||
$"선분 수: {_currentData.TableSegments.Count}\n" +
|
||||
$"텍스트 수: {_currentData.TextEntities.Count}\n" +
|
||||
$"교차점 수: {_currentData.IntersectionPoints.Count}\n" +
|
||||
$"대각선 수: {_currentData.DiagonalLines.Count}";
|
||||
|
||||
txtStatus.Text = $"{_currentData.FileName} - 셀 {_currentData.Cells.Count}개";
|
||||
}
|
||||
|
||||
private void RefreshVisualization()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
|
||||
cnvVisualization.Children.Clear();
|
||||
|
||||
// 좌표계 변환 계산
|
||||
var bounds = CalculateBounds();
|
||||
if (bounds == null) return;
|
||||
|
||||
var (minX, minY, maxX, maxY) = bounds.Value;
|
||||
var dataWidth = maxX - minX;
|
||||
var dataHeight = maxY - minY;
|
||||
|
||||
// 캔버스 크기 설정 (여백 포함)
|
||||
var canvasWidth = cnvVisualization.Width;
|
||||
var canvasHeight = cnvVisualization.Height;
|
||||
|
||||
// 스케일 계산 (데이터가 캔버스에 맞도록)
|
||||
var scaleX = (canvasWidth - 2 * MARGIN) / dataWidth;
|
||||
var scaleY = (canvasHeight - 2 * MARGIN) / dataHeight;
|
||||
_scale = Math.Min(scaleX, scaleY) * 0.9; // 여유분 10%
|
||||
|
||||
// Note 경계 표시
|
||||
if (chkShowNoteBounds.IsChecked == true)
|
||||
{
|
||||
DrawNoteBounds(minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
// 선분 표시
|
||||
if (chkShowSegments.IsChecked == true)
|
||||
{
|
||||
DrawSegments();
|
||||
}
|
||||
|
||||
// 셀 경계 표시 (기존)
|
||||
if (chkShowCells.IsChecked == true)
|
||||
{
|
||||
DrawCells();
|
||||
}
|
||||
|
||||
// 정확한 셀 경계 표시 (새로운)
|
||||
if (chkShowCellBoundaries.IsChecked == true)
|
||||
{
|
||||
DrawCellBoundaries();
|
||||
}
|
||||
|
||||
// 텍스트 표시
|
||||
if (chkShowTexts.IsChecked == true)
|
||||
{
|
||||
DrawTexts();
|
||||
}
|
||||
|
||||
// 교차점 표시
|
||||
if (chkShowIntersections.IsChecked == true)
|
||||
{
|
||||
DrawIntersections();
|
||||
}
|
||||
|
||||
// 대각선 표시 (셀 디버깅용)
|
||||
if (chkShowDiagonals.IsChecked == true)
|
||||
{
|
||||
DrawDiagonals();
|
||||
}
|
||||
}
|
||||
|
||||
private (double minX, double minY, double maxX, double maxY)? CalculateBounds()
|
||||
{
|
||||
if (_currentData == null || _currentData.Cells.Count == 0) return null;
|
||||
|
||||
var minX = _currentData.Cells.Min(c => c.MinX);
|
||||
var minY = _currentData.Cells.Min(c => c.MinY);
|
||||
var maxX = _currentData.Cells.Max(c => c.MaxX);
|
||||
var maxY = _currentData.Cells.Max(c => c.MaxY);
|
||||
|
||||
// 선분도 고려
|
||||
if (_currentData.TableSegments.Count > 0)
|
||||
{
|
||||
minX = Math.Min(minX, _currentData.TableSegments.Min(s => Math.Min(s.StartX, s.EndX)));
|
||||
minY = Math.Min(minY, _currentData.TableSegments.Min(s => Math.Min(s.StartY, s.EndY)));
|
||||
maxX = Math.Max(maxX, _currentData.TableSegments.Max(s => Math.Max(s.StartX, s.EndX)));
|
||||
maxY = Math.Max(maxY, _currentData.TableSegments.Max(s => Math.Max(s.StartY, s.EndY)));
|
||||
}
|
||||
|
||||
// 교차점도 고려
|
||||
if (_currentData.IntersectionPoints.Count > 0)
|
||||
{
|
||||
minX = Math.Min(minX, _currentData.IntersectionPoints.Min(i => i.X));
|
||||
minY = Math.Min(minY, _currentData.IntersectionPoints.Min(i => i.Y));
|
||||
maxX = Math.Max(maxX, _currentData.IntersectionPoints.Max(i => i.X));
|
||||
maxY = Math.Max(maxY, _currentData.IntersectionPoints.Max(i => i.Y));
|
||||
}
|
||||
|
||||
// 대각선도 고려
|
||||
if (_currentData.DiagonalLines.Count > 0)
|
||||
{
|
||||
minX = Math.Min(minX, _currentData.DiagonalLines.Min(d => Math.Min(d.StartX, d.EndX)));
|
||||
minY = Math.Min(minY, _currentData.DiagonalLines.Min(d => Math.Min(d.StartY, d.EndY)));
|
||||
maxX = Math.Max(maxX, _currentData.DiagonalLines.Max(d => Math.Max(d.StartX, d.EndX)));
|
||||
maxY = Math.Max(maxY, _currentData.DiagonalLines.Max(d => Math.Max(d.StartY, d.EndY)));
|
||||
}
|
||||
|
||||
// 정확한 셀 경계도 고려
|
||||
if (_currentData.CellBoundaries != null && _currentData.CellBoundaries.Count > 0)
|
||||
{
|
||||
var allCellX = _currentData.CellBoundaries.SelectMany(cb => new[] { cb.TopLeftX, cb.TopRightX, cb.BottomLeftX, cb.BottomRightX });
|
||||
var allCellY = _currentData.CellBoundaries.SelectMany(cb => new[] { cb.TopLeftY, cb.TopRightY, cb.BottomLeftY, cb.BottomRightY });
|
||||
|
||||
minX = Math.Min(minX, allCellX.Min());
|
||||
minY = Math.Min(minY, allCellY.Min());
|
||||
maxX = Math.Max(maxX, allCellX.Max());
|
||||
maxY = Math.Max(maxY, allCellY.Max());
|
||||
}
|
||||
|
||||
return (minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
private Point TransformPoint(double x, double y)
|
||||
{
|
||||
var bounds = CalculateBounds();
|
||||
if (bounds == null) return new Point(0, 0);
|
||||
|
||||
var (minX, minY, maxX, maxY) = bounds.Value;
|
||||
|
||||
// CAD 좌표계 -> WPF 좌표계 변환 (Y축 뒤집기)
|
||||
var transformedX = (x - minX) * _scale + MARGIN;
|
||||
var transformedY = cnvVisualization.Height - ((y - minY) * _scale + MARGIN);
|
||||
|
||||
return new Point(transformedX, transformedY);
|
||||
}
|
||||
|
||||
private void DrawNoteBounds(double minX, double minY, double maxX, double maxY)
|
||||
{
|
||||
var topLeft = TransformPoint(minX, maxY);
|
||||
var bottomRight = TransformPoint(maxX, minY);
|
||||
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Width = bottomRight.X - topLeft.X,
|
||||
Height = bottomRight.Y - topLeft.Y,
|
||||
Stroke = Brushes.Red,
|
||||
StrokeThickness = 2,
|
||||
StrokeDashArray = new DoubleCollection { 5, 5 },
|
||||
Fill = null
|
||||
};
|
||||
|
||||
Canvas.SetLeft(rect, topLeft.X);
|
||||
Canvas.SetTop(rect, topLeft.Y);
|
||||
cnvVisualization.Children.Add(rect);
|
||||
}
|
||||
|
||||
private void DrawSegments()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
foreach (var segment in _currentData.TableSegments)
|
||||
{
|
||||
var startPoint = TransformPoint(segment.StartX, segment.StartY);
|
||||
var endPoint = TransformPoint(segment.EndX, segment.EndY);
|
||||
|
||||
var line = new Line
|
||||
{
|
||||
X1 = startPoint.X,
|
||||
Y1 = startPoint.Y,
|
||||
X2 = endPoint.X,
|
||||
Y2 = endPoint.Y,
|
||||
Stroke = segment.IsHorizontal ? Brushes.Blue : Brushes.Green,
|
||||
StrokeThickness = 1
|
||||
};
|
||||
|
||||
cnvVisualization.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCells()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
var colors = new[] { Brushes.Red, Brushes.Blue, Brushes.Green, Brushes.Purple, Brushes.Orange };
|
||||
|
||||
for (int i = 0; i < _currentData.Cells.Count; i++)
|
||||
{
|
||||
var cell = _currentData.Cells[i];
|
||||
var topLeft = TransformPoint(cell.MinX, cell.MaxY);
|
||||
var bottomRight = TransformPoint(cell.MaxX, cell.MinY);
|
||||
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Width = bottomRight.X - topLeft.X,
|
||||
Height = bottomRight.Y - topLeft.Y,
|
||||
Stroke = colors[i % colors.Length],
|
||||
StrokeThickness = 2,
|
||||
Fill = null
|
||||
};
|
||||
|
||||
Canvas.SetLeft(rect, topLeft.X);
|
||||
Canvas.SetTop(rect, topLeft.Y);
|
||||
cnvVisualization.Children.Add(rect);
|
||||
|
||||
// 셀 번호 표시
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = $"R{cell.Row}C{cell.Column}", // 이미 1-based 인덱싱 적용됨
|
||||
FontSize = 8, // 폰트 크기 조정
|
||||
Foreground = colors[i % colors.Length],
|
||||
FontWeight = FontWeights.Bold
|
||||
};
|
||||
|
||||
Canvas.SetLeft(label, topLeft.X + 2); // 좌상단에 위치 + 약간의 패딩
|
||||
Canvas.SetTop(label, topLeft.Y + 2); // 좌상단에 위치 + 약간의 패딩
|
||||
cnvVisualization.Children.Add(label);
|
||||
|
||||
// 셀 텍스트 표시
|
||||
if (!string.IsNullOrEmpty(cell.Text))
|
||||
{
|
||||
var textLabel = new TextBlock
|
||||
{
|
||||
Text = cell.Text,
|
||||
FontSize = 8,
|
||||
Foreground = Brushes.Black,
|
||||
Background = Brushes.LightYellow
|
||||
};
|
||||
|
||||
// 셀 텍스트는 셀 중앙에 표시
|
||||
var centerPoint = TransformPoint(cell.CenterX, cell.CenterY);
|
||||
Canvas.SetLeft(textLabel, centerPoint.X - (textLabel.ActualWidth / 2)); // 텍스트 중앙 정렬
|
||||
Canvas.SetTop(textLabel, centerPoint.Y - (textLabel.ActualHeight / 2)); // 텍스트 중앙 정렬
|
||||
cnvVisualization.Children.Add(textLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCellBoundaries()
|
||||
{
|
||||
if (_currentData == null || _currentData.CellBoundaries == null) return;
|
||||
|
||||
for (int i = 0; i < _currentData.CellBoundaries.Count; i++)
|
||||
{
|
||||
var cellBoundary = _currentData.CellBoundaries[i];
|
||||
|
||||
// 4개 모서리 좌표 변환
|
||||
var topLeft = TransformPoint(cellBoundary.TopLeftX, cellBoundary.TopLeftY);
|
||||
var topRight = TransformPoint(cellBoundary.TopRightX, cellBoundary.TopRightY);
|
||||
var bottomLeft = TransformPoint(cellBoundary.BottomLeftX, cellBoundary.BottomLeftY);
|
||||
var bottomRight = TransformPoint(cellBoundary.BottomRightX, cellBoundary.BottomRightY);
|
||||
|
||||
// 셀 경계를 Path로 그리기 (정확한 4개 모서리 연결)
|
||||
var pathGeometry = new PathGeometry();
|
||||
var pathFigure = new PathFigure();
|
||||
pathFigure.StartPoint = topLeft;
|
||||
pathFigure.Segments.Add(new LineSegment(topRight, true));
|
||||
pathFigure.Segments.Add(new LineSegment(bottomRight, true));
|
||||
pathFigure.Segments.Add(new LineSegment(bottomLeft, true));
|
||||
pathFigure.IsClosed = true;
|
||||
pathGeometry.Figures.Add(pathFigure);
|
||||
|
||||
var path = new Path
|
||||
{
|
||||
Data = pathGeometry,
|
||||
Stroke = Brushes.DarkBlue,
|
||||
StrokeThickness = 3,
|
||||
Fill = null
|
||||
};
|
||||
|
||||
cnvVisualization.Children.Add(path);
|
||||
|
||||
// 라벨 표시 (셀 중앙)
|
||||
if (!string.IsNullOrEmpty(cellBoundary.Label))
|
||||
{
|
||||
var centerX = (topLeft.X + bottomRight.X) / 2;
|
||||
var centerY = (topLeft.Y + bottomRight.Y) / 2;
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = cellBoundary.Label,
|
||||
FontSize = 10,
|
||||
Foreground = Brushes.DarkBlue,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Background = Brushes.LightCyan
|
||||
};
|
||||
|
||||
Canvas.SetLeft(label, centerX - 15); // 대략적인 중앙 정렬
|
||||
Canvas.SetTop(label, centerY - 6);
|
||||
cnvVisualization.Children.Add(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTexts()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
foreach (var text in _currentData.TextEntities)
|
||||
{
|
||||
var point = TransformPoint(text.X, text.Y);
|
||||
|
||||
var textBlock = new TextBlock
|
||||
{
|
||||
Text = text.Text,
|
||||
FontSize = 9,
|
||||
Foreground = text.IsInTable ? Brushes.DarkBlue : Brushes.Gray
|
||||
};
|
||||
|
||||
Canvas.SetLeft(textBlock, point.X);
|
||||
Canvas.SetTop(textBlock, point.Y);
|
||||
cnvVisualization.Children.Add(textBlock);
|
||||
|
||||
// 텍스트 위치 점 표시
|
||||
var dot = new Ellipse
|
||||
{
|
||||
Width = 3,
|
||||
Height = 3,
|
||||
Fill = text.IsInTable ? Brushes.Blue : Brushes.Gray
|
||||
};
|
||||
|
||||
Canvas.SetLeft(dot, point.X - 1.5);
|
||||
Canvas.SetTop(dot, point.Y - 1.5);
|
||||
cnvVisualization.Children.Add(dot);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawIntersections()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
foreach (var intersection in _currentData.IntersectionPoints)
|
||||
{
|
||||
var point = TransformPoint(intersection.X, intersection.Y);
|
||||
|
||||
// 교차점 원 표시
|
||||
var circle = new Ellipse
|
||||
{
|
||||
Width = 8,
|
||||
Height = 8,
|
||||
Fill = GetIntersectionColor(intersection),
|
||||
Stroke = Brushes.Black,
|
||||
StrokeThickness = 1
|
||||
};
|
||||
|
||||
Canvas.SetLeft(circle, point.X - 4);
|
||||
Canvas.SetTop(circle, point.Y - 4);
|
||||
cnvVisualization.Children.Add(circle);
|
||||
|
||||
// 교차점 타입 숫자 표시 (우측에)
|
||||
var numberLabel = new TextBlock
|
||||
{
|
||||
Text = intersection.DirectionBits.ToString(),
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Foreground = Brushes.Black,
|
||||
Background = Brushes.Yellow,
|
||||
Padding = new Thickness(2)
|
||||
};
|
||||
|
||||
Canvas.SetLeft(numberLabel, point.X + 8); // 교차점 우측에 표시
|
||||
Canvas.SetTop(numberLabel, point.Y - 6); // 약간 위쪽으로 조정
|
||||
cnvVisualization.Children.Add(numberLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDiagonals()
|
||||
{
|
||||
if (_currentData == null) return;
|
||||
foreach (var diagonal in _currentData.DiagonalLines)
|
||||
{
|
||||
var startPoint = TransformPoint(diagonal.StartX, diagonal.StartY);
|
||||
var endPoint = TransformPoint(diagonal.EndX, diagonal.EndY);
|
||||
|
||||
// 대각선 표시
|
||||
var line = new Line
|
||||
{
|
||||
X1 = startPoint.X,
|
||||
Y1 = startPoint.Y,
|
||||
X2 = endPoint.X,
|
||||
Y2 = endPoint.Y,
|
||||
Stroke = Brushes.Green,
|
||||
StrokeThickness = 2,
|
||||
StrokeDashArray = new DoubleCollection { 5, 3 } // 점선으로 표시
|
||||
};
|
||||
cnvVisualization.Children.Add(line);
|
||||
|
||||
// 대각선 중앙에 라벨 표시
|
||||
if (!string.IsNullOrEmpty(diagonal.Label))
|
||||
{
|
||||
var centerX = (startPoint.X + endPoint.X) / 2;
|
||||
var centerY = (startPoint.Y + endPoint.Y) / 2;
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = diagonal.Label,
|
||||
FontSize = 9,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Foreground = Brushes.Green,
|
||||
Background = Brushes.White
|
||||
};
|
||||
Canvas.SetLeft(label, centerX - 20); // 중앙에서 약간 왼쪽으로
|
||||
Canvas.SetTop(label, centerY - 10); // 중앙에서 약간 위쪽으로
|
||||
cnvVisualization.Children.Add(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private System.Windows.Media.Brush GetIntersectionColor(IntersectionInfo intersection)
|
||||
{
|
||||
if (intersection.IsTopLeft && intersection.IsBottomRight)
|
||||
return Brushes.Purple; // 둘 다 가능
|
||||
else if (intersection.IsTopLeft)
|
||||
return Brushes.Green; // topLeft 후보
|
||||
else if (intersection.IsBottomRight)
|
||||
return Brushes.Blue; // bottomRight 후보
|
||||
else
|
||||
return Brushes.Red; // 기타
|
||||
}
|
||||
|
||||
private void RefreshVisualization(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RefreshVisualization();
|
||||
}
|
||||
|
||||
private void BtnZoomFit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
svViewer.Reset();
|
||||
}
|
||||
|
||||
private void CnvVisualization_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
var pos = e.GetPosition(cnvVisualization);
|
||||
txtMousePos.Text = $"마우스: ({pos.X:F0}, {pos.Y:F0})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,37 +8,37 @@ using System.Text.Json.Serialization;
|
||||
public class MappingTableData
|
||||
{
|
||||
[JsonPropertyName("mapping_table")]
|
||||
public MappingTable MappingTable { get; set; }
|
||||
public MappingTable MappingTable { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class MappingTable
|
||||
{
|
||||
[JsonPropertyName("ailabel_to_systems")]
|
||||
public Dictionary<string, SystemFields> AilabelToSystems { get; set; }
|
||||
public Dictionary<string, SystemFields> AilabelToSystems { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("system_mappings")]
|
||||
public SystemMappings SystemMappings { get; set; }
|
||||
public SystemMappings SystemMappings { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class SystemFields
|
||||
{
|
||||
[JsonPropertyName("molit")]
|
||||
public string Molit { get; set; }
|
||||
public string Molit { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("expressway")]
|
||||
public string Expressway { get; set; }
|
||||
public string Expressway { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("railway")]
|
||||
public string Railway { get; set; }
|
||||
public string Railway { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("docaikey")]
|
||||
public string DocAiKey { get; set; }
|
||||
public string DocAiKey { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class SystemMappings
|
||||
{
|
||||
[JsonPropertyName("expressway_to_transportation")]
|
||||
public Dictionary<string, string> ExpresswayToTransportation { get; set; }
|
||||
public Dictionary<string, string> ExpresswayToTransportation { get; set; } = default!;
|
||||
}
|
||||
|
||||
// 필드 매퍼 클래스
|
||||
@@ -73,7 +73,7 @@ public class FieldMapper
|
||||
|
||||
var mappingData = JsonSerializer.Deserialize<MappingTableData>(jsonContent, options);
|
||||
Console.WriteLine($"[DEBUG] 매핑 테이블 로드 성공: {mappingData?.MappingTable?.AilabelToSystems?.Count ?? 0}개 항목");
|
||||
return new FieldMapper(mappingData);
|
||||
return new FieldMapper(mappingData!);
|
||||
}
|
||||
catch (JsonException jsonEx)
|
||||
{
|
||||
@@ -213,7 +213,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// AI 라벨을 고속도로공사 필드명으로 변환
|
||||
/// </summary>
|
||||
public string AilabelToExpressway(string ailabel)
|
||||
public string? AilabelToExpressway(string ailabel)
|
||||
{
|
||||
if (_mappingData.MappingTable.AilabelToSystems.TryGetValue(ailabel, out var systemFields))
|
||||
{
|
||||
@@ -225,7 +225,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// AI 라벨을 DocAiKey 값으로 변환
|
||||
/// </summary>
|
||||
public string AilabelToDocAiKey(string ailabel)
|
||||
public string? AilabelToDocAiKey(string ailabel)
|
||||
{
|
||||
if (_mappingData.MappingTable.AilabelToSystems.TryGetValue(ailabel, out var systemFields))
|
||||
{
|
||||
@@ -237,7 +237,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// 고속도로공사 필드명을 교통부 필드명으로 변환
|
||||
/// </summary>
|
||||
public string ExpresswayToTransportation(string expresswayField)
|
||||
public string? ExpresswayToTransportation(string expresswayField)
|
||||
{
|
||||
if (_mappingData.MappingTable.SystemMappings.ExpresswayToTransportation.TryGetValue(expresswayField, out var transportationField))
|
||||
{
|
||||
@@ -249,7 +249,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// DocAiKey 값으로부터 해당하는 AI 라벨을 반환
|
||||
/// </summary>
|
||||
public string DocAiKeyToAilabel(string docAiKey)
|
||||
public string? DocAiKeyToAilabel(string docAiKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(docAiKey))
|
||||
{
|
||||
@@ -269,7 +269,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// Expressway 필드값으로부터 해당하는 AI 라벨을 반환
|
||||
/// </summary>
|
||||
public string ExpresswayToAilabel(string expresswayField)
|
||||
public string? ExpresswayToAilabel(string expresswayField)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expresswayField))
|
||||
{
|
||||
@@ -289,7 +289,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// AI 라벨 → 고속도로공사 → 교통부 순서로 변환
|
||||
/// </summary>
|
||||
public string AilabelToTransportationViaExpressway(string ailabel)
|
||||
public string? AilabelToTransportationViaExpressway(string ailabel)
|
||||
{
|
||||
var expresswayField = AilabelToExpressway(ailabel);
|
||||
if (!string.IsNullOrEmpty(expresswayField))
|
||||
@@ -302,7 +302,7 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// AI 라벨에 해당하는 모든 시스템의 필드명을 반환
|
||||
/// </summary>
|
||||
public SystemFields GetAllSystemFields(string ailabel)
|
||||
public SystemFields? GetAllSystemFields(string ailabel)
|
||||
{
|
||||
if (_mappingData.MappingTable.AilabelToSystems.TryGetValue(ailabel, out var systemFields))
|
||||
{
|
||||
@@ -314,9 +314,9 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// 여러 AI 라벨을 한번에 고속도로공사 필드명으로 변환
|
||||
/// </summary>
|
||||
public Dictionary<string, string> BatchConvertAilabelToExpressway(IEnumerable<string> ailabels)
|
||||
public Dictionary<string, string?> BatchConvertAilabelToExpressway(IEnumerable<string> ailabels)
|
||||
{
|
||||
var results = new Dictionary<string, string>();
|
||||
var results = new Dictionary<string, string?>();
|
||||
foreach (var label in ailabels)
|
||||
{
|
||||
results[label] = AilabelToExpressway(label);
|
||||
@@ -327,9 +327,9 @@ public class FieldMapper
|
||||
/// <summary>
|
||||
/// 여러 고속도로공사 필드를 한번에 교통부 필드명으로 변환
|
||||
/// </summary>
|
||||
public Dictionary<string, string> BatchConvertExpresswayToTransportation(IEnumerable<string> expresswayFields)
|
||||
public Dictionary<string, string?> BatchConvertExpresswayToTransportation(IEnumerable<string> expresswayFields)
|
||||
{
|
||||
var results = new Dictionary<string, string>();
|
||||
var results = new Dictionary<string, string?>();
|
||||
foreach (var field in expresswayFields)
|
||||
{
|
||||
results[field] = ExpresswayToTransportation(field);
|
||||
|
||||
Reference in New Issue
Block a user