This commit is contained in:
backuppc
2026-01-14 15:29:38 +09:00
parent 5801137d63
commit d5516f9708
17 changed files with 605 additions and 402 deletions

View File

@@ -126,7 +126,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// <summary>
/// 단순 경로 찾기 (복잡한 제약조건/방향전환 로직 없이 A* 결과만 반환)
/// </summary>
public AGVPathResult FindBasicPath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection prevDirection)
public AGVPathResult FindBasicPath(MapNode startNode, MapNode targetNode, MapNode _prevNode, AgvDirection prevDirection)
{
// 1. 입력 검증
if (startNode == null || targetNode == null)
@@ -134,13 +134,13 @@ namespace AGVNavigationCore.PathFinding.Planning
// 2. A* 경로 탐색
var pathResult = _basicPathfinder.FindPathAStar(startNode, targetNode);
pathResult.PrevNode = prevNode;
pathResult.PrevNode = _prevNode;
pathResult.PrevDirection = prevDirection;
if (!pathResult.Success)
return AGVPathResult.CreateFailure(pathResult.ErrorMessage ?? "경로 없음", 0, 0);
// 3. 상세 데이터 생성 (단순화: 방향 전환 없이 현재 방향 유지)
// 3. 상세 데이터 생성 (갈림길 마그넷 방향 계산 포함)
if (pathResult.Path != null && pathResult.Path.Count > 0)
{
var detailedPath = new List<NodeMotorInfo>();
@@ -149,8 +149,47 @@ namespace AGVNavigationCore.PathFinding.Planning
var node = pathResult.Path[i];
string nextNodeId = (i + 1 < pathResult.Path.Count) ? pathResult.Path[i + 1].Id : null;
// 단순화: 입력된 현재 방향을 그대로 사용
var nodeInfo = new NodeMotorInfo(i + 1, node.Id, node.RfidId, prevDirection, nextNodeId, MagnetDirection.Straight);
// 마그넷 방향 계산 (갈림길인 경우)
MagnetDirection magnetDirection = MagnetDirection.Straight;
if (node.ConnectedNodes != null && node.ConnectedNodes.Count >= 3 && i > 0 && nextNodeId != null)
{
// 갈림길인 경우: 진입 방향과 진출 방향의 각도 계산
var prevNode = pathResult.Path[i - 1];
var nextNode = pathResult.Path[i + 1];
// 진입 각도 계산 (이전 노드 → 현재 노드)
double entryAngle = Math.Atan2(
node.Position.Y - prevNode.Position.Y,
node.Position.X - prevNode.Position.X
) * 180.0 / Math.PI;
// 진출 각도 계산 (현재 노드 → 다음 노드)
double exitAngle = Math.Atan2(
nextNode.Position.Y - node.Position.Y,
nextNode.Position.X - node.Position.X
) * 180.0 / Math.PI;
// 각도 차이 계산 (-180~180 범위로 정규화)
double angleDiff = exitAngle - entryAngle;
while (angleDiff > 180) angleDiff -= 360;
while (angleDiff < -180) angleDiff += 360;
// 10도 이상 차이나면 좌/우회전 처리
if (Math.Abs(angleDiff) >= 10)
{
if (angleDiff > 0)
{
magnetDirection = MagnetDirection.Right;
}
else
{
magnetDirection = MagnetDirection.Left;
}
}
}
var nodeInfo = new NodeMotorInfo(i + 1, node.Id, node.RfidId, prevDirection, nextNodeId, magnetDirection);
// 속도 설정
var mapNode = _mapNodes.FirstOrDefault(n => n.Id == node.Id);
@@ -164,75 +203,7 @@ namespace AGVNavigationCore.PathFinding.Planning
return pathResult;
}
/// <summary>
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
/// </summary>
/// <param name="path1"></param>
/// <param name="currentDirection"></param>
public void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
{
if (path1.Success && path1.Path != null && path1.Path.Count > 0)
{
var detailedPath1 = new List<NodeMotorInfo>();
for (int i = 0; i < path1.Path.Count; i++)
{
var node = path1.Path[i];
string nodeId = node.Id;
var RfidId = node.RfidId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 노드 정보 생성 (현재 방향 유지)
var nodeInfo = new NodeMotorInfo(i + 1,
nodeId, RfidId,
currentDirection,
nextNodeId,
MagnetDirection.Straight
);
// [Speed Control] MapNode의 속도 설정 적용
var mapNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (mapNode != null)
{
nodeInfo.Speed = mapNode.SpeedLimit;
}
detailedPath1.Add(nodeInfo);
}
// path1에 상세 경로 정보 설정
path1.DetailedPath = detailedPath1;
}
}
/// <summary>
/// Path에 등록된 방향을 확인하여 마그넷정보를 업데이트 합니다
/// </summary>
/// <param name="path1"></param>
private void MakeMagnetDirection(AGVPathResult path1)
{
if (path1.Success && path1.DetailedPath != null && path1.DetailedPath.Count > 0)
{
for (int i = 0; i < path1.DetailedPath.Count; i++)
{
var detailPath = path1.DetailedPath[i];
string nodeId = path1.Path[i].Id;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 마그넷 방향 계산 (3개 이상 연결된 교차로에서만 좌/우 가중치 적용)
if (i > 0 && nextNodeId != null)
{
string prevNodeId = path1.Path[i - 1].Id;
if (path1.DetailedPath[i - 1].MotorDirection != detailPath.MotorDirection)
detailPath.MagnetDirection = MagnetDirection.Straight;
else
detailPath.MagnetDirection = _junctionAnalyzer.GetRequiredMagnetDirection(prevNodeId, nodeId, nextNodeId, detailPath.MotorDirection);
}
else detailPath.MagnetDirection = MagnetDirection.Straight;
}
}
}
}

View File

@@ -120,6 +120,7 @@ namespace AGVSimulator.Forms
this._liftDirectionLabel = new System.Windows.Forms.Label();
this._motorDirectionLabel = new System.Windows.Forms.Label();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.btSelectMapEditor = new System.Windows.Forms.ToolStripMenuItem();
this._menuStrip.SuspendLayout();
this._toolStrip.SuspendLayout();
this._statusStrip.SuspendLayout();
@@ -154,6 +155,7 @@ namespace AGVSimulator.Forms
this.ToolStripMenuItem,
this.toolStripSeparator1,
this.launchMapEditorToolStripMenuItem,
this.btSelectMapEditor,
this.toolStripSeparator4,
this.exitToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
@@ -547,7 +549,7 @@ namespace AGVSimulator.Forms
//
// btPath2
//
this.btPath2.Location = new System.Drawing.Point(12, 201);
this.btPath2.Location = new System.Drawing.Point(12, 177);
this.btPath2.Name = "btPath2";
this.btPath2.Size = new System.Drawing.Size(106, 25);
this.btPath2.TabIndex = 10;
@@ -588,6 +590,7 @@ namespace AGVSimulator.Forms
// _targetNodeCombo
//
this._targetNodeCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this._targetNodeCombo.Font = new System.Drawing.Font("돋움체", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._targetNodeCombo.Location = new System.Drawing.Point(10, 97);
this._targetNodeCombo.Name = "_targetNodeCombo";
this._targetNodeCombo.Size = new System.Drawing.Size(210, 20);
@@ -605,6 +608,7 @@ namespace AGVSimulator.Forms
// _startNodeCombo
//
this._startNodeCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this._startNodeCombo.Font = new System.Drawing.Font("돋움체", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._startNodeCombo.Location = new System.Drawing.Point(10, 45);
this._startNodeCombo.Name = "_startNodeCombo";
this._startNodeCombo.Size = new System.Drawing.Size(210, 20);
@@ -812,6 +816,13 @@ namespace AGVSimulator.Forms
this.timer1.Interval = 500;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// btSelectMapEditor
//
this.btSelectMapEditor.Name = "btSelectMapEditor";
this.btSelectMapEditor.Size = new System.Drawing.Size(221, 22);
this.btSelectMapEditor.Text = "Mapeditor 선택";
this.btSelectMapEditor.Click += new System.EventHandler(this.btSelectMapEditor_Click);
//
// SimulatorForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
@@ -925,5 +936,6 @@ namespace AGVSimulator.Forms
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.PropertyGrid propertyNode;
private System.Windows.Forms.Button btPath2;
private System.Windows.Forms.ToolStripMenuItem btSelectMapEditor;
}
}

View File

@@ -833,8 +833,8 @@ namespace AGVSimulator.Forms
{
for (int i = 0; i < _startNodeCombo.Items.Count; i++)
{
var item = _startNodeCombo.Items[i].ToString();
if (item.Contains($"[{nodeId}]"))
var item = _startNodeCombo.Items[i] as ComboBoxItem<MapNode>;//.ToString();
if (item.Value.Id.Equals(nodeId))
{
_startNodeCombo.SelectedIndex = i;
Program.WriteLine($"[SYSTEM] 시작 노드를 '{nodeId}'로 자동 선택했습니다.");
@@ -958,10 +958,10 @@ namespace AGVSimulator.Forms
{
foreach (var node in _simulatorCanvas.Nodes)
{
if (node.IsActive && node.HasRfid())
if (node.IsActive)
{
// {rfid} - [{node}] {name} 형식으로 ComboBoxItem 생성
var displayText = $"{node.RfidId} - [{node.Id}]";
// {rfid} - [{node}] {name} 형식으로 ComboBoxItem 생성
var displayText = $"{node.StationType.ToString().PadRight(7)} | {node.ID2}";
var item = new ComboBoxItem<MapNode>(node, displayText);
_startNodeCombo.Items.Add(item);
@@ -1007,8 +1007,8 @@ namespace AGVSimulator.Forms
_stopSimulationButton.Enabled = _simulationState.IsRunning;
_removeAgvButton.Enabled = _agvListCombo.SelectedItem != null;
// btPath1.Enabled = _startNodeCombo.SelectedItem != null &&
// _targetNodeCombo.SelectedItem != null;
// btPath1.Enabled = _startNodeCombo.SelectedItem != null &&
// _targetNodeCombo.SelectedItem != null;
// RFID 위치 설정 관련
var hasSelectedAGV = _agvListCombo.SelectedItem != null;
@@ -2571,8 +2571,8 @@ namespace AGVSimulator.Forms
if (!pathToGateway.Success) return (false, $"Gateway({gatewayNode.ID2})까지 경로 실패: {pathToGateway.ErrorMessage}");
//마지막경로는 게이트웨이이므로 제거하낟.
if(pathToGateway.Path.Count > 1)
//마지막경로는 게이트웨이이므로 제거하낟.(260113)
if (pathToGateway.Path.Count > 1 && pathToGateway.Path.Last().Id == gatewayNode.Id)
{
pathToGateway.Path.RemoveAt(pathToGateway.Path.Count - 1);
pathToGateway.DetailedPath.RemoveAt(pathToGateway.DetailedPath.Count - 1);
@@ -2584,7 +2584,7 @@ namespace AGVSimulator.Forms
MapNode GateprevNode = pathToGateway.Path.Last();
NodeMotorInfo GatePrevDetail = pathToGateway.DetailedPath.Last();
var arrivalOrientation = GatePrevDetail.MotorDirection;
//아래코드오류발생함
@@ -2643,21 +2643,33 @@ namespace AGVSimulator.Forms
var isMonitorLeft = false;
bool requiredDir = false;
if (deltaX > 0) //게이트웨이가 우측에 있다
{
//이떄 모터방향이 후진이라면 모니터는 왼쪽이고, 반대는 오른쪽이다
isMonitorLeft = PrevDirection == AgvDirection.Backward;
}
else
{
isMonitorLeft = PrevDirection == AgvDirection.Forward;
}
//로더는 상/하 개념으로 처리해야한다.X축이아닌 Y축을 봐야한다.
if (targetNode.StationType == StationType.Loader || targetNode.StationType == StationType.Charger2)
{
deltaX = GTNode.Position.Y - PrevNode.Position.Y;
if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward;
else isMonitorLeft = PrevDirection == AgvDirection.Forward;
}
switch (targetNode.StationType)
{
case StationType.Loader:
case StationType.Charger2:
case StationType.Charger1:
case StationType.UnLoader:
case StationType.Clearner:
case StationType.Buffer:
if (deltaX > 0) //게이트웨이가 우측에 있다
{
//이떄 모터방향이 후진이라면 모니터는 왼쪽이고, 반대는 오른쪽이다
isMonitorLeft = PrevDirection == AgvDirection.Backward;
}
else
{
isMonitorLeft = PrevDirection == AgvDirection.Forward;
}
//버퍼는 모니터가 왼쪽에 있으면 안된다.
//충전기1만 전진 도킹을 한다.
@@ -2932,6 +2944,29 @@ namespace AGVSimulator.Forms
_simulatorCanvas.FitToNodes();
}
private void btSelectMapEditor_Click(object sender, EventArgs e)
{
using (var openDialog = new OpenFileDialog())
{
openDialog.Filter = "실행 파일 (*.exe)|*.exe|모든 파일 (*.*)|*.*";
openDialog.Title = "MapEditor 실행 파일 선택";
if (!string.IsNullOrEmpty(_config.MapEditorExecutablePath) && File.Exists(_config.MapEditorExecutablePath))
{
openDialog.InitialDirectory = Path.GetDirectoryName(_config.MapEditorExecutablePath);
openDialog.FileName = Path.GetFileName(_config.MapEditorExecutablePath);
}
if (openDialog.ShowDialog() == DialogResult.OK)
{
_config.MapEditorExecutablePath = openDialog.FileName;
_config.Save();
_statusLabel.Text = $"MapEditor 경로 설정: {Path.GetFileName(openDialog.FileName)}";
MessageBox.Show($"MapEditor 실행 파일이 설정되었습니다:\n{openDialog.FileName}",
"경로 설정 완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
}