feat: Add AGV Map Editor and Simulator tools

- Add AGVMapEditor: Visual map editing with drag-and-drop node placement
  * RFID mapping separation (physical ID ↔ logical node mapping)
  * A* pathfinding algorithm with AGV directional constraints
  * JSON map data persistence with structured format
  * Interactive map canvas with zoom/pan functionality

- Add AGVSimulator: Real-time AGV movement simulation
  * Virtual AGV with state machine (Idle, Moving, Rotating, Docking, Charging, Error)
  * Path execution and visualization from calculated routes
  * Real-time position tracking and battery simulation
  * Integration with map editor data format

- Update solution structure and build configuration
- Add comprehensive documentation in CLAUDE.md
- Implement AGV-specific constraints (forward/backward docking, rotation limits)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ChiKyun Kim
2025-09-10 17:39:23 +09:00
parent 27dcc6befa
commit 7567602479
33 changed files with 6304 additions and 2 deletions

View File

@@ -0,0 +1,64 @@
namespace AGVSimulator.Forms
{
partial class SimulatorForm
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
// 시뮬레이션 정지
if (_simulationTimer != null)
{
_simulationTimer.Stop();
_simulationTimer.Dispose();
}
// AGV 정리
if (_agvList != null)
{
foreach (var agv in _agvList)
{
agv.Dispose();
}
}
base.Dispose(disposing);
}
#region Windows Form
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// SimulatorForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1200, 800);
this.Name = "SimulatorForm";
this.Text = "AGV 시뮬레이터";
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.ResumeLayout(false);
}
#endregion
}
}

View File

@@ -0,0 +1,688 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using AGVMapEditor.Models;
using AGVSimulator.Controls;
using AGVSimulator.Models;
using Newtonsoft.Json;
namespace AGVSimulator.Forms
{
/// <summary>
/// AGV 시뮬레이터 메인 폼
/// </summary>
public partial class SimulatorForm : Form
{
#region Fields
private SimulatorCanvas _simulatorCanvas;
private List<MapNode> _mapNodes;
private List<RfidMapping> _rfidMappings;
private NodeResolver _nodeResolver;
private PathCalculator _pathCalculator;
private List<VirtualAGV> _agvList;
private SimulationState _simulationState;
private Timer _simulationTimer;
// UI Controls
private MenuStrip _menuStrip;
private ToolStrip _toolStrip;
private StatusStrip _statusStrip;
private Panel _controlPanel;
private Panel _canvasPanel;
// Control Panel Controls
private GroupBox _agvControlGroup;
private ComboBox _agvListCombo;
private Button _addAgvButton;
private Button _removeAgvButton;
private Button _startSimulationButton;
private Button _stopSimulationButton;
private Button _resetButton;
private GroupBox _pathGroup;
private ComboBox _startNodeCombo;
private ComboBox _targetNodeCombo;
private Button _calculatePathButton;
private Button _startPathButton;
private Button _clearPathButton;
private GroupBox _viewGroup;
private Button _fitToMapButton;
private Button _resetZoomButton;
private GroupBox _statusGroup;
private Label _simulationStatusLabel;
private Label _agvCountLabel;
private Label _pathLengthLabel;
// Status Labels
private ToolStripStatusLabel _statusLabel;
private ToolStripStatusLabel _coordLabel;
#endregion
#region Properties
/// <summary>
/// 시뮬레이션 상태
/// </summary>
public SimulationState SimulationState => _simulationState;
#endregion
#region Constructor
public SimulatorForm()
{
InitializeComponent();
InitializeForm();
}
#endregion
#region Initialization
private void InitializeForm()
{
// 폼 설정
Text = "AGV 시뮬레이터";
Size = new Size(1200, 800);
StartPosition = FormStartPosition.CenterScreen;
// 데이터 초기화
_mapNodes = new List<MapNode>();
_rfidMappings = new List<RfidMapping>();
_agvList = new List<VirtualAGV>();
_simulationState = new SimulationState();
// UI 컨트롤 생성
CreateMenuStrip();
CreateToolStrip();
CreateStatusStrip();
CreateControlPanel();
CreateSimulatorCanvas();
// 레이아웃 설정
SetupLayout();
// 타이머 초기화
_simulationTimer = new Timer();
_simulationTimer.Interval = 100; // 100ms 간격
_simulationTimer.Tick += OnSimulationTimer_Tick;
// 초기 상태 설정
UpdateUI();
}
private void CreateMenuStrip()
{
_menuStrip = new MenuStrip();
// 파일 메뉴
var fileMenu = new ToolStripMenuItem("파일(&F)");
fileMenu.DropDownItems.Add(new ToolStripMenuItem("맵 열기(&O)...", null, OnOpenMap_Click) { ShortcutKeys = Keys.Control | Keys.O });
fileMenu.DropDownItems.Add(new ToolStripSeparator());
fileMenu.DropDownItems.Add(new ToolStripMenuItem("종료(&X)", null, OnExit_Click) { ShortcutKeys = Keys.Alt | Keys.F4 });
// 시뮬레이션 메뉴
var simMenu = new ToolStripMenuItem("시뮬레이션(&S)");
simMenu.DropDownItems.Add(new ToolStripMenuItem("시작(&S)", null, OnStartSimulation_Click) { ShortcutKeys = Keys.F5 });
simMenu.DropDownItems.Add(new ToolStripMenuItem("정지(&T)", null, OnStopSimulation_Click) { ShortcutKeys = Keys.F6 });
simMenu.DropDownItems.Add(new ToolStripMenuItem("초기화(&R)", null, OnReset_Click) { ShortcutKeys = Keys.F7 });
// 보기 메뉴
var viewMenu = new ToolStripMenuItem("보기(&V)");
viewMenu.DropDownItems.Add(new ToolStripMenuItem("맵 맞춤(&F)", null, OnFitToMap_Click) { ShortcutKeys = Keys.Control | Keys.F });
viewMenu.DropDownItems.Add(new ToolStripMenuItem("줌 초기화(&Z)", null, OnResetZoom_Click) { ShortcutKeys = Keys.Control | Keys.D0 });
// 도움말 메뉴
var helpMenu = new ToolStripMenuItem("도움말(&H)");
helpMenu.DropDownItems.Add(new ToolStripMenuItem("정보(&A)...", null, OnAbout_Click));
_menuStrip.Items.AddRange(new ToolStripItem[] { fileMenu, simMenu, viewMenu, helpMenu });
Controls.Add(_menuStrip);
MainMenuStrip = _menuStrip;
}
private void CreateToolStrip()
{
_toolStrip = new ToolStrip();
_toolStrip.Items.Add(new ToolStripButton("맵 열기", null, OnOpenMap_Click) { ToolTipText = "맵 파일을 엽니다" });
_toolStrip.Items.Add(new ToolStripSeparator());
_toolStrip.Items.Add(new ToolStripButton("시뮬레이션 시작", null, OnStartSimulation_Click) { ToolTipText = "시뮬레이션을 시작합니다" });
_toolStrip.Items.Add(new ToolStripButton("시뮬레이션 정지", null, OnStopSimulation_Click) { ToolTipText = "시뮬레이션을 정지합니다" });
_toolStrip.Items.Add(new ToolStripButton("초기화", null, OnReset_Click) { ToolTipText = "시뮬레이션을 초기화합니다" });
_toolStrip.Items.Add(new ToolStripSeparator());
_toolStrip.Items.Add(new ToolStripButton("맵 맞춤", null, OnFitToMap_Click) { ToolTipText = "맵 전체를 화면에 맞춥니다" });
_toolStrip.Items.Add(new ToolStripButton("줌 초기화", null, OnResetZoom_Click) { ToolTipText = "줌을 초기화합니다" });
Controls.Add(_toolStrip);
}
private void CreateStatusStrip()
{
_statusStrip = new StatusStrip();
_statusLabel = new ToolStripStatusLabel("준비");
_coordLabel = new ToolStripStatusLabel();
_statusStrip.Items.AddRange(new ToolStripItem[] { _statusLabel, _coordLabel });
Controls.Add(_statusStrip);
}
private void CreateControlPanel()
{
_controlPanel = new Panel();
_controlPanel.Width = 250;
_controlPanel.Dock = DockStyle.Right;
_controlPanel.BackColor = SystemColors.Control;
// AGV 제어 그룹
CreateAGVControlGroup();
// 경로 제어 그룹
CreatePathControlGroup();
// 뷰 제어 그룹
CreateViewControlGroup();
// 상태 그룹
CreateStatusGroup();
Controls.Add(_controlPanel);
}
private void CreateAGVControlGroup()
{
_agvControlGroup = new GroupBox();
_agvControlGroup.Text = "AGV 제어";
_agvControlGroup.Location = new Point(10, 10);
_agvControlGroup.Size = new Size(230, 120);
_agvListCombo = new ComboBox();
_agvListCombo.DropDownStyle = ComboBoxStyle.DropDownList;
_agvListCombo.Location = new Point(10, 25);
_agvListCombo.Size = new Size(210, 21);
_agvListCombo.SelectedIndexChanged += OnAGVList_SelectedIndexChanged;
_addAgvButton = new Button();
_addAgvButton.Text = "AGV 추가";
_addAgvButton.Location = new Point(10, 55);
_addAgvButton.Size = new Size(100, 25);
_addAgvButton.Click += OnAddAGV_Click;
_removeAgvButton = new Button();
_removeAgvButton.Text = "AGV 제거";
_removeAgvButton.Location = new Point(120, 55);
_removeAgvButton.Size = new Size(100, 25);
_removeAgvButton.Click += OnRemoveAGV_Click;
_startSimulationButton = new Button();
_startSimulationButton.Text = "시뮬레이션 시작";
_startSimulationButton.Location = new Point(10, 85);
_startSimulationButton.Size = new Size(100, 25);
_startSimulationButton.Click += OnStartSimulation_Click;
_stopSimulationButton = new Button();
_stopSimulationButton.Text = "시뮬레이션 정지";
_stopSimulationButton.Location = new Point(120, 85);
_stopSimulationButton.Size = new Size(100, 25);
_stopSimulationButton.Click += OnStopSimulation_Click;
_agvControlGroup.Controls.AddRange(new Control[] {
_agvListCombo, _addAgvButton, _removeAgvButton, _startSimulationButton, _stopSimulationButton
});
_controlPanel.Controls.Add(_agvControlGroup);
}
private void CreatePathControlGroup()
{
_pathGroup = new GroupBox();
_pathGroup.Text = "경로 제어";
_pathGroup.Location = new Point(10, 140);
_pathGroup.Size = new Size(230, 150);
var startLabel = new Label();
startLabel.Text = "시작 노드:";
startLabel.Location = new Point(10, 25);
startLabel.Size = new Size(70, 15);
_startNodeCombo = new ComboBox();
_startNodeCombo.DropDownStyle = ComboBoxStyle.DropDownList;
_startNodeCombo.Location = new Point(10, 45);
_startNodeCombo.Size = new Size(210, 21);
var targetLabel = new Label();
targetLabel.Text = "목표 노드:";
targetLabel.Location = new Point(10, 75);
targetLabel.Size = new Size(70, 15);
_targetNodeCombo = new ComboBox();
_targetNodeCombo.DropDownStyle = ComboBoxStyle.DropDownList;
_targetNodeCombo.Location = new Point(10, 95);
_targetNodeCombo.Size = new Size(210, 21);
_calculatePathButton = new Button();
_calculatePathButton.Text = "경로 계산";
_calculatePathButton.Location = new Point(10, 120);
_calculatePathButton.Size = new Size(65, 25);
_calculatePathButton.Click += OnCalculatePath_Click;
_startPathButton = new Button();
_startPathButton.Text = "경로 시작";
_startPathButton.Location = new Point(80, 120);
_startPathButton.Size = new Size(65, 25);
_startPathButton.Click += OnStartPath_Click;
_clearPathButton = new Button();
_clearPathButton.Text = "경로 지우기";
_clearPathButton.Location = new Point(150, 120);
_clearPathButton.Size = new Size(70, 25);
_clearPathButton.Click += OnClearPath_Click;
_pathGroup.Controls.AddRange(new Control[] {
startLabel, _startNodeCombo, targetLabel, _targetNodeCombo,
_calculatePathButton, _startPathButton, _clearPathButton
});
_controlPanel.Controls.Add(_pathGroup);
}
private void CreateViewControlGroup()
{
_viewGroup = new GroupBox();
_viewGroup.Text = "화면 제어";
_viewGroup.Location = new Point(10, 300);
_viewGroup.Size = new Size(230, 60);
_fitToMapButton = new Button();
_fitToMapButton.Text = "맵 맞춤";
_fitToMapButton.Location = new Point(10, 25);
_fitToMapButton.Size = new Size(100, 25);
_fitToMapButton.Click += OnFitToMap_Click;
_resetZoomButton = new Button();
_resetZoomButton.Text = "줌 초기화";
_resetZoomButton.Location = new Point(120, 25);
_resetZoomButton.Size = new Size(100, 25);
_resetZoomButton.Click += OnResetZoom_Click;
_resetButton = new Button();
_resetButton.Text = "전체 초기화";
_resetButton.Location = new Point(65, 55);
_resetButton.Size = new Size(100, 25);
_resetButton.Click += OnReset_Click;
_viewGroup.Controls.AddRange(new Control[] { _fitToMapButton, _resetZoomButton });
_controlPanel.Controls.Add(_viewGroup);
}
private void CreateStatusGroup()
{
_statusGroup = new GroupBox();
_statusGroup.Text = "상태 정보";
_statusGroup.Location = new Point(10, 370);
_statusGroup.Size = new Size(230, 100);
_simulationStatusLabel = new Label();
_simulationStatusLabel.Text = "시뮬레이션: 정지";
_simulationStatusLabel.Location = new Point(10, 25);
_simulationStatusLabel.Size = new Size(210, 15);
_agvCountLabel = new Label();
_agvCountLabel.Text = "AGV 수: 0";
_agvCountLabel.Location = new Point(10, 45);
_agvCountLabel.Size = new Size(210, 15);
_pathLengthLabel = new Label();
_pathLengthLabel.Text = "경로 길이: -";
_pathLengthLabel.Location = new Point(10, 65);
_pathLengthLabel.Size = new Size(210, 15);
_statusGroup.Controls.AddRange(new Control[] {
_simulationStatusLabel, _agvCountLabel, _pathLengthLabel
});
_controlPanel.Controls.Add(_statusGroup);
}
private void CreateSimulatorCanvas()
{
_canvasPanel = new Panel();
_canvasPanel.Dock = DockStyle.Fill;
_simulatorCanvas = new SimulatorCanvas();
_simulatorCanvas.Dock = DockStyle.Fill;
_canvasPanel.Controls.Add(_simulatorCanvas);
Controls.Add(_canvasPanel);
}
private void SetupLayout()
{
// Z-Order 설정
_canvasPanel.BringToFront();
_controlPanel.BringToFront();
_toolStrip.BringToFront();
_menuStrip.BringToFront();
}
#endregion
#region Event Handlers
private void OnOpenMap_Click(object sender, EventArgs e)
{
using (var openDialog = new OpenFileDialog())
{
openDialog.Filter = "맵 파일 (*.json)|*.json|모든 파일 (*.*)|*.*";
openDialog.Title = "맵 파일 열기";
if (openDialog.ShowDialog() == DialogResult.OK)
{
try
{
LoadMapFile(openDialog.FileName);
_statusLabel.Text = $"맵 로드 완료: {Path.GetFileName(openDialog.FileName)}";
}
catch (Exception ex)
{
MessageBox.Show($"맵 파일을 로드할 수 없습니다:\n{ex.Message}", "오류",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void OnExit_Click(object sender, EventArgs e)
{
Close();
}
private void OnStartSimulation_Click(object sender, EventArgs e)
{
if (_simulationState.IsRunning)
return;
_simulationState.IsRunning = true;
_simulationTimer.Start();
_statusLabel.Text = "시뮬레이션 실행 중";
UpdateUI();
}
private void OnStopSimulation_Click(object sender, EventArgs e)
{
if (!_simulationState.IsRunning)
return;
_simulationState.IsRunning = false;
_simulationTimer.Stop();
_statusLabel.Text = "시뮬레이션 정지";
UpdateUI();
}
private void OnReset_Click(object sender, EventArgs e)
{
// 시뮬레이션 정지
if (_simulationState.IsRunning)
{
OnStopSimulation_Click(sender, e);
}
// AGV 초기화
_simulatorCanvas.ClearAGVs();
_agvList.Clear();
// 경로 초기화
_simulatorCanvas.CurrentPath = null;
// UI 업데이트
UpdateAGVComboBox();
UpdateNodeComboBoxes();
UpdateUI();
_statusLabel.Text = "초기화 완료";
}
private void OnFitToMap_Click(object sender, EventArgs e)
{
_simulatorCanvas.FitToMap();
}
private void OnResetZoom_Click(object sender, EventArgs e)
{
_simulatorCanvas.ResetZoom();
}
private void OnAbout_Click(object sender, EventArgs e)
{
MessageBox.Show("AGV 시뮬레이터 v1.0\n\nENIG AGV 시스템용 시뮬레이터", "정보",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void OnAddAGV_Click(object sender, EventArgs e)
{
if (_mapNodes == null || _mapNodes.Count == 0)
{
MessageBox.Show("먼저 맵을 로드해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var agvId = $"AGV{_agvList.Count + 1:D2}";
var startPosition = _mapNodes.First().Position; // 첫 번째 노드에서 시작
var newAGV = new VirtualAGV(agvId, startPosition);
_agvList.Add(newAGV);
_simulatorCanvas.AddAGV(newAGV);
UpdateAGVComboBox();
UpdateUI();
_statusLabel.Text = $"{agvId} 추가됨";
}
private void OnRemoveAGV_Click(object sender, EventArgs e)
{
if (_agvListCombo.SelectedItem == null)
return;
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
if (selectedAGV != null)
{
_simulatorCanvas.RemoveAGV(selectedAGV.AgvId);
_agvList.Remove(selectedAGV);
UpdateAGVComboBox();
UpdateUI();
_statusLabel.Text = $"{selectedAGV.AgvId} 제거됨";
}
}
private void OnAGVList_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateUI();
}
private void OnCalculatePath_Click(object sender, EventArgs e)
{
if (_startNodeCombo.SelectedItem == null || _targetNodeCombo.SelectedItem == null)
{
MessageBox.Show("시작 노드와 목표 노드를 선택해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var startNode = _startNodeCombo.SelectedItem as MapNode;
var targetNode = _targetNodeCombo.SelectedItem as MapNode;
if (_pathCalculator == null)
{
_pathCalculator = new PathCalculator(_mapNodes, _nodeResolver);
}
var result = _pathCalculator.CalculatePath(startNode.NodeId, targetNode.NodeId, AgvDirection.Forward);
if (result.Success)
{
_simulatorCanvas.CurrentPath = result;
_pathLengthLabel.Text = $"경로 길이: {result.TotalDistance:F1}";
_statusLabel.Text = $"경로 계산 완료 ({result.CalculationTime}ms)";
}
else
{
MessageBox.Show($"경로를 찾을 수 없습니다:\n{result.ErrorMessage}", "경로 계산 실패",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void OnStartPath_Click(object sender, EventArgs e)
{
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
if (selectedAGV == null)
{
MessageBox.Show("AGV를 선택해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (_simulatorCanvas.CurrentPath == null || !_simulatorCanvas.CurrentPath.Success)
{
MessageBox.Show("먼저 경로를 계산해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
selectedAGV.StartPath(_simulatorCanvas.CurrentPath, _mapNodes);
_statusLabel.Text = $"{selectedAGV.AgvId} 경로 시작";
}
private void OnClearPath_Click(object sender, EventArgs e)
{
_simulatorCanvas.CurrentPath = null;
_pathLengthLabel.Text = "경로 길이: -";
_statusLabel.Text = "경로 지움";
}
private void OnSimulationTimer_Tick(object sender, EventArgs e)
{
// 시뮬레이션 업데이트는 각 AGV의 내부 타이머에서 처리됨
UpdateUI();
}
#endregion
#region Private Methods
private void LoadMapFile(string filePath)
{
try
{
var json = File.ReadAllText(filePath);
// 구조체로 직접 역직렬화
var mapData = JsonConvert.DeserializeObject<MapFileData>(json);
if (mapData != null)
{
_mapNodes = mapData.MapNodes ?? new List<MapNode>();
_rfidMappings = mapData.RfidMappings ?? new List<RfidMapping>();
}
else
{
_mapNodes = new List<MapNode>();
_rfidMappings = new List<RfidMapping>();
}
// NodeResolver 초기화
_nodeResolver = new NodeResolver(_rfidMappings, _mapNodes);
// 시뮬레이터 캔버스에 맵 설정
_simulatorCanvas.MapNodes = _mapNodes;
// UI 업데이트
UpdateNodeComboBoxes();
UpdateUI();
// 맵에 맞춤
_simulatorCanvas.FitToMap();
}
catch (Exception ex)
{
throw new InvalidOperationException($"맵 파일 로드 실패: {ex.Message}", ex);
}
}
// 맵 파일 데이터 구조체
private class MapFileData
{
public List<MapNode> MapNodes { get; set; }
public List<RfidMapping> RfidMappings { get; set; }
}
private void UpdateNodeComboBoxes()
{
_startNodeCombo.Items.Clear();
_targetNodeCombo.Items.Clear();
if (_mapNodes != null)
{
foreach (var node in _mapNodes)
{
_startNodeCombo.Items.Add(node);
_targetNodeCombo.Items.Add(node);
}
}
_startNodeCombo.DisplayMember = "NodeId";
_targetNodeCombo.DisplayMember = "NodeId";
}
private void UpdateAGVComboBox()
{
_agvListCombo.Items.Clear();
if (_agvList != null)
{
foreach (var agv in _agvList)
{
_agvListCombo.Items.Add(agv);
}
}
_agvListCombo.DisplayMember = "AgvId";
if (_agvListCombo.Items.Count > 0)
{
_agvListCombo.SelectedIndex = 0;
}
}
private void UpdateUI()
{
// 시뮬레이션 상태
_simulationStatusLabel.Text = _simulationState.IsRunning ? "시뮬레이션: 실행 중" : "시뮬레이션: 정지";
// AGV 수
_agvCountLabel.Text = $"AGV 수: {_agvList?.Count ?? 0}";
// 버튼 상태
_startSimulationButton.Enabled = !_simulationState.IsRunning && _agvList?.Count > 0;
_stopSimulationButton.Enabled = _simulationState.IsRunning;
_removeAgvButton.Enabled = _agvListCombo.SelectedItem != null;
_startPathButton.Enabled = _agvListCombo.SelectedItem != null &&
_simulatorCanvas.CurrentPath != null &&
_simulatorCanvas.CurrentPath.Success;
_calculatePathButton.Enabled = _startNodeCombo.SelectedItem != null &&
_targetNodeCombo.SelectedItem != null;
}
#endregion
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>