Compare commits
2 Commits
48befb60b2
...
8bbac47244
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bbac47244 | ||
|
|
041c15550c |
@ -15,6 +15,7 @@ using CoreUnit.Scanner.UserControl.ScannerCanvas;
|
||||
using DB;
|
||||
using GUI;
|
||||
using Hermes;
|
||||
using ILSOS_PrismaticRD_PTOP.Alarm;
|
||||
using ILSOS_PrismaticRD_PTOP.Device;
|
||||
using ILSOS_PrismaticRD_PTOP.Device.IO;
|
||||
using ILSOS_PrismaticRD_PTOP.Settings;
|
||||
@ -567,6 +568,50 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD);
|
||||
|
||||
Log.I($"Welding List Space : {_Scanner.GetListSpace()}");
|
||||
|
||||
// === Head Theta 인터락 (① 안전 게이트 + ② 레시피별 방향 차단) — Manual 경로 신규 추가 ===
|
||||
var thetaSetting = context.GetSetting<LaserInterlockSetting>(SettingIds.LaserInterlock);
|
||||
if (thetaSetting != null && thetaSetting.HeadThetaInterlockEnabled)
|
||||
{
|
||||
bool blocked = false;
|
||||
if (_plc == null || !_plc.IsActivated)
|
||||
{
|
||||
Log.W("[DoComponentWelding] LaserInterlock - PLC 미연결/미활성 → 발진 차단(fail-closed)");
|
||||
blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
short thetaRaw = _plc.ReadWord(eWordInput.HEAD_THETA_POSITION).data;
|
||||
double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드
|
||||
double thetaDeg = thetaRaw / scale;
|
||||
bool okFront = Math.Abs(thetaDeg - thetaSetting.HeadThetaFront) <= thetaSetting.HeadThetaTolerance;
|
||||
bool okDown = Math.Abs(thetaDeg - thetaSetting.HeadThetaDown) <= thetaSetting.HeadThetaTolerance;
|
||||
Log.I($"[DoComponentWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (Front={thetaSetting.HeadThetaFront}/Down={thetaSetting.HeadThetaDown}/±{thetaSetting.HeadThetaTolerance})");
|
||||
// ① 안전 게이트: 안전 구역(Front/Down 부근) 아니면 차단
|
||||
if (!okFront && !okDown)
|
||||
{
|
||||
Log.W($"[DoComponentWelding] ① HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
||||
blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ② 레시피별 방향 차단: 체크된 방향의 구역이면 차단
|
||||
var rl = RecipeLaserInterlock.Load();
|
||||
if ((rl.Front && okFront) || (rl.Down && okDown))
|
||||
{
|
||||
Log.W($"[DoComponentWelding] ② 레시피 차단 deg={thetaDeg:F1} Front?{rl.Front}/{okFront} Down?{rl.Down}/{okDown} → 발진 차단");
|
||||
blocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blocked)
|
||||
{
|
||||
try { AlarmManager.Alarm(AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK); }
|
||||
catch (Exception ex) { Log.E($"[DoComponentWelding] 알람 표시 실패: {ex.Message}"); }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime weldingTime = DateTime.Now;
|
||||
_Scanner.ExecuteList(_WeldingScannerListNo);
|
||||
_Scanner.WaitForListTaskCompleted(1000);//(_SystemParameter.WeldLinesTaskTimeOut);
|
||||
|
||||
@ -767,11 +767,22 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
bool okFront = Math.Abs(thetaDeg - thetaSetting.HeadThetaFront) <= thetaSetting.HeadThetaTolerance;
|
||||
bool okDown = Math.Abs(thetaDeg - thetaSetting.HeadThetaDown) <= thetaSetting.HeadThetaTolerance;
|
||||
Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (Front={thetaSetting.HeadThetaFront}/Down={thetaSetting.HeadThetaDown}/±{thetaSetting.HeadThetaTolerance})");
|
||||
// ① 안전 게이트(기존): 안전 구역(Front/Down 부근) 아니면 차단
|
||||
if (!okFront && !okDown)
|
||||
{
|
||||
Log.W($"[ExecuteWelding] HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
||||
Log.W($"[ExecuteWelding] ① HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
||||
blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ② 레시피별 방향 차단(신규): 체크된 방향의 구역이면 차단
|
||||
var rl = WeldingCommon.Settings.RecipeLaserInterlock.Load();
|
||||
if ((rl.Front && okFront) || (rl.Down && okDown))
|
||||
{
|
||||
Log.W($"[ExecuteWelding] ② 레시피 차단 deg={thetaDeg:F1} Front?{rl.Front}/{okFront} Down?{rl.Down}/{okDown} → 발진 차단");
|
||||
blocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blocked)
|
||||
{
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using DB;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace WeldingCommon.Settings
|
||||
{
|
||||
// 레시피별 Laser 방향(Front/Down) 발진 차단 체크값.
|
||||
// 활성 레시피 폴더의 Recipe_Laser_Interlock.json 에 저장/로드한다.
|
||||
// 경로 소스는 FieldHelper.json 과 동일한 DB.Dao.SystemRecipe.WorkRecipeFile 을 사용해
|
||||
// 같은 레시피 폴더에 떨어지도록 보장한다.
|
||||
public class RecipeLaserInterlock
|
||||
{
|
||||
public bool Front { get; set; } // json 1=true(차단) / 0=false
|
||||
public bool Down { get; set; }
|
||||
|
||||
private const string FileName = "Recipe_Laser_Interlock.json";
|
||||
private const string WrapperKey = "Laser Interlock";
|
||||
|
||||
// 활성 레시피 폴더의 Recipe_Laser_Interlock.json 전체 경로.
|
||||
// (FieldHelper.json 과 동일 소스/폴더. 파일명만 치환)
|
||||
public static string GetActiveFilePath()
|
||||
{
|
||||
string recipeFile = DB.Dao.SystemRecipe.WorkRecipeFile;
|
||||
if (string.IsNullOrEmpty(recipeFile))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return recipeFile.Replace(Path.GetFileName(recipeFile), FileName);
|
||||
}
|
||||
|
||||
// 파일이 없거나 파싱 실패 시 기본값(Front=false, Down=false) = 차단 없음.
|
||||
public static RecipeLaserInterlock Load()
|
||||
{
|
||||
var result = new RecipeLaserInterlock();
|
||||
try
|
||||
{
|
||||
string path = GetActiveFilePath();
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(path));
|
||||
var node = root[WrapperKey];
|
||||
if (node != null)
|
||||
{
|
||||
result.Front = (node["Front"]?.Value<int>() ?? 0) == 1;
|
||||
result.Down = (node["Down"]?.Value<int>() ?? 0) == 1;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 손상/파싱 실패 → 기본값(차단 없음) 반환
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void Save(RecipeLaserInterlock value)
|
||||
{
|
||||
string path = GetActiveFilePath();
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var root = new JObject
|
||||
{
|
||||
[WrapperKey] = new JObject
|
||||
{
|
||||
["Front"] = value.Front ? 1 : 0,
|
||||
["Down"] = value.Down ? 1 : 0,
|
||||
}
|
||||
};
|
||||
File.WriteAllText(path, root.ToString(Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,6 +98,7 @@
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"></ColumnDefinition>
|
||||
@ -158,6 +159,15 @@
|
||||
<Button Grid.Column="1" Margin="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto" FontWeight="Bold" Style="{StaticResource StButtonAccentPurple}" Command="{Binding CMD_StopTeachingRun}" Content="Stop" IsEnabled="{Binding IsSequenceRunning_TeachingRun}"/>
|
||||
</Grid>
|
||||
|
||||
<GroupBox Header="Laser Interlock" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 5 0 0" Style="{StaticResource StGroupBox4th}">
|
||||
<StackPanel>
|
||||
<CheckBox Margin="6 4" FontSize="12" FontWeight="Bold" Foreground="#FFBBBBBB"
|
||||
Content="FRONT INTERLOCK" IsChecked="{Binding IsFrontInterlock}"/>
|
||||
<CheckBox Margin="6 4" FontSize="12" FontWeight="Bold" Foreground="#FFBBBBBB"
|
||||
Content="DOWN INTERLOCK" IsChecked="{Binding IsDownInterlock}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
|
||||
@ -82,6 +82,40 @@ namespace WeldingCommon.Views
|
||||
private bool _IsObjectSelectAll;
|
||||
public bool IsObjectSelectAll { get => _IsObjectSelectAll; set => SetProperty(ref _IsObjectSelectAll, value); }
|
||||
|
||||
// 레시피별 Laser 방향 발진 차단 체크 (Recipe_Laser_Interlock.json) — 변경 시 즉시 활성 레시피에 저장
|
||||
private bool _IsFrontInterlock;
|
||||
public bool IsFrontInterlock
|
||||
{
|
||||
get => _IsFrontInterlock;
|
||||
set { SetProperty(ref _IsFrontInterlock, value); SaveLaserInterlock(); }
|
||||
}
|
||||
private bool _IsDownInterlock;
|
||||
public bool IsDownInterlock
|
||||
{
|
||||
get => _IsDownInterlock;
|
||||
set { SetProperty(ref _IsDownInterlock, value); SaveLaserInterlock(); }
|
||||
}
|
||||
// 로드로 인한 set 시 저장 억제(레시피 전환 시 stale 덮어쓰기 방지)
|
||||
private bool _suppressInterlockSave;
|
||||
private void SaveLaserInterlock()
|
||||
{
|
||||
if (_suppressInterlockSave) {
|
||||
return;
|
||||
}
|
||||
RecipeLaserInterlock.Save(new RecipeLaserInterlock { Front = _IsFrontInterlock, Down = _IsDownInterlock });
|
||||
}
|
||||
private void LoadLaserInterlock()
|
||||
{
|
||||
var rl = RecipeLaserInterlock.Load();
|
||||
_suppressInterlockSave = true;
|
||||
try {
|
||||
IsFrontInterlock = rl.Front;
|
||||
IsDownInterlock = rl.Down;
|
||||
} finally {
|
||||
_suppressInterlockSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ScoutNormal 행 전환 sleep 분리 (같은 행 / 행 전환)
|
||||
private int _ScoutSameRowSleepMs;
|
||||
public int ScoutSameRowSleepMs
|
||||
@ -135,6 +169,8 @@ namespace WeldingCommon.Views
|
||||
|
||||
InitVariable();
|
||||
|
||||
LoadLaserInterlock();
|
||||
|
||||
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
|
||||
|
||||
CMD_RunScoutNormal = new DelegateCommand(PerformCmdRunScoutNormal);
|
||||
@ -154,6 +190,14 @@ namespace WeldingCommon.Views
|
||||
.ObserveOnDispatcher()
|
||||
.Subscribe(OnSettingReloaded);
|
||||
|
||||
// 레시피 전환 시 체크 상태를 새 레시피의 json 값으로 갱신.
|
||||
// SYS_RECIPE_ACTIVATED(1-홉)에서 WorkRecipeFile은 아직 미갱신(ReloadRecipe는 2-홉 뒤) →
|
||||
// Background 우선순위로 지연 실행해 ReloadRecipe(Normal) 완료 후 새 경로로 로드.
|
||||
_Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_RECIPE_ACTIVATED)
|
||||
.ObserveOnDispatcher()
|
||||
.Subscribe(_ => Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(LoadLaserInterlock), DispatcherPriority.Background));
|
||||
|
||||
_MonitorTimer = new DispatcherTimer();
|
||||
_MonitorTimer.Tick += OnMonitoring;
|
||||
_MonitorTimer.Interval = TimeSpan.FromMilliseconds(100);
|
||||
@ -182,6 +226,7 @@ namespace WeldingCommon.Views
|
||||
{
|
||||
bool visible = (bool)obj;
|
||||
if (visible) {
|
||||
LoadLaserInterlock(); // 패널 표시 시 현재 활성 레시피의 체크값 반영
|
||||
_MonitorTimer.Start();
|
||||
} else {
|
||||
_MonitorTimer.Stop();
|
||||
|
||||
@ -253,6 +253,7 @@
|
||||
<DependentUpon>SequenceDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Settings\LaserPowerCalParam.cs" />
|
||||
<Compile Include="Settings\RecipeLaserInterlock.cs" />
|
||||
<Compile Include="Settings\SequenceSetting.cs" />
|
||||
<Compile Include="Settings\SettingRmsDataList.cs" />
|
||||
<Compile Include="Settings\SettingUploadDataList.cs" />
|
||||
|
||||
@ -192,16 +192,4 @@ ILSOS_PrismaticRD_PTOP의 아키텍처·구현·빌드를 종합 분석하는
|
||||
|
||||
## 8. 용어 정리 (UNICELL PTOP WELDER)
|
||||
|
||||
> 협력사: **UBISAM** / 프로젝트: **UNICELL PTOP WELDING**(약칭 **PTOP**)
|
||||
|
||||
### 가접 용접 (Tack Welding)
|
||||
- **정의**: U-Frame 본(메인) 용접 전에, **팩 제품과 프레임 간 밀착이 안 된 자재를 먼저 붙여 고정**하는 예비 용접.
|
||||
- **이유**: 자재가 떠 있으면(밀착 불량) 본 용접 품질이 안 나오므로, **Align → 가접으로 밀착 → 메인 용접** 순서로 진행.
|
||||
- **방식**: **Dot(점) 또는 Short Line(짧은 선)** 출사로 미리 몇 점 붙여 밀착.
|
||||
- **운용**: 기존 AutoRun 시나리오엔 **가접 시나리오가 없었어서 신규 추가**됨. Manual 모드로 전환 후 Recipe 화면에서 **직접 Scout Image** 촬영 → 수동으로 가접(밀착) 용접 수행.
|
||||
- **PLC 연동**: 이를 위해 PLC와 협의하여 **15번 bit(0x0F)** 추가 — `M70515 PLC_WELD_READY_REQ`(Weld_01 Ready) / `M70015 PC_WELD_READY_ACK`. PLC ON 시 Recipe/Page 확인 팝업 + Auto→Manual + Resume 트리거. (기존 M70516/M70016은 `..._TRIGGER_*`로 rename)
|
||||
- **레시피**: PTOP는 **레시피 21,22(가접용)** 에 가접 용접용 시퀀스 수정이 들어감. (가변/가접은 레시피 추가로 처리, 시퀀스 본체 무수정 방침)
|
||||
- **최종 방침(2026-06-22 현장 확정)**:
|
||||
- 가접 실행은 bit15 Recipe/Page 확인 팝업+Resume 같은 복잡한 방식 대신 **"PLC가 Scout TRIGGER 신호를 주면 ScoutImage를 단순 실행"** 으로 단순화. (bit15는 신호 라인으로 유지하되 운용은 단순 트리거)
|
||||
- **시나리오 상 용접 전 위치에서 ScoutImage 시퀀스 추가**. 추가로 **용접 전/후 이미지를 남기는 방안**으로 요청됨. (작업일지 2026-06-22)
|
||||
- 연관 메모리: [[uframe-page5-gajeop-recipe]], [[bit15-resume-feature]], [[recipe-page-handshake-redesign]]
|
||||
@import ./용어정리.md — 협력사/프로젝트 약칭, 가접 용접(Tack Welding) 정의·운용·PLC 연동
|
||||
|
||||
15
docs/용어정리.md
Normal file
15
docs/용어정리.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 용어 정리 (UNICELL PTOP WELDER)
|
||||
|
||||
> 협력사: **UBISAM** / 프로젝트: **UNICELL PTOP WELDING**(약칭 **PTOP**)
|
||||
|
||||
## 가접 용접 (Tack Welding)
|
||||
- **정의**: U-Frame 본(메인) 용접 전에, **팩 제품과 프레임 간 밀착이 안 된 자재를 먼저 붙여 고정**하는 예비 용접.
|
||||
- **이유**: 자재가 떠 있으면(밀착 불량) 본 용접 품질이 안 나오므로, **Align → 가접으로 밀착 → 메인 용접** 순서로 진행.
|
||||
- **방식**: **Dot(점) 또는 Short Line(짧은 선)** 출사로 미리 몇 점 붙여 밀착.
|
||||
- **운용**: 기존 AutoRun 시나리오엔 **가접 시나리오가 없었어서 신규 추가**됨. Manual 모드로 전환 후 Recipe 화면에서 **직접 Scout Image** 촬영 → 수동으로 가접(밀착) 용접 수행.
|
||||
- **PLC 연동**: 이를 위해 PLC와 협의하여 **15번 bit(0x0F)** 추가 — `M70515 PLC_WELD_READY_REQ`(Weld_01 Ready) / `M70015 PC_WELD_READY_ACK`. PLC ON 시 Recipe/Page 확인 팝업 + Auto→Manual + Resume 트리거. (기존 M70516/M70016은 `..._TRIGGER_*`로 rename)
|
||||
- **레시피**: PTOP는 **레시피 21,22(가접용)** 에 가접 용접용 시퀀스 수정이 들어감. (가변/가접은 레시피 추가로 처리, 시퀀스 본체 무수정 방침)
|
||||
- **최종 방침(2026-06-22 현장 확정)**:
|
||||
- 가접 실행은 bit15 Recipe/Page 확인 팝업+Resume 같은 복잡한 방식 대신 **"PLC가 Scout TRIGGER 신호를 주면 ScoutImage를 단순 실행"** 으로 단순화. (bit15는 신호 라인으로 유지하되 운용은 단순 트리거)
|
||||
- **시나리오 상 용접 전 위치에서 ScoutImage 시퀀스 추가**. 추가로 **용접 전/후 이미지를 남기는 방안**으로 요청됨. (작업일지 2026-06-22)
|
||||
- 연관 메모리: [[uframe-page5-gajeop-recipe]], [[bit15-resume-feature]], [[recipe-page-handshake-redesign]]
|
||||
Loading…
x
Reference in New Issue
Block a user