diff --git a/Machines/ILSOS_PrismaticRD_PTOP/ILSOS_PrismaticRD_PTOP.csproj b/Machines/ILSOS_PrismaticRD_PTOP/ILSOS_PrismaticRD_PTOP.csproj index 79bba3b..876db8c 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/ILSOS_PrismaticRD_PTOP.csproj +++ b/Machines/ILSOS_PrismaticRD_PTOP/ILSOS_PrismaticRD_PTOP.csproj @@ -106,6 +106,7 @@ + diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/LaserAngleOffsetSelector.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/LaserAngleOffsetSelector.cs new file mode 100644 index 0000000..91145aa --- /dev/null +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/LaserAngleOffsetSelector.cs @@ -0,0 +1,46 @@ +using Aurora.Api; +using ILSOS_PrismaticRD_PTOP.Device; +using ILSOS_PrismaticRD_PTOP.Settings; +using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP; +using SystemParameter = WeldingCommon.Settings.SystemParameter; + +namespace ILSOS_PrismaticRD_PTOP.Sequences +{ + // 카메라 헤드 각도(D70510)에 따라 Front/Down 발진 offset 을 선택한다. + // thetaDeg = D70510 / PlcScale 가 (Front angle + Down angle)/2 보다 크면 Down, 아니면 Front. + // PLC 미연결/미활성으로 각도를 못 읽으면 Front offset 적용(fallback). + internal static class LaserAngleOffsetSelector + { + public static (double x, double y) Select( + PLCControllerPTOP plc, SystemParameter sp, LaserInterlockSetting setting, Logger log, string tag) + { + double frontAngle = setting != null ? setting.HeadThetaFront : 0; + double downAngle = setting != null ? setting.HeadThetaDown : 90; + double avg = (frontAngle + downAngle) / 2.0; + double scale = (setting != null && setting.HeadThetaPlcScale != 0) ? setting.HeadThetaPlcScale : 1; + + bool useDown = false; + double thetaDeg = double.NaN; + if (plc != null && plc.IsActivated) + { + short raw = plc.ReadWord(eWordInput.HEAD_THETA_POSITION).data; // D70510 + thetaDeg = raw / scale; + useDown = thetaDeg > avg; // 평균보다 높으면 Down + } + else + { + log.W($"[{tag}] 각도 offset: PLC 미연결/미활성 → Front offset 적용(fallback)"); + } + + double selX = useDown ? sp.DownOffsetX : sp.FrontOffsetX; + double selY = useDown ? sp.DownOffsetY : sp.FrontOffsetY; + + log.I($"[{tag}] 각도 offset 선택: D70510={thetaDeg:F1}deg, avg={avg:F1} " + + $"(FrontAngle={frontAngle}, DownAngle={downAngle}) → {(useDown ? "Down" : "Front")} 적용 | " + + $"FrontOffset=({sp.FrontOffsetX},{sp.FrontOffsetY}) DownOffset=({sp.DownOffsetX},{sp.DownOffsetY}) " + + $"→ 적용=({selX},{selY})"); + + return (selX, selY); + } + } +} diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Manual/SeqComponentTeachingRun.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Manual/SeqComponentTeachingRun.cs index 0c95add..ca1b89e 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Manual/SeqComponentTeachingRun.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Manual/SeqComponentTeachingRun.cs @@ -17,6 +17,7 @@ using GUI; using Hermes; using ILSOS_PrismaticRD_PTOP.Device; using ILSOS_PrismaticRD_PTOP.Device.IO; +using ILSOS_PrismaticRD_PTOP.Settings; using Matrox.MatroxImagingLibrary; using OpenCvSharp.Dnn; using System; @@ -502,21 +503,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences { Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST); + // 각도(D70510) 기반 Front/Down offset 선택 (기존 단일 CellCenterOffset 대체) + var thetaOffsetSetting = context.GetSetting(SettingIds.LaserInterlock); + var (angleOffX, angleOffY) = LaserAngleOffsetSelector.Select(_plc, _SystemParameter, thetaOffsetSetting, Log, "ComponentTeachingRun"); + // TeachingRun에는 용접 시퀀스만 정의해서 Auto Run에서도 사용하므로 Offset 넣어줘야 함 SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() { NeedSetLaserTiming = _InitParam.NeedSetLaserTiming, IncludeListStartEnd = true, - CellMeasureCenterOffsetX = _SystemParameter.CellPosMeasureCenterOffsetX + _InitParam.CellCenterOffsetX, - CellMeasureCenterOffsetY = _SystemParameter.CellPosMeasureCenterOffsetY + _InitParam.CellCenterOffsetY, + CellMeasureCenterOffsetX = angleOffX + _InitParam.CellCenterOffsetX, + CellMeasureCenterOffsetY = angleOffY + _InitParam.CellCenterOffsetY, SelectedDrawObjectList = _DivideObjectsQueue.Peek(), WeldingScannerListNo = _WeldingScannerListNo, }; var _PageNo = _SequenceSetting.LastPageNo; - Log.W($"page {_PageNo}({Status.GetProcessingWeldingData().CellId}) : Cell Offset Value Check ; X : {_SystemParameter.CellPosMeasureCenterOffsetX} + {_InitParam.CellCenterOffsetX} ; Y : {_SystemParameter.CellPosMeasureCenterOffsetY} + {_InitParam.CellCenterOffsetY}"); - - Log.D(string.Format($"[ComponentTeachingRun] Set UploadWelindList Seq Param")); + Log.D(string.Format($"[ComponentTeachingRun] Set UploadWelindList Seq Param (page {_PageNo}, Cell {Status.GetProcessingWeldingData().CellId})")); return SequenceAction.Ok(seqParams); } diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs index 0138773..4a132b0 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs @@ -636,14 +636,17 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences } #endregion - // TeachingRun 에서는 CellMeasureCenterOffset 사용하지 않음. 0 으로 넘기자 + // 각도(D70510) 기반 Front/Down offset 선택 (기존 단일 CellCenterOffset 대체) + var thetaOffsetSetting = Application.Current.AuroraEnv().SettingEntry[SettingIds.LaserInterlock] as LaserInterlockSetting; + var (angleOffX, angleOffY) = LaserAngleOffsetSelector.Select(_PLC, _SystemParameter, thetaOffsetSetting, Log, "ExecuteWelding"); + SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() { NeedSetLaserTiming = _InitParam.NeedSetLaserTiming, IncludeListStartEnd = true, - CellMeasureCenterOffsetX = _SystemParameter.CellPosMeasureCenterOffsetX + _InitParam.CellCenterOffsetX, - CellMeasureCenterOffsetY = _SystemParameter.CellPosMeasureCenterOffsetY + _InitParam.CellCenterOffsetY, - SelectedDrawObjectList = _SelectedObjectList, // 이게 중요 포인트 + CellMeasureCenterOffsetX = angleOffX + _InitParam.CellCenterOffsetX, + CellMeasureCenterOffsetY = angleOffY + _InitParam.CellCenterOffsetY, + SelectedDrawObjectList = _SelectedObjectList, // 이게 중요 포인트 WeldingScannerListNo = _WeldingScannerListNo, }; @@ -761,12 +764,12 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences short thetaRaw = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION).data; double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드 double thetaDeg = thetaRaw / scale; - bool okH = Math.Abs(thetaDeg - thetaSetting.HeadThetaHorizontal) <= thetaSetting.HeadThetaTolerance; - bool okV = Math.Abs(thetaDeg - thetaSetting.HeadThetaVertical) <= thetaSetting.HeadThetaTolerance; - Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (H={thetaSetting.HeadThetaHorizontal}/V={thetaSetting.HeadThetaVertical}/±{thetaSetting.HeadThetaTolerance})"); - if (!okH && !okV) + 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})"); + if (!okFront && !okDown) { - Log.W($"[ExecuteWelding] HeadTheta 인터락 → 수평/수직 ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단"); + Log.W($"[ExecuteWelding] HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단"); blocked = true; } } diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Settings/LaserInterlockSetting.cs b/Machines/ILSOS_PrismaticRD_PTOP/Settings/LaserInterlockSetting.cs index 7fd46c2..cbaa34d 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Settings/LaserInterlockSetting.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Settings/LaserInterlockSetting.cs @@ -16,14 +16,14 @@ namespace ILSOS_PrismaticRD_PTOP.Settings [Browsable(true)] [ReadOnly(false)] [Category("Head Theta Interlock")] - [DisplayName("Horizontal Angle (deg)")] - public double HeadThetaHorizontal { get; set; } = 0; + [DisplayName("Front Angle (deg)")] + public double HeadThetaFront { get; set; } = 0; [Browsable(true)] [ReadOnly(false)] [Category("Head Theta Interlock")] - [DisplayName("Vertical Angle (deg)")] - public double HeadThetaVertical { get; set; } = 90; + [DisplayName("Down Angle (deg)")] + public double HeadThetaDown { get; set; } = 90; [Browsable(true)] [ReadOnly(false)] diff --git a/WeldingComponents/WeldingCommon/Settings/SystemParameter.cs b/WeldingComponents/WeldingCommon/Settings/SystemParameter.cs index 7ea33a7..a86b39c 100644 --- a/WeldingComponents/WeldingCommon/Settings/SystemParameter.cs +++ b/WeldingComponents/WeldingCommon/Settings/SystemParameter.cs @@ -387,13 +387,23 @@ namespace WeldingCommon.Settings [Browsable(true)] [ReadOnly(false)] [Category("CellPosMeasure")] - [DisplayName("CellCenterOffsetX")] - public double CellPosMeasureCenterOffsetX { get; set; } = 0; + [DisplayName("Front Offset X")] + public double FrontOffsetX { get; set; } = 0; [Browsable(true)] [ReadOnly(false)] [Category("CellPosMeasure")] - [DisplayName("CellCenterOffsetY")] - public double CellPosMeasureCenterOffsetY { get; set; } = 0; + [DisplayName("Front Offset Y")] + public double FrontOffsetY { get; set; } = 0; + [Browsable(true)] + [ReadOnly(false)] + [Category("CellPosMeasure")] + [DisplayName("Down Offset X")] + public double DownOffsetX { get; set; } = 0; + [Browsable(true)] + [ReadOnly(false)] + [Category("CellPosMeasure")] + [DisplayName("Down Offset Y")] + public double DownOffsetY { get; set; } = 0; [Category("Transfer")] [ReadOnly(false)] diff --git a/docs/작업일지/2026_06_24.md b/docs/작업일지/2026_06_24.md new file mode 100644 index 0000000..0ee4599 --- /dev/null +++ b/docs/작업일지/2026_06_24.md @@ -0,0 +1,18 @@ +# 작업일지 — 2026-06-24 + +## 각도(D70510) 기반 Laser 발진 Offset 분기 구현 + +- **배경**: D70510(카메라 헤드 각도, Head_Theta_Position)에 따라 Laser 발진 위치가 달라지는데, 기존엔 단일 Cell center offset 하나만 적용해 각도 변화 시 위치가 어긋남. → 각도에 따라 Front/Down 두 벌의 offset을 자동 선택 적용하도록 변경. +- **선택 로직**: `thetaDeg = D70510 / HeadThetaPlcScale` vs `avg = (Front angle + Down angle)/2`. + - `thetaDeg > avg` → **Down offset**, 이하 → **Front offset** 적용. + - PLC 미연결/미활성으로 각도 못 읽으면 **Front offset** 적용(fallback) + 경고 로그. +- **적용 범위**: 자동(`SeqAutoRunPTOP.SetUploadWeldingList_SeqParams`)·수동(`SeqComponentTeachingRun.SetUploadWeldingList_SeqParams`) 발진 모두. 발진 offset = `선택 Front/Down offset + 비전 offset(_InitParam.CellCenterOffset)` — 기존 SystemParameter 단일 offset을 **대체**. +- **변경 파일**: + - `Settings/LaserInterlockSetting.cs`: `HeadThetaHorizontal/Vertical` → `HeadThetaFront/Down` (DisplayName "Front/Down Angle (deg)") + - `WeldingCommon/Settings/SystemParameter.cs`: `CellPosMeasureCenterOffsetX/Y`(2개) → `FrontOffsetX/Y` + `DownOffsetX/Y`(4개) + - `Sequences/LaserAngleOffsetSelector.cs`(신규): 각도 비교 → offset 선택 + 로그 헬퍼 + - `Sequences/SeqAutoRunPTOP.cs` / `Sequences/Manual/SeqComponentTeachingRun.cs`: 헬퍼 호출로 offset 대체. (자동 인터락 참조·로그 라벨도 Horizontal/Vertical → Front/Down) + - `ILSOS_PrismaticRD_PTOP.csproj`: 신규 헬퍼 Compile Include 등록 +- **LOG**: 발진 시 Front/Down angle, Front/Down offset(x,y), 선택 결과를 한 줄로 기록. +- **검증**: Debug|x64 컴파일 통과. sub agent 점검 완료(Critical 없음, 옛 이름 잔존 참조 0건, 공유 모듈 부작용 없음). +- **⚠ 배포 전 주의**: 설정 클래스 프로퍼티명 rename으로 **기존 저장 설정값이 자동 이관되지 않음**(Front/Down offset·angle 기본값으로 리셋). 현장 적용 전 기존 보정값을 신규 Front/Down 필드에 **수동 재입력** 필요.