Browse Source

아이콘 추가

master
seuuung 2 weeks ago
parent
commit
2e32173db8
  1. BIN
      .vs/marking_gui.slnx/FileContentIndex/a26e37eb-5b6f-417f-8426-dfd11cf0e629.vsidx
  2. BIN
      .vs/marking_gui.slnx/v18/.suo
  3. 2
      .vs/marking_gui.slnx/v18/DocumentLayout.backup.json
  4. 2
      .vs/marking_gui.slnx/v18/DocumentLayout.json
  5. 361
      docs/developer_manual.md
  6. 69
      docs/knowledge_base.md
  7. 13
      marking_gui.Setup/Bundle.wxs
  8. 147
      marking_gui.Setup/Product.wxs
  9. 87
      marking_gui/.agents/skills/laser_marking/SKILL.md
  10. 60
      marking_gui/App.xaml.cs
  11. 23
      marking_gui/LoginOptions.json
  12. 312
      marking_gui/MainWindow.xaml
  13. 1825
      marking_gui/MainWindow.xaml.cs
  14. 3
      marking_gui/Manual/.obsidian/app.json
  15. 1
      marking_gui/Manual/.obsidian/appearance.json
  16. 33
      marking_gui/Manual/.obsidian/core-plugins.json
  17. 196
      marking_gui/Manual/.obsidian/workspace.json
  18. 67
      marking_gui/Manual/01_시스템_개요.md
  19. 117
      marking_gui/Manual/02_초기_설정.md
  20. 63
      marking_gui/Manual/03_메인_화면.md
  21. 37
      marking_gui/Manual/04_통신_설정.md
  22. 44
      marking_gui/Manual/05_시험_설정.md
  23. 53
      marking_gui/Manual/06_데이터_조회.md
  24. 75
      marking_gui/Manual/07_시험_프로세스.md
  25. 39
      marking_gui/Manual/08_로그_관리.md
  26. 59
      marking_gui/Manual/09_오류_대응.md
  27. 844
      marking_gui/Manual/Manual_Merged.md
  28. 974
      marking_gui/Manual/Manual_for_pdf.html
  29. 163
      marking_gui/Manual/build_pdf.py
  30. 213
      marking_gui/Manual/pdf_style.css
  31. 44
      marking_gui/Manual/사용자_메뉴얼.md
  32. BIN
      marking_gui/Marking.ico
  33. 70
      marking_gui/Models/MarkingRequest.cs
  34. 35
      marking_gui/Models/MarkingResult.cs
  35. 127
      marking_gui/Models/MeasurementLimits.cs
  36. 6
      marking_gui/Models/Product.cs
  37. 82
      marking_gui/PortSettingsWindow.xaml
  38. 156
      marking_gui/PortSettingsWindow.xaml.cs
  39. 15
      marking_gui/Properties/PublishProfiles/FolderProfile.pubxml
  40. 389
      marking_gui/Services/Board4251Service.cs
  41. 142
      marking_gui/Services/CommunicationBase.cs
  42. 406
      marking_gui/Services/DatabaseService.cs
  43. 417
      marking_gui/Services/EquipmentMeasurementService.cs
  44. 336
      marking_gui/Services/EzdMarkingService.cs
  45. 32
      marking_gui/Services/IMarkingService.cs
  46. 17
      marking_gui/Services/IScpiClient.cs
  47. 96
      marking_gui/Services/LoggerService.cs
  48. 171
      marking_gui/Services/LoginServicesStub.cs
  49. 259
      marking_gui/Services/MarkingConfigService.cs
  50. 30
      marking_gui/Services/MockDatabase.cs
  51. 75
      marking_gui/Services/MockMeasurer.cs
  52. 173
      marking_gui/Services/MssqlDatabaseService.cs
  53. 200
      marking_gui/Services/TcpScpiClient.cs
  54. 113
      marking_gui/Setting.ini
  55. 1
      marking_gui/SettingsWindow.xaml
  56. 8
      marking_gui/SettingsWindow.xaml.cs
  57. 220
      marking_gui/StartupLoginWindow.xaml
  58. 649
      marking_gui/StartupLoginWindow.xaml.cs
  59. BIN
      marking_gui/bin/Debug/marking_gui.exe
  60. 11
      marking_gui/bin/Debug/marking_gui.exe.config
  61. BIN
      marking_gui/bin/Debug/marking_gui.pdb
  62. 262
      marking_gui/marking_gui.csproj
  63. 363
      marking_gui/marking_gui_1wcr445j_wpftmp.csproj
  64. BIN
      marking_gui/marking_gui_TemporaryKey.pfx
  65. 363
      marking_gui/marking_gui_colf0m00_wpftmp.csproj
  66. 4
      marking_gui/obj/Debug/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs
  67. BIN
      marking_gui/obj/Debug/App.baml
  68. 77
      marking_gui/obj/Debug/App.g.cs
  69. 77
      marking_gui/obj/Debug/App.g.i.cs
  70. BIN
      marking_gui/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache
  71. BIN
      marking_gui/obj/Debug/MainWindow.baml
  72. 387
      marking_gui/obj/Debug/MainWindow.g.cs
  73. 387
      marking_gui/obj/Debug/MainWindow.g.i.cs
  74. BIN
      marking_gui/obj/Debug/SettingsWindow.baml
  75. 177
      marking_gui/obj/Debug/SettingsWindow.g.cs
  76. 177
      marking_gui/obj/Debug/SettingsWindow.g.i.cs
  77. BIN
      marking_gui/obj/Debug/StartupLoginWindow.baml
  78. 193
      marking_gui/obj/Debug/StartupLoginWindow.g.cs
  79. 193
      marking_gui/obj/Debug/StartupLoginWindow.g.i.cs
  80. BIN
      marking_gui/obj/Debug/marking_gui.Properties.Resources.resources
  81. BIN
      marking_gui/obj/Debug/marking_gui.csproj.AssemblyReference.cache
  82. 1
      marking_gui/obj/Debug/marking_gui.csproj.CoreCompileInputs.cache
  83. 41
      marking_gui/obj/Debug/marking_gui.csproj.FileListAbsolute.txt
  84. BIN
      marking_gui/obj/Debug/marking_gui.csproj.GenerateResource.cache
  85. BIN
      marking_gui/obj/Debug/marking_gui.exe
  86. BIN
      marking_gui/obj/Debug/marking_gui.g.resources
  87. BIN
      marking_gui/obj/Debug/marking_gui.pdb
  88. 20
      marking_gui/obj/Debug/marking_gui_MarkupCompile.cache
  89. 20
      marking_gui/obj/Debug/marking_gui_MarkupCompile.i.cache
  90. 4
      marking_gui/obj/Debug/marking_gui_MarkupCompile.i.lref
  91. 4
      marking_gui/obj/Debug/marking_gui_MarkupCompile.lref
  92. 15
      marking_installer/Empty.resx
  93. 161
      marking_installer/Program.cs
  94. 12
      marking_installer/app.manifest
  95. 38
      marking_installer/marking_installer.csproj
  96. 116
      program_flow.md
  97. 14
      sql/database_setup.sql

BIN
.vs/marking_gui.slnx/FileContentIndex/a26e37eb-5b6f-417f-8426-dfd11cf0e629.vsidx

Binary file not shown.

BIN
.vs/marking_gui.slnx/v18/.suo

Binary file not shown.

2
.vs/marking_gui.slnx/v18/DocumentLayout.backup.json

@ -29,7 +29,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml", "ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml",
"RelativeToolTip": "marking_gui\\MainWindow.xaml", "RelativeToolTip": "marking_gui\\MainWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
"WhenOpened": "2026-06-16T05:11:43.746Z", "WhenOpened": "2026-07-03T01:49:59.337Z",
"EditorCaption": "" "EditorCaption": ""
} }
] ]

2
.vs/marking_gui.slnx/v18/DocumentLayout.json

@ -29,7 +29,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml", "ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml",
"RelativeToolTip": "marking_gui\\MainWindow.xaml", "RelativeToolTip": "marking_gui\\MainWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
"WhenOpened": "2026-06-16T05:11:43.746Z", "WhenOpened": "2026-07-03T01:49:59.337Z",
"EditorCaption": "" "EditorCaption": ""
} }
] ]

361
docs/developer_manual.md

@ -0,0 +1,361 @@
# Performance Inspect & Laser Marking System - 개발자 메뉴얼
본 문서는 **양산형 테스트 기기 모니터링 시스템**의 유지보수, 릴리즈, 시스템 통합 및 디버깅을 위한 개발자용 상세 기술 사양서입니다. 시스템 설계 정보, 하드웨어 연동 규격, 그리고 제어 시퀀스를 포함하고 있습니다.
---
## 1. 시스템 아키텍처 및 요구사항
### 1.1 기술 스택
- **프레임워크**: .NET Framework 4.7.2 (WPF)
- **UI 패턴**: MVVM (MainWindow, SettingsWindow, StartupLoginWindow 등)
- **데이터베이스**: Microsoft SQL Server 2012 이상 (ADO.NET 연동)
- **장비 연동**:
- LMC1 레이저 각인 컨트롤러 (C++ Wrapped DLL `MarkEzdWrapper.dll` 로드)
- Keysight DMM & PSU (TCP/IP 기반 SCPI 통신)
- 4251 센서 판독 보드 (시리얼 RS-232 기반 고유 프로토콜)
### 1.2 개발 환경 구동 요구사항
- Visual Studio 2022 이상
- MSBuild 엔진 (XAML 컴파일 지원)
- MS-SQL Server Express 또는 로컬 DB 인프라
- EZCAD 2 소프트웨어 및 LMC USB 각인 카드 드라이버
---
## 2. 설정 파일 (INI) 상세 사양
실행 파일 경로에 위치하는 INI 설정 파일들의 각 항목별 역할 및 타입 정의입니다.
### 2.1 Setting.ini
계측 장비 접속 주소, SCPI 제어 명령어 매개변수 및 레이저 각인 도안 태그 매핑(`[Tags]`) 설정을 포함합니다.
```ini
; =================================================================
; 성능 검사 모니터링 시스템 - 레이저 마킹기 연동 설정 파일
; =================================================================
[Settings]
; 마킹 제어 라이브러리 및 드라이버(EzCad) 폴더 경로
EzdFolderPath=c:\EzCad
; 테스트 모드 여부 (true 시 하단 테스트 마킹, 강제 검사 버튼 노출)
test=false
[Tags]
; 도안(.ezd) 파일 내의 텍스트 오브젝트 치환용 태그명 매핑 리스트
; (도안에 정의된 오브젝트명과 동일하게 설정해야 정상 작동합니다)
; 일련번호 (Serial Number) 치환 태그
SerialTag=SERIAL
; 작업 년월일 (Lot No) 치환 태그
LotNoTag=YYMMDD
; 제품 모델명 (Product Name) 치환 태그
ProductTag=PRODUCT
; 2D QR 코드 데이터 치환 태그
QrCodeTag=QRCODETAG
; 검사 라인 코드 치환 태그
LineTag=LCODE
; 작업 조 (Shift) 치환 태그
ShiftTag=BAN
; =================================================================
; 계측기 연동 설정 (SCPI / LAN)
; =================================================================
[Equipment]
; 계측기 통신 타임아웃 (ms)
Timeout=5000
; 측정 안정화 대기 시간 (ms) — Power Supply ON 후 이 시간만큼 대기 후 읽음
SettleMilliseconds=2000
; 검사 전 보드 전원 공급 커맨드 (세미콜론으로 구분)
PreBoardCommand=VOLT 5, (@2);OUTP ON, (@2)
PreBoardDelayMilliseconds=2000
; 로그아웃 시 전원 차단 커맨드
LogoutCommand=OUTP OFF, (@1)
[DMM]
; Digital Multimeter — 전압 및 저항 측정 전용 (예시 IP: 192.168.1.100)
VoltageConnection=LAN
VoltageHost=192.168.1.100
VoltagePort=5025
VoltageIdnMatch=34465A
VoltageBaudRate=115200
VoltageTerminator=LF
VoltageSetupCommand=
VoltageReadCommand=MEAS:VOLT:DC?
ResistanceReadCommand=MEAS:RES?
[CurrentDMM]
; Digital Multimeter — 전류 측정 전용 (예시 IP: 192.168.1.102 - 현재 제어 시퀀스에서 미사용)
CurrentDMMConnection=LAN
CurrentDMMHost=192.168.1.102
CurrentDMMPort=5025
CurrentDMMIdnMatch=34465A
CurrentDMMBaudRate=115200
CurrentDMMTerminator=LF
CurrentDMMReadCommand=MEAS:CURR:DC?
[PowerSupply]
; Power Supply — 출력 제어 및 전류 측정 (예시 IP: 192.168.1.101)
CurrentConnection=LAN
CurrentHost=192.168.1.101
CurrentPort=5025
CurrentIdnMatch=E36233A
CurrentBaudRate=9600
CurrentTerminator=LF
CurrentSetupCommand=OUTP ON, (@1)
CurrentCleanupCommand=OUTP OFF, (@1)
CurrentReadCommand=MEAS:CURR? (@1)
[Limits]
; 최종 계측 합/불 판정 규격 임계치 (Min: 하한치, Max: 상한치)
; UI [시험 설정] 메뉴에서 변경 시 자동 저장 및 갱신됩니다.
MinVoltage=4.0
MaxVoltage=6.0
MinCurrent=8.0
MaxCurrent=10.0
MinResistance=0.4
MaxResistance=0.8
```
### 2.2 Database.ini
생산 이력 조회 및 적재 목적의 DB 연결 설정입니다.
```ini
[Database]
# SQL 데이터베이스 접속 주소 및 계정 정보 (예시 주소)
Db_Server=192.168.1.200
Db_Name=MarkingDb
Db_User=db_user
Db_Password=db_password
# 로컬 테스트 및 DB 점검 시 우회용 플래그
OfflineMode=False
[Login]
# 작업자 로그인 검증 시 호출할 저장 프로시저명
Procedure=dbo.CheckOperator
# 작업 공정 식별 이름 (저장 프로시저 매개변수 바인딩용)
ProcessName=Marking
# 오프라인 로그인 미리보기 활성화 여부
OfflinePreview=false
```
### 2.3 Sensor.ini
4251 센서 통신 보드 시리얼 통신을 위한 포트 매핑 파일입니다.
```ini
[Sensor]
# 시리얼 COM 포트 번호
Port=COM3
# 통신 속도
BaudRate=115200
# 보드 내부 멀티 채널 스위칭 인덱스 (1 또는 2)
Channel=1
```
---
## 3. 데이터베이스 스키마 및 연동 규격
### 3.1 테이블 구성 정보
- **`[dbo].[Users]` (작업자 계정 정보 테이블)**
- `OperatorId` (VARCHAR(50), UNIQUE): 작업자 로그인 ID
- `Password` (VARCHAR(50)): 비밀번호
- `Name` (VARCHAR(50)): 작업자 성명
- `IsActive` (BIT): 활성 상태 여부
- **`[dbo].[Marking]` (최종 성능 계측값 및 마킹 결과 저장 테이블)**
- `IC_SN` (VARCHAR(50), PRIMARY KEY): 센서 일련번호
- `Marking_QR` (VARCHAR(100), UNIQUE): 고유 QR 코드 문자열 (yyMMdd + 일련번호)
- `Maker` (VARCHAR(50)), `Model` (VARCHAR(50)): 제품 사양 정보
- `Variant_1`, `Variant_2` (VARCHAR(50))
- `Operator` (VARCHAR(50)): 담당 작업자 ID
- `Production_Date` (DATETIME): 생산 일자 및 계측 시간
- `Line` (VARCHAR(10)): 생산 라인 (최대 10자 제한, 초과 시 코드 내에서 절삭)
- `Lot_No` (VARCHAR(8)): 로트 번호 (최대 8자 제한, 초과 시 코드 내에서 절삭)
- `Jig_No` (VARCHAR(10)): 지그 번호
- `PT_Vol_1` (DECIMAL(18,2)), `PT_Current_1` (DECIMAL(18,2)), `PT_Resistance_1` (DECIMAL(18,3)): 실측 전압, 전류, 저항값
- `Result` (VARCHAR(10)): 최종 종합 판정 (`PASS` 또는 `FAIL`)
- `MarkedSerial` (VARCHAR(50)): 최종 마킹된 시퀀스 코드
### 3.2 이전 공정 결과 교차 검증 (Subquery)
검사 개시 시점, 제품의 S/N(`IC_SN`)을 기반으로 선행 공정이 완료되었는지 조회합니다.
```sql
SELECT
(SELECT Result FROM Housing_Assembly WHERE IC_SN = @IC_SN) AS HousingResult,
(SELECT Final_Result FROM Cal_EOL_Result WHERE IC_SN = @IC_SN) AS CalResult,
(SELECT Result FROM EOL WHERE IC_SN = @IC_SN) AS EolResult
```
- **판정 조건**: 조회 결과 3개 항목이 모두 `PASS` 또는 `OK` 상태이어야 최종 검사 기동이 허용됩니다.
### 3.3 로그인 저장 프로시저 (`[dbo].[CheckOperator]`)
- **프로시저 스펙**:
```sql
CREATE PROCEDURE [dbo].[CheckOperator]
@Operator VARCHAR(50),
@Password VARCHAR(50),
@ProcessName VARCHAR(50) = NULL
AS
BEGIN
SELECT COUNT(*)
FROM Users
WHERE OperatorId = @Operator
AND Password = @Password
AND IsActive = 1
END
```
- **호출 매핑 코드**:
ADO.NET `SqlCommand` 객체를 사용하여 `CommandType.StoredProcedure` 형식으로 호출하며 리턴값이 `1`일 경우 검증 처리합니다.
---
## 4. 하드웨어 제어 프로토콜 및 SCPI 사양
### 4.1 4251 센서 통신 보드 시리얼 패킷 규격
센서 일련번호를 추출하고 보드를 제어하기 위해 RS-232 시리얼(115200bps)을 통해 다음 패킷 명령을 전송합니다.
1. **보드 대기 및 리셋 명령**: `"x00o"`
2. **보드 채널 결속 명령**: `"x00c_00{channel}101:owt28006727ea97c7801"`
- `{channel}``Sensor.ini`에서 파싱한 값 (예: `1` 또는 `2`)
3. **고유 ID(IC_SN) 읽기 명령**: `"x00c_00{channel}101:ow2800326003e"`
- 송신 후 보드 응답 버퍼에서 16진수 일련번호 데이터(16자)를 추출합니다.
4. **보정 기본값 전송**: `"x00c_00{channel}001:owt28006727ea97c7801"`
### 4.2 계측 장비 제어 SCPI 명령어 구성 및 시퀀스
모든 TCP/IP 계측기는 포트 **5025**를 통해 SCPI 표준 명령으로 조작됩니다.
1. **장비 연결 및 아이덴티티 검증 (Handshake)**:
- 각 계측기 접속 후 `*IDN?` 명령을 전송하여 응답 텍스트에 `Setting.ini`에 지정된 장비 모델명(예: `34465A`, `E36233A`)이 수록되어 있는지 확인합니다.
2. **테스트 주전원(CH1) 인가**:
- `OUTP ON, (@1)`
- 전원 공급 후 신호 안정을 위해 `SettleMilliseconds`(기본 2000ms, 즉 2초)만큼 대기합니다.
3. **측정 데이터 수집**:
- **전압(V)**: 전압 DMM(예: 192.168.1.100)에 `MEAS:VOLT:DC?`를 질의하여 획득.
- **저항(Ω)**: 저항 DMM(예: 192.168.1.100)에 `MEAS:RES?`를 질의하여 획득. (획득값은 kΩ 단위 조정을 위해 1000으로 나눔)
- **전류(A)**: 파워서플라이(예: 192.168.1.101)의 CH1에 `MEAS:CURR? (@1)`을 질의하여 획득. (획득값은 mA 단위 조정을 위해 1000을 곱함)
- *주의: 전류 측정 전용 DMM(예: 192.168.1.102) 장비는 설정 파일에 로드되나 실제 측정 시퀀스 상에서는 사용되지 않습니다.*
4. **주전원(CH1) 차단**:
- `OUTP OFF, (@1)`
- 계측 도중 예외가 발생하더라도 `finally` 구문 내에서 차단 명령을 전송합니다.
---
## 5. 레이저 마킹기 연동 사양 (LMC1 API)
C++로 개발된 레이저 각인기 드라이버 DLL인 `MarkEzdWrapper.dll`을 P/Invoke를 통해 호출합니다.
### 5.1 연동 시 핵심 규칙
1. **세션 핸들 (`hHandle`) 관리**:
- 프로그램 시작 시 `InitMarkEzd()` 함수를 호출하여 세션 포인터(`IntPtr`)를 받아 전역 변수로 관리합니다.
- 각인 관련 API 함수 호출 시 이 `IntPtr` 핸들을 첫 번째 매개변수로 전달해야 합니다.
2. **유니코드 마샬링 (`CharSet.Unicode`)**:
- C++ DLL 단의 모든 문자열 인자가 `const wchar_t*` (유니코드)를 사용하므로, DllImport 선언의 문자셋 속성은 `CharSet = CharSet.Unicode`로 설정해야 정상 작동합니다.
3. **HWND 부모 창 핸들 지연 초기화**:
- `Init` 함수는 부모 WPF 윈도우의 핸들(`IntPtr`)을 요구합니다.
- Window 생성자 시점에는 핸들이 미생성 상태이므로, 반드시 **`Dispatcher.BeginInvoke(..., DispatcherPriority.Loaded)`**를 활용하여 메인 윈도우 로드가 완료된 후에 핸들을 추출해 초기화해야 합니다.
### 5.2 DLL 가져오기 (P/Invoke) 선언 명세
```csharp
[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr InitMarkEzd();
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)]
private static extern int Mark(IntPtr hHandle);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)]
private static extern int Close(IntPtr hHandle);
```
---
## 6. 데모용 시뮬레이션 격리 및 릴리즈 배포 가이드
### 6.1 데모 모드 격리 코드 해제 방법
상용 빌드 릴리즈 시, 데이터베이스에 실제 로그인하지 않고 통과하는 데모 우회 로직을 소스 코드 단에서 제거해야 합니다.
1. **[LoginServicesStub.cs](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/marking_gui/Services/LoginServicesStub.cs)** 파일을 엽니다.
2. `ExistsAsync` 메서드 안의 `catch (Exception ex)` 구문 내부에 위치한 아래의 **DEMO MODE 주석 배너** 영역 코드를 삭제하거나 주석 처리하여 릴리즈 빌드를 만드십시오.
```csharp
// ==================== [DEMO MODE START] ====================
// 데모 시뮬레이션용 프로시저 성공 연출 코드
// 상용 릴리즈 시 이 블록은 완전히 제거(삭제)할 수 있습니다.
if (!string.IsNullOrWhiteSpace(_loginSettings.Procedure))
{
System.Diagnostics.Debug.WriteLine(string.Format("[데모 시뮬레이션] 프로시저 '{0}' 호출 모사 (ID: {1}, PW: {2}, Process: {3})",
_loginSettings.Procedure, operatorId, password, _loginSettings.ProcessName));
return IsDefaultAccount(operatorId, password);
}
// ==================== [DEMO MODE END] ====================
```
### 6.2 릴리즈 게시 및 단일 설치 파일(MarkingSetup.exe) 자동 생성 가이드
본 프로젝트는 Visual Studio 게시 프로필 및 WiX Toolset 커스텀 타깃이 적용되어 있어, CLI 게시 명령 한 번으로 단일 실행 설치 파일(`MarkingSetup.exe`)을 자동으로 컴파일 및 생성합니다.
- **권장 CLI 게시 명령어 (Terminal)**:
```powershell
dotnet publish marking_gui/marking_gui.csproj -c Release -r win-x86 --self-contained
```
- **자동 빌드 및 패키징 동작 순서**:
1. 프로젝트 릴리즈 바이너리 및 의존 라이브러리 일체 자동 수집
2. `C:\marking\libs\` 경로로 모든 런타임 DLL 격리 구성
3. WiX Toolset(`wix build`)이 가동하여 MSI 패키지 및 단일 EXE 부트스트랩 번들 컴파일
4. 최종 설치 파일 `MarkingSetup.exe``C:\marking\``bin\Release\net9.0-windows\win-x86\publish\` 폴더로 자동 생성 이관
- **배포 및 전달 대상 파일**:
- `MarkingSetup.exe` (단일 파일만 현장에 전달하여 1-Click 설치 진행)
### 6.3 배포 설치 디렉터리(`C:\marking\`) 파일 구조 및 런타임 생성 폴더 사양
`MarkingSetup.exe`로 인스톨된 후 `C:\marking\` 아래에 배치되는 파일 및 런타임 가동 중 생성되는 디렉터리 명세입니다.
```
C:\marking\
├── marking_gui.exe # 메인 WPF 실행 바이너리
├── marking_gui.dll # 핵심 애플리케이션 어셈블리
├── marking_gui.deps.json # 종속성 매니페스트
├── marking_gui.runtimeconfig.json # .NET 9.0 런타임 환경 구성
├── Setting.ini # 마킹기 경로, 태그 매핑, 계측기 IP, SCPI 전원, [Limits] 판정 임계치 종합 설정
├── Database.ini # MS-SQL 접속 계정 및 오프라인 모드(OfflineMode) 설정
├── Sensor.ini # 4251 센서 통신 보드 시리얼 포트(COM), BaudRate, 채널 설정
├── libs\ # C++ LMC 드라이버 라이브러리 격리 폴더
│ ├── MarkEzdWrapper.dll # C# - C++ LMC P/Invoke 래퍼
│ ├── DataMgr.dll # EZCAD 데이터 매니저
│ ├── MarkEzd.dll # EZCAD 커널 마킹 라이브러리
│ └── haspms32.dll # 동글/라이선스 보안 라이브러리 일체
├── logs\ # 런타임 가동 중 일별 자동 생성되는 시스템/통신/마킹 이력 로그
│ └── 2026-07-23.log # 텍스트 형태의 일자별 로그 파일
└── publish_backup\ # 게시 및 버전 업데이트 시 기존 파일 자동 백업 보관 폴더
```

69
docs/knowledge_base.md

@ -69,20 +69,35 @@
``` ```
4. 삭제 후 빌드를 다시 수행하면, DB 통신 실패 시 로그인이 우회되지 않고 정상적으로 "로그인 DB 확인 실패" 경고가 발생하게 됩니다. 4. 삭제 후 빌드를 다시 수행하면, DB 통신 실패 시 로그인이 우회되지 않고 정상적으로 "로그인 DB 확인 실패" 경고가 발생하게 됩니다.
### 3.2. 빌드 실행 방법 ### 3.2. 릴리즈 게시 및 단일 설치 파일 패키징 가이드
본 프로젝트는 레거시 .NET Framework 4.7.2 타깃의 WPF 애플리케이션으로, XAML 마크업 빌드가 정상 동작하기 위해서는 Visual Studio 환경의 MSBuild 엔진이 필요합니다. 본 프로젝트는 **.NET 9.0 (WPF)** 타깃의 32비트(x86) 자립형 애플리케이션으로, 프로젝트 파일([marking_gui.csproj](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/marking_gui/marking_gui.csproj))에 WiX Toolset 단일 통합 설치 마법사 컴파일 타깃이 통합되어 있습니다.
- **MSBuild 권장 경로:** `C:\Program Files\Microsoft Visual Studio\18\Insiders\MSBuild\Current\Bin\MSBuild.exe` (설치 버전에 따라 Insiders, Community, Professional 등으로 달라질 수 있음)
- **빌드 명령어 (터미널):** - **권장 CLI 게시 명령어 (터미널):**
```powershell ```powershell
& "C:\Program Files\Microsoft Visual Studio\18\Insiders\MSBuild\Current\Bin\MSBuild.exe" /t:rebuild dotnet publish marking_gui/marking_gui.csproj -c Release -r win-x86 --self-contained
``` ```
- **출력 경로:** 빌드 완수 후 `marking_gui\bin\Debug\` 또는 `Release\` 폴더에 실행 바이너리 및 `Database.ini` 파일이 함께 생성됩니다. - **빌드 및 패키징 동작 메커니즘:**
1. `.NET 9.0 런타임`이 포함된 자립형(Self-Contained) 싱글 실행 바이너리를 빌드합니다.
2. 계측 및 각인기 드라이버 DLL들을 `C:\marking\libs\` 디렉터리로 격리 수집합니다.
3. `wix build`가 백그라운드에서 동작하여 `Product.wxs``Bundle.wxs`를 단일 실행 설치 파일(`MarkingSetup.exe`)로 원스톱 패키징합니다.
- **출력 경로 및 최종 결과물:**
- `C:\marking\MarkingSetup.exe``marking_gui\bin\Release\net9.0-windows\win-x86\publish\MarkingSetup.exe`
- 현장 라인에는 단일 파일 `MarkingSetup.exe`만 배포하여 1-Click 설치를 진행할 수 있습니다.
### 3.3. 설치 경로(`C:\marking\`) 내 파일 구성 및 런타임 생성 폴더
- `marking_gui.exe` (메인 실행 파일) 및 .NET 9.0 런타임 DLL
- `Setting.ini` (장비 IP, 마킹 태그, SCPI 커맨드, `[Limits]` 규격 임계치)
- `Database.ini` (DB 접속 주소 및 `OfflineMode=true` 설정)
- `Sensor.ini` (4251 센서 통신 보드 COM 포트/속도/채널)
- `libs/` (LMC 레이저 각인 드라이버 격리 디렉터리: `MarkEzdWrapper.dll` 등)
- `logs/` (런타임 가동 시 일자별 자동 생성되는 시스템/각인/통신 이력 로그 폴더)
- `publish_backup/` (빌드 배포 시 이전 릴리즈 자동 백업 폴더)
--- ---
## 4. 데이터베이스 구성 가이드 (Database Configuration) ## 4. 데이터베이스 구성 가이드 (Database Configuration)
본 시스템은 **Microsoft SQL Server 2012** 이상의 버전에 완전히 호환되도록 구성되었습니다. 데이터베이스 구성 시 [docs/database_setup.sql](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/docs/database_setup.sql) 스크립트를 활용할 수 있습니다. 본 시스템은 **Microsoft SQL Server 2012** 이상의 버전에 완전히 호환되도록 구성되었습니다. 데이터베이스 구성 시 [sql/database_setup.sql](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/sql/database_setup.sql) 스크립트를 활용할 수 있습니다.
### 4.1. 데이터베이스 및 스키마 구조 ### 4.1. 데이터베이스 및 스키마 구조
- **데이터베이스명:** `MarkingDb` - **데이터베이스명:** `MarkingDb`
@ -103,3 +118,43 @@
- `@ProcessName` (VARCHAR(50)): 호출 프로세스명 (선택사항, 기본값 NULL) - `@ProcessName` (VARCHAR(50)): 호출 프로세스명 (선택사항, 기본값 NULL)
- **리턴:** 매칭되는 활성 사용자가 존재할 시 `1`, 존재하지 않을 시 `0`을 스칼라 결과로 반환합니다. - **리턴:** 매칭되는 활성 사용자가 존재할 시 `1`, 존재하지 않을 시 `0`을 스칼라 결과로 반환합니다.
---
## 5. 레이저 각인기 연동 규격 및 구현 사양 (Laser Marking Device Integration)
본 프로그램은 **LMC1 레이저 각인 컨트롤러 보드** 제어를 위해 C++ DLL Wrapper인 `MarkEzdWrapper.dll`을 활용하며, 하드웨어와의 메모리 정합성 및 실기 작동 안전성을 완벽히 조율한 최종 기술 사양은 다음과 같습니다.
### 5.1. 핵심 연동 규칙 및 메커니즘
1. **IntPtr 기반의 기기 세션 핸들(hHandle) 관리**:
- `InitMarkEzd()` 함수를 기동하여 생성되는 장치 관리 메모리 세션 포인터(`IntPtr`)를 받아 전역 변수에 저장합니다.
- 이후 모든 연동 API(`Init`, `LoadEzdFile`, `ChangeTextByName`, `Mark`, `Close`)의 첫 번째 매개변수로 이 `IntPtr` 핸들을 인계하여 호출해야 합니다. 임의의 정수 값(`0` 등)이나 `ref int` 를 대입하여 호출하면 메모리 위반 크래시(`AccessViolationException`)가 발생합니다.
2. **유니코드(`CharSet.Unicode`) 마샬링 준수**:
- C++ DLL 단의 모든 문자열 인자는 `const wchar_t*` (유니코드)를 사용합니다.
- 따라서 모든 DllImport 선언의 문자셋 속성은 반드시 **`CharSet = CharSet.Unicode`** 여야 합니다. `Ansi`로 지정할 시 경로명 및 파일명이 한자 깨진 글자로 읽혀 `EZCAD.CFG failed` 에러를 냅니다.
3. **WPF 윈도우 핸들(HWND) 지연 초기화**:
- `Init` API 호출 시 실제 유효한 부모 윈도우 핸들(`IntPtr windowHandle`)이 필요합니다.
- MainWindow의 생성자 시점에는 핸들이 미생성 상태(`IntPtr.Zero`)이므로 즉시 초기화하면 안 됩니다.
- 반드시 WPF **`Dispatcher.BeginInvoke(..., DispatcherPriority.Loaded)`** 지연 큐를 활용하여 메인 윈도우 로드가 완료된 후에 유효한 창 핸들을 추출해 이니셜라이즈 해야 합니다.
### 5.2. P/Invoke C# 선언 명세
```csharp
[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr InitMarkEzd();
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)]
private static extern int Mark(IntPtr hHandle);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)]
private static extern int Close(IntPtr hHandle);
```

13
marking_gui.Setup/Bundle.wxs

@ -0,0 +1,13 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal">
<Bundle Name="Marking System" Version="1.0.0" Manufacturer="MobiDigm" UpgradeCode="51A2E3B4-F5C6-4A7B-8C9D-0E1F2A3B4C5D" IconSourceFile="C:\marking\Marking.ico">
<BootstrapperApplication>
<bal:WixStandardBootstrapperApplication LicenseUrl="https://www.mobidigm.co.kr" Theme="hyperlinkLicense" SuppressOptionsUI="yes" />
</BootstrapperApplication>
<Chain>
<!-- 빌드 프로세스 하위에 생성된 MSI 패키지를 래핑 -->
<MsiPackage SourceFile="C:\marking\MarkingSetup.msi" Compressed="yes" Vital="yes" />
</Chain>
</Bundle>
</Wix>

147
marking_gui.Setup/Product.wxs

@ -0,0 +1,147 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="Marking System" Language="1042" Version="1.0.0" Manufacturer="MobiDigm" UpgradeCode="62B3F4C5-0D1E-5B8C-9D0A-1F2E3A4B5C6D" Scope="perMachine">
<MajorUpgrade DowngradeErrorMessage="이미 최신 버전의 프로그램이 설치되어 있습니다." />
<MediaTemplate EmbedCab="yes" />
<!-- 제어판 프로그램 및 바로가기용 아이콘 정의 -->
<Icon Id="ProductIcon.ico" SourceFile="C:\marking\Marking.ico" />
<Property Id="ARPPRODUCTICON" Value="ProductIcon.ico" />
<!-- 바탕화면 바로가기용 표준 디렉토리 지정 -->
<StandardDirectory Id="DesktopFolder" />
<!-- C 드라이브 루트 하위에 marking 폴더 생성 강제 고정 -->
<StandardDirectory Id="TARGETDIR" />
<SetDirectory Id="MarkingFolder" Value="C:\marking" />
<Directory Id="MarkingFolder">
<!-- 하위 폴더 구조 정의 -->
<Directory Id="libsFolder" Name="libs" />
</Directory>
<!-- 1. C:\marking 루트 파일 구성 -->
<ComponentGroup Id="RootComponents" Directory="MarkingFolder">
<Component Id="MainExecutable" Guid="73C4A5D6-1E2F-6C9D-0A1B-2C3D4E5F6A7B">
<File Id="marking_gui.exe" Source="C:\marking\marking_gui.exe" KeyPath="yes">
<Shortcut Id="DesktopShortcut" Directory="DesktopFolder" Name="Marking System" WorkingDirectory="MarkingFolder" Advertise="no" Icon="ProductIcon.ico" />
</File>
</Component>
<Component Id="ConfigFile" Guid="84D5B6E7-2F3A-7D0A-1B2C-3D4E5F6A7B8C">
<File Id="marking_gui.dll.config" Source="C:\marking\marking_gui.dll.config" KeyPath="yes" />
</Component>
<Component Id="DatabaseIni" Guid="95E6C7F8-3A4B-8E1B-2C3D-4E5F6A7B8C9D">
<File Id="Database.ini" Source="C:\marking\Database.ini" KeyPath="yes" />
</Component>
<Component Id="SettingIni" Guid="06F7D8A9-4B5C-9F2B-3C4D-5E6F7A8B9C0D">
<File Id="Setting.ini" Source="C:\marking\Setting.ini" KeyPath="yes" />
</Component>
<Component Id="LoginOptionsJson" Guid="17A8B9C0-D1E2-3F4A-5B6C-7D8E9F0A1B2C">
<File Id="LoginOptions.json" Source="C:\marking\LoginOptions.json" KeyPath="yes" />
</Component>
<Component Id="MarkingIcoFile" Guid="28B9C0D1-E2F3-4A5B-6C7D-8E9F0A1B2C3D">
<File Id="Marking.ico" Source="C:\marking\Marking.ico" KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- 2. C:\marking\libs 하위 DLL 구성 -->
<ComponentGroup Id="LibComponents" Directory="libsFolder">
<Component Id="libs_DataMgr" Guid="11111111-1111-1111-1111-111111111111">
<File Id="libs_DataMgr.dll" Source="C:\marking\libs\DataMgr.dll" KeyPath="yes" />
</Component>
<Component Id="libs_haspms32" Guid="22222222-2222-2222-2222-222222222222">
<File Id="libs_haspms32.dll" Source="C:\marking\libs\haspms32.dll" KeyPath="yes" />
</Component>
<Component Id="libs_HVAPI" Guid="33333333-3333-3333-3333-333333333333">
<File Id="libs_HVAPI.dll" Source="C:\marking\libs\HVAPI.dll" KeyPath="yes" />
</Component>
<Component Id="libs_HVDAILT" Guid="44444444-4444-4444-4444-444444444444">
<File Id="libs_HVDAILT.dll" Source="C:\marking\libs\HVDAILT.dll" KeyPath="yes" />
</Component>
<Component Id="libs_JczDog" Guid="55555555-5555-5555-5555-555555555555">
<File Id="libs_JczDog.dll" Source="C:\marking\libs\JczDog.dll" KeyPath="yes" />
</Component>
<Component Id="libs_Lmc1" Guid="66666666-6666-6666-6666-666666666666">
<File Id="libs_Lmc1.dll" Source="C:\marking\libs\Lmc1.dll" KeyPath="yes" />
</Component>
<Component Id="libs_LMCMIO" Guid="77777777-7777-7777-7777-777777777777">
<File Id="libs_LMCMIO.dll" Source="C:\marking\libs\LMCMIO.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MarkEzd" Guid="88888888-8888-8888-8888-888888888888">
<File Id="libs_MarkEzd.dll" Source="C:\marking\libs\MarkEzd.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MarkEzdWrapper_Copy" Guid="99999999-9999-9999-9999-999999999999">
<File Id="libs_MarkEzdWrapper_Copy.dll" Source="C:\marking\libs\MarkEzdWrapper - 복사본.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MarkEzdWrapper" Guid="00000000-0000-0000-0000-000000000000">
<File Id="libs_MarkEzdWrapper.dll" Source="C:\marking\libs\MarkEzdWrapper.dll" KeyPath="yes" />
</Component>
<Component Id="libs_mathlib" Guid="12121212-1212-1212-1212-121212121212">
<File Id="libs_mathlib.dll" Source="C:\marking\libs\mathlib.dll" KeyPath="yes" />
</Component>
<Component Id="libs_mfc42u" Guid="23232323-2323-2323-2323-232323232323">
<File Id="libs_mfc42u.dll" Source="C:\marking\libs\mfc42u.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MFC71" Guid="34343434-3434-3434-3434-343434343434">
<File Id="libs_MFC71.dll" Source="C:\marking\libs\MFC71.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MilProcess" Guid="45454545-4545-4545-4545-454545454545">
<File Id="libs_MilProcess.dll" Source="C:\marking\libs\MilProcess.dll" KeyPath="yes" />
</Component>
<Component Id="libs_Msvcp71" Guid="56565656-5656-5656-5656-565656565656">
<File Id="libs_Msvcp71.dll" Source="C:\marking\libs\Msvcp71.dll" KeyPath="yes" />
</Component>
<Component Id="libs_Msvcr71" Guid="67676767-6767-6767-6767-676767676767">
<File Id="libs_Msvcr71.dll" Source="C:\marking\libs\Msvcr71.dll" KeyPath="yes" />
</Component>
<Component Id="libs_msvcrt" Guid="78787878-7878-7878-7878-787878787878">
<File Id="libs_msvcrt.dll" Source="C:\marking\libs\msvcrt.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MVAPI" Guid="89898989-8989-8989-8989-898989898989">
<File Id="libs_MVAPI.dll" Source="C:\marking\libs\MVAPI.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MVBayerDec" Guid="90909090-9090-9090-9090-909090909090">
<File Id="libs_MVBayerDec.dll" Source="C:\marking\libs\MVBayerDec.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MVCAPI" Guid="01010101-0101-0101-0101-010101010101">
<File Id="libs_MVCAPI.dll" Source="C:\marking\libs\MVCAPI.dll" KeyPath="yes" />
</Component>
<Component Id="libs_MVParm" Guid="13131313-1313-1313-1313-131313131313">
<File Id="libs_MVParm.dll" Source="C:\marking\libs\MVParm.dll" KeyPath="yes" />
</Component>
<Component Id="libs_NcPath" Guid="24242424-2424-2424-2424-242424242424">
<File Id="libs_NcPath.dll" Source="C:\marking\libs\NcPath.dll" KeyPath="yes" />
</Component>
<Component Id="libs_QDib" Guid="35353535-3535-3535-3535-353535353535">
<File Id="libs_QDib.dll" Source="C:\marking\libs\QDib.dll" KeyPath="yes" />
</Component>
<Component Id="libs_QLib" Guid="46464646-4646-4646-4646-464646464646">
<File Id="libs_QLib.dll" Source="C:\marking\libs\QLib.dll" KeyPath="yes" />
</Component>
<Component Id="libs_Sense3" Guid="57575757-5757-5757-5757-575757575757">
<File Id="libs_Sense3.dll" Source="C:\marking\libs\Sense3.dll" KeyPath="yes" />
</Component>
<Component Id="libs_SQLite_Interop" Guid="68686868-6868-6868-6868-686868686868">
<File Id="libs_SQLite.Interop.dll" Source="C:\marking\libs\SQLite.Interop.dll" KeyPath="yes" />
</Component>
<Component Id="libs_System_Data_SQLite" Guid="79797979-7979-7979-7979-797979797979">
<File Id="libs_System.Data.SQLite.dll" Source="C:\marking\libs\System.Data.SQLite.dll" KeyPath="yes" />
</Component>
<Component Id="libs_UseSapera" Guid="91919191-9191-9191-9191-919191919191">
<File Id="libs_UseSapera.dll" Source="C:\marking\libs\UseSapera.dll" KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- 설치 대상 패키지 Feature 지정 -->
<Feature Id="ProductFeature" Title="Marking System Setup" Level="1">
<ComponentGroupRef Id="RootComponents" />
<ComponentGroupRef Id="LibComponents" />
</Feature>
</Package>
</Wix>

87
marking_gui/.agents/skills/laser_marking/SKILL.md

@ -0,0 +1,87 @@
---
name: laser_marking_integration
description: Guidelines and P/Invoke signatures for integrating the LMC laser marking machine via MarkEzdWrapper.dll without crashes.
---
# LMC 레이저 각인기 연동 규격 및 에이전트 행동 규칙
이 규칙은 `MarkEzdWrapper.dll` 파일과 WPF 모니터링 시스템 간의 레이저 각인(마킹) 장치 연동 설계 사양을 고정합니다. **이 사양은 어떠한 경우에도 임의로 단순 값 형식의 int 핸들이나 Ansi 마샬링으로 되돌려서는 안 되며, 반드시 이 명세를 유지해야 합니다.**
---
## 1. 연동 핵심 원칙 (Core Rules)
1. **IntPtr 기반의 기기 세션 핸들 관리**:
- `InitMarkEzd()` 함수를 최초 실행하여 생성되는 장치 관리 메모리 세션 포인터(`IntPtr`)를 받아 전역 변수에 저장합니다.
- 이후 모든 연동 API(`Init`, `LoadEzdFile`, `ChangeTextByName`, `Mark`, `Close`)의 첫 번째 매개변수로 이 `IntPtr` 핸들을 인계하여 호출해야 합니다. 임의의 정수 값(`0` 등)이나 `ref int` 를 대입하여 호출하면 메모리 위반 크래시(`AccessViolationException`)가 발생합니다.
2. **유니코드(`CharSet.Unicode`) 마샬링 준수**:
- C++ DLL 단의 모든 문자열 인자는 `const wchar_t*` (유니코드)를 사용합니다.
- 따라서 모든 DllImport 선언의 문자셋 속성은 반드시 **`CharSet = CharSet.Unicode`** 여야 합니다. `Ansi`로 지정할 시 경로명 및 파일명이 한자 깨진 글자로 읽혀 `EZCAD.CFG failed` 에러를 냅니다.
3. **WPF 윈도우 핸들(HWND) 지연 초기화**:
- `Init` API 호출 시 실제 유효한 부모 윈도우 핸들(`IntPtr windowHandle`)이 필요합니다.
- MainWindow의 생성자 시점에는 핸들이 미생성 상태(`IntPtr.Zero`)이므로 즉시 초기화하면 안 됩니다.
- 반드시 WPF **`Dispatcher.BeginInvoke(..., DispatcherPriority.Loaded)`** 지연 큐를 활용하여 메인 윈도우 로드가 완료된 후에 유효한 창 핸들을 추출해 이니셜라이즈 해야 합니다.
---
## 2. P/Invoke 표준 선언부 (Standard C# Declarations)
```csharp
[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr InitMarkEzd();
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)]
private static extern int Mark(IntPtr hHandle);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)]
private static extern int Close(IntPtr hHandle);
```
---
## 3. 핵심 비즈니스 흐름 코드 (Core Implementation Patterns)
### 3.1. 장치 초기화 흐름
```csharp
// 1. 선행 엔진 초기화 및 포인터 획득
IntPtr markingHandle = InitMarkEzd();
if (markingHandle == IntPtr.Zero)
{
// 보드 또는 동글 락키 인식 실패 예외 처리
return false;
}
// 2. WPF 메인 윈도우 실제 창 핸들 획득 (Loaded 시점)
IntPtr windowHandle = new System.Windows.Interop.WindowInteropHelper(this).Handle;
// 3. 컨트롤러 장비 초기화 수행
int initResult = Init(markingHandle, ezcadFolderPath, windowHandle);
if (initResult == 0)
{
// 연결 완료 상태 전환
}
```
### 3.2. 공정 각인 요청 흐름
```csharp
// 1. 도안 로드
int loadResult = LoadEzdFile(markingHandle, absoluteEzdPath);
// 2. 도안 내 변수 태그 치환 (Marking.ini 매핑 기준)
ChangeTextByName(markingHandle, "SERIAL", request.Serial);
ChangeTextByName(markingHandle, "YYMMDD", request.LotNo);
// 3. 각인 기계 동작 기동
int markResult = Mark(markingHandle);
```

60
marking_gui/App.xaml.cs

@ -13,12 +13,72 @@ namespace marking_gui
/// </summary> /// </summary>
public partial class App : Application public partial class App : Application
{ {
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
// 바로가기 등으로 실행 시 작업 디렉토리가 꼬이는 현상을 방지하기 위해 작업 디렉토리를 실행 파일 폴더로 강제 동기화
try
{
System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("작업 디렉토리 초기화 중 에러: " + ex.Message);
}
// 전역 예외 처리 이벤트 핸들러 등록
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
Services.LoggerService.Info("=================== [프로그램 시작] ===================");
try
{
// DLL 탐색 경로에 실행 디렉토리 하위의 libs 폴더 추가
string libsDir = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs");
if (System.IO.Directory.Exists(libsDir))
{
SetDllDirectory(libsDir);
Services.LoggerService.Info("libs 폴더 경로 추가 완료: " + libsDir);
}
}
catch (Exception ex)
{
Services.LoggerService.Error("SetDllDirectory 설정 중 예외 발생", ex);
}
base.OnStartup(e); base.OnStartup(e);
var mainWindow = new MainWindow(); var mainWindow = new MainWindow();
mainWindow.Show(); mainWindow.Show();
} }
protected override void OnExit(ExitEventArgs e)
{
Services.LoggerService.Info("=================== [프로그램 정상 종료] ===================");
base.OnExit(e);
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
Services.LoggerService.Error("[비정상 종료] 처리되지 않은 도메인 예외 (UnhandledException) 발생", ex);
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Services.LoggerService.Error("[비정상 종료] 처리되지 않은 UI 스레드 예외 (DispatcherUnhandledException) 발생", e.Exception);
// 메인 루프를 유지하기 위해 예외를 처리된 것으로 설정
e.Handled = true;
}
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Services.LoggerService.Error("[작동 오류] 처리되지 않은 비동기 태스크 예외 (UnobservedTaskException) 발생", e.Exception);
e.SetObserved();
}
} }
} }

23
marking_gui/LoginOptions.json

@ -0,0 +1,23 @@
{
"loginAccounts": [
{
"id": "su",
"password": "su"
}
],
"makers": [
"AMO",
"ABC"
],
"models": [
"MODEL-1"
],
"variant1Values": [],
"variant2Values": [],
"lineValues": [
"LINE-1"
],
"jigNoValues": [
"JIG-1"
]
}

312
marking_gui/MainWindow.xaml

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:marking_gui" xmlns:local="clr-namespace:marking_gui"
mc:Ignorable="d" mc:Ignorable="d"
Title="양산형 테스트 기기 모니터링 시스템" Height="700" Width="900" Title="양산형 테스트 기기 모니터링 시스템" Height="700" Width="900"
Icon="pack://application:,,,/Marking.ico"
MinWidth="900" MinHeight="700" WindowState="Maximized" WindowStyle="None" MinWidth="900" MinHeight="700" WindowState="Maximized" WindowStyle="None"
WindowStartupLocation="CenterScreen" Background="#F3F4F6" WindowStartupLocation="CenterScreen" Background="#F3F4F6"
ContentRendered="Window_ContentRendered"> ContentRendered="Window_ContentRendered">
@ -111,6 +112,34 @@
</Trigger> </Trigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<!-- 테스트 마킹용 파란색 버튼 스타일 -->
<Style x:Key="TestMarkingButtonStyle" TargetType="Button">
<Setter Property="Background" Value="#3B82F6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2563EB"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#E5E7EB"/>
<Setter Property="Foreground" Value="#9CA3AF"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources> </Window.Resources>
<Grid> <Grid>
@ -132,35 +161,39 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<!-- [좌측 영역] 로고(Marking v1.0.0) 및 시간 표시 -->
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
<TextBlock Text="Marking" Foreground="White" FontSize="20" FontWeight="Bold" VerticalAlignment="Center"/> <TextBlock Text="Marking" Foreground="White" FontSize="20" FontWeight="Bold" VerticalAlignment="Center"/>
<TextBlock Text="v1.0.0 (Updated: 2026-06-05)" Foreground="#6B7280" FontSize="12" VerticalAlignment="Bottom" Margin="12,0,0,2"/> <TextBlock Text="v1.0.0" Foreground="#6B7280" FontSize="11" VerticalAlignment="Bottom" Margin="8,0,20,2"/>
<TextBlock x:Name="txtCurrentTime" Text="0000-00-00 00:00:00" Foreground="#E5E7EB" FontSize="15" FontWeight="Bold" VerticalAlignment="Bottom" Margin="25,0,0,2"/> <!-- 시간을 Marking v1.0.0 오른쪽에 배치 -->
<TextBlock Text="상태: 대기 중" x:Name="txtSystemStatus" Foreground="#10B981" FontSize="15" FontWeight="Bold" VerticalAlignment="Bottom" Margin="25,0,0,2" Visibility="Collapsed"/> <TextBlock x:Name="txtCurrentTime" Text="0000-00-00 00:00:00" Foreground="#E5E7EB" FontSize="14.5" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<!-- [우측 영역] 사용자 정보 및 제어 영역 -->
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
<!-- 작업자 정보 배지 --> <!-- 작업자 정보 배지 -->
<Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,10,0"> <Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,8,0">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="작업자" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,6,0"/> <TextBlock Text="작업자" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock x:Name="txtOperatorInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/> <TextBlock x:Name="txtOperatorInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- 모델 정보 배지 --> <!-- 모델 정보 배지 -->
<Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,10,0"> <Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,8,0">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="모델" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,6,0"/> <TextBlock Text="모델" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock x:Name="txtModelInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/> <TextBlock x:Name="txtModelInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- 라인 정보 배지 --> <!-- 라인 정보 배지 -->
<Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,15,0"> <Border Background="#1F2937" CornerRadius="4" Padding="10,6" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="라인" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,6,0"/> <TextBlock Text="라인" Foreground="#9CA3AF" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock x:Name="txtLineInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/> <TextBlock x:Name="txtLineInfo" Text="---" Foreground="White" FontSize="13" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</Border> </Border>
<Button x:Name="btnLogout" Content="로그아웃" Style="{StaticResource RedButtonStyle}" Click="btnLogout_Click" VerticalAlignment="Center" Width="70" Height="28" Margin="0,0,20,0"/> <Button x:Name="btnLogout" Content="로그아웃" Style="{StaticResource RedButtonStyle}" Click="btnLogout_Click" VerticalAlignment="Center" Width="70" Height="28" Margin="0,0,15,0"/>
<!-- 시스템 창 제어 버튼 --> <!-- 시스템 창 제어 버튼 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
@ -179,7 +212,8 @@
</Border> </Border>
<!-- 2. 메인 콘텐츠 영역 --> <!-- 2. 메인 콘텐츠 영역 -->
<Grid Grid.Row="1" Margin="10"> <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid Margin="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<!-- 좌측: S/N 입력 및 이전 단계 결과 (비율 3.5:6.5) --> <!-- 좌측: S/N 입력 및 이전 단계 결과 (비율 3.5:6.5) -->
<ColumnDefinition Width="3.5*"/> <ColumnDefinition Width="3.5*"/>
@ -194,13 +228,49 @@
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- 시리얼 입력 카드 --> <!-- 마킹 정보 및 S/N 입력 설정 카드 -->
<Border Grid.Row="0" Style="{StaticResource CardBorderStyle}"> <Border Grid.Row="0" Style="{StaticResource CardBorderStyle}">
<StackPanel> <StackPanel>
<TextBlock Text="S/N 입력" FontSize="16" FontWeight="Bold" Foreground="#374151" Margin="0,0,0,10"/> <!-- 마킹 도안 모델 선택기 -->
<TextBox x:Name="txtSerial" FontSize="18" Padding="8,6" <TextBlock Text="작업 모델 설정 (EZD 파일)" FontSize="17" FontWeight="Bold" Foreground="#374151" Margin="0,0,0,8"/>
<Grid Margin="0,0,0,18">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtEzdFile" Grid.Column="0" FontSize="16" Padding="8,6" IsReadOnly="True"
Text="AIC1.ezd" BorderBrush="#D1D5DB" BorderThickness="1" VerticalContentAlignment="Center"/>
<Button x:Name="btnSelectEzd" Grid.Column="1" Content="📁 파일 선택" Margin="5,0,0,0" Width="110" Height="36"
Style="{StaticResource ModernButtonStyle}" FontSize="15" Padding="5,0" Click="btnSelectEzd_Click"/>
</Grid>
<!-- S/N 입력 -->
<TextBlock Text="IC_SN" FontSize="17" FontWeight="Bold" Foreground="#374151" Margin="0,0,0,8"/>
<TextBox x:Name="txtSerial" FontSize="20" Padding="8,6"
VerticalContentAlignment="Center" BorderBrush="#D1D5DB" BorderThickness="1" IsReadOnly="True"/> VerticalContentAlignment="Center" BorderBrush="#D1D5DB" BorderThickness="1" IsReadOnly="True"/>
<TextBlock Text="※ 테스트 S/N: PASS1234 / FAIL5678" Foreground="#9CA3AF" FontSize="11" Margin="0,8,0,0"/>
<!-- 실시간 각인 일련번호 정보 표시 -->
<Border Background="#F9FAFB" BorderBrush="#E5E7EB" BorderThickness="1" CornerRadius="6" Padding="14" Margin="0,15,0,0">
<StackPanel>
<TextBlock Text="실시간 각인 정보" FontSize="17" FontWeight="Bold" Foreground="#4B5563" Margin="0,0,0,8"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 현재 Lot No -->
<StackPanel Grid.Column="0">
<TextBlock Text="현재 Lot No" FontSize="14" Foreground="#9CA3AF" FontWeight="SemiBold"/>
<TextBlock x:Name="txtCurrentLotNo" Text="---" FontSize="20" FontWeight="Bold" Foreground="#111827" Margin="0,2,0,0"/>
</StackPanel>
<!-- 다음 각인 카운트 -->
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="다음 각인 순번" FontSize="14" Foreground="#9CA3AF" FontWeight="SemiBold"/>
<TextBlock x:Name="txtNextSequence" Text="--- 차례" FontSize="22" FontWeight="Bold" Foreground="#2563EB" Margin="0,2,0,0"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</Border> </Border>
@ -211,7 +281,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="이전 시험 단계 결과" FontSize="16" FontWeight="Bold" Foreground="#374151" Margin="0,0,0,10"/> <TextBlock Grid.Row="0" Text="이전 시험 단계 결과" FontSize="18" FontWeight="Bold" Foreground="#374151" Margin="0,0,0,10"/>
<Grid Grid.Row="1" Margin="0,5,0,0"> <Grid Grid.Row="1" Margin="0,5,0,0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -228,8 +298,8 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="1. Housing" FontSize="18" Foreground="#4B5563" FontWeight="SemiBold"/> <TextBlock Text="1. Housing" FontSize="20" Foreground="#4B5563" FontWeight="SemiBold"/>
<TextBlock x:Name="txtPrevHousing" Grid.Column="1" Text="대기" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF"/> <TextBlock x:Name="txtPrevHousing" Grid.Column="1" Text="대기" FontSize="22" FontWeight="Bold" Foreground="#9CA3AF"/>
</Grid> </Grid>
<Separator Grid.Row="1" Background="#F3F4F6"/> <Separator Grid.Row="1" Background="#F3F4F6"/>
@ -239,8 +309,8 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="2. Calibration" FontSize="18" Foreground="#4B5563" FontWeight="SemiBold"/> <TextBlock Text="2. Calibration" FontSize="20" Foreground="#4B5563" FontWeight="SemiBold"/>
<TextBlock x:Name="txtPrevCal" Grid.Column="1" Text="대기" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF"/> <TextBlock x:Name="txtPrevCal" Grid.Column="1" Text="대기" FontSize="22" FontWeight="Bold" Foreground="#9CA3AF"/>
</Grid> </Grid>
<Separator Grid.Row="3" Background="#F3F4F6"/> <Separator Grid.Row="3" Background="#F3F4F6"/>
@ -250,8 +320,8 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="3. EOL" FontSize="18" Foreground="#4B5563" FontWeight="SemiBold"/> <TextBlock Text="3. EOL" FontSize="20" Foreground="#4B5563" FontWeight="SemiBold"/>
<TextBlock x:Name="txtPrevEol" Grid.Column="1" Text="대기" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF"/> <TextBlock x:Name="txtPrevEol" Grid.Column="1" Text="대기" FontSize="22" FontWeight="Bold" Foreground="#9CA3AF"/>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
@ -276,81 +346,198 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 전압 --> <!-- 전압 -->
<Border Grid.Column="0" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10"> <Border x:Name="borderVoltage" Grid.Column="0" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="측정 전압 (Voltage)" FontSize="14" Foreground="#6B7280" HorizontalAlignment="Center"/> <TextBlock Grid.Row="0" Text="측정 전압 (Voltage)" FontSize="16" Foreground="#6B7280" HorizontalAlignment="Center"/>
<Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5"> <Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="txtVoltage" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/> <TextBlock x:Name="txtVoltage" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/>
<TextBlock Text=" V" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/> <TextBlock Text=" V" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/>
</StackPanel> </StackPanel>
</Viewbox> </Viewbox>
<TextBlock x:Name="txtVoltageRangeGuide" Grid.Row="2" Text="기준범위: 4.0V ~ 6.0V" FontSize="12" Foreground="#9CA3AF" HorizontalAlignment="Center"/> <Viewbox Grid.Row="2" Stretch="Uniform" MaxHeight="18" HorizontalAlignment="Center">
<TextBlock x:Name="txtVoltageRangeGuide" Text="기준범위: 0.0V ~ 0.5V" FontSize="14" Foreground="#9CA3AF" HorizontalAlignment="Center"/>
</Viewbox>
</Grid> </Grid>
</Border> </Border>
<!-- 전류 --> <!-- 전류 -->
<Border Grid.Column="1" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10"> <Border x:Name="borderCurrent" Grid.Column="1" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="측정 전류 (Current)" FontSize="14" Foreground="#6B7280" HorizontalAlignment="Center"/> <TextBlock Grid.Row="0" Text="측정 전류 (Current)" FontSize="16" Foreground="#6B7280" HorizontalAlignment="Center"/>
<Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5"> <Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="txtCurrent" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/> <TextBlock x:Name="txtCurrent" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/>
<TextBlock Text=" A" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/> <TextBlock Text=" mA" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/>
</StackPanel> </StackPanel>
</Viewbox> </Viewbox>
<TextBlock x:Name="txtCurrentRangeGuide" Grid.Row="2" Text="기준범위: 8.0A ~ 10.0A" FontSize="12" Foreground="#9CA3AF" HorizontalAlignment="Center"/> <Viewbox Grid.Row="2" Stretch="Uniform" MaxHeight="18" HorizontalAlignment="Center">
<TextBlock x:Name="txtCurrentRangeGuide" Text="기준범위: 8.00mA ~ 10.00mA" FontSize="14" Foreground="#9CA3AF" HorizontalAlignment="Center"/>
</Viewbox>
</Grid> </Grid>
</Border> </Border>
<!-- 저항 --> <!-- 저항 -->
<Border Grid.Column="2" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10"> <Border x:Name="borderResistance" Grid.Column="2" Style="{StaticResource CardBorderStyle}" MinWidth="150" MinHeight="160" Padding="15,10">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="측정 저항 (Resistance)" FontSize="14" Foreground="#6B7280" HorizontalAlignment="Center"/> <TextBlock Grid.Row="0" Text="측정 저항 (Resistance)" FontSize="16" Foreground="#6B7280" HorizontalAlignment="Center"/>
<Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5"> <Viewbox Grid.Row="1" Stretch="Uniform" Margin="0,5">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="txtResistance" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/> <TextBlock x:Name="txtResistance" Text="-.--" FontSize="54" FontWeight="Bold" Foreground="#1F2937"/>
<TextBlock Text=" Ω" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/> <TextBlock Text=" kΩ" FontSize="20" FontWeight="Bold" Foreground="#9CA3AF" VerticalAlignment="Bottom" Margin="5,0,0,8"/>
</StackPanel> </StackPanel>
</Viewbox> </Viewbox>
<TextBlock x:Name="txtResistanceRangeGuide" Grid.Row="2" Text="기준범위: 0.4Ω ~ 0.8Ω" FontSize="12" Foreground="#9CA3AF" HorizontalAlignment="Center"/> <Viewbox Grid.Row="2" Stretch="Uniform" MaxHeight="18" HorizontalAlignment="Center">
<TextBlock x:Name="txtResistanceRangeGuide" Text="기준범위: 0.400kΩ ~ 0.800kΩ" FontSize="14" Foreground="#9CA3AF" HorizontalAlignment="Center"/>
</Viewbox>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
<!-- 최종 합/불 판정 영역 카드 --> <!-- 최종 합/불 판정 영역 카드 (상단 가로형 판정 뱃지 및 하단 2x2 섹션 그리드 배치) -->
<Border Grid.Row="1" Style="{StaticResource CardBorderStyle}" x:Name="borderFinalResult" Background="#E5E7EB" MinHeight="200"> <Border Grid.Row="1" Style="{StaticResource CardBorderStyle}" x:Name="borderFinalResult" Background="#F3F4F6" Padding="20,18" MinHeight="290" VerticalAlignment="Stretch">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <!-- 상단: 가로로 긴 판정 결과 배지 -->
<RowDefinition Height="*"/> <RowDefinition Height="12"/> <!-- 여백 -->
<RowDefinition Height="*"/> <!-- 하단: 2x2 형태의 세부 정보 섹션 -->
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="최종 판정 결과" FontSize="16" FontWeight="SemiBold" Foreground="#4B5563" HorizontalAlignment="Center" Margin="10,15,10,5"/>
<Viewbox Grid.Row="1" Stretch="Uniform" Margin="10"> <!-- [상단 영역] 가로로 긴 진행 상황 뱃지 (진행상황은 하얀색 카드로 고정) -->
<StackPanel HorizontalAlignment="Center"> <Border x:Name="badgeFinalResult" Grid.Row="0" Background="White" BorderBrush="#E5E7EB" BorderThickness="1" CornerRadius="8" Padding="20,10" MinHeight="75" VerticalAlignment="Stretch">
<TextBlock x:Name="txtFinalResult" Text="대 기 중" FontSize="80" FontWeight="Black" Foreground="#9CA3AF" HorizontalAlignment="Center"/> <Grid>
<TextBlock x:Name="txtFinalSerial" Text="S/N: ---" FontSize="16" FontWeight="Bold" Foreground="#6B7280" HorizontalAlignment="Center" Margin="0,10,0,0"/> <Grid.ColumnDefinitions>
</StackPanel> <ColumnDefinition Width="Auto"/> <!-- "진행 상황" 타이틀 -->
</Viewbox> <ColumnDefinition Width="*"/> <!-- 진행 상황 텍스트 (가운데 정렬) -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="💬 진행 상황" FontSize="18" FontWeight="Bold" Foreground="#4B5563" VerticalAlignment="Center"/>
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Center">
<TextBlock x:Name="txtProgressStatus" Text="대기 상태" FontSize="20" FontWeight="Bold" Foreground="#1F2937" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap"/>
</ScrollViewer>
</Grid>
</Border>
<!-- [하단 영역] 나머지 섹션들 2x2 배치 -->
<Grid Grid.Row="2" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="10"/> <!-- 행 간 여백 -->
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15"/> <!-- 열 간 여백 -->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- (0,0) 1. 검사 시간 정보 -->
<Border Grid.Row="0" Grid.Column="0" Background="White" BorderBrush="#E5E7EB" BorderThickness="1" CornerRadius="6" Padding="20,16" MinHeight="110" VerticalAlignment="Stretch">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="⏱ 검사 시간 정보" FontSize="18" FontWeight="Bold" Foreground="#6B7280" Margin="0,0,0,8"/>
<!-- 줄바꿈하여 시작 시간과 종료 시간을 수직으로 배열하고 Viewbox로 자동 축소/확대 밸런싱 -->
<StackPanel>
<Viewbox Stretch="Uniform" HorizontalAlignment="Left" MaxHeight="30" Margin="0,0,0,4">
<TextBlock x:Name="txtTestStartTime" Text="시작 시간: --:--:--" FontSize="22" Foreground="#1F2937" FontWeight="Bold"/>
</Viewbox>
<Viewbox Stretch="Uniform" HorizontalAlignment="Left" MaxHeight="30">
<TextBlock x:Name="txtTestEndTime" Text="종료 시간: --:--:--" FontSize="22" Foreground="#1F2937" FontWeight="Bold"/>
</Viewbox>
</StackPanel>
</StackPanel>
</Border>
<!-- (0,2) 2. 일련번호 정보 -->
<Border Grid.Row="0" Grid.Column="2" Background="White" BorderBrush="#E5E7EB" BorderThickness="1" CornerRadius="6" Padding="20,16" MinHeight="110" VerticalAlignment="Stretch">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="🏷 일련번호 정보" FontSize="18" FontWeight="Bold" Foreground="#6B7280" Margin="0,0,0,8"/>
<!-- 줄바꿈하여 입력 S/N과 각인 번호를 수직으로 배열하고 Viewbox로 잘림 방지 -->
<StackPanel>
<!-- 입력 S/N (IC) 행 -->
<Viewbox Stretch="Uniform" HorizontalAlignment="Left" MaxHeight="28" Margin="0,0,0,4">
<StackPanel Orientation="Horizontal">
<TextBlock Text="IC S/N: " FontSize="20" Foreground="#4B5563" FontWeight="SemiBold"/>
<TextBlock x:Name="txtFinalSerial" Text="---" FontSize="20" Foreground="#1F2937" FontWeight="Bold"/>
</StackPanel>
</Viewbox>
<!-- PCB 바코드 행 -->
<Viewbox Stretch="Uniform" HorizontalAlignment="Left" MaxHeight="28" Margin="0,0,0,4">
<StackPanel Orientation="Horizontal">
<TextBlock Text="PCB 바코드: " FontSize="20" Foreground="#4B5563" FontWeight="SemiBold"/>
<TextBlock x:Name="txtPcbBarcode" Text="---" FontSize="20" Foreground="#1F2937" FontWeight="Bold"/>
</StackPanel>
</Viewbox>
<!-- 각인 번호 행 -->
<Viewbox Stretch="Uniform" HorizontalAlignment="Left" MaxHeight="28">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="각인 번호: " FontSize="20" Foreground="#4B5563" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Border Background="#EFF6FF" CornerRadius="3" Padding="6,2" VerticalAlignment="Center" Margin="5,0,0,0">
<TextBlock x:Name="txtMarkedSerial" Text="---" FontSize="20" Foreground="#2563EB" FontWeight="Bold"/>
</Border>
</StackPanel>
</Viewbox>
</StackPanel>
</StackPanel>
</Border>
<!-- (2,0) 3. 최종 판정결과 (PASS/FAIL 시 카드 테두리 및 배경이 초록/빨강으로 변경됨) -->
<Border x:Name="borderVerdictCard" Grid.Row="2" Grid.Column="0" Background="White" BorderBrush="#E5E7EB" BorderThickness="1" CornerRadius="6" Padding="20,16" MinHeight="130" VerticalAlignment="Stretch">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="🏆 최종 판정결과" FontSize="18" FontWeight="Bold" Foreground="#6B7280" Margin="0,0,0,5"/>
<Viewbox Stretch="Uniform" HorizontalAlignment="Center" MaxHeight="75">
<TextBlock x:Name="txtFinalResult" Text="대기 중" FontSize="64" FontWeight="Black" Foreground="#1F2937" HorizontalAlignment="Center"/>
</Viewbox>
</StackPanel>
</Border>
<!-- (2,2) 4. 시스템 상태/오류 (정상 상태) -->
<Border x:Name="borderNormalStatus" Grid.Row="2" Grid.Column="2" Background="#F0FDF4" BorderBrush="#BBF7D0" BorderThickness="1" CornerRadius="6" Padding="16,12" MinHeight="130" Visibility="Visible" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="⚠️ 시스템 상태" FontSize="18" FontWeight="Bold" Foreground="#16A34A" Margin="0,0,0,5"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<TextBlock x:Name="txtSystemStatus" Text="상태: 대기 중" FontSize="15" FontWeight="Bold" Foreground="#16A34A" TextWrapping="Wrap"/>
</ScrollViewer>
</Grid>
</Border>
<!-- (2,2) 4. 시스템 상태/오류 (에러 발생 시) -->
<Border x:Name="borderErrorBanner" Grid.Row="2" Grid.Column="2" Background="#FEF2F2" BorderBrush="#FCA5A5" BorderThickness="1" CornerRadius="6" Padding="16,12" MinHeight="130" Visibility="Collapsed" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="❌ 시스템 오류" FontSize="18" FontWeight="Bold" Foreground="#DC2626" Margin="0,0,0,5"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<TextBlock x:Name="txtErrorMessage" Text="" FontSize="15" FontWeight="Bold" Foreground="#DC2626" TextWrapping="Wrap"/>
</ScrollViewer>
</Grid>
</Border>
</Grid>
</Grid> </Grid>
</Border> </Border>
</Grid>
</Grid> </Grid> <!-- 우측 패널 Grid 닫기 -->
</Grid> <!-- 메인 콘텐츠 Grid 닫기 -->
</ScrollViewer>
<!-- 3. 하단 푸터 (전체 영역이 스타트 버튼) --> <!-- 3. 하단 푸터 (전체 영역이 스타트 버튼) -->
<Border Grid.Row="2" BorderBrush="#D1D5DB" BorderThickness="0,1,0,0"> <Border Grid.Row="2" BorderBrush="#D1D5DB" BorderThickness="0,1,0,0">
@ -359,13 +546,32 @@
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border> </Border>
<!-- 4. 최하단 바 (파라미터 버튼 배치) --> <!-- 4. 최하단 바 (파라미터 버튼 및 마킹 상태 표시 배지 배치) -->
<Border Grid.Row="3" Background="#1F2937" BorderBrush="#111827" BorderThickness="0,1,0,0" Padding="20,0"> <Border Grid.Row="3" Background="#1F2937" BorderBrush="#111827" BorderThickness="0,1,0,0" Padding="20,0">
<Grid> <Grid>
<!-- 파라미터 설정 버튼 (좌측 정렬) --> <!-- 설정 버튼 그룹 (좌측 정렬) -->
<Button x:Name="btnOpenSettings" Content="⚙ 파라미터 설정" Width="140" Height="36" <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
Style="{StaticResource ModernButtonStyle}" Click="btnOpenSettings_Click" <Button x:Name="btnOpenSettings" Content="파라미터 설정" Width="135" Height="36"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Padding="10,0"/> Style="{StaticResource ModernButtonStyle}" Click="btnOpenSettings_Click"
Margin="0,0,10,0" FontSize="13" Padding="8,0"/>
<Button x:Name="btnOpenPortSettings" Content="포트 설정" Width="120" Height="36"
Style="{StaticResource ModernButtonStyle}" Click="btnOpenPortSettings_Click"
Margin="0,0,10,0" FontSize="13" Padding="8,0"/>
<Button x:Name="btnBypassStart" Content="강제 검사 (스킵)" Width="145" Height="36"
Style="{StaticResource ModernButtonStyle}" Click="btnBypassStart_Click"
Margin="0,0,10,0" FontSize="13" Padding="8,0"/>
<Button x:Name="btnTestMarking" Content="테스트 마킹" Width="120" Height="36"
Style="{StaticResource ModernButtonStyle}" Click="btnTestMarking_Click"
FontSize="13" Padding="8,0"/>
</StackPanel>
<!-- 마킹 연동 상태 표시기 (숨김 처리 - UI에서 제거됨) -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="Collapsed">
<TextBlock Text="마킹 기계 상태:" Foreground="#9CA3AF" FontSize="13" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,10,0"/>
<Border x:Name="borderMarkingStatus" Background="#374151" CornerRadius="4" Padding="15,6" VerticalAlignment="Center">
<TextBlock x:Name="txtMarkingStatus" Text="대기 중" Foreground="White" FontSize="13" FontWeight="Bold"/>
</Border>
</StackPanel>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>

1825
marking_gui/MainWindow.xaml.cs

File diff suppressed because it is too large

3
marking_gui/Manual/.obsidian/app.json

@ -0,0 +1,3 @@
{
"spellcheck": false
}

1
marking_gui/Manual/.obsidian/appearance.json

@ -0,0 +1 @@
{}

33
marking_gui/Manual/.obsidian/core-plugins.json

@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}

196
marking_gui/Manual/.obsidian/workspace.json

@ -0,0 +1,196 @@
{
"main": {
"id": "e986c9dffdb55d9e",
"type": "split",
"children": [
{
"id": "e5576011528e6b5c",
"type": "tabs",
"children": [
{
"id": "b98d55f22f4d8058",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "02_초기_설정.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "02_초기_설정"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "c57355fcc7fdff6f",
"type": "split",
"children": [
{
"id": "0dd5370634e2a0cb",
"type": "tabs",
"children": [
{
"id": "34581207ae14950d",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "파일 탐색기"
}
},
{
"id": "9392a937e5fa675d",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "검색"
}
},
{
"id": "885558dab49fcbe9",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "북마크"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "99e0f4ec73247bac",
"type": "split",
"children": [
{
"id": "e73c85dfbcab7caf",
"type": "tabs",
"children": [
{
"id": "563edc0360ae71a5",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "06_데이터_조회.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "06_데이터_조회 의 백링크"
}
},
{
"id": "5a190c7cdaac43a6",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "06_데이터_조회.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "06_데이터_조회 의 나가는 링크"
}
},
{
"id": "b2a637895408e226",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "태그"
}
},
{
"id": "c2aa104400708a18",
"type": "leaf",
"state": {
"type": "all-properties",
"state": {
"sortOrder": "frequency",
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-archive",
"title": "모든 속성"
}
},
{
"id": "210708f9a623a5be",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "06_데이터_조회.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "06_데이터_조회 의 개요"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:빠른 전환기 열기": false,
"graph:그래프 뷰 열기": false,
"canvas:새 캔버스 만들기": false,
"daily-notes:오늘의 일일 노트 열기": false,
"templates:템플릿 삽입": false,
"command-palette:명령어 팔레트 열기": false,
"bases:새 베이스 생성하기": false
}
},
"active": "b98d55f22f4d8058",
"lastOpenFiles": [
"Manual_for_pdf.html",
"05_시험_설정.md",
"04_통신_설정.md",
"03_메인_화면.md",
"02_초기_설정.md",
"01_시스템_개요.md",
"06_데이터_조회.md"
]
}

67
marking_gui/Manual/01_시스템_개요.md

@ -0,0 +1,67 @@
# 1. 시스템 개요 및 요구사항
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 1.1 시스템 개요
**Performance Inspect & Laser Marking System**은 온압센서 완제품의 최종 성능 검사를 수행하고, 이전 공정 단계들의 합격(PASS) 이력을 조회하여 최종 적합 제품에 한해 제품 표면에 레이저 마킹을 수행하는 시스템 제어 프로그램입니다.
본 프로그램은 측정 계측기와 센서 판독기, 레이저 각인 장치 및 공장 생산 데이터베이스와 연동되어 작동합니다.
### 주요 기능 요약
| 기능 | 설명 |
|------|------|
| **일련번호(S/N) 자동 추출** | 센서 판독 보드를 통해 제품 안착 시 제품 내부 메모리로부터 일련번호를 추출합니다. |
| **선행 공정 검사 교차 조회** | 이전 단계(하우징 조립, 센서 보정, 1차 EOL 성능 검사)가 모두 합격 판정되었는지 생산 데이터베이스에서 조회합니다. |
| **자동 계측 시퀀스** | 전원 공급 장치와 전압/저항 측정 장비들과 연동하여 제품에 테스트 전원을 투입하고 전압, 저항, 전류 값을 차례대로 계측합니다. |
| **실제 저항값 측정** | 전압/저항 DMM 측정기를 통해 센서의 실제 저항값(Ω)을 측정합니다. |
| **전류 측정** | 전원 공급 장치(Power Supply)의 출력 채널 1을 통해 제품의 소모 전류를 계측합니다. |
| **레이저 각인 연동** | 성능 검사가 모두 통과된 합격품에 대해 제품 표면에 2D QR 코드 및 단축 일련번호(생산일자 + 순번)를 마킹합니다. |
| **결과 기록** | 계측 판정 규격(전압, 전류, 저항)과 마킹 성공 여부를 최종 판정하여 생산 데이터베이스에 저장합니다. |
---
## 1.2 소프트웨어 요구사항
| 항목 | 요구사항 | 비고 |
|------|----------|------|
| **통합 설치 프로그램** | `MarkingSetup.exe` (WiX 번들) | 1-Click 자동 설치 및 실행 환경, 드라이버, 설정 일체 셋업 |
| **운영체제** | Windows 10 이상 | 프로그램 구동 및 장비 드라이버 운용용 |
| **데이터베이스** | Microsoft SQL Server (MS-SQL) | 생산 공정 데이터 및 계측 결과 연동용 |
| **레이저 제어 프로그램** | EZCAD 각인 제어 프로그램 및 드라이버 | 레이저 마킹기 구동 및 전용 도면(.ezd) 운용용 |
| **네트워크 환경** | LAN (TCP/IP) 통신 | 제어 PC와 측정 기기 간의 연결 |
| **센서 보드 통신** | COM 포트 (RS-232 시리얼) | 4251 센서 통신 보드 연동 |
---
## 1.3 하드웨어 구성
| 장비 | 역할 | 통신 방식 |
|------|------|-----------|
| **센서 판독 보드 (4251 보드)** | 제품 내부 칩의 일련번호(IC_SN)를 인식합니다. | RS-232 시리얼 통신 |
| **전압 및 저항 측정 장비 (DMM)** | 제품의 전압(V) 및 전기적 저항(Ω)을 측정합니다. (예: 192.168.1.100) | TCP/IP 네트워크 통신 |
| **전류 측정 및 전원 공급 장비 (PSU)** | 제품 구동을 위한 전원 공급 및 소모 전류(A)를 측정합니다. (예: 192.168.1.101) | TCP/IP 네트워크 통신 |
| **레이저 마킹기** | 2D QR 코드 및 텍스트 일련번호를 제품 표면에 인쇄합니다. | 마킹 제어 보드 연동 |
| *(전류 측정 DMM)* | *설정 파일(예: 192.168.1.102)에 존재하나 현재 제어 시퀀스에서는 사용하지 않습니다.* | *(미사용)* |
## 1.4 설치 경로(`C:\marking\`) 구조 및 생성 파일 역할
단일 통합 설치 마법사(`MarkingSetup.exe`)를 통해 시스템을 설치하면 기본 설치 디렉터리(`C:\marking\`) 아래에 다음과 같은 실행 바이너리, 설정 파일, 드라이버 라이브러리 및 런타임 로그 폴더가 배치·생성됩니다.
| 구분 | 파일 / 폴더명 | 상세 역할 및 기능 설명 |
|------|----------------|------------------------|
| **실행 프로그램** | `marking_gui.exe` | 메인 성능 검사 및 레이저 마킹 GUI 제어 실행 파일 |
| **종속 라이브러리** | `marking_gui.dll`, `*.deps.json`, `*.runtimeconfig.json` | .NET 9.0 (WPF) 자립형 런타임 및 종속 구성 바이너리 |
| **통합 설정 파일** | `Setting.ini` | 마킹기 폴더 경로, 테스트 모드, 도안 태그 매핑, 계측기 LAN IP, SCPI 전원 커맨드, 계측 판정 규격 임계치(`[Limits]`) 종합 설정 파일 |
| **DB 설정 파일** | `Database.ini` | MS-SQL 데이터베이스 접속 주소, DB명, 사용자 계정/PW, 오프라인 모드(`OfflineMode=true`) 설정 파일 |
| **센서 설정 파일** | `Sensor.ini` | 4251 센서 통신 보드 시리얼 포트(`Port=COM3`), 통신 속도(`BaudRate=115200`), 통신 채널(`Channel=1`) 설정 파일 |
| **드라이버 폴더** | `libs/` | C++ LMC 각인 드라이버 격리 디렉터리 (`MarkEzdWrapper.dll`, `DataMgr.dll`, `MarkEzd.dll`, `haspms32.dll` 등) |
| **로그 저장 폴더** | `logs/` *(자동 생성)* | 시스템 운용 중 발생하는 일별 이벤트 로그, 통신 이력, 각인 성공/실패 기록 텍스트 저장 디렉터리 (예: `logs/2026-07-23.log`) |
| **백업 보관 폴더** | `publish_backup/` | 프로그램 업데이트 및 빌드 배포 시 이전 릴리즈 바이너리/설정 자동 백업 폴더 |
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

117
marking_gui/Manual/02_초기_설정.md

@ -0,0 +1,117 @@
# 2. 초기 설정 가이드
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 2.1 프로그램 설정 구성 개요
본 프로그램은 정상적인 계측기 통신 및 데이터베이스 연동을 위해 몇 가지 설정 파일을 사용합니다. 이 설정 파일들은 프로그램 설치 및 라인 도입 시 최초 1회 셋업을 원칙으로 하며, 일반 운영자가 직접 설정 파일을 열어 수정할 필요는 없습니다.
*참고: 대부분의 검사 기준 및 통신 정보는 프로그램 내의 **'통신 설정'** 및 **'시험 설정'** 화면을 통해 수정할 수 있습니다.*
### 2.1.1 주요 설정 정보 구분
1. **장비 및 계측 설정**
- 계측기들과의 네트워크 연결 주소와 대기 시간 등을 정의합니다.
- 제품 합격 여부를 판정하는 전압, 전류, 저항의 기본 합격 규격 범위가 이 파일에 저장되며, 화면 내의 **'시험 설정'** 창에서 수정한 사항이 이 파일에 동적으로 반영됩니다.
- 프로그램은 전압/저항 측정 장치와 전원/전류 공급 장치를 제어하여 계측을 진행합니다.
2. **생산 데이터베이스 연동 설정**
- 공장 생산 관리 시스템 데이터베이스와의 통신 경로가 기록되어 있습니다.
- 전산망이나 DB 서버 점검 등으로 인해 통신이 어려울 경우, 설정 파일을 통해 **'오프라인 모드'**로 전환하여 단독으로 장비 작동 테스트를 해볼 수 있습니다.
- **오프라인 로그인 지원**: 데이터베이스 확인 단계 없이 로그인하여 프로그램을 기동합니다.
- **오프라인 검사 지원**: 제품 이력 조회 및 검사 결과 저장을 건너뛰고 단독으로 계측 및 마킹을 실행합니다.
3. **센서 판독기 설정**
- 제품 일련번호를 로드하기 위한 통신 채널 및 연결 포트 주소가 정의됩니다.
- 화면 내의 **'통신 설정'** 창에서 수정한 사항이 이 파일에 자동으로 저장됩니다.
### 2.1.2 설치 경로(`C:\marking\`) 내 파일 배치 구조
프로그램 설치 완료 후 `C:\marking\` 경로에는 다음과 같이 구동 파일, 설정 파일, 드라이버 라이브러리 및 런타임 자동 생성 디렉터리가 배치됩니다.
* **`C:\marking\marking_gui.exe`**: GUI 제어 메인 실행 파일
* **`C:\marking\Setting.ini`**: 장비 통신 IP, 마킹 태그, 규격 임계치(`[Limits]`) 종합 설정 파일
* **`C:\marking\Database.ini`**: MS-SQL DB 접속 정보 및 오프라인 모드 설정 파일
* **`C:\marking\Sensor.ini`**: 4251 센서 통신 보드 포트/속도/채널 설정 파일
* **`C:\marking\libs\`**: C++ LMC 레이저 각인 드라이버 라이브러리 격리 폴더 (`MarkEzdWrapper.dll` 등)
* **`C:\marking\logs\`**: 런타임 가동 중 자동 생성되는 일별 이력 로그 폴더 (`yyyy-MM-dd.log`)
* **`C:\marking\publish_backup\`**: 업데이트/배포 시 이전 버전 바이너리 및 설정 자동 백업 폴더
### 2.1.3 기본적인 설정 파일 편집 방법
> [!IMPORTANT]
> **[권장 조치 안내]**
> 현장 라인 검사자 및 일반 작업자는 프로그램 UI의 **[통신 설정]** 및 **[시험 설정]** 메뉴를 통해 안전하게 파라미터를 변경하십시오.
> 아래의 `.ini` 파일 직접 수정은 라인 엔지니어 또는 설비 관리자 전용 권한 작업이며, 잘못된 파일 수정 시 장비 통신 오류가 발생할 수 있습니다.
설비 이동이나 네트워크 환경 변경 시, 프로그램 폴더 내의 각 `.ini` 파일을 텍스트 편집기(메모장 등)로 열어 아래의 전체 설정 레이아웃 항목을 참조하여 수정할 수 있습니다.
#### 1) Setting.ini (주요 변경 가능 설정)
현장 네트워크 IP, 도안 파일 경로 및 검사 규격(임계치) 변경 시 수정하는 주요 설정 항목입니다.
```ini
[Settings]
EzdFolderPath=c:\EzCad\EasyCfg ; EZD 도안 파일 기본 탐색/저장 폴더 경로
test=false ; 테스트 마킹 버튼 및 개발 모드 노출 여부 (true/false)
[DMM]
VoltageHost=192.168.1.100 ; 전압/저항 측정용 계측기(DMM) IP 주소
[CurrentDMM]
CurrentDMMHost=192.168.1.102 ; 전류 측정용 계측기(DMM) IP 주소
[PowerSupply]
CurrentHost=192.168.1.101 ; 전원 공급기(Power Supply) IP 주소
[Limits]
MinVoltage=4.0 ; 전압 하한 판정 기준값 (V)
MaxVoltage=6.0 ; 전압 상한 판정 기준값 (V)
MinCurrent=8.0 ; 전류 하한 판정 기준값 (mA)
MaxCurrent=10.0 ; 전류 상한 판정 기준값 (mA)
MinResistance=0.4 ; 저항 하한 판정 기준값 (kΩ)
MaxResistance=0.8 ; 저항 상한 판정 기준값 (kΩ)
```
#### 2) Database.ini (주요 변경 가능 설정)
DB 서버 접속 주소 및 점검용 오프라인 모드 변경 시 수정하는 항목입니다.
```ini
[Database]
Db_Server=192.168.1.200 ; MSSQL 데이터베이스 서버 IP 주소
Db_Name=MarkingDb ; 연결할 데이터베이스 이름
Db_User=db_user ; DB 접속 사용자 계정
Db_Password=db_password ; DB 접속 비밀번호
OfflineMode=False ; DB 미연결 점검 모드 (True: DB 저장 생략, False: 정상 검증/적재)
[Login]
OfflinePreview=False ; 로그인 DB 검증 우회 여부 (True: 로그인 생략, False: 정상 검증)
```
#### 3) Sensor.ini (주요 변경 가능 설정)
바코드 스캐너 시리얼 포트 및 채널 설정 항목입니다.
```ini
[Sensor]
Port=COM3 ; 바코드 스캐너 시리얼 포트 (장치 관리자 COM 포트 번호)
Channel=1 ; 통신 채널 번호 (1 또는 2)
```
---
## 2.2 최초 실행 및 장비 점검 순서
라인 가동 전 최초 실행할 때는 아래 단계를 순서대로 이행하여 정상 작동 여부를 점검하십시오.
1. **프로그램 단일 설치 (MarkingSetup.exe)**
- 배포받은 단일 설치 마법사 `MarkingSetup.exe`를 실행하여 1-Click으로 `C:\marking\` 설치 및 바탕화면 바로가기를 자동 생성합니다.
2. **데이터베이스 연결 상태 확인**
- 데이터베이스가 점검 중인 경우 설정 파일을 통해 오프라인 모드로 설정하면 로그인 창을 통과하여 단독 테스트를 진행할 수 있습니다.
3. **장비 네트워크 통신 점검**
- 제어 PC와 계측 장비들(전압/저항 측정 장비, 전원 공급 장치) 간의 LAN 케이블 연결을 확인합니다.
- 제어 PC의 네트워크 IP 환경이 장비들의 대역과 동일한지 확인하고, 통신 신호가 정상적으로 송수신되는지 점검합니다.
4. **상시전원 자동 인가 확인**
- 프로그램 실행 후 작업자 계정으로 로그인을 시도합니다.
- 로그인이 완료되면 전원 공급 장치의 상시전원 채널 전원 램프가 자동으로 켜지는지 확인합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

63
marking_gui/Manual/03_메인_화면.md

@ -0,0 +1,63 @@
# 3. 메인 화면 (Home View)
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 3.1 메인 화면 레이아웃 구성
본 프로그램은 작업자가 화면 구성 및 각 항목 상태를 파악할 수 있도록 4개의 카드 레이아웃으로 화면을 구성했습니다.
### 3.1.1 상단 헤더 영역 (Header Bar)
프로그램 최상단 영역에는 시스템 정보 배지와 제어 버튼이 배치되어 있습니다.
* **실시간 시계**: 좌측 상단에 날짜와 시간이 표시됩니다.
* **작업 정보 배지**:
* **공정 라인**: 로그인 단계에서 지정한 생산 라인 코드 (`Line No`)
* **로트 번호**: 로그인 단계에서 지정한 생산 로트 번호 (`Lot No`)
* **모델명**: 현재 가동 중인 제품의 사양 모델 (`Model`)
* **작업자**: 로그인한 작업자의 이름 (`Operator`)
* **제어 버튼**: 우측 상단에 `로그아웃` 버튼이 위치하며 최하단 바에 설정 관련 버튼이 배치됩니다.
---
## 3.2 상태 및 제어 카드 영역
화면 중앙에는 역할을 구분한 4개의 상태 카드가 배치되어 있어, 제품 상태와 계측 단계를 모니터링할 수 있습니다.
### 3.2.1 [카드 1] 제품 정보 및 이전 결과 (좌측 상단)
제품의 일련번호와 이전 생산 공정에서의 합격 이력을 대조하는 영역입니다.
* **작업 모델 설정**:
- `파일 선택` 버튼을 사용하여 각인용 도면 파일을 불러옵니다.
- 도면 파일 선택이 완료되면 상단 헤더 영역의 모델명(Model) 배지 텍스트가 선택한 도면명으로 자동 변경됩니다.
* **일련번호 (IC_SN)**: 제품이 안착되면 센서 판독기를 통해 자동으로 인식되어 표시됩니다. (읽기 전용)
* **이전 공정 이력 배지**:
* **Housing**: 하우징 조립 공정 판정 상태 (`PASS` / `FAIL` / `대기`)
* **Calibration**: 압력 보정 공정 판정 상태 (`PASS` / `FAIL` / `대기`)
* **EOL**: EOL 1차 성능 평가 판정 상태 (`PASS` / `FAIL` / `대기`)
* *주의: 이전 공정 이력 배지 중 하나라도 `FAIL` 상태인 경우 성능 검사를 시작할 수 없습니다.*
### 3.2.2 [카드 2] 측정 결과 (우측 상단)
현재 장비에서 실제 계측한 3대 전기 파라미터(전압, 전류, 저항) 수치와 설정된 기준 규격 범위를 표시하는 영역입니다.
* **측정 전압 (Voltage)**: 전압 측정 장치가 측정한 전압 수치 (**V** 단위)
* **측정 전류 (Current)**: 전원 공급 장치가 측정한 소모 전류 수치 (**mA** 단위)
* **측정 저항 (Resistance)**: 저항 측정 장치가 측정한 전기적 저항 수치 (**kΩ** 단위)
* *알림: 각 물리량의 하단에는 현재 적용 중인 기준 규격 범위가 문구(`기준범위: X ~ Y`)로 표출되며, 실측값이 규격 범위 내에 도달하면 카드 테두리가 **초록색(PASS)**으로 표시되고, 범위를 벗어나면 테두리가 **빨간색(FAIL)**으로 바뀝니다.*
### 3.2.3 [카드 3] 진행상황 (좌측 하단)
현재 설비가 수행하고 있는 단계별 자동 제어 시퀀스를 보여주는 안내 창입니다.
* "대기 상태", "제품 정보 수집 중...", "이전 공정 검사 결과 조회 중...", "측정 계측기 데이터 수집 중...", "제품 합격 - 레이저 각인 중..." 등 작동 상태를 실시간 텍스트로 표출합니다.
### 3.2.4 [카드 4] 판정결과 (우측 하단)
제품의 최종 합격(PASS) 및 불합격(FAIL) 상태를 표시합니다.
* 계측 검사와 데이터 저장, 레이저 각인이 완료되었을 때 최종 합격인 경우 카드 배경이 **초록색(PASS)**으로 전환되며, 불합격인 경우 카드 배경이 **빨간색(FAIL)**으로 변경됩니다.
### 3.2.5 최하단 제어 영역 (Control Box)
* **검사 시작 (START)**: 전체 검사 시퀀스를 기동하는 메인 버튼입니다. 이 버튼을 누르면 이전 계측 수치들이 초기화되고 새로운 측정 프로세스가 개시됩니다.
* **강제 검사 (스킵)**: 이전 가공 공정의 합격 이력이 없더라도 검증 단계를 건너뛰고 성능 계측 및 각인 시퀀스를 기동하는 버튼입니다.
* **테스트 마킹**: 성능 계측 없이 즉시 각인기에 테스트 마킹 신호를 전송하는 버튼입니다.
* *참고: `강제 검사 (스킵)``테스트 마킹` 버튼은 환경 설정에서 테스트 모드가 활성화된 상태에서만 화면 하단에 노출됩니다.*
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

37
marking_gui/Manual/04_통신_설정.md

@ -0,0 +1,37 @@
# 4. 통신 설정 (Comm. Settings)
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 4.1 포트 설정 화면 사용법
프로그램 상단의 `통신 설정` 버튼을 클릭하면 포트 설정 대화 상자가 나타납니다. 이 대화 상자에서 센서 판독 장치(4251 보드)의 연결 포트 및 채널을 설정할 수 있습니다.
### 주요 설정 항목 및 설명
1. **통신 포트 (Port)**:
- 센서 판독기가 연결되어 있는 PC의 연결 포트를 선택합니다. (예: `COM1`, `COM3`, `COM4` 등)
- 현재 PC에 인식되어 있는 포트 목록이 자동으로 표시됩니다.
2. **통신 속도 (BaudRate)**:
- 센서 판독 장치와의 데이터 송수신 속도를 설정합니다.
- 기본 설정값은 **`115200`** 이며, 특수한 경우가 아니면 변경하지 않는 것을 권장합니다.
3. **통신 채널 (Channel)**:
- 센서 판독기 내부의 연결 채널을 결정합니다.
- 드롭다운 메뉴를 통해 **`CH1`** 또는 **`CH2`**를 선택할 수 있습니다.
- 선택된 채널에 따라 센서 인식 모드가 변경됩니다.
---
## 4.2 설정 저장
* **설정 저장**:
- 통신 포트와 채널을 선택한 후 `저장` 버튼을 누르면 설정 정보가 저장됩니다.
- 저장 시 기존 센서 연결을 해제하고 새로운 설정값으로 센서 인식 서비스를 다시 시작합니다. 프로그램을 재시작할 필요 없이 변경 사항이 즉시 적용됩니다.
- 저장하지 않고 창을 닫으려면 `취소` 버튼을 누릅니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

44
marking_gui/Manual/05_시험_설정.md

@ -0,0 +1,44 @@
# 5. 시험 설정 (Parameters)
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 5.1 판정 기준 설정
프로그램 상단 우측의 `시험 설정` 버튼을 클릭하면 판정 기준을 구성하는 대화 상자가 나타납니다. 이 화면에서 제품 합불 판정에 사용되는 전압, 전류, 저항의 기준값을 입력할 수 있습니다.
### 5.1.1 각 항목별 한계치 입력 가이드
* **전압 판정 범위**:
* **하한값**: 합격 전압 하한 규격 (V 단위)
* **상한값**: 합격 전압 상한 규격 (V 단위)
* **전류 판정 범위**:
* **하한값**: 합격 전류 하한 규격 (설정창 표기 단위 참조, 기본 mA/A)
* **상한값**: 합격 전류 상한 규격 (설정창 표기 단위 참조, 기본 mA/A)
* **저항 판정 범위**:
* **하한값**: 합격 저항 하한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)
* **상한값**: 합격 저항 상한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)
*참고: 저장 버튼을 클릭하면 메인 화면의 하단 가이드 문구(`기준범위: X ~ Y`)가 즉시 갱신되어 새로 입력한 한계치가 다음 검사 시퀀스부터 반영됩니다.*
---
## 5.2 레이저 각인 도면 지정
* **각인 파일 설정 경로**:
* 레이저 마킹기에서 사용할 작업 도면 파일 선택은 설정 대화 상자가 아닌, **메인 화면 좌측 상단의 '작업 모델 설정' 영역에 있는 '파일 선택' 버튼**을 클릭하여 수행합니다.
* 도면이 선택되면 상단 헤더 배지의 모델명 정보도 해당 도면 파일명으로 자동으로 변경됩니다.
* **유의 사항**:
* 지정해 놓은 도면 파일이 실제 경로에 존재하지 않는 경우, 계측 성능 검사가 합격하더라도 각인 단계에서 에러가 납니다. 검사 전 파일 경로 상태를 확인하십시오.
---
## 5.3 설정 저장 및 적용
* 설정 창의 `저장` 버튼을 클릭하면 변경된 파라미터가 시스템 설정에 저장됩니다.
* 저장 완료 즉시 **메인 화면 하단에 표시되는 판정 기준 가이드 텍스트**가 업데이트되며, 이후에 새로 시작하는 성능 검사 시퀀스부터 새로운 규격치가 적용됩니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

53
marking_gui/Manual/06_데이터_조회.md

@ -0,0 +1,53 @@
# 6. 측정 데이터 조회 및 데이터베이스 연동
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 6.1 선행 공정 결과 조회 (이력 확인)
본 프로그램은 검사 시작 시 입력된 제품 일련번호를 기반으로 생산 데이터베이스에 접속하여 이전 제조 공정의 합격 여부를 조회합니다.
### 6.1.1 이력 검토 대상 공정
시스템은 다음 3개의 선행 공정 검사 결과를 확인합니다.
1. **하우징 조립 공정**
2. **센서 오차 보정 공정**
3. **1차 EOL 성능 검사**
### 6.1.2 이전 단계 규격 교차 확인 동작
* 이전 3개 선행 단계의 이력이 **모두 합격(PASS 또는 OK)**된 제품인 경우에만 최종 성능검사(EOL2)를 진행할 수 있습니다.
* 선행 이력이 존재하지 않거나, 단 한 곳이라도 불합격(FAIL 또는 NG) 판정을 받은 경우 화면 진행 상황 표시창에 경고가 표시되며 최종 성능검사 시작이 제한됩니다.
---
## 6.2 검사 결과 자동 적재 및 최종 판정 기록
자동 계측 프로세스가 종료되면 다음 정보가 생산 데이터베이스에 저장됩니다.
* **저장 정보**:
- 제품 일련번호
- 생산 라인, 로트 번호, 작업자 정보
- 실측 전압, 전류, 저항값
- 실측값은 데이터베이스 저장 규격에 맞춰 시스템 내부에서 소수점 이하 자리로 정밀 정리되어 저장됩니다.
- 최종 판정 결과 (`PASS` / `FAIL`)
- 마킹 시퀀스 번호 및 성공 기록
* **비동기 데이터 처리**:
- 데이터베이스 통신 대기로 인한 화면 멈춤 현상을 방지하기 위해 데이터 조회 및 저장 작업은 프로그램 화면 동작과 별개로 백그라운드에서 비동기식으로 안전하게 처리됩니다.
---
## 6.3 오프라인 모드 운용
데이터베이스 서버 접속이 불가능한 환경에서도 단독으로 설비를 가동할 수 있도록 **오프라인 모드**를 지원합니다.
* **활성화 목적**:
- 네트워크 연결이 끊긴 상태에서 프로그램의 단독 동작 및 장비 제어 상태를 확인하기 위함입니다.
* **오프라인 작동 패턴**:
- **공정 조회 우회**: 입력된 일련번호에 대해 생산 데이터베이스 서버 접속 없이 이전 공정을 합격한 것으로 우회 처리합니다.
- **임시 일련번호 생성**: 일련번호가 비어 있는 상태에서 테스트를 개시하는 경우 현재 시간 기반의 임시 일련번호가 자동으로 부여되어 진행합니다.
- **각인 모사**: 실제 각인기에 데이터를 전송하는 단계를 가상 성공 처리하여 전체 흐름을 테스트합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

75
marking_gui/Manual/07_시험_프로세스.md

@ -0,0 +1,75 @@
# 7. 자동 시험 프로세스 및 15단계 시퀀스
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 7.1 자동 시험 및 마킹 프로세스 개요
본 프로그램은 사용자가 메인 화면 하단의 `검사 시작 (START)` 버튼을 누르면 데이터 저장까지의 과정이 자동 계측 시퀀스에 따라 순차적으로 동작하도록 구성되어 있습니다.
```mermaid
graph TD
A[START 버튼 클릭] --> B[UI 화면 수치 초기화]
B --> C[4~8단계: 센서 S/N 읽기 및 보정]
C --> D{선행 공정 이력 확인}
D -- 불합격/미진행 --> E[검사 중단 & 경고 팝업]
E --> F{강제 검사 스킵?}
F -- 취소 --> Z[대기 복구]
F -- 스킵 실행 --> G[9~15단계: 계측 시퀀스 기동]
D -- 3개 공정 모두 PASS --> G
G --> H[10단계: 테스트 주전원 ON]
H --> I[12~14단계: 전압 / 저항 / 전류 실측]
I --> J[15단계: 테스트 주전원 안전 차단]
J --> K{최종 규격 비교 판정}
K -- 1개 이상 이탈 FAIL --> L[대형 FAIL 표시 & 마킹 제외 & DB 불합격 적재]
K -- 모두 통과 PASS --> M[대형 PASS 표시 & DB 합격 적재 & LMC1 레이저 QR 각인]
L --> Z
M --> Z
```
---
## 7.2 상세 15단계 자동 제어 시퀀스
최종 검사가 시작되면 장비와 계측기들은 다음 15가지 단계에 따라 동작을 차례대로 수행합니다.
### [1단계] 사전 전원 인가 단계 (최초 가동/로그인 시 1회 진행)
* **1단계 [장비 확인]**: 전원 공급 장치와의 통신 연결 상태를 점검합니다.
* **2단계 [센서 구동 전압 설정]**: 센서 보드를 구동하는 데 필요한 전압을 전원 장치에 설정합니다.
* **3단계 [센서 구동 전원 출력]**: 설정된 전원을 출력하여 보드를 부팅합니다.
* *(참고: 이 단계는 로그인 성공 시 1회만 실행되며, 이후의 반복 검사 과정에서는 이미 투입된 전원을 재사용하여 4단계부터 즉각 검사가 수행됩니다)*
### [2단계] 제품 일련번호 수집 및 센서 통신 대기 단계
* **4단계 [판독 장치 대기]**: 센서 판독 장치를 초기 대기 상태로 설정합니다.
* **5단계 [보드 통신 연결]**: 통신 설정에 명시된 채널을 선택하여 센서 보드와 연결합니다.
* **6단계 [일련번호 읽기]**: 제품 메모리로부터 스캔한 고유 일련번호를 로드합니다.
* **7단계 [보드 통신 해제]**: 센서 데이터 전송을 중지하고 대기 상태로 되돌립니다.
* **8단계 [보정 기준 전송]**: 센서 장치에 보정 기준값을 인계하고 동작을 종료합니다.
### [3단계] 계측기 연동 성능 평가 및 전원 차단 단계
* **9단계 [전원 장비 확인]**: 주 테스트 전원을 공급할 전원 장치의 통신 상태를 확인합니다.
* **10단계 [테스트 전원 인가]**: 테스트 전원 출력을 ON시키고, 전원 인가 후 신호 안정을 위해 잠시 대기합니다.
* **11단계 [측정 장비 확인]**: 측정 장치의 통신 상태를 확인합니다.
* **12단계 [전압 측정]**: 제품의 현재 작동 전압을 측정합니다.
* **13단계 [저항 측정]**: 제품의 전기적 저항값을 직접 실측합니다.
* **14단계 [전류 측정]**: 전원 공급 장치의 출력 채널을 통해 제품의 작동 소모 전류를 측정합니다.
* **15단계 [테스트 전원 차단]**: 수집 완료 후 테스트 전원 출력을 자동으로 차단하여 제품을 안전하게 보호합니다. (계측 도중 오류가 발생하더라도 강제로 전원이 차단되도록 안전 장치가 적용되어 있습니다.)
---
## 7.3 최종 판정 및 마킹 실행 조건
계측이 완료되면 측정된 전압(V), 저항(kΩ), 전류(mA) 값을 설정한 기준 규격값(SPEC)과 비교하여 최종 판정을 내립니다.
1. **기준 규격을 벗어난 경우 (FAIL)**:
* 전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(FAIL) 판정이 내려집니다.
* 이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, 불합격 내역으로 DB에 기록됩니다.
2. **모든 항목이 합격인 경우 (PASS)**:
* 모든 수치가 기준치 내에 들어오면 최종 합격(PASS) 판정이 내려집니다.
* 합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 2D QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.
* 마킹 완료 기록과 계측 수치는 최종 PASS 상태로 데이터베이스에 저장됩니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

39
marking_gui/Manual/08_로그_관리.md

@ -0,0 +1,39 @@
# 8. 로그 및 데이터 관리
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 8.1 시스템 로그 수집 및 관리
본 제어 프로그램은 설비 가동 중에 발생하는 계측기와의 통신 상태, 측정값 정보 및 각종 예외 오류 상황을 로그 파일로 기록합니다.
### 8.1.1 로그 파일 보관 방법
* **보관 폴더**: 프로그램 실행 파일 경로의 하위 **`Logs`** 폴더에 저장됩니다.
* **파일명 규칙**: 날짜별로 파일이 자동으로 생성되어 보관됩니다.
* **로그 중요도 분류**:
* **정보 (INFO)**: 정상적인 검사 프로세스 시작, 계측기 연결 성공, 레이저 인쇄 성공 및 데이터베이스 저장 완료 기록.
* **경고 (WARN)**: 센서 판독기 인식 지연, 제품 한계 기준치를 벗어난 불합격(FAIL) 발생 기록 등 설비 동작은 유지되나 품질 확인이 필요한 정보.
* **오류 (ERROR)**: 데이터베이스 네트워크 접속 끊김, 마킹 장치 연결 실패 등 조치가 필요한 장비 오류 정보.
---
## 8.2 장비 교체 및 프로그램 백업 방법
장비를 교환하거나 PC 복구 상황에 대비하여, 정상 작동 상태에서의 설정 파일들을 정기적으로 백업해 둘 것을 권장합니다.
### 8.2.1 백업 대상 리스트
1. **계측기 설정 및 검사 기준값 백업**:
* 계측기들의 연결 설정 정보와 합격 판정 기준 범위가 저장됩니다.
2. **데이터베이스 서버 정보 백업**:
* 생산 정보 데이터베이스 서버 주소와 로그인 정보가 저장됩니다.
3. **센서 채널 정보 백업**:
* 일련번호 판독 통신 포트 번호와 설정 채널 정보가 저장됩니다.
4. **레이저 마킹 도면 파일 백업**:
* 제품 표면에 각인할 마킹 도면 파일입니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

59
marking_gui/Manual/09_오류_대응.md

@ -0,0 +1,59 @@
# 9. 오류 대응 및 프로그램 종료
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 9.1 현장 장애 유형별 점검 및 예외 조치 요령
공정 가동 중 장비 상태나 전산망에 이상이 생길 경우 발생할 수 있는 주요 화면 경고창 및 조치 요령입니다.
### 9.1.1 [오류 1] "제품 ID(IC_SN)를 읽어오지 못했습니다"
* **현상**: 검사 시작 시 제품 일련번호 읽기 단계에서 경고창이 발생하고 검사가 취소됨.
* **원인 및 해결**:
1. **접촉 상태 점검**: 제품이 안착 지그에 완벽하게 안착되었는지 확인하고, 프레스 실린더 또는 핀 단자의 밀착 상태를 확인하십시오.
2. **통신 설정 점검**: 프로그램 상단의 `통신 설정` 창을 열어 현재 설정된 포트 번호 및 물리 채널 번호가 실제 케이블 연결 상태와 일치하는지 확인하십시오.
### 9.1.2 [오류 2] "이전 공정 이력이 조회되지 않는 제품입니다"
* **현상**: 검사 시작 시 이전 단계 결과가 조회되지 않아 검사가 진행되지 않음.
* **원인 및 해결**:
- 해당 제품이 하우징 조립, 센서 보정, EOL 1차 성능 검사 등 이전 가공 공정을 진행하지 않았거나, 이전 설비에서 생산 DB에 데이터를 올바르게 전송하지 않은 상태입니다. 제품 바코드를 재확인하고 이전 설비의 데이터 전송 유무를 검토하십시오.
* **비상 조치 (강제 검사)**:
- 이전 이력 등록 상태와 무관하게 **제품 성능 계측 및 마킹을 실행하고 결과를 저장해야 하는 특수 테스트 상황**인 경우, 화면 최하단의 **`강제 검사 (스킵)`** 버튼을 누르면 DB 검증을 건너뛰고 성능 검사를 강제로 실행할 수 있습니다.
### 9.1.3 [오류 3] "이전 공정 단계가 불합격 상태이므로 최종 성능 검사를 시작할 수 없습니다"
* **현상**: DB 조회 결과 이전 공정 중 불합격(FAIL 또는 NG) 판정을 받은 내역이 발견되어 검사가 중단됨.
* **원인 및 해결**:
- 이전 공정에서 불합격 판정을 받은 제품은 최종 평가 및 레이저 마킹을 가동할 수 없습니다. 불량 제품 격리 기준에 따라 해당 제품을 생산 라인에서 제외 조치하십시오.
* **비상 조치 (강제 검사)**:
- 설비 작동 확인이나 품질 분석 등을 위해 반드시 검사를 진행해야 하는 경우, 하단바의 **`강제 검사 (스킵)`** 버튼을 클릭하면 경고 상태를 건너뛰고 계측과 마킹을 실행할 수 있습니다.
### 9.1.4 [오류 4] "레이저 각인 작업 전송 실패"
* **현상**: 성능 검사는 합격(PASS)했으나, 레이저 장비로의 데이터 전송 단계에서 경고 에러가 발생함.
* **원인 및 해결**:
1. **도면 파일 점검**: `시험 설정` 화면에서 세팅해 놓은 작업 도면 파일이 실제 해당 경로에 존재하는지 점검하십시오.
2. **마킹기 전원/연결 점검**: 레이저 마킹기의 전원이 켜져 있는지 확인하고, 제어 PC와 마킹기 간의 연결 케이블 상태를 확인하십시오.
### 9.1.5 [오류 5] "각인 순번이 9999를 초과했습니다. 다음 Lot No로 변경하십시오."
* **현상**:
- 프로그램 실행 또는 로그인 완료 후, 메인 화면 상태 알림창에 빨간색 경고 배너로 문구가 표시됩니다.
- `검사 시작 (START)` 또는 `강제 검사 (스킵)` 버튼을 누르면 작동 차단 경고창이 나타나며 설비 검사가 시작되지 않습니다.
* **원인 및 해결**:
- **순번 범위 초과**: 현재 로트 번호(`Lot No`)에서 최종 합격을 받아 각인이 완료된 누적 수량이 9999개를 초과하여 더 이상 순차 번호를 인쇄할 수 없습니다.
- **해결 방법**: 새로운 생산 조 또는 로트 번호(`Lot No`)를 수정하여 **프로그램에 재로그인**하십시오. 로트 번호가 변경되면 순번 카운트가 다시 초기화됩니다.
---
## 9.2 프로그램 정상 종료 및 전원 자동 차단 규칙
본 설비는 고전력 계측 및 전원 제어를 함께 수행하므로 대기 전력 소모 방지와 안전을 위해 아래 절차에 따라 정상 종료 프로세스를 밟아야 합니다.
* **로그아웃 (Logout)**:
- 상단 메뉴의 `로그아웃` 버튼을 클릭하면 현재 작업자 정보가 해제되고 초기 로그인 창으로 전환됩니다.
- **이와 동시에 연동된 전원 공급 장치로 차단 신호가 자동으로 전송되어 주전원 및 보드 부팅용 상시전원 출력이 OFF 상태로 차단됩니다.**
* **프로그램 완전 종료**:
- 화면 우측 상단의 `닫기(X)` 버튼을 눌러 프로그램을 최종 종료할 때도, 자동으로 상시전원 차단 명령을 장비에 인계한 뒤 레이저 각인 드라이버 리소스를 닫고 메모리를 환원합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

844
marking_gui/Manual/Manual_Merged.md

@ -0,0 +1,844 @@
<style>
/*
* A4 용지 기준 PDF 출력을 위한 스타일시트 (pdf_style.css)
*/
@page {
size: A4;
margin: 20mm 15mm 20mm 15mm;
@top-left { content: none !important; }
@top-right { content: none !important; }
@bottom-left { content: none !important; }
@bottom-right { content: none !important; }
@bottom-center {
content: counter(page) !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 8.5pt;
color: #7f8c8d;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 10pt;
line-height: 1.6;
color: #333333;
background-color: #ffffff;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* 제목 스타일 */
h1, h2, h3, h4, h5, h6 {
color: #111111;
font-weight: bold;
page-break-after: avoid;
page-break-inside: avoid;
}
h1 {
font-size: 18pt;
border-bottom: 2px solid #007acc;
padding-bottom: 4px;
margin-top: 20px;
margin-bottom: 12px;
}
h2 {
font-size: 13pt;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 3px;
margin-top: 16px;
margin-bottom: 10px;
}
h3 {
font-size: 11pt;
margin-top: 12px;
margin-bottom: 6px;
}
h4 {
font-size: 10pt;
margin-top: 10px;
margin-bottom: 6px;
}
/* 문단 및 리스트 */
p {
margin-top: 0;
margin-bottom: 8px;
text-align: justify;
}
ul, ol {
margin-top: 0;
margin-bottom: 8px;
padding-left: 20px;
}
li {
margin-bottom: 3px;
}
/* 구분선 */
hr {
height: 1px;
padding: 0;
margin: 15px 0;
background-color: #e1e4e8;
border: 0;
}
/* 테이블 스타일 */
table {
width: 100%;
border-collapse: collapse;
margin-top: 0;
margin-bottom: 10px;
page-break-inside: avoid;
}
table th, table td {
padding: 4px 8px;
border: 1px solid #dfe2e5;
font-size: 8.5pt;
word-break: keep-all;
}
table th {
font-weight: 600;
background-color: #f6f8fa;
text-align: left;
}
table tr:nth-child(even) {
background-color: #f9fbfd;
}
table tr {
page-break-inside: avoid; /* 표의 중간 행이 반으로 쪼개지는 것만 방지 */
}
/* 인용구 스타일 */
blockquote {
padding: 6px 12px;
color: #6a737d;
border-left: 0.25em solid #007acc;
background-color: #f6f8fa;
margin: 0 0 10px 0;
page-break-inside: avoid;
}
blockquote p {
margin: 0;
}
/* 코드 블록 (아스크 아트 등 포함) */
pre, code {
font-family: "Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", monospace;
font-size: 8.5pt;
background-color: #f6f8fa;
border-radius: 3px;
}
code {
padding: 0.2em 0.4em;
margin: 0;
border: 1px solid #eaecef;
}
pre {
padding: 12px;
overflow: auto;
line-height: 1.45;
border: 1px solid #e1e4e8;
word-wrap: normal;
white-space: pre; /* 아스키 아트 정렬 유지를 위해 강제 줄바꿈 해제 */
page-break-inside: avoid;
}
pre code {
padding: 0;
margin: 0;
background-color: transparent;
border: none;
font-size: inherit;
white-space: inherit;
}
/* 이미지 스타일 */
img {
max-width: 100%;
max-height: 100mm; /* A4 높이의 약 절반 이하로 제한 */
display: block;
margin: 15px auto;
object-fit: contain;
page-break-inside: avoid;
border: 1px solid #e1e4e8;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 페이지 나누기 요소 */
.page-break {
page-break-before: always !important;
}
/* 내비게이션 숨김 (인쇄 시) */
.nav-link, .nav-links, .back-to-toc, .manual-nav,
a[href$=".md"] {
display: none !important;
}
/* 링크 스타일 */
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 특별 배지 스타일 */
.badge {
display: inline-block;
padding: 2px 6px;
font-size: 8pt;
font-weight: 600;
line-height: 1;
border-radius: 2px;
background-color: #e1e4e8;
color: #24292e;
margin-right: 4px;
}
</style>
# Performance Inspect & Laser Marking System - 사용자 메뉴얼
> **버전**: 1.0.0 | **최종 수정**: 2026-07-14
---
## 메뉴얼 목차
본 메뉴얼은 각 주제별로 별도 문서로 구성되어 있습니다. 아래 링크를 클릭하여 해당 문서로 이동하세요.
| 번호 | 문서 | 설명 |
|------|------|------|
| 1 | [시스템 개요 및 요구사항](#1-시스템-개요-및-요구사항) | 프로그램 소개, 주요 기능, 하드웨어/소프트웨어 요구사항, 장비 구성 |
| 2 | [초기 설정 가이드](#2-초기-설정-가이드) | 설정 파일의 구성 요소 안내 및 최초 장비 가동 시 점검 순서 |
| 3 | [메인 화면 (Home View)](#3-메인-화면-home-view) | 상단 정보 표시, 제품 정보 카드, 검사 결과 화면, 진행 상황 확인 및 버튼 제어 가이드 |
| 4 | [통신 설정 (Comm. Settings)](#4-통신-설정-comm-settings) | 4251 센서 통신 채널 설정 및 PC 통신 포트 연동 방법 |
| 5 | [시험 설정 (Parameters)](#5-시험-설정-parameters) | 제품 규격치(전압, 전류, 저항 범위) 및 레이저 마킹 도면 파일 연동 |
| 6 | [측정 데이터 조회 (Data View)](#6-측정-데이터-조회-및-데이터베이스-연동) | 생산 데이터베이스를 통한 이전 공정 이력 자동 조회 및 최종 결과 저장 원리 |
| 7 | [자동 시험 프로세스](#7-자동-시험-프로세스-및-14단계-시퀀스) | 자동 계측 시퀀스 기동, 마킹 수행 및 최종 합격 판정 흐름 |
| 8 | [로그 및 데이터 관리](#8-로그-및-데이터-관리) | 시스템 로그 파일 확인 방법 및 장비 설정 파일 백업 방법 |
| 9 | [오류 대응 및 프로그램 종료](#9-오류-대응-및-프로그램-종료) | 센서 통신 오류, DB 조회 에러, 마킹 불량 등 현장 장애 대응 및 안전한 프로그램 종료 방법 |
---
## 빠른 시작 가이드 (Quick Start)
처음 시스템을 가동하여 검사를 진행하는 경우 다음 순서에 따라 작업을 수행해 주십시오.
1. **프로그램 설치 (최초 1회)**
- 배포받은 단일 설치 프로그램 `MarkingSetup.exe`를 실행하여 클릭 한 번으로 `C:\marking` 경로 및 바탕화면 바로가기를 자동 생성합니다.
2. **하드웨어 연결 상태 확인**
- 계측 장비들과 제어용 PC 간의 네트워크(LAN)선 및 센서 리더용 시리얼 포트 케이블 연결을 확인합니다.
3. **프로그램 실행 및 로그인**
- 바탕화면의 `Marking System` (`marking_gui`) 프로그램을 실행한 뒤, 작업자 계정 정보를 입력하여 로그인합니다. (데이터베이스 점검 중인 경우 설정 파일을 통해 오프라인 모드로의 진입이 가능합니다.)
3. **통신 상태 확인 및 채널 설정**
- 우측 상단의 `통신 설정` 메뉴에서 센서가 올바른 포트와 채널에 연결되었는지 확인합니다.
4. **검사 규격값 검증**
- 우측 상단의 `시험 설정` 메뉴를 열고, 전압/전류/저항 검사 기준치와 각인 템플릿 도면 파일의 경로가 올바른지 점검합니다.
5. **성능 검사 및 마킹 시작**
- 안착 지그에 제품을 바르게 장착한 뒤 메인 화면 하단의 `검사 시작 (START)` 버튼을 눌러 계측 및 마킹을 수행합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 1. 시스템 개요 및 요구사항
---
## 1.1 시스템 개요
**Performance Inspect & Laser Marking System**은 온압센서 완제품의 최종 성능 검사를 수행하고, 이전 공정 단계들의 합격(PASS) 이력을 조회하여 최종 적합 제품에 한해 제품 표면에 레이저 마킹을 수행하는 시스템 제어 프로그램입니다.
본 프로그램은 측정 계측기와 센서 판독기, 레이저 각인 장치 및 공장 생산 데이터베이스와 연동되어 작동합니다.
### 주요 기능 요약
| 기능 | 설명 |
|------|------|
| **일련번호(S/N) 자동 추출** | 센서 판독 보드를 통해 제품 안착 시 제품 내부 메모리로부터 일련번호를 추출합니다. |
| **선행 공정 검사 교차 조회** | 이전 단계(하우징 조립, 센서 보정, 1차 EOL 성능 검사)가 모두 합격 판정되었는지 생산 데이터베이스에서 조회합니다. |
| **자동 계측 시퀀스** | 전원 공급 장치와 전압/저항 측정 장비들과 연동하여 제품에 테스트 전원을 투입하고 전압, 저항, 전류 값을 차례대로 계측합니다. |
| **실제 저항값 측정** | 전압/저항 DMM 측정기를 통해 센서의 실제 저항값(Ω)을 측정합니다. |
| **전류 측정** | 전원 공급 장치(Power Supply)의 출력 채널 1을 통해 제품의 소모 전류를 계측합니다. |
| **레이저 각인 연동** | 성능 검사가 모두 통과된 합격품에 대해 제품 표면에 2D QR 코드 및 단축 일련번호(생산일자 + 순번)를 마킹합니다. |
| **결과 기록** | 계측 판정 규격(전압, 전류, 저항)과 마킹 성공 여부를 최종 판정하여 생산 데이터베이스에 저장합니다. |
---
## 1.2 소프트웨어 요구사항
| 항목 | 요구사항 | 비고 |
|------|----------|------|
| **통합 설치 프로그램** | `MarkingSetup.exe` (WiX 번들) | 1-Click 자동 설치 및 실행 환경, 드라이버, 설정 일체 셋업 |
| **운영체제** | Windows 10 이상 | 프로그램 구동 및 장비 드라이버 운용용 |
| **데이터베이스** | Microsoft SQL Server (MS-SQL) | 생산 공정 데이터 및 계측 결과 연동용 |
| **레이저 제어 프로그램** | EZCAD 각인 제어 프로그램 및 드라이버 | 레이저 마킹기 구동 및 전용 도면(.ezd) 운용용 |
| **네트워크 환경** | LAN (TCP/IP) 통신 | 제어 PC와 측정 기기 간의 연결 |
| **센서 보드 통신** | COM 포트 (RS-232 시리얼) | 4251 센서 통신 보드 연동 |
---
## 1.3 하드웨어 구성
| 장비 | 역할 | 통신 방식 |
|------|------|-----------|
| **센서 판독 보드 (4251 보드)** | 제품 내부 칩의 일련번호(IC_SN)를 인식합니다. | RS-232 시리얼 통신 |
| **전압 및 저항 측정 장비 (DMM)** | 제품의 전압(V) 및 전기적 저항(Ω)을 측정합니다. (예: 192.168.1.100) | TCP/IP 네트워크 통신 |
| **전류 측정 및 전원 공급 장비 (PSU)** | 제품 구동을 위한 전원 공급 및 소모 전류(A)를 측정합니다. (예: 192.168.1.101) | TCP/IP 네트워크 통신 |
| **레이저 마킹기** | 2D QR 코드 및 텍스트 일련번호를 제품 표면에 인쇄합니다. | 마킹 제어 보드 연동 |
| *(전류 측정 DMM)* | *설정 파일(예: 192.168.1.102)에 존재하나 현재 제어 시퀀스에서는 사용하지 않습니다.* | *(미사용)* |
## 1.4 설치 경로(`C:\marking\`) 구조 및 생성 파일 역할
단일 통합 설치 마법사(`MarkingSetup.exe`)를 통해 시스템을 설치하면 기본 설치 디렉터리(`C:\marking\`) 아래에 다음과 같은 실행 바이너리, 설정 파일, 드라이버 라이브러리 및 런타임 로그 폴더가 배치·생성됩니다.
| 구분 | 파일 / 폴더명 | 상세 역할 및 기능 설명 |
|------|----------------|------------------------|
| **실행 프로그램** | `marking_gui.exe` | 메인 성능 검사 및 레이저 마킹 GUI 제어 실행 파일 |
| **종속 라이브러리** | `marking_gui.dll`, `*.deps.json`, `*.runtimeconfig.json` | .NET 9.0 (WPF) 자립형 런타임 및 종속 구성 바이너리 |
| **통합 설정 파일** | `Setting.ini` | 마킹기 폴더 경로, 테스트 모드, 도안 태그 매핑, 계측기 LAN IP, SCPI 전원 커맨드, 계측 판정 규격 임계치(`[Limits]`) 종합 설정 파일 |
| **DB 설정 파일** | `Database.ini` | MS-SQL 데이터베이스 접속 주소, DB명, 사용자 계정/PW, 오프라인 모드(`OfflineMode=true`) 설정 파일 |
| **센서 설정 파일** | `Sensor.ini` | 4251 센서 통신 보드 시리얼 포트(`Port=COM3`), 통신 속도(`BaudRate=115200`), 통신 채널(`Channel=1`) 설정 파일 |
| **드라이버 폴더** | `libs/` | C++ LMC 각인 드라이버 격리 디렉터리 (`MarkEzdWrapper.dll`, `DataMgr.dll`, `MarkEzd.dll`, `haspms32.dll` 등) |
| **로그 저장 폴더** | `logs/` *(자동 생성)* | 시스템 운용 중 발생하는 일별 이벤트 로그, 통신 이력, 각인 성공/실패 기록 텍스트 저장 디렉터리 (예: `logs/2026-07-23.log`) |
| **백업 보관 폴더** | `publish_backup/` | 프로그램 업데이트 및 빌드 배포 시 이전 릴리즈 바이너리/설정 자동 백업 폴더 |
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 2. 초기 설정 가이드
---
## 2.1 프로그램 설정 구성 개요
본 프로그램은 정상적인 계측기 통신 및 데이터베이스 연동을 위해 몇 가지 설정 파일을 사용합니다. 이 설정 파일들은 프로그램 설치 및 라인 도입 시 최초 1회 셋업을 원칙으로 하며, 일반 운영자가 직접 설정 파일을 열어 수정할 필요는 없습니다.
*참고: 대부분의 검사 기준 및 통신 정보는 프로그램 내의 **'통신 설정'** 및 **'시험 설정'** 화면을 통해 수정할 수 있습니다.*
### 2.1.1 주요 설정 정보 구분
1. **장비 및 계측 설정**
- 계측기들과의 네트워크 연결 주소와 대기 시간 등을 정의합니다.
- 제품 합격 여부를 판정하는 전압, 전류, 저항의 기본 합격 규격 범위가 이 파일에 저장되며, 화면 내의 **'시험 설정'** 창에서 수정한 사항이 이 파일에 동적으로 반영됩니다.
- 프로그램은 전압/저항 측정 장치와 전원/전류 공급 장치를 제어하여 계측을 진행합니다.
2. **생산 데이터베이스 연동 설정**
- 공장 생산 관리 시스템 데이터베이스와의 통신 경로가 기록되어 있습니다.
- 전산망이나 DB 서버 점검 등으로 인해 통신이 어려울 경우, 설정 파일을 통해 **'오프라인 모드'**로 전환하여 단독으로 장비 작동 테스트를 해볼 수 있습니다.
- **오프라인 로그인 지원**: 데이터베이스 확인 단계 없이 로그인하여 프로그램을 기동합니다.
- **오프라인 검사 지원**: 제품 이력 조회 및 검사 결과 저장을 건너뛰고 단독으로 계측 및 마킹을 실행합니다.
3. **센서 판독기 설정**
- 제품 일련번호를 로드하기 위한 통신 채널 및 연결 포트 주소가 정의됩니다.
- 화면 내의 **'통신 설정'** 창에서 수정한 사항이 이 파일에 자동으로 저장됩니다.
### 2.1.2 설치 경로(`C:\marking\`) 내 파일 배치 구조
프로그램 설치 완료 후 `C:\marking\` 경로에는 다음과 같이 구동 파일, 설정 파일, 드라이버 라이브러리 및 런타임 자동 생성 디렉터리가 배치됩니다.
* **`C:\marking\marking_gui.exe`**: GUI 제어 메인 실행 파일
* **`C:\marking\Setting.ini`**: 장비 통신 IP, 마킹 태그, 규격 임계치(`[Limits]`) 종합 설정 파일
* **`C:\marking\Database.ini`**: MS-SQL DB 접속 정보 및 오프라인 모드 설정 파일
* **`C:\marking\Sensor.ini`**: 4251 센서 통신 보드 포트/속도/채널 설정 파일
* **`C:\marking\libs\`**: C++ LMC 레이저 각인 드라이버 라이브러리 격리 폴더 (`MarkEzdWrapper.dll` 등)
* **`C:\marking\logs\`**: 런타임 가동 중 자동 생성되는 일별 이력 로그 폴더 (`yyyy-MM-dd.log`)
* **`C:\marking\publish_backup\`**: 업데이트/배포 시 이전 버전 바이너리 및 설정 자동 백업 폴더
### 2.1.3 기본적인 설정 파일 편집 방법
> [!IMPORTANT]
> **[권장 조치 안내]**
> 현장 라인 검사자 및 일반 작업자는 프로그램 UI의 **[통신 설정]** 및 **[시험 설정]** 메뉴를 통해 안전하게 파라미터를 변경하십시오.
> 아래의 `.ini` 파일 직접 수정은 라인 엔지니어 또는 설비 관리자 전용 권한 작업이며, 잘못된 파일 수정 시 장비 통신 오류가 발생할 수 있습니다.
설비 이동이나 네트워크 환경 변경 시, 프로그램 폴더 내의 각 `.ini` 파일을 텍스트 편집기(메모장 등)로 열어 아래의 전체 설정 레이아웃 항목을 참조하여 수정할 수 있습니다.
#### 1) Setting.ini (주요 변경 가능 설정)
현장 네트워크 IP, 도안 파일 경로 및 검사 규격(임계치) 변경 시 수정하는 주요 설정 항목입니다.
```ini
[Settings]
EzdFolderPath=c:\EzCad\EasyCfg ; EZD 도안 파일 기본 탐색/저장 폴더 경로
test=false ; 테스트 마킹 버튼 및 개발 모드 노출 여부 (true/false)
[DMM]
VoltageHost=192.168.1.100 ; 전압/저항 측정용 계측기(DMM) IP 주소
[CurrentDMM]
CurrentDMMHost=192.168.1.102 ; 전류 측정용 계측기(DMM) IP 주소
[PowerSupply]
CurrentHost=192.168.1.101 ; 전원 공급기(Power Supply) IP 주소
[Limits]
MinVoltage=4.0 ; 전압 하한 판정 기준값 (V)
MaxVoltage=6.0 ; 전압 상한 판정 기준값 (V)
MinCurrent=8.0 ; 전류 하한 판정 기준값 (mA)
MaxCurrent=10.0 ; 전류 상한 판정 기준값 (mA)
MinResistance=0.4 ; 저항 하한 판정 기준값 (kΩ)
MaxResistance=0.8 ; 저항 상한 판정 기준값 (kΩ)
```
#### 2) Database.ini (주요 변경 가능 설정)
DB 서버 접속 주소 및 점검용 오프라인 모드 변경 시 수정하는 항목입니다.
```ini
[Database]
Db_Server=192.168.1.200 ; MSSQL 데이터베이스 서버 IP 주소
Db_Name=MarkingDb ; 연결할 데이터베이스 이름
Db_User=db_user ; DB 접속 사용자 계정
Db_Password=db_password ; DB 접속 비밀번호
OfflineMode=False ; DB 미연결 점검 모드 (True: DB 저장 생략, False: 정상 검증/적재)
[Login]
OfflinePreview=False ; 로그인 DB 검증 우회 여부 (True: 로그인 생략, False: 정상 검증)
```
#### 3) Sensor.ini (주요 변경 가능 설정)
바코드 스캐너 시리얼 포트 및 채널 설정 항목입니다.
```ini
[Sensor]
Port=COM3 ; 바코드 스캐너 시리얼 포트 (장치 관리자 COM 포트 번호)
Channel=1 ; 통신 채널 번호 (1 또는 2)
```
---
## 2.2 최초 실행 및 장비 점검 순서
라인 가동 전 최초 실행할 때는 아래 단계를 순서대로 이행하여 정상 작동 여부를 점검하십시오.
1. **프로그램 단일 설치 (MarkingSetup.exe)**
- 배포받은 단일 설치 마법사 `MarkingSetup.exe`를 실행하여 1-Click으로 `C:\marking\` 설치 및 바탕화면 바로가기를 자동 생성합니다.
2. **데이터베이스 연결 상태 확인**
- 데이터베이스가 점검 중인 경우 설정 파일을 통해 오프라인 모드로 설정하면 로그인 창을 통과하여 단독 테스트를 진행할 수 있습니다.
3. **장비 네트워크 통신 점검**
- 제어 PC와 계측 장비들(전압/저항 측정 장비, 전원 공급 장치) 간의 LAN 케이블 연결을 확인합니다.
- 제어 PC의 네트워크 IP 환경이 장비들의 대역과 동일한지 확인하고, 통신 신호가 정상적으로 송수신되는지 점검합니다.
4. **상시전원 자동 인가 확인**
- 프로그램 실행 후 작업자 계정으로 로그인을 시도합니다.
- 로그인이 완료되면 전원 공급 장치의 상시전원 채널 전원 램프가 자동으로 켜지는지 확인합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 3. 메인 화면 (Home View)
---
## 3.1 메인 화면 레이아웃 구성
본 프로그램은 작업자가 화면 구성 및 각 항목 상태를 파악할 수 있도록 4개의 카드 레이아웃으로 화면을 구성했습니다.
### 3.1.1 상단 헤더 영역 (Header Bar)
프로그램 최상단 영역에는 시스템 정보 배지와 제어 버튼이 배치되어 있습니다.
* **실시간 시계**: 좌측 상단에 날짜와 시간이 표시됩니다.
* **작업 정보 배지**:
* **공정 라인**: 로그인 단계에서 지정한 생산 라인 코드 (`Line No`)
* **로트 번호**: 로그인 단계에서 지정한 생산 로트 번호 (`Lot No`)
* **모델명**: 현재 가동 중인 제품의 사양 모델 (`Model`)
* **작업자**: 로그인한 작업자의 이름 (`Operator`)
* **제어 버튼**: 우측 상단에 `로그아웃` 버튼이 위치하며 최하단 바에 설정 관련 버튼이 배치됩니다.
---
## 3.2 상태 및 제어 카드 영역
화면 중앙에는 역할을 구분한 4개의 상태 카드가 배치되어 있어, 제품 상태와 계측 단계를 모니터링할 수 있습니다.
### 3.2.1 [카드 1] 제품 정보 및 이전 결과 (좌측 상단)
제품의 일련번호와 이전 생산 공정에서의 합격 이력을 대조하는 영역입니다.
* **작업 모델 설정**:
- `파일 선택` 버튼을 사용하여 각인용 도면 파일을 불러옵니다.
- 도면 파일 선택이 완료되면 상단 헤더 영역의 모델명(Model) 배지 텍스트가 선택한 도면명으로 자동 변경됩니다.
* **일련번호 (IC_SN)**: 제품이 안착되면 센서 판독기를 통해 자동으로 인식되어 표시됩니다. (읽기 전용)
* **이전 공정 이력 배지**:
* **Housing**: 하우징 조립 공정 판정 상태 (`PASS` / `FAIL` / `대기`)
* **Calibration**: 압력 보정 공정 판정 상태 (`PASS` / `FAIL` / `대기`)
* **EOL**: EOL 1차 성능 평가 판정 상태 (`PASS` / `FAIL` / `대기`)
* *주의: 이전 공정 이력 배지 중 하나라도 `FAIL` 상태인 경우 성능 검사를 시작할 수 없습니다.*
### 3.2.2 [카드 2] 측정 결과 (우측 상단)
현재 장비에서 실제 계측한 3대 전기 파라미터(전압, 전류, 저항) 수치와 설정된 기준 규격 범위를 표시하는 영역입니다.
* **측정 전압 (Voltage)**: 전압 측정 장치가 측정한 전압 수치 (**V** 단위)
* **측정 전류 (Current)**: 전원 공급 장치가 측정한 소모 전류 수치 (**mA** 단위)
* **측정 저항 (Resistance)**: 저항 측정 장치가 측정한 전기적 저항 수치 (**kΩ** 단위)
* *알림: 각 물리량의 하단에는 현재 적용 중인 기준 규격 범위가 문구(`기준범위: X ~ Y`)로 표출되며, 실측값이 규격 범위 내에 도달하면 카드 테두리가 **초록색(PASS)**으로 표시되고, 범위를 벗어나면 테두리가 **빨간색(FAIL)**으로 바뀝니다.*
### 3.2.3 [카드 3] 진행상황 (좌측 하단)
현재 설비가 수행하고 있는 단계별 자동 제어 시퀀스를 보여주는 안내 창입니다.
* "대기 상태", "제품 정보 수집 중...", "이전 공정 검사 결과 조회 중...", "측정 계측기 데이터 수집 중...", "제품 합격 - 레이저 각인 중..." 등 작동 상태를 실시간 텍스트로 표출합니다.
### 3.2.4 [카드 4] 판정결과 (우측 하단)
제품의 최종 합격(PASS) 및 불합격(FAIL) 상태를 표시합니다.
* 계측 검사와 데이터 저장, 레이저 각인이 완료되었을 때 최종 합격인 경우 카드 배경이 **초록색(PASS)**으로 전환되며, 불합격인 경우 카드 배경이 **빨간색(FAIL)**으로 변경됩니다.
### 3.2.5 최하단 제어 영역 (Control Box)
* **검사 시작 (START)**: 전체 검사 시퀀스를 기동하는 메인 버튼입니다. 이 버튼을 누르면 이전 계측 수치들이 초기화되고 새로운 측정 프로세스가 개시됩니다.
* **강제 검사 (스킵)**: 이전 가공 공정의 합격 이력이 없더라도 검증 단계를 건너뛰고 성능 계측 및 각인 시퀀스를 기동하는 버튼입니다.
* **테스트 마킹**: 성능 계측 없이 즉시 각인기에 테스트 마킹 신호를 전송하는 버튼입니다.
* *참고: `강제 검사 (스킵)``테스트 마킹` 버튼은 환경 설정에서 테스트 모드가 활성화된 상태에서만 화면 하단에 노출됩니다.*
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 4. 통신 설정 (Comm. Settings)
---
## 4.1 포트 설정 화면 사용법
프로그램 상단의 `통신 설정` 버튼을 클릭하면 포트 설정 대화 상자가 나타납니다. 이 대화 상자에서 센서 판독 장치(4251 보드)의 연결 포트 및 채널을 설정할 수 있습니다.
### 주요 설정 항목 및 설명
1. **통신 포트 (Port)**:
- 센서 판독기가 연결되어 있는 PC의 연결 포트를 선택합니다. (예: `COM1`, `COM3`, `COM4` 등)
- 현재 PC에 인식되어 있는 포트 목록이 자동으로 표시됩니다.
2. **통신 속도 (BaudRate)**:
- 센서 판독 장치와의 데이터 송수신 속도를 설정합니다.
- 기본 설정값은 **`115200`** 이며, 특수한 경우가 아니면 변경하지 않는 것을 권장합니다.
3. **통신 채널 (Channel)**:
- 센서 판독기 내부의 연결 채널을 결정합니다.
- 드롭다운 메뉴를 통해 **`CH1`** 또는 **`CH2`**를 선택할 수 있습니다.
- 선택된 채널에 따라 센서 인식 모드가 변경됩니다.
---
## 4.2 설정 저장
* **설정 저장**:
- 통신 포트와 채널을 선택한 후 `저장` 버튼을 누르면 설정 정보가 저장됩니다.
- 저장 시 기존 센서 연결을 해제하고 새로운 설정값으로 센서 인식 서비스를 다시 시작합니다. 프로그램을 재시작할 필요 없이 변경 사항이 즉시 적용됩니다.
- 저장하지 않고 창을 닫으려면 `취소` 버튼을 누릅니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 5. 시험 설정 (Parameters)
---
## 5.1 판정 기준 설정
프로그램 상단 우측의 `시험 설정` 버튼을 클릭하면 판정 기준을 구성하는 대화 상자가 나타납니다. 이 화면에서 제품 합불 판정에 사용되는 전압, 전류, 저항의 기준값을 입력할 수 있습니다.
### 5.1.1 각 항목별 한계치 입력 가이드
* **전압 판정 범위**:
* **하한값**: 합격 전압 하한 규격 (V 단위)
* **상한값**: 합격 전압 상한 규격 (V 단위)
* **전류 판정 범위**:
* **하한값**: 합격 전류 하한 규격 (설정창 표기 단위 참조, 기본 mA/A)
* **상한값**: 합격 전류 상한 규격 (설정창 표기 단위 참조, 기본 mA/A)
* **저항 판정 범위**:
* **하한값**: 합격 저항 하한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)
* **상한값**: 합격 저항 상한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)
*참고: 저장 버튼을 클릭하면 메인 화면의 하단 가이드 문구(`기준범위: X ~ Y`)가 즉시 갱신되어 새로 입력한 한계치가 다음 검사 시퀀스부터 반영됩니다.*
---
## 5.2 레이저 각인 도면 지정
* **각인 파일 설정 경로**:
* 레이저 마킹기에서 사용할 작업 도면 파일 선택은 설정 대화 상자가 아닌, **메인 화면 좌측 상단의 '작업 모델 설정' 영역에 있는 '파일 선택' 버튼**을 클릭하여 수행합니다.
* 도면이 선택되면 상단 헤더 배지의 모델명 정보도 해당 도면 파일명으로 자동으로 변경됩니다.
* **유의 사항**:
* 지정해 놓은 도면 파일이 실제 경로에 존재하지 않는 경우, 계측 성능 검사가 합격하더라도 각인 단계에서 에러가 납니다. 검사 전 파일 경로 상태를 확인하십시오.
---
## 5.3 설정 저장 및 적용
* 설정 창의 `저장` 버튼을 클릭하면 변경된 파라미터가 시스템 설정에 저장됩니다.
* 저장 완료 즉시 **메인 화면 하단에 표시되는 판정 기준 가이드 텍스트**가 업데이트되며, 이후에 새로 시작하는 성능 검사 시퀀스부터 새로운 규격치가 적용됩니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 6. 측정 데이터 조회 및 데이터베이스 연동
---
## 6.1 선행 공정 결과 조회 (이력 확인)
본 프로그램은 검사 시작 시 입력된 제품 일련번호를 기반으로 생산 데이터베이스에 접속하여 이전 제조 공정의 합격 여부를 조회합니다.
### 6.1.1 이력 검토 대상 공정
시스템은 다음 3개의 선행 공정 검사 결과를 확인합니다.
1. **하우징 조립 공정**
2. **센서 오차 보정 공정**
3. **1차 EOL 성능 검사**
### 6.1.2 이전 단계 규격 교차 확인 동작
* 이전 3개 선행 단계의 이력이 **모두 합격(PASS 또는 OK)**된 제품인 경우에만 최종 성능검사(EOL2)를 진행할 수 있습니다.
* 선행 이력이 존재하지 않거나, 단 한 곳이라도 불합격(FAIL 또는 NG) 판정을 받은 경우 화면 진행 상황 표시창에 경고가 표시되며 최종 성능검사 시작이 제한됩니다.
---
## 6.2 검사 결과 자동 적재 및 최종 판정 기록
자동 계측 프로세스가 종료되면 다음 정보가 생산 데이터베이스에 저장됩니다.
* **저장 정보**:
- 제품 일련번호
- 생산 라인, 로트 번호, 작업자 정보
- 실측 전압, 전류, 저항값
- 실측값은 데이터베이스 저장 규격에 맞춰 시스템 내부에서 소수점 이하 자리로 정밀 정리되어 저장됩니다.
- 최종 판정 결과 (`PASS` / `FAIL`)
- 마킹 시퀀스 번호 및 성공 기록
* **비동기 데이터 처리**:
- 데이터베이스 통신 대기로 인한 화면 멈춤 현상을 방지하기 위해 데이터 조회 및 저장 작업은 프로그램 화면 동작과 별개로 백그라운드에서 비동기식으로 안전하게 처리됩니다.
---
## 6.3 오프라인 모드 운용
데이터베이스 서버 접속이 불가능한 환경에서도 단독으로 설비를 가동할 수 있도록 **오프라인 모드**를 지원합니다.
* **활성화 목적**:
- 네트워크 연결이 끊긴 상태에서 프로그램의 단독 동작 및 장비 제어 상태를 확인하기 위함입니다.
* **오프라인 작동 패턴**:
- **공정 조회 우회**: 입력된 일련번호에 대해 생산 데이터베이스 서버 접속 없이 이전 공정을 합격한 것으로 우회 처리합니다.
- **임시 일련번호 생성**: 일련번호가 비어 있는 상태에서 테스트를 개시하는 경우 현재 시간 기반의 임시 일련번호가 자동으로 부여되어 진행합니다.
- **각인 모사**: 실제 각인기에 데이터를 전송하는 단계를 가상 성공 처리하여 전체 흐름을 테스트합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 7. 자동 시험 프로세스 및 15단계 시퀀스
---
## 7.1 자동 시험 및 마킹 프로세스 개요
본 프로그램은 사용자가 메인 화면 하단의 `검사 시작 (START)` 버튼을 누르면 데이터 저장까지의 과정이 자동 계측 시퀀스에 따라 순차적으로 동작하도록 구성되어 있습니다.
```mermaid
graph TD
A[START 버튼 클릭] --> B[UI 화면 수치 초기화]
B --> C[4~8단계: 센서 S/N 읽기 및 보정]
C --> D{선행 공정 이력 확인}
D -- 불합격/미진행 --> E[검사 중단 & 경고 팝업]
E --> F{강제 검사 스킵?}
F -- 취소 --> Z[대기 복구]
F -- 스킵 실행 --> G[9~15단계: 계측 시퀀스 기동]
D -- 3개 공정 모두 PASS --> G
G --> H[10단계: 테스트 주전원 ON]
H --> I[12~14단계: 전압 / 저항 / 전류 실측]
I --> J[15단계: 테스트 주전원 안전 차단]
J --> K{최종 규격 비교 판정}
K -- 1개 이상 이탈 FAIL --> L[대형 FAIL 표시 & 마킹 제외 & DB 불합격 적재]
K -- 모두 통과 PASS --> M[대형 PASS 표시 & DB 합격 적재 & LMC1 레이저 QR 각인]
L --> Z
M --> Z
```
---
## 7.2 상세 15단계 자동 제어 시퀀스
최종 검사가 시작되면 장비와 계측기들은 다음 15가지 단계에 따라 동작을 차례대로 수행합니다.
### [1단계] 사전 전원 인가 단계 (최초 가동/로그인 시 1회 진행)
* **1단계 [장비 확인]**: 전원 공급 장치와의 통신 연결 상태를 점검합니다.
* **2단계 [센서 구동 전압 설정]**: 센서 보드를 구동하는 데 필요한 전압을 전원 장치에 설정합니다.
* **3단계 [센서 구동 전원 출력]**: 설정된 전원을 출력하여 보드를 부팅합니다.
* *(참고: 이 단계는 로그인 성공 시 1회만 실행되며, 이후의 반복 검사 과정에서는 이미 투입된 전원을 재사용하여 4단계부터 즉각 검사가 수행됩니다)*
### [2단계] 제품 일련번호 수집 및 센서 통신 대기 단계
* **4단계 [판독 장치 대기]**: 센서 판독 장치를 초기 대기 상태로 설정합니다.
* **5단계 [보드 통신 연결]**: 통신 설정에 명시된 채널을 선택하여 센서 보드와 연결합니다.
* **6단계 [일련번호 읽기]**: 제품 메모리로부터 스캔한 고유 일련번호를 로드합니다.
* **7단계 [보드 통신 해제]**: 센서 데이터 전송을 중지하고 대기 상태로 되돌립니다.
* **8단계 [보정 기준 전송]**: 센서 장치에 보정 기준값을 인계하고 동작을 종료합니다.
### [3단계] 계측기 연동 성능 평가 및 전원 차단 단계
* **9단계 [전원 장비 확인]**: 주 테스트 전원을 공급할 전원 장치의 통신 상태를 확인합니다.
* **10단계 [테스트 전원 인가]**: 테스트 전원 출력을 ON시키고, 전원 인가 후 신호 안정을 위해 잠시 대기합니다.
* **11단계 [측정 장비 확인]**: 측정 장치의 통신 상태를 확인합니다.
* **12단계 [전압 측정]**: 제품의 현재 작동 전압을 측정합니다.
* **13단계 [저항 측정]**: 제품의 전기적 저항값을 직접 실측합니다.
* **14단계 [전류 측정]**: 전원 공급 장치의 출력 채널을 통해 제품의 작동 소모 전류를 측정합니다.
* **15단계 [테스트 전원 차단]**: 수집 완료 후 테스트 전원 출력을 자동으로 차단하여 제품을 안전하게 보호합니다. (계측 도중 오류가 발생하더라도 강제로 전원이 차단되도록 안전 장치가 적용되어 있습니다.)
---
## 7.3 최종 판정 및 마킹 실행 조건
계측이 완료되면 측정된 전압(V), 저항(kΩ), 전류(mA) 값을 설정한 기준 규격값(SPEC)과 비교하여 최종 판정을 내립니다.
1. **기준 규격을 벗어난 경우 (FAIL)**:
* 전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(FAIL) 판정이 내려집니다.
* 이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, 불합격 내역으로 DB에 기록됩니다.
2. **모든 항목이 합격인 경우 (PASS)**:
* 모든 수치가 기준치 내에 들어오면 최종 합격(PASS) 판정이 내려집니다.
* 합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 2D QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.
* 마킹 완료 기록과 계측 수치는 최종 PASS 상태로 데이터베이스에 저장됩니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 8. 로그 및 데이터 관리
---
## 8.1 시스템 로그 수집 및 관리
본 제어 프로그램은 설비 가동 중에 발생하는 계측기와의 통신 상태, 측정값 정보 및 각종 예외 오류 상황을 로그 파일로 기록합니다.
### 8.1.1 로그 파일 보관 방법
* **보관 폴더**: 프로그램 실행 파일 경로의 하위 **`Logs`** 폴더에 저장됩니다.
* **파일명 규칙**: 날짜별로 파일이 자동으로 생성되어 보관됩니다.
* **로그 중요도 분류**:
* **정보 (INFO)**: 정상적인 검사 프로세스 시작, 계측기 연결 성공, 레이저 인쇄 성공 및 데이터베이스 저장 완료 기록.
* **경고 (WARN)**: 센서 판독기 인식 지연, 제품 한계 기준치를 벗어난 불합격(FAIL) 발생 기록 등 설비 동작은 유지되나 품질 확인이 필요한 정보.
* **오류 (ERROR)**: 데이터베이스 네트워크 접속 끊김, 마킹 장치 연결 실패 등 조치가 필요한 장비 오류 정보.
---
## 8.2 장비 교체 및 프로그램 백업 방법
장비를 교환하거나 PC 복구 상황에 대비하여, 정상 작동 상태에서의 설정 파일들을 정기적으로 백업해 둘 것을 권장합니다.
### 8.2.1 백업 대상 리스트
1. **계측기 설정 및 검사 기준값 백업**:
* 계측기들의 연결 설정 정보와 합격 판정 기준 범위가 저장됩니다.
2. **데이터베이스 서버 정보 백업**:
* 생산 정보 데이터베이스 서버 주소와 로그인 정보가 저장됩니다.
3. **센서 채널 정보 백업**:
* 일련번호 판독 통신 포트 번호와 설정 채널 정보가 저장됩니다.
4. **레이저 마킹 도면 파일 백업**:
* 제품 표면에 각인할 마킹 도면 파일입니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*
<div class="page-break"></div>
# 9. 오류 대응 및 프로그램 종료
---
## 9.1 현장 장애 유형별 점검 및 예외 조치 요령
공정 가동 중 장비 상태나 전산망에 이상이 생길 경우 발생할 수 있는 주요 화면 경고창 및 조치 요령입니다.
### 9.1.1 [오류 1] "제품 ID(IC_SN)를 읽어오지 못했습니다"
* **현상**: 검사 시작 시 제품 일련번호 읽기 단계에서 경고창이 발생하고 검사가 취소됨.
* **원인 및 해결**:
1. **접촉 상태 점검**: 제품이 안착 지그에 완벽하게 안착되었는지 확인하고, 프레스 실린더 또는 핀 단자의 밀착 상태를 확인하십시오.
2. **통신 설정 점검**: 프로그램 상단의 `통신 설정` 창을 열어 현재 설정된 포트 번호 및 물리 채널 번호가 실제 케이블 연결 상태와 일치하는지 확인하십시오.
### 9.1.2 [오류 2] "이전 공정 이력이 조회되지 않는 제품입니다"
* **현상**: 검사 시작 시 이전 단계 결과가 조회되지 않아 검사가 진행되지 않음.
* **원인 및 해결**:
- 해당 제품이 하우징 조립, 센서 보정, EOL 1차 성능 검사 등 이전 가공 공정을 진행하지 않았거나, 이전 설비에서 생산 DB에 데이터를 올바르게 전송하지 않은 상태입니다. 제품 바코드를 재확인하고 이전 설비의 데이터 전송 유무를 검토하십시오.
* **비상 조치 (강제 검사)**:
- 이전 이력 등록 상태와 무관하게 **제품 성능 계측 및 마킹을 실행하고 결과를 저장해야 하는 특수 테스트 상황**인 경우, 화면 최하단의 **`강제 검사 (스킵)`** 버튼을 누르면 DB 검증을 건너뛰고 성능 검사를 강제로 실행할 수 있습니다.
### 9.1.3 [오류 3] "이전 공정 단계가 불합격 상태이므로 최종 성능 검사를 시작할 수 없습니다"
* **현상**: DB 조회 결과 이전 공정 중 불합격(FAIL 또는 NG) 판정을 받은 내역이 발견되어 검사가 중단됨.
* **원인 및 해결**:
- 이전 공정에서 불합격 판정을 받은 제품은 최종 평가 및 레이저 마킹을 가동할 수 없습니다. 불량 제품 격리 기준에 따라 해당 제품을 생산 라인에서 제외 조치하십시오.
* **비상 조치 (강제 검사)**:
- 설비 작동 확인이나 품질 분석 등을 위해 반드시 검사를 진행해야 하는 경우, 하단바의 **`강제 검사 (스킵)`** 버튼을 클릭하면 경고 상태를 건너뛰고 계측과 마킹을 실행할 수 있습니다.
### 9.1.4 [오류 4] "레이저 각인 작업 전송 실패"
* **현상**: 성능 검사는 합격(PASS)했으나, 레이저 장비로의 데이터 전송 단계에서 경고 에러가 발생함.
* **원인 및 해결**:
1. **도면 파일 점검**: `시험 설정` 화면에서 세팅해 놓은 작업 도면 파일이 실제 해당 경로에 존재하는지 점검하십시오.
2. **마킹기 전원/연결 점검**: 레이저 마킹기의 전원이 켜져 있는지 확인하고, 제어 PC와 마킹기 간의 연결 케이블 상태를 확인하십시오.
### 9.1.5 [오류 5] "각인 순번이 9999를 초과했습니다. 다음 Lot No로 변경하십시오."
* **현상**:
- 프로그램 실행 또는 로그인 완료 후, 메인 화면 상태 알림창에 빨간색 경고 배너로 문구가 표시됩니다.
- `검사 시작 (START)` 또는 `강제 검사 (스킵)` 버튼을 누르면 작동 차단 경고창이 나타나며 설비 검사가 시작되지 않습니다.
* **원인 및 해결**:
- **순번 범위 초과**: 현재 로트 번호(`Lot No`)에서 최종 합격을 받아 각인이 완료된 누적 수량이 9999개를 초과하여 더 이상 순차 번호를 인쇄할 수 없습니다.
- **해결 방법**: 새로운 생산 조 또는 로트 번호(`Lot No`)를 수정하여 **프로그램에 재로그인**하십시오. 로트 번호가 변경되면 순번 카운트가 다시 초기화됩니다.
---
## 9.2 프로그램 정상 종료 및 전원 자동 차단 규칙
본 설비는 고전력 계측 및 전원 제어를 함께 수행하므로 대기 전력 소모 방지와 안전을 위해 아래 절차에 따라 정상 종료 프로세스를 밟아야 합니다.
* **로그아웃 (Logout)**:
- 상단 메뉴의 `로그아웃` 버튼을 클릭하면 현재 작업자 정보가 해제되고 초기 로그인 창으로 전환됩니다.
- **이와 동시에 연동된 전원 공급 장치로 차단 신호가 자동으로 전송되어 주전원 및 보드 부팅용 상시전원 출력이 OFF 상태로 차단됩니다.**
* **프로그램 완전 종료**:
- 화면 우측 상단의 `닫기(X)` 버튼을 눌러 프로그램을 최종 종료할 때도, 자동으로 상시전원 차단 명령을 장비에 인계한 뒤 레이저 각인 드라이버 리소스를 닫고 메모리를 환원합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

974
marking_gui/Manual/Manual_for_pdf.html

@ -0,0 +1,974 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Performance Inspect & Laser Marking System - 사용자 메뉴얼</title>
<style>
/*
* A4 용지 기준 PDF 출력을 위한 스타일시트 (pdf_style.css)
*/
@page {
size: A4;
margin: 20mm 15mm 20mm 15mm;
@top-left { content: none !important; }
@top-right { content: none !important; }
@bottom-left { content: none !important; }
@bottom-right { content: none !important; }
@bottom-center {
content: counter(page) !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 8.5pt;
color: #7f8c8d;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 10pt;
line-height: 1.6;
color: #333333;
background-color: #ffffff;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* 제목 스타일 */
h1, h2, h3, h4, h5, h6 {
color: #111111;
font-weight: bold;
page-break-after: avoid;
page-break-inside: avoid;
}
h1 {
font-size: 18pt;
border-bottom: 2px solid #007acc;
padding-bottom: 4px;
margin-top: 20px;
margin-bottom: 12px;
}
h2 {
font-size: 13pt;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 3px;
margin-top: 16px;
margin-bottom: 10px;
}
h3 {
font-size: 11pt;
margin-top: 12px;
margin-bottom: 6px;
}
h4 {
font-size: 10pt;
margin-top: 10px;
margin-bottom: 6px;
}
/* 문단 및 리스트 */
p {
margin-top: 0;
margin-bottom: 8px;
text-align: justify;
}
ul, ol {
margin-top: 0;
margin-bottom: 8px;
padding-left: 20px;
}
li {
margin-bottom: 3px;
}
/* 구분선 */
hr {
height: 1px;
padding: 0;
margin: 15px 0;
background-color: #e1e4e8;
border: 0;
}
/* 테이블 스타일 */
table {
width: 100%;
border-collapse: collapse;
margin-top: 0;
margin-bottom: 10px;
page-break-inside: avoid;
}
table th, table td {
padding: 4px 8px;
border: 1px solid #dfe2e5;
font-size: 8.5pt;
word-break: keep-all;
}
table th {
font-weight: 600;
background-color: #f6f8fa;
text-align: left;
}
table tr:nth-child(even) {
background-color: #f9fbfd;
}
table tr {
page-break-inside: avoid; /* 표의 중간 행이 반으로 쪼개지는 것만 방지 */
}
/* 인용구 스타일 */
blockquote {
padding: 6px 12px;
color: #6a737d;
border-left: 0.25em solid #007acc;
background-color: #f6f8fa;
margin: 0 0 10px 0;
page-break-inside: avoid;
}
blockquote p {
margin: 0;
}
/* 코드 블록 (아스크 아트 등 포함) */
pre, code {
font-family: "Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", monospace;
font-size: 8.5pt;
background-color: #f6f8fa;
border-radius: 3px;
}
code {
padding: 0.2em 0.4em;
margin: 0;
border: 1px solid #eaecef;
}
pre {
padding: 12px;
overflow: auto;
line-height: 1.45;
border: 1px solid #e1e4e8;
word-wrap: normal;
white-space: pre; /* 아스키 아트 정렬 유지를 위해 강제 줄바꿈 해제 */
page-break-inside: avoid;
}
pre code {
padding: 0;
margin: 0;
background-color: transparent;
border: none;
font-size: inherit;
white-space: inherit;
}
/* 이미지 스타일 */
img {
max-width: 100%;
max-height: 100mm; /* A4 높이의 약 절반 이하로 제한 */
display: block;
margin: 15px auto;
object-fit: contain;
page-break-inside: avoid;
border: 1px solid #e1e4e8;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 페이지 나누기 요소 */
.page-break {
page-break-before: always !important;
}
/* 내비게이션 숨김 (인쇄 시) */
.nav-link, .nav-links, .back-to-toc, .manual-nav,
a[href$=".md"] {
display: none !important;
}
/* 링크 스타일 */
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 특별 배지 스타일 */
.badge {
display: inline-block;
padding: 2px 6px;
font-size: 8pt;
font-weight: 600;
line-height: 1;
border-radius: 2px;
background-color: #e1e4e8;
color: #24292e;
margin-right: 4px;
}
</style>
</head>
<body>
<h1>Performance Inspect &amp; Laser Marking System - 사용자 메뉴얼</h1>
<blockquote>
<p><strong>버전</strong>: 1.0.0 | <strong>최종 수정</strong>: 2026-07-14</p>
</blockquote>
<hr />
<h2>메뉴얼 목차</h2>
<p>본 메뉴얼은 각 주제별로 별도 문서로 구성되어 있습니다. 아래 링크를 클릭하여 해당 문서로 이동하세요.</p>
<table>
<thead>
<tr>
<th>번호</th>
<th>문서</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1-시스템-개요-및-요구사항">시스템 개요 및 요구사항</a></td>
<td>프로그램 소개, 주요 기능, 하드웨어/소프트웨어 요구사항, 장비 구성</td>
</tr>
<tr>
<td>2</td>
<td><a href="#2-초기-설정-가이드">초기 설정 가이드</a></td>
<td>설정 파일의 구성 요소 안내 및 최초 장비 가동 시 점검 순서</td>
</tr>
<tr>
<td>3</td>
<td><a href="#3-메인-화면-home-view">메인 화면 (Home View)</a></td>
<td>상단 정보 표시, 제품 정보 카드, 검사 결과 화면, 진행 상황 확인 및 버튼 제어 가이드</td>
</tr>
<tr>
<td>4</td>
<td><a href="#4-통신-설정-comm-settings">통신 설정 (Comm. Settings)</a></td>
<td>4251 센서 통신 채널 설정 및 PC 통신 포트 연동 방법</td>
</tr>
<tr>
<td>5</td>
<td><a href="#5-시험-설정-parameters">시험 설정 (Parameters)</a></td>
<td>제품 규격치(전압, 전류, 저항 범위) 및 레이저 마킹 도면 파일 연동</td>
</tr>
<tr>
<td>6</td>
<td><a href="#6-측정-데이터-조회-및-데이터베이스-연동">측정 데이터 조회 (Data View)</a></td>
<td>생산 데이터베이스를 통한 이전 공정 이력 자동 조회 및 최종 결과 저장 원리</td>
</tr>
<tr>
<td>7</td>
<td><a href="#7-자동-시험-프로세스-및-14단계-시퀀스">자동 시험 프로세스</a></td>
<td>자동 계측 시퀀스 기동, 마킹 수행 및 최종 합격 판정 흐름</td>
</tr>
<tr>
<td>8</td>
<td><a href="#8-로그-및-데이터-관리">로그 및 데이터 관리</a></td>
<td>시스템 로그 파일 확인 방법 및 장비 설정 파일 백업 방법</td>
</tr>
<tr>
<td>9</td>
<td><a href="#9-오류-대응-및-프로그램-종료">오류 대응 및 프로그램 종료</a></td>
<td>센서 통신 오류, DB 조회 에러, 마킹 불량 등 현장 장애 대응 및 안전한 프로그램 종료 방법</td>
</tr>
</tbody>
</table>
<hr />
<h2>빠른 시작 가이드 (Quick Start)</h2>
<p>처음 시스템을 가동하여 검사를 진행하는 경우 다음 순서에 따라 작업을 수행해 주십시오.</p>
<ol>
<li><strong>프로그램 설치 (최초 1회)</strong></li>
<li>배포받은 단일 설치 프로그램 <code>MarkingSetup.exe</code>를 실행하여 클릭 한 번으로 <code>C:\marking</code> 경로 및 바탕화면 바로가기를 자동 생성합니다.</li>
<li><strong>하드웨어 연결 상태 확인</strong></li>
<li>계측 장비들과 제어용 PC 간의 네트워크(LAN)선 및 센서 리더용 시리얼 포트 케이블 연결을 확인합니다.</li>
<li><strong>프로그램 실행 및 로그인</strong></li>
<li>바탕화면의 <code>Marking System</code> (<code>marking_gui</code>) 프로그램을 실행한 뒤, 작업자 계정 정보를 입력하여 로그인합니다. (데이터베이스 점검 중인 경우 설정 파일을 통해 오프라인 모드로의 진입이 가능합니다.)</li>
<li><strong>통신 상태 확인 및 채널 설정</strong></li>
<li>우측 상단의 <code>통신 설정</code> 메뉴에서 센서가 올바른 포트와 채널에 연결되었는지 확인합니다.</li>
<li><strong>검사 규격값 검증</strong></li>
<li>우측 상단의 <code>시험 설정</code> 메뉴를 열고, 전압/전류/저항 검사 기준치와 각인 템플릿 도면 파일의 경로가 올바른지 점검합니다.</li>
<li><strong>성능 검사 및 마킹 시작</strong></li>
<li>안착 지그에 제품을 바르게 장착한 뒤 메인 화면 하단의 <code>검사 시작 (START)</code> 버튼을 눌러 계측 및 마킹을 수행합니다.</li>
</ol>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>1. 시스템 개요 및 요구사항</h1>
<hr />
<h2>1.1 시스템 개요</h2>
<p><strong>Performance Inspect &amp; Laser Marking System</strong>은 온압센서 완제품의 최종 성능 검사를 수행하고, 이전 공정 단계들의 합격(PASS) 이력을 조회하여 최종 적합 제품에 한해 제품 표면에 레이저 마킹을 수행하는 시스템 제어 프로그램입니다.</p>
<p>본 프로그램은 측정 계측기와 센서 판독기, 레이저 각인 장치 및 공장 생산 데이터베이스와 연동되어 작동합니다.</p>
<h3>주요 기능 요약</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>일련번호(S/N) 자동 추출</strong></td>
<td>센서 판독 보드를 통해 제품 안착 시 제품 내부 메모리로부터 일련번호를 추출합니다.</td>
</tr>
<tr>
<td><strong>선행 공정 검사 교차 조회</strong></td>
<td>이전 단계(하우징 조립, 센서 보정, 1차 EOL 성능 검사)가 모두 합격 판정되었는지 생산 데이터베이스에서 조회합니다.</td>
</tr>
<tr>
<td><strong>자동 계측 시퀀스</strong></td>
<td>전원 공급 장치와 전압/저항 측정 장비들과 연동하여 제품에 테스트 전원을 투입하고 전압, 저항, 전류 값을 차례대로 계측합니다.</td>
</tr>
<tr>
<td><strong>실제 저항값 측정</strong></td>
<td>전압/저항 DMM 측정기를 통해 센서의 실제 저항값(Ω)을 측정합니다.</td>
</tr>
<tr>
<td><strong>전류 측정</strong></td>
<td>전원 공급 장치(Power Supply)의 출력 채널 1을 통해 제품의 소모 전류를 계측합니다.</td>
</tr>
<tr>
<td><strong>레이저 각인 연동</strong></td>
<td>성능 검사가 모두 통과된 합격품에 대해 제품 표면에 2D QR 코드 및 단축 일련번호(생산일자 + 순번)를 마킹합니다.</td>
</tr>
<tr>
<td><strong>결과 기록</strong></td>
<td>계측 판정 규격(전압, 전류, 저항)과 마킹 성공 여부를 최종 판정하여 생산 데이터베이스에 저장합니다.</td>
</tr>
</tbody>
</table>
<hr />
<h2>1.2 소프트웨어 요구사항</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>요구사항</th>
<th>비고</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>통합 설치 프로그램</strong></td>
<td><code>MarkingSetup.exe</code> (WiX 번들)</td>
<td>1-Click 자동 설치 및 실행 환경, 드라이버, 설정 일체 셋업</td>
</tr>
<tr>
<td><strong>운영체제</strong></td>
<td>Windows 10 이상</td>
<td>프로그램 구동 및 장비 드라이버 운용용</td>
</tr>
<tr>
<td><strong>데이터베이스</strong></td>
<td>Microsoft SQL Server (MS-SQL)</td>
<td>생산 공정 데이터 및 계측 결과 연동용</td>
</tr>
<tr>
<td><strong>레이저 제어 프로그램</strong></td>
<td>EZCAD 각인 제어 프로그램 및 드라이버</td>
<td>레이저 마킹기 구동 및 전용 도면(.ezd) 운용용</td>
</tr>
<tr>
<td><strong>네트워크 환경</strong></td>
<td>LAN (TCP/IP) 통신</td>
<td>제어 PC와 측정 기기 간의 연결</td>
</tr>
<tr>
<td><strong>센서 보드 통신</strong></td>
<td>COM 포트 (RS-232 시리얼)</td>
<td>4251 센서 통신 보드 연동</td>
</tr>
</tbody>
</table>
<hr />
<h2>1.3 하드웨어 구성</h2>
<table>
<thead>
<tr>
<th>장비</th>
<th>역할</th>
<th>통신 방식</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>센서 판독 보드 (4251 보드)</strong></td>
<td>제품 내부 칩의 일련번호(IC_SN)를 인식합니다.</td>
<td>RS-232 시리얼 통신</td>
</tr>
<tr>
<td><strong>전압 및 저항 측정 장비 (DMM)</strong></td>
<td>제품의 전압(V) 및 전기적 저항(Ω)을 측정합니다. (예: 192.168.1.100)</td>
<td>TCP/IP 네트워크 통신</td>
</tr>
<tr>
<td><strong>전류 측정 및 전원 공급 장비 (PSU)</strong></td>
<td>제품 구동을 위한 전원 공급 및 소모 전류(A)를 측정합니다. (예: 192.168.1.101)</td>
<td>TCP/IP 네트워크 통신</td>
</tr>
<tr>
<td><strong>레이저 마킹기</strong></td>
<td>2D QR 코드 및 텍스트 일련번호를 제품 표면에 인쇄합니다.</td>
<td>마킹 제어 보드 연동</td>
</tr>
<tr>
<td><em>(전류 측정 DMM)</em></td>
<td><em>설정 파일(예: 192.168.1.102)에 존재하나 현재 제어 시퀀스에서는 사용하지 않습니다.</em></td>
<td><em>(미사용)</em></td>
</tr>
</tbody>
</table>
<h2>1.4 설치 경로(<code>C:\marking\</code>) 구조 및 생성 파일 역할</h2>
<p>단일 통합 설치 마법사(<code>MarkingSetup.exe</code>)를 통해 시스템을 설치하면 기본 설치 디렉터리(<code>C:\marking\</code>) 아래에 다음과 같은 실행 바이너리, 설정 파일, 드라이버 라이브러리 및 런타임 로그 폴더가 배치·생성됩니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>파일 / 폴더명</th>
<th>상세 역할 및 기능 설명</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>실행 프로그램</strong></td>
<td><code>marking_gui.exe</code></td>
<td>메인 성능 검사 및 레이저 마킹 GUI 제어 실행 파일</td>
</tr>
<tr>
<td><strong>종속 라이브러리</strong></td>
<td><code>marking_gui.dll</code>, <code>*.deps.json</code>, <code>*.runtimeconfig.json</code></td>
<td>.NET 9.0 (WPF) 자립형 런타임 및 종속 구성 바이너리</td>
</tr>
<tr>
<td><strong>통합 설정 파일</strong></td>
<td><code>Setting.ini</code></td>
<td>마킹기 폴더 경로, 테스트 모드, 도안 태그 매핑, 계측기 LAN IP, SCPI 전원 커맨드, 계측 판정 규격 임계치(<code>[Limits]</code>) 종합 설정 파일</td>
</tr>
<tr>
<td><strong>DB 설정 파일</strong></td>
<td><code>Database.ini</code></td>
<td>MS-SQL 데이터베이스 접속 주소, DB명, 사용자 계정/PW, 오프라인 모드(<code>OfflineMode=true</code>) 설정 파일</td>
</tr>
<tr>
<td><strong>센서 설정 파일</strong></td>
<td><code>Sensor.ini</code></td>
<td>4251 센서 통신 보드 시리얼 포트(<code>Port=COM3</code>), 통신 속도(<code>BaudRate=115200</code>), 통신 채널(<code>Channel=1</code>) 설정 파일</td>
</tr>
<tr>
<td><strong>드라이버 폴더</strong></td>
<td><code>libs/</code></td>
<td>C++ LMC 각인 드라이버 격리 디렉터리 (<code>MarkEzdWrapper.dll</code>, <code>DataMgr.dll</code>, <code>MarkEzd.dll</code>, <code>haspms32.dll</code> 등)</td>
</tr>
<tr>
<td><strong>로그 저장 폴더</strong></td>
<td><code>logs/</code> <em>(자동 생성)</em></td>
<td>시스템 운용 중 발생하는 일별 이벤트 로그, 통신 이력, 각인 성공/실패 기록 텍스트 저장 디렉터리 (예: <code>logs/2026-07-23.log</code>)</td>
</tr>
<tr>
<td><strong>백업 보관 폴더</strong></td>
<td><code>publish_backup/</code></td>
<td>프로그램 업데이트 및 빌드 배포 시 이전 릴리즈 바이너리/설정 자동 백업 폴더</td>
</tr>
</tbody>
</table>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>2. 초기 설정 가이드</h1>
<hr />
<h2>2.1 프로그램 설정 구성 개요</h2>
<p>본 프로그램은 정상적인 계측기 통신 및 데이터베이스 연동을 위해 몇 가지 설정 파일을 사용합니다. 이 설정 파일들은 프로그램 설치 및 라인 도입 시 최초 1회 셋업을 원칙으로 하며, 일반 운영자가 직접 설정 파일을 열어 수정할 필요는 없습니다.</p>
<p><em>참고: 대부분의 검사 기준 및 통신 정보는 프로그램 내의 </em><em>'통신 설정'</em><em></em><em>'시험 설정'</em><em> 화면을 통해 수정할 수 있습니다.</em></p>
<h3>2.1.1 주요 설정 정보 구분</h3>
<ol>
<li><strong>장비 및 계측 설정</strong></li>
<li>계측기들과의 네트워크 연결 주소와 대기 시간 등을 정의합니다.</li>
<li>제품 합격 여부를 판정하는 전압, 전류, 저항의 기본 합격 규격 범위가 이 파일에 저장되며, 화면 내의 <strong>'시험 설정'</strong> 창에서 수정한 사항이 이 파일에 동적으로 반영됩니다.</li>
<li>
<p>프로그램은 전압/저항 측정 장치와 전원/전류 공급 장치를 제어하여 계측을 진행합니다.</p>
</li>
<li>
<p><strong>생산 데이터베이스 연동 설정</strong></p>
</li>
<li>공장 생산 관리 시스템 데이터베이스와의 통신 경로가 기록되어 있습니다.</li>
<li>
<p>전산망이나 DB 서버 점검 등으로 인해 통신이 어려울 경우, 설정 파일을 통해 <strong>'오프라인 모드'</strong>로 전환하여 단독으로 장비 작동 테스트를 해볼 수 있습니다.</p>
<ul>
<li><strong>오프라인 로그인 지원</strong>: 데이터베이스 확인 단계 없이 로그인하여 프로그램을 기동합니다.</li>
<li><strong>오프라인 검사 지원</strong>: 제품 이력 조회 및 검사 결과 저장을 건너뛰고 단독으로 계측 및 마킹을 실행합니다.</li>
</ul>
</li>
<li>
<p><strong>센서 판독기 설정</strong></p>
</li>
<li>제품 일련번호를 로드하기 위한 통신 채널 및 연결 포트 주소가 정의됩니다.</li>
<li>화면 내의 <strong>'통신 설정'</strong> 창에서 수정한 사항이 이 파일에 자동으로 저장됩니다.</li>
</ol>
<h3>2.1.2 설치 경로(<code>C:\marking\</code>) 내 파일 배치 구조</h3>
<p>프로그램 설치 완료 후 <code>C:\marking\</code> 경로에는 다음과 같이 구동 파일, 설정 파일, 드라이버 라이브러리 및 런타임 자동 생성 디렉터리가 배치됩니다.</p>
<ul>
<li><strong><code>C:\marking\marking_gui.exe</code></strong>: GUI 제어 메인 실행 파일</li>
<li><strong><code>C:\marking\Setting.ini</code></strong>: 장비 통신 IP, 마킹 태그, 규격 임계치(<code>[Limits]</code>) 종합 설정 파일</li>
<li><strong><code>C:\marking\Database.ini</code></strong>: MS-SQL DB 접속 정보 및 오프라인 모드 설정 파일</li>
<li><strong><code>C:\marking\Sensor.ini</code></strong>: 4251 센서 통신 보드 포트/속도/채널 설정 파일</li>
<li><strong><code>C:\marking\libs\</code></strong>: C++ LMC 레이저 각인 드라이버 라이브러리 격리 폴더 (<code>MarkEzdWrapper.dll</code> 등)</li>
<li><strong><code>C:\marking\logs\</code></strong>: 런타임 가동 중 자동 생성되는 일별 이력 로그 폴더 (<code>yyyy-MM-dd.log</code>)</li>
<li><strong><code>C:\marking\publish_backup\</code></strong>: 업데이트/배포 시 이전 버전 바이너리 및 설정 자동 백업 폴더</li>
</ul>
<h3>2.1.3 기본적인 설정 파일 편집 방법</h3>
<blockquote>
<p>[!IMPORTANT]
<strong>[권장 조치 안내]</strong>
현장 라인 검사자 및 일반 작업자는 프로그램 UI의 <strong>[통신 설정]</strong><strong>[시험 설정]</strong> 메뉴를 통해 안전하게 파라미터를 변경하십시오.
아래의 <code>.ini</code> 파일 직접 수정은 라인 엔지니어 또는 설비 관리자 전용 권한 작업이며, 잘못된 파일 수정 시 장비 통신 오류가 발생할 수 있습니다.</p>
</blockquote>
<p>설비 이동이나 네트워크 환경 변경 시, 프로그램 폴더 내의 각 <code>.ini</code> 파일을 텍스트 편집기(메모장 등)로 열어 아래의 전체 설정 레이아웃 항목을 참조하여 수정할 수 있습니다.</p>
<h4>1) Setting.ini (주요 변경 가능 설정)</h4>
<p>현장 네트워크 IP, 도안 파일 경로 및 검사 규격(임계치) 변경 시 수정하는 주요 설정 항목입니다.</p>
<pre><code class="language-ini">[Settings]
EzdFolderPath=c:\EzCad\EasyCfg ; EZD 도안 파일 기본 탐색/저장 폴더 경로
test=false ; 테스트 마킹 버튼 및 개발 모드 노출 여부 (true/false)
[DMM]
VoltageHost=192.168.1.100 ; 전압/저항 측정용 계측기(DMM) IP 주소
[CurrentDMM]
CurrentDMMHost=192.168.1.102 ; 전류 측정용 계측기(DMM) IP 주소
[PowerSupply]
CurrentHost=192.168.1.101 ; 전원 공급기(Power Supply) IP 주소
[Limits]
MinVoltage=4.0 ; 전압 하한 판정 기준값 (V)
MaxVoltage=6.0 ; 전압 상한 판정 기준값 (V)
MinCurrent=8.0 ; 전류 하한 판정 기준값 (mA)
MaxCurrent=10.0 ; 전류 상한 판정 기준값 (mA)
MinResistance=0.4 ; 저항 하한 판정 기준값 (kΩ)
MaxResistance=0.8 ; 저항 상한 판정 기준값 (kΩ)
</code></pre>
<h4>2) Database.ini (주요 변경 가능 설정)</h4>
<p>DB 서버 접속 주소 및 점검용 오프라인 모드 변경 시 수정하는 항목입니다.</p>
<pre><code class="language-ini">[Database]
Db_Server=192.168.1.200 ; MSSQL 데이터베이스 서버 IP 주소
Db_Name=MarkingDb ; 연결할 데이터베이스 이름
Db_User=db_user ; DB 접속 사용자 계정
Db_Password=db_password ; DB 접속 비밀번호
OfflineMode=False ; DB 미연결 점검 모드 (True: DB 저장 생략, False: 정상 검증/적재)
[Login]
OfflinePreview=False ; 로그인 DB 검증 우회 여부 (True: 로그인 생략, False: 정상 검증)
</code></pre>
<h4>3) Sensor.ini (주요 변경 가능 설정)</h4>
<p>바코드 스캐너 시리얼 포트 및 채널 설정 항목입니다.</p>
<pre><code class="language-ini">[Sensor]
Port=COM3 ; 바코드 스캐너 시리얼 포트 (장치 관리자 COM 포트 번호)
Channel=1 ; 통신 채널 번호 (1 또는 2)
</code></pre>
<hr />
<h2>2.2 최초 실행 및 장비 점검 순서</h2>
<p>라인 가동 전 최초 실행할 때는 아래 단계를 순서대로 이행하여 정상 작동 여부를 점검하십시오.</p>
<ol>
<li><strong>프로그램 단일 설치 (MarkingSetup.exe)</strong></li>
<li>배포받은 단일 설치 마법사 <code>MarkingSetup.exe</code>를 실행하여 1-Click으로 <code>C:\marking\</code> 설치 및 바탕화면 바로가기를 자동 생성합니다.</li>
<li><strong>데이터베이스 연결 상태 확인</strong></li>
<li>데이터베이스가 점검 중인 경우 설정 파일을 통해 오프라인 모드로 설정하면 로그인 창을 통과하여 단독 테스트를 진행할 수 있습니다.</li>
<li><strong>장비 네트워크 통신 점검</strong></li>
<li>제어 PC와 계측 장비들(전압/저항 측정 장비, 전원 공급 장치) 간의 LAN 케이블 연결을 확인합니다.</li>
<li>제어 PC의 네트워크 IP 환경이 장비들의 대역과 동일한지 확인하고, 통신 신호가 정상적으로 송수신되는지 점검합니다.</li>
<li><strong>상시전원 자동 인가 확인</strong></li>
<li>프로그램 실행 후 작업자 계정으로 로그인을 시도합니다.</li>
<li>로그인이 완료되면 전원 공급 장치의 상시전원 채널 전원 램프가 자동으로 켜지는지 확인합니다.</li>
</ol>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>3. 메인 화면 (Home View)</h1>
<hr />
<h2>3.1 메인 화면 레이아웃 구성</h2>
<p>본 프로그램은 작업자가 화면 구성 및 각 항목 상태를 파악할 수 있도록 4개의 카드 레이아웃으로 화면을 구성했습니다.</p>
<h3>3.1.1 상단 헤더 영역 (Header Bar)</h3>
<p>프로그램 최상단 영역에는 시스템 정보 배지와 제어 버튼이 배치되어 있습니다.
* <strong>실시간 시계</strong>: 좌측 상단에 날짜와 시간이 표시됩니다.
* <strong>작업 정보 배지</strong>:
* <strong>공정 라인</strong>: 로그인 단계에서 지정한 생산 라인 코드 (<code>Line No</code>)
* <strong>로트 번호</strong>: 로그인 단계에서 지정한 생산 로트 번호 (<code>Lot No</code>)
* <strong>모델명</strong>: 현재 가동 중인 제품의 사양 모델 (<code>Model</code>)
* <strong>작업자</strong>: 로그인한 작업자의 이름 (<code>Operator</code>)
* <strong>제어 버튼</strong>: 우측 상단에 <code>로그아웃</code> 버튼이 위치하며 최하단 바에 설정 관련 버튼이 배치됩니다.</p>
<hr />
<h2>3.2 상태 및 제어 카드 영역</h2>
<p>화면 중앙에는 역할을 구분한 4개의 상태 카드가 배치되어 있어, 제품 상태와 계측 단계를 모니터링할 수 있습니다.</p>
<h3>3.2.1 [카드 1] 제품 정보 및 이전 결과 (좌측 상단)</h3>
<p>제품의 일련번호와 이전 생산 공정에서의 합격 이력을 대조하는 영역입니다.
* <strong>작업 모델 설정</strong>:
- <code>파일 선택</code> 버튼을 사용하여 각인용 도면 파일을 불러옵니다.
- 도면 파일 선택이 완료되면 상단 헤더 영역의 모델명(Model) 배지 텍스트가 선택한 도면명으로 자동 변경됩니다.
* <strong>일련번호 (IC_SN)</strong>: 제품이 안착되면 센서 판독기를 통해 자동으로 인식되어 표시됩니다. (읽기 전용)
* <strong>이전 공정 이력 배지</strong>:
* <strong>Housing</strong>: 하우징 조립 공정 판정 상태 (<code>PASS</code> / <code>FAIL</code> / <code>대기</code>)
* <strong>Calibration</strong>: 압력 보정 공정 판정 상태 (<code>PASS</code> / <code>FAIL</code> / <code>대기</code>)
* <strong>EOL</strong>: EOL 1차 성능 평가 판정 상태 (<code>PASS</code> / <code>FAIL</code> / <code>대기</code>)
* <em>주의: 이전 공정 이력 배지 중 하나라도 <code>FAIL</code> 상태인 경우 성능 검사를 시작할 수 없습니다.</em></p>
<h3>3.2.2 [카드 2] 측정 결과 (우측 상단)</h3>
<p>현재 장비에서 실제 계측한 3대 전기 파라미터(전압, 전류, 저항) 수치와 설정된 기준 규격 범위를 표시하는 영역입니다.
* <strong>측정 전압 (Voltage)</strong>: 전압 측정 장치가 측정한 전압 수치 (<strong>V</strong> 단위)
* <strong>측정 전류 (Current)</strong>: 전원 공급 장치가 측정한 소모 전류 수치 (<strong>mA</strong> 단위)
* <strong>측정 저항 (Resistance)</strong>: 저항 측정 장치가 측정한 전기적 저항 수치 (<strong></strong> 단위)
* <em>알림: 각 물리량의 하단에는 현재 적용 중인 기준 규격 범위가 문구(<code>기준범위: X ~ Y</code>)로 표출되며, 실측값이 규격 범위 내에 도달하면 카드 테두리가 </em><em>초록색(PASS)</em><em>으로 표시되고, 범위를 벗어나면 테두리가 </em><em>빨간색(FAIL)</em><em>으로 바뀝니다.</em></p>
<h3>3.2.3 [카드 3] 진행상황 (좌측 하단)</h3>
<p>현재 설비가 수행하고 있는 단계별 자동 제어 시퀀스를 보여주는 안내 창입니다.
* "대기 상태", "제품 정보 수집 중...", "이전 공정 검사 결과 조회 중...", "측정 계측기 데이터 수집 중...", "제품 합격 - 레이저 각인 중..." 등 작동 상태를 실시간 텍스트로 표출합니다.</p>
<h3>3.2.4 [카드 4] 판정결과 (우측 하단)</h3>
<p>제품의 최종 합격(PASS) 및 불합격(FAIL) 상태를 표시합니다.
* 계측 검사와 데이터 저장, 레이저 각인이 완료되었을 때 최종 합격인 경우 카드 배경이 <strong>초록색(PASS)</strong>으로 전환되며, 불합격인 경우 카드 배경이 <strong>빨간색(FAIL)</strong>으로 변경됩니다.</p>
<h3>3.2.5 최하단 제어 영역 (Control Box)</h3>
<ul>
<li><strong>검사 시작 (START)</strong>: 전체 검사 시퀀스를 기동하는 메인 버튼입니다. 이 버튼을 누르면 이전 계측 수치들이 초기화되고 새로운 측정 프로세스가 개시됩니다.</li>
<li><strong>강제 검사 (스킵)</strong>: 이전 가공 공정의 합격 이력이 없더라도 검증 단계를 건너뛰고 성능 계측 및 각인 시퀀스를 기동하는 버튼입니다.</li>
<li><strong>테스트 마킹</strong>: 성능 계측 없이 즉시 각인기에 테스트 마킹 신호를 전송하는 버튼입니다.</li>
<li><em>참고: <code>강제 검사 (스킵)</code><code>테스트 마킹</code> 버튼은 환경 설정에서 테스트 모드가 활성화된 상태에서만 화면 하단에 노출됩니다.</em></li>
</ul>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>4. 통신 설정 (Comm. Settings)</h1>
<hr />
<h2>4.1 포트 설정 화면 사용법</h2>
<p>프로그램 상단의 <code>통신 설정</code> 버튼을 클릭하면 포트 설정 대화 상자가 나타납니다. 이 대화 상자에서 센서 판독 장치(4251 보드)의 연결 포트 및 채널을 설정할 수 있습니다.</p>
<h3>주요 설정 항목 및 설명</h3>
<ol>
<li><strong>통신 포트 (Port)</strong>:</li>
<li>센서 판독기가 연결되어 있는 PC의 연결 포트를 선택합니다. (예: <code>COM1</code>, <code>COM3</code>, <code>COM4</code> 등)</li>
<li>
<p>현재 PC에 인식되어 있는 포트 목록이 자동으로 표시됩니다.</p>
</li>
<li>
<p><strong>통신 속도 (BaudRate)</strong>:</p>
</li>
<li>센서 판독 장치와의 데이터 송수신 속도를 설정합니다.</li>
<li>
<p>기본 설정값은 <strong><code>115200</code></strong> 이며, 특수한 경우가 아니면 변경하지 않는 것을 권장합니다.</p>
</li>
<li>
<p><strong>통신 채널 (Channel)</strong>:</p>
</li>
<li>센서 판독기 내부의 연결 채널을 결정합니다.</li>
<li>드롭다운 메뉴를 통해 <strong><code>CH1</code></strong> 또는 <strong><code>CH2</code></strong>를 선택할 수 있습니다.</li>
<li>선택된 채널에 따라 센서 인식 모드가 변경됩니다.</li>
</ol>
<hr />
<h2>4.2 설정 저장</h2>
<ul>
<li><strong>설정 저장</strong>:<ul>
<li>통신 포트와 채널을 선택한 후 <code>저장</code> 버튼을 누르면 설정 정보가 저장됩니다.</li>
<li>저장 시 기존 센서 연결을 해제하고 새로운 설정값으로 센서 인식 서비스를 다시 시작합니다. 프로그램을 재시작할 필요 없이 변경 사항이 즉시 적용됩니다.</li>
<li>저장하지 않고 창을 닫으려면 <code>취소</code> 버튼을 누릅니다.</li>
</ul>
</li>
</ul>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>5. 시험 설정 (Parameters)</h1>
<hr />
<h2>5.1 판정 기준 설정</h2>
<p>프로그램 상단 우측의 <code>시험 설정</code> 버튼을 클릭하면 판정 기준을 구성하는 대화 상자가 나타납니다. 이 화면에서 제품 합불 판정에 사용되는 전압, 전류, 저항의 기준값을 입력할 수 있습니다.</p>
<h3>5.1.1 각 항목별 한계치 입력 가이드</h3>
<ul>
<li><strong>전압 판정 범위</strong>:<ul>
<li><strong>하한값</strong>: 합격 전압 하한 규격 (V 단위)</li>
<li><strong>상한값</strong>: 합격 전압 상한 규격 (V 단위)</li>
</ul>
</li>
<li><strong>전류 판정 범위</strong>:<ul>
<li><strong>하한값</strong>: 합격 전류 하한 규격 (설정창 표기 단위 참조, 기본 mA/A)</li>
<li><strong>상한값</strong>: 합격 전류 상한 규격 (설정창 표기 단위 참조, 기본 mA/A)</li>
</ul>
</li>
<li><strong>저항 판정 범위</strong>:<ul>
<li><strong>하한값</strong>: 합격 저항 하한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)</li>
<li><strong>상한값</strong>: 합격 저항 상한 규격 (설정창 표기 단위 참조, 기본 Ω/kΩ)</li>
</ul>
</li>
</ul>
<p><em>참고: 저장 버튼을 클릭하면 메인 화면의 하단 가이드 문구(<code>기준범위: X ~ Y</code>)가 즉시 갱신되어 새로 입력한 한계치가 다음 검사 시퀀스부터 반영됩니다.</em></p>
<hr />
<h2>5.2 레이저 각인 도면 지정</h2>
<ul>
<li><strong>각인 파일 설정 경로</strong>:<ul>
<li>레이저 마킹기에서 사용할 작업 도면 파일 선택은 설정 대화 상자가 아닌, <strong>메인 화면 좌측 상단의 '작업 모델 설정' 영역에 있는 '파일 선택' 버튼</strong>을 클릭하여 수행합니다.</li>
<li>도면이 선택되면 상단 헤더 배지의 모델명 정보도 해당 도면 파일명으로 자동으로 변경됩니다.</li>
</ul>
</li>
<li><strong>유의 사항</strong>:<ul>
<li>지정해 놓은 도면 파일이 실제 경로에 존재하지 않는 경우, 계측 성능 검사가 합격하더라도 각인 단계에서 에러가 납니다. 검사 전 파일 경로 상태를 확인하십시오.</li>
</ul>
</li>
</ul>
<hr />
<h2>5.3 설정 저장 및 적용</h2>
<ul>
<li>설정 창의 <code>저장</code> 버튼을 클릭하면 변경된 파라미터가 시스템 설정에 저장됩니다.</li>
<li>저장 완료 즉시 <strong>메인 화면 하단에 표시되는 판정 기준 가이드 텍스트</strong>가 업데이트되며, 이후에 새로 시작하는 성능 검사 시퀀스부터 새로운 규격치가 적용됩니다.</li>
</ul>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>6. 측정 데이터 조회 및 데이터베이스 연동</h1>
<hr />
<h2>6.1 선행 공정 결과 조회 (이력 확인)</h2>
<p>본 프로그램은 검사 시작 시 입력된 제품 일련번호를 기반으로 생산 데이터베이스에 접속하여 이전 제조 공정의 합격 여부를 조회합니다.</p>
<h3>6.1.1 이력 검토 대상 공정</h3>
<p>시스템은 다음 3개의 선행 공정 검사 결과를 확인합니다.
1. <strong>하우징 조립 공정</strong>
2. <strong>센서 오차 보정 공정</strong>
3. <strong>1차 EOL 성능 검사</strong></p>
<h3>6.1.2 이전 단계 규격 교차 확인 동작</h3>
<ul>
<li>이전 3개 선행 단계의 이력이 <strong>모두 합격(PASS 또는 OK)</strong>된 제품인 경우에만 최종 성능검사(EOL2)를 진행할 수 있습니다.</li>
<li>선행 이력이 존재하지 않거나, 단 한 곳이라도 불합격(FAIL 또는 NG) 판정을 받은 경우 화면 진행 상황 표시창에 경고가 표시되며 최종 성능검사 시작이 제한됩니다.</li>
</ul>
<hr />
<h2>6.2 검사 결과 자동 적재 및 최종 판정 기록</h2>
<p>자동 계측 프로세스가 종료되면 다음 정보가 생산 데이터베이스에 저장됩니다.</p>
<ul>
<li><strong>저장 정보</strong>:<ul>
<li>제품 일련번호</li>
<li>생산 라인, 로트 번호, 작업자 정보</li>
<li>실측 전압, 전류, 저항값<ul>
<li>실측값은 데이터베이스 저장 규격에 맞춰 시스템 내부에서 소수점 이하 자리로 정밀 정리되어 저장됩니다.</li>
</ul>
</li>
<li>최종 판정 결과 (<code>PASS</code> / <code>FAIL</code>)</li>
<li>마킹 시퀀스 번호 및 성공 기록</li>
</ul>
</li>
<li><strong>비동기 데이터 처리</strong>:<ul>
<li>데이터베이스 통신 대기로 인한 화면 멈춤 현상을 방지하기 위해 데이터 조회 및 저장 작업은 프로그램 화면 동작과 별개로 백그라운드에서 비동기식으로 안전하게 처리됩니다.</li>
</ul>
</li>
</ul>
<hr />
<h2>6.3 오프라인 모드 운용</h2>
<p>데이터베이스 서버 접속이 불가능한 환경에서도 단독으로 설비를 가동할 수 있도록 <strong>오프라인 모드</strong>를 지원합니다.</p>
<ul>
<li><strong>활성화 목적</strong>:<ul>
<li>네트워크 연결이 끊긴 상태에서 프로그램의 단독 동작 및 장비 제어 상태를 확인하기 위함입니다.</li>
</ul>
</li>
<li><strong>오프라인 작동 패턴</strong>:<ul>
<li><strong>공정 조회 우회</strong>: 입력된 일련번호에 대해 생산 데이터베이스 서버 접속 없이 이전 공정을 합격한 것으로 우회 처리합니다.</li>
<li><strong>임시 일련번호 생성</strong>: 일련번호가 비어 있는 상태에서 테스트를 개시하는 경우 현재 시간 기반의 임시 일련번호가 자동으로 부여되어 진행합니다.</li>
<li><strong>각인 모사</strong>: 실제 각인기에 데이터를 전송하는 단계를 가상 성공 처리하여 전체 흐름을 테스트합니다.</li>
</ul>
</li>
</ul>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>7. 자동 시험 프로세스 및 15단계 시퀀스</h1>
<hr />
<h2>7.1 자동 시험 및 마킹 프로세스 개요</h2>
<p>본 프로그램은 사용자가 메인 화면 하단의 <code>검사 시작 (START)</code> 버튼을 누르면 데이터 저장까지의 과정이 자동 계측 시퀀스에 따라 순차적으로 동작하도록 구성되어 있습니다.</p>
<pre><code class="language-mermaid">graph TD
A[START 버튼 클릭] --&gt; B[UI 화면 수치 초기화]
B --&gt; C[4~8단계: 센서 S/N 읽기 및 보정]
C --&gt; D{선행 공정 이력 확인}
D -- 불합격/미진행 --&gt; E[검사 중단 &amp; 경고 팝업]
E --&gt; F{강제 검사 스킵?}
F -- 취소 --&gt; Z[대기 복구]
F -- 스킵 실행 --&gt; G[9~15단계: 계측 시퀀스 기동]
D -- 3개 공정 모두 PASS --&gt; G
G --&gt; H[10단계: 테스트 주전원 ON]
H --&gt; I[12~14단계: 전압 / 저항 / 전류 실측]
I --&gt; J[15단계: 테스트 주전원 안전 차단]
J --&gt; K{최종 규격 비교 판정}
K -- 1개 이상 이탈 FAIL --&gt; L[대형 FAIL 표시 &amp; 마킹 제외 &amp; DB 불합격 적재]
K -- 모두 통과 PASS --&gt; M[대형 PASS 표시 &amp; DB 합격 적재 &amp; LMC1 레이저 QR 각인]
L --&gt; Z
M --&gt; Z
</code></pre>
<hr />
<h2>7.2 상세 15단계 자동 제어 시퀀스</h2>
<p>최종 검사가 시작되면 장비와 계측기들은 다음 15가지 단계에 따라 동작을 차례대로 수행합니다.</p>
<h3>[1단계] 사전 전원 인가 단계 (최초 가동/로그인 시 1회 진행)</h3>
<ul>
<li><strong>1단계 [장비 확인]</strong>: 전원 공급 장치와의 통신 연결 상태를 점검합니다.</li>
<li><strong>2단계 [센서 구동 전압 설정]</strong>: 센서 보드를 구동하는 데 필요한 전압을 전원 장치에 설정합니다.</li>
<li><strong>3단계 [센서 구동 전원 출력]</strong>: 설정된 전원을 출력하여 보드를 부팅합니다.</li>
<li><em>(참고: 이 단계는 로그인 성공 시 1회만 실행되며, 이후의 반복 검사 과정에서는 이미 투입된 전원을 재사용하여 4단계부터 즉각 검사가 수행됩니다)</em></li>
</ul>
<h3>[2단계] 제품 일련번호 수집 및 센서 통신 대기 단계</h3>
<ul>
<li><strong>4단계 [판독 장치 대기]</strong>: 센서 판독 장치를 초기 대기 상태로 설정합니다.</li>
<li><strong>5단계 [보드 통신 연결]</strong>: 통신 설정에 명시된 채널을 선택하여 센서 보드와 연결합니다.</li>
<li><strong>6단계 [일련번호 읽기]</strong>: 제품 메모리로부터 스캔한 고유 일련번호를 로드합니다.</li>
<li><strong>7단계 [보드 통신 해제]</strong>: 센서 데이터 전송을 중지하고 대기 상태로 되돌립니다.</li>
<li><strong>8단계 [보정 기준 전송]</strong>: 센서 장치에 보정 기준값을 인계하고 동작을 종료합니다.</li>
</ul>
<h3>[3단계] 계측기 연동 성능 평가 및 전원 차단 단계</h3>
<ul>
<li><strong>9단계 [전원 장비 확인]</strong>: 주 테스트 전원을 공급할 전원 장치의 통신 상태를 확인합니다.</li>
<li><strong>10단계 [테스트 전원 인가]</strong>: 테스트 전원 출력을 ON시키고, 전원 인가 후 신호 안정을 위해 잠시 대기합니다.</li>
<li><strong>11단계 [측정 장비 확인]</strong>: 측정 장치의 통신 상태를 확인합니다.</li>
<li><strong>12단계 [전압 측정]</strong>: 제품의 현재 작동 전압을 측정합니다.</li>
<li><strong>13단계 [저항 측정]</strong>: 제품의 전기적 저항값을 직접 실측합니다.</li>
<li><strong>14단계 [전류 측정]</strong>: 전원 공급 장치의 출력 채널을 통해 제품의 작동 소모 전류를 측정합니다.</li>
<li><strong>15단계 [테스트 전원 차단]</strong>: 수집 완료 후 테스트 전원 출력을 자동으로 차단하여 제품을 안전하게 보호합니다. (계측 도중 오류가 발생하더라도 강제로 전원이 차단되도록 안전 장치가 적용되어 있습니다.)</li>
</ul>
<hr />
<h2>7.3 최종 판정 및 마킹 실행 조건</h2>
<p>계측이 완료되면 측정된 전압(V), 저항(kΩ), 전류(mA) 값을 설정한 기준 규격값(SPEC)과 비교하여 최종 판정을 내립니다.</p>
<ol>
<li><strong>기준 규격을 벗어난 경우 (FAIL)</strong>:<ul>
<li>전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(FAIL) 판정이 내려집니다.</li>
<li>이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, 불합격 내역으로 DB에 기록됩니다.</li>
</ul>
</li>
<li><strong>모든 항목이 합격인 경우 (PASS)</strong>:<ul>
<li>모든 수치가 기준치 내에 들어오면 최종 합격(PASS) 판정이 내려집니다.</li>
<li>합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 2D QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.</li>
<li>마킹 완료 기록과 계측 수치는 최종 PASS 상태로 데이터베이스에 저장됩니다.</li>
</ul>
</li>
</ol>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>8. 로그 및 데이터 관리</h1>
<hr />
<h2>8.1 시스템 로그 수집 및 관리</h2>
<p>본 제어 프로그램은 설비 가동 중에 발생하는 계측기와의 통신 상태, 측정값 정보 및 각종 예외 오류 상황을 로그 파일로 기록합니다.</p>
<h3>8.1.1 로그 파일 보관 방법</h3>
<ul>
<li><strong>보관 폴더</strong>: 프로그램 실행 파일 경로의 하위 <strong><code>Logs</code></strong> 폴더에 저장됩니다.</li>
<li><strong>파일명 규칙</strong>: 날짜별로 파일이 자동으로 생성되어 보관됩니다.</li>
<li><strong>로그 중요도 분류</strong>:<ul>
<li><strong>정보 (INFO)</strong>: 정상적인 검사 프로세스 시작, 계측기 연결 성공, 레이저 인쇄 성공 및 데이터베이스 저장 완료 기록.</li>
<li><strong>경고 (WARN)</strong>: 센서 판독기 인식 지연, 제품 한계 기준치를 벗어난 불합격(FAIL) 발생 기록 등 설비 동작은 유지되나 품질 확인이 필요한 정보.</li>
<li><strong>오류 (ERROR)</strong>: 데이터베이스 네트워크 접속 끊김, 마킹 장치 연결 실패 등 조치가 필요한 장비 오류 정보.</li>
</ul>
</li>
</ul>
<hr />
<h2>8.2 장비 교체 및 프로그램 백업 방법</h2>
<p>장비를 교환하거나 PC 복구 상황에 대비하여, 정상 작동 상태에서의 설정 파일들을 정기적으로 백업해 둘 것을 권장합니다.</p>
<h3>8.2.1 백업 대상 리스트</h3>
<ol>
<li><strong>계측기 설정 및 검사 기준값 백업</strong>:<ul>
<li>계측기들의 연결 설정 정보와 합격 판정 기준 범위가 저장됩니다.</li>
</ul>
</li>
<li><strong>데이터베이스 서버 정보 백업</strong>:<ul>
<li>생산 정보 데이터베이스 서버 주소와 로그인 정보가 저장됩니다.</li>
</ul>
</li>
<li><strong>센서 채널 정보 백업</strong>:<ul>
<li>일련번호 판독 통신 포트 번호와 설정 채널 정보가 저장됩니다.</li>
</ul>
</li>
<li><strong>레이저 마킹 도면 파일 백업</strong>:<ul>
<li>제품 표면에 각인할 마킹 도면 파일입니다.</li>
</ul>
</li>
</ol>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
<div class="page-break"></div>
<h1>9. 오류 대응 및 프로그램 종료</h1>
<hr />
<h2>9.1 현장 장애 유형별 점검 및 예외 조치 요령</h2>
<p>공정 가동 중 장비 상태나 전산망에 이상이 생길 경우 발생할 수 있는 주요 화면 경고창 및 조치 요령입니다.</p>
<h3>9.1.1 [오류 1] "제품 ID(IC_SN)를 읽어오지 못했습니다"</h3>
<ul>
<li><strong>현상</strong>: 검사 시작 시 제품 일련번호 읽기 단계에서 경고창이 발생하고 검사가 취소됨.</li>
<li><strong>원인 및 해결</strong>:<ol>
<li><strong>접촉 상태 점검</strong>: 제품이 안착 지그에 완벽하게 안착되었는지 확인하고, 프레스 실린더 또는 핀 단자의 밀착 상태를 확인하십시오.</li>
<li><strong>통신 설정 점검</strong>: 프로그램 상단의 <code>통신 설정</code> 창을 열어 현재 설정된 포트 번호 및 물리 채널 번호가 실제 케이블 연결 상태와 일치하는지 확인하십시오.</li>
</ol>
</li>
</ul>
<h3>9.1.2 [오류 2] "이전 공정 이력이 조회되지 않는 제품입니다"</h3>
<ul>
<li><strong>현상</strong>: 검사 시작 시 이전 단계 결과가 조회되지 않아 검사가 진행되지 않음.</li>
<li><strong>원인 및 해결</strong>:<ul>
<li>해당 제품이 하우징 조립, 센서 보정, EOL 1차 성능 검사 등 이전 가공 공정을 진행하지 않았거나, 이전 설비에서 생산 DB에 데이터를 올바르게 전송하지 않은 상태입니다. 제품 바코드를 재확인하고 이전 설비의 데이터 전송 유무를 검토하십시오.</li>
</ul>
</li>
<li><strong>비상 조치 (강제 검사)</strong>:<ul>
<li>이전 이력 등록 상태와 무관하게 <strong>제품 성능 계측 및 마킹을 실행하고 결과를 저장해야 하는 특수 테스트 상황</strong>인 경우, 화면 최하단의 <strong><code>강제 검사 (스킵)</code></strong> 버튼을 누르면 DB 검증을 건너뛰고 성능 검사를 강제로 실행할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3>9.1.3 [오류 3] "이전 공정 단계가 불합격 상태이므로 최종 성능 검사를 시작할 수 없습니다"</h3>
<ul>
<li><strong>현상</strong>: DB 조회 결과 이전 공정 중 불합격(FAIL 또는 NG) 판정을 받은 내역이 발견되어 검사가 중단됨.</li>
<li><strong>원인 및 해결</strong>:<ul>
<li>이전 공정에서 불합격 판정을 받은 제품은 최종 평가 및 레이저 마킹을 가동할 수 없습니다. 불량 제품 격리 기준에 따라 해당 제품을 생산 라인에서 제외 조치하십시오.</li>
</ul>
</li>
<li><strong>비상 조치 (강제 검사)</strong>:<ul>
<li>설비 작동 확인이나 품질 분석 등을 위해 반드시 검사를 진행해야 하는 경우, 하단바의 <strong><code>강제 검사 (스킵)</code></strong> 버튼을 클릭하면 경고 상태를 건너뛰고 계측과 마킹을 실행할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3>9.1.4 [오류 4] "레이저 각인 작업 전송 실패"</h3>
<ul>
<li><strong>현상</strong>: 성능 검사는 합격(PASS)했으나, 레이저 장비로의 데이터 전송 단계에서 경고 에러가 발생함.</li>
<li><strong>원인 및 해결</strong>:<ol>
<li><strong>도면 파일 점검</strong>: <code>시험 설정</code> 화면에서 세팅해 놓은 작업 도면 파일이 실제 해당 경로에 존재하는지 점검하십시오.</li>
<li><strong>마킹기 전원/연결 점검</strong>: 레이저 마킹기의 전원이 켜져 있는지 확인하고, 제어 PC와 마킹기 간의 연결 케이블 상태를 확인하십시오.</li>
</ol>
</li>
</ul>
<h3>9.1.5 [오류 5] "각인 순번이 9999를 초과했습니다. 다음 Lot No로 변경하십시오."</h3>
<ul>
<li><strong>현상</strong>:<ul>
<li>프로그램 실행 또는 로그인 완료 후, 메인 화면 상태 알림창에 빨간색 경고 배너로 문구가 표시됩니다.</li>
<li><code>검사 시작 (START)</code> 또는 <code>강제 검사 (스킵)</code> 버튼을 누르면 작동 차단 경고창이 나타나며 설비 검사가 시작되지 않습니다.</li>
</ul>
</li>
<li><strong>원인 및 해결</strong>:<ul>
<li><strong>순번 범위 초과</strong>: 현재 로트 번호(<code>Lot No</code>)에서 최종 합격을 받아 각인이 완료된 누적 수량이 9999개를 초과하여 더 이상 순차 번호를 인쇄할 수 없습니다.</li>
<li><strong>해결 방법</strong>: 새로운 생산 조 또는 로트 번호(<code>Lot No</code>)를 수정하여 <strong>프로그램에 재로그인</strong>하십시오. 로트 번호가 변경되면 순번 카운트가 다시 초기화됩니다.</li>
</ul>
</li>
</ul>
<hr />
<h2>9.2 프로그램 정상 종료 및 전원 자동 차단 규칙</h2>
<p>본 설비는 고전력 계측 및 전원 제어를 함께 수행하므로 대기 전력 소모 방지와 안전을 위해 아래 절차에 따라 정상 종료 프로세스를 밟아야 합니다.</p>
<ul>
<li><strong>로그아웃 (Logout)</strong>:<ul>
<li>상단 메뉴의 <code>로그아웃</code> 버튼을 클릭하면 현재 작업자 정보가 해제되고 초기 로그인 창으로 전환됩니다.</li>
<li><strong>이와 동시에 연동된 전원 공급 장치로 차단 신호가 자동으로 전송되어 주전원 및 보드 부팅용 상시전원 출력이 OFF 상태로 차단됩니다.</strong></li>
</ul>
</li>
<li><strong>프로그램 완전 종료</strong>:<ul>
<li>화면 우측 상단의 <code>닫기(X)</code> 버튼을 눌러 프로그램을 최종 종료할 때도, 자동으로 상시전원 차단 명령을 장비에 인계한 뒤 레이저 각인 드라이버 리소스를 닫고 메모리를 환원합니다.</li>
</ul>
</li>
</ul>
<hr />
<p><em>본 문서는 Performance Inspect &amp; Laser Marking System v1.0.0 기준으로 작성되었습니다.</em></p>
</body>
</html>

163
marking_gui/Manual/build_pdf.py

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
import os
import re
def clean_markdown_content(filename, content, link_mapping):
# 1. 목차/이전/다음 등 내비게이션 링크 제거 패턴
lines = content.split('\n')
cleaned_lines = []
for line in lines:
stripped = line.strip()
is_nav = False
if "메뉴얼 목차로 돌아가기" in stripped:
is_nav = True
elif "이전:" in stripped and "다음:" in stripped:
is_nav = True
elif stripped.startswith("[다음:") and stripped.endswith(")"):
is_nav = True
elif stripped.startswith("[← 이전:") and stripped.endswith(")"):
is_nav = True
if not is_nav:
cleaned_lines.append(line)
content = '\n'.join(cleaned_lines)
# 2. Obsidian 위키링크 이미지 문법 -> HTML img 태그로 치환
content = re.sub(r'!\[\[(.*?)\]\]', r'<img src="./\1" alt="\1" />', content)
# 3. 마크다운 파일 참조 링크 -> 로컬 앵커로 치환
for md_file, anchor in link_mapping.items():
content = content.replace(f"({md_file})", f"({anchor})")
# 4. 각 파일 시작 전에 페이지 나누기 추가 (표지 파일은 제외)
is_cover = "사용자" in filename or "메뉴얼" in filename
if not is_cover:
content = '<div class="page-break"></div>\n\n' + content
# 5. 불필요하게 연속된 빈 라인 및 구분선 정리
content = re.sub(r'^\s*---\s*\n', '', content) # 문서 시작 부분의 --- 제거
content = re.sub(r'\n\s*---\s*$', '', content) # 문서 끝 부분의 --- 제거
return content
def main():
manual_dir = os.path.dirname(os.path.abspath(__file__))
merged_md_path = os.path.join(manual_dir, "Manual_Merged.md")
style_path = os.path.join(manual_dir, "pdf_style.css")
# CSS 파일 로드
css_content = ""
if os.path.exists(style_path):
with open(style_path, "r", encoding="utf-8") as f:
css_content = f.read()
else:
print(f"경고: 스타일시트 파일을 찾을 수 없습니다: {style_path}")
# 디렉토리 파일들을 UTF-8 유니코드로 스캔
all_files = os.listdir(manual_dir)
# 표지 파일 찾기
cover_file = None
for f in all_files:
if f.endswith(".md") and ("사용자" in f or "메뉴얼" in f) and "Merged" not in f:
cover_file = f
break
# "01_" ~ "09_"로 시작하는 검사 매뉴얼 파일 수집
numbered_files = []
for f in all_files:
if f.endswith(".md") and re.match(r'^\d+', f):
numbered_files.append(f)
# 순서 보장을 위해 정렬
numbered_files.sort()
# 최종 병합 리스트 빌드
final_file_list = []
if cover_file:
final_file_list.append(cover_file)
final_file_list.extend(numbered_files)
print("감지 및 병합할 파일 목록:")
for f in final_file_list:
print(f" - {f}")
# 링크 치환용 맵 작성 (인코딩 호환성을 위해 파일명 동적 파싱)
link_mapping = {}
for f in final_file_list:
if "사용자" in f or "메뉴얼" in f:
link_mapping[f] = "#performance-inspect--laser-marking-system---사용자-메뉴얼"
elif f.startswith("01"):
link_mapping[f] = "#1-시스템-개요-및-요구사항"
elif f.startswith("02"):
link_mapping[f] = "#2-초기-설정-가이드"
elif f.startswith("03"):
link_mapping[f] = "#3-메인-화면-home-view"
elif f.startswith("04"):
link_mapping[f] = "#4-통신-설정-comm-settings"
elif f.startswith("05"):
link_mapping[f] = "#5-시험-설정-parameters"
elif f.startswith("06"):
link_mapping[f] = "#6-측정-데이터-조회-및-데이터베이스-연동"
elif f.startswith("07"):
link_mapping[f] = "#7-자동-시험-프로세스-및-14단계-시퀀스"
elif f.startswith("08"):
link_mapping[f] = "#8-로그-및-데이터-관리"
elif f.startswith("09"):
link_mapping[f] = "#9-오류-대응-및-프로그램-종료"
merged_markdown_only = ""
for file_name in final_file_list:
file_path = os.path.join(manual_dir, file_name)
print(f"병합 처리 중: {file_name}...")
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
cleaned = clean_markdown_content(file_name, content, link_mapping)
merged_markdown_only += cleaned + "\n\n"
# md 파일 저장용에는 style 포함
merged_content = ""
if css_content:
merged_content += f"<style>\n{css_content}\n</style>\n\n"
merged_content += merged_markdown_only
# 병합된 마크다운 저장
with open(merged_md_path, "w", encoding="utf-8") as f:
f.write(merged_content)
print(f"\n성공: 통합 마크다운 파일이 생성되었습니다 -> {merged_md_path}")
# HTML 생성 시도
try:
import markdown
merged_html_path = os.path.join(manual_dir, "Manual_for_pdf.html")
# HTML 뼈대 구성
html_output = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Performance Inspect & Laser Marking System - 사용자 메뉴얼</title>
<style>
{css_content}
</style>
</head>
<body>
"""
html_body = markdown.markdown(merged_markdown_only, extensions=['tables', 'fenced_code'])
html_output += html_body
html_output += "\n</body>\n</html>"
with open(merged_html_path, "w", encoding="utf-8") as f:
f.write(html_output)
print(f"성공: 통합 HTML 파일이 생성되었습니다 -> {merged_html_path}")
except ImportError:
print("\n참고: 파이썬 'markdown' 패키지가 설치되지 않아 HTML 빌드를 건너뜁니다. (pip install markdown)")
if __name__ == "__main__":
main()

213
marking_gui/Manual/pdf_style.css

@ -0,0 +1,213 @@
/*
* A4 용지 기준 PDF 출력을 위한 스타일시트 (pdf_style.css)
*/
@page {
size: A4;
margin: 20mm 15mm 20mm 15mm;
@top-left { content: none !important; }
@top-right { content: none !important; }
@bottom-left { content: none !important; }
@bottom-right { content: none !important; }
@bottom-center {
content: counter(page) !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 8.5pt;
color: #7f8c8d;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
font-size: 10pt;
line-height: 1.6;
color: #333333;
background-color: #ffffff;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* 제목 스타일 */
h1, h2, h3, h4, h5, h6 {
color: #111111;
font-weight: bold;
page-break-after: avoid;
page-break-inside: avoid;
}
h1 {
font-size: 18pt;
border-bottom: 2px solid #007acc;
padding-bottom: 4px;
margin-top: 20px;
margin-bottom: 12px;
}
h2 {
font-size: 13pt;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 3px;
margin-top: 16px;
margin-bottom: 10px;
}
h3 {
font-size: 11pt;
margin-top: 12px;
margin-bottom: 6px;
}
h4 {
font-size: 10pt;
margin-top: 10px;
margin-bottom: 6px;
}
/* 문단 및 리스트 */
p {
margin-top: 0;
margin-bottom: 8px;
text-align: justify;
}
ul, ol {
margin-top: 0;
margin-bottom: 8px;
padding-left: 20px;
}
li {
margin-bottom: 3px;
}
/* 구분선 */
hr {
height: 1px;
padding: 0;
margin: 15px 0;
background-color: #e1e4e8;
border: 0;
}
/* 테이블 스타일 */
table {
width: 100%;
border-collapse: collapse;
margin-top: 0;
margin-bottom: 10px;
page-break-inside: avoid;
}
table th, table td {
padding: 4px 8px;
border: 1px solid #dfe2e5;
font-size: 8.5pt;
word-break: keep-all;
}
table th {
font-weight: 600;
background-color: #f6f8fa;
text-align: left;
}
table tr:nth-child(even) {
background-color: #f9fbfd;
}
table tr {
page-break-inside: avoid; /* 표의 중간 행이 반으로 쪼개지는 것만 방지 */
}
/* 인용구 스타일 */
blockquote {
padding: 6px 12px;
color: #6a737d;
border-left: 0.25em solid #007acc;
background-color: #f6f8fa;
margin: 0 0 10px 0;
page-break-inside: avoid;
}
blockquote p {
margin: 0;
}
/* 코드 블록 (아스크 아트 등 포함) */
pre, code {
font-family: "Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", monospace;
font-size: 8.5pt;
background-color: #f6f8fa;
border-radius: 3px;
}
code {
padding: 0.2em 0.4em;
margin: 0;
border: 1px solid #eaecef;
}
pre {
padding: 12px;
overflow: auto;
line-height: 1.45;
border: 1px solid #e1e4e8;
word-wrap: normal;
white-space: pre; /* 아스키 아트 정렬 유지를 위해 강제 줄바꿈 해제 */
page-break-inside: avoid;
}
pre code {
padding: 0;
margin: 0;
background-color: transparent;
border: none;
font-size: inherit;
white-space: inherit;
}
/* 이미지 스타일 */
img {
max-width: 100%;
max-height: 100mm; /* A4 높이의 약 절반 이하로 제한 */
display: block;
margin: 15px auto;
object-fit: contain;
page-break-inside: avoid;
border: 1px solid #e1e4e8;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 페이지 나누기 요소 */
.page-break {
page-break-before: always !important;
}
/* 내비게이션 숨김 (인쇄 시) */
.nav-link, .nav-links, .back-to-toc, .manual-nav,
a[href$=".md"] {
display: none !important;
}
/* 링크 스타일 */
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 특별 배지 스타일 */
.badge {
display: inline-block;
padding: 2px 6px;
font-size: 8pt;
font-weight: 600;
line-height: 1;
border-radius: 2px;
background-color: #e1e4e8;
color: #24292e;
margin-right: 4px;
}

44
marking_gui/Manual/사용자_메뉴얼.md

@ -0,0 +1,44 @@
# Performance Inspect & Laser Marking System - 사용자 메뉴얼
> **버전**: 1.0.0 | **최종 수정**: 2026-07-14
---
## 메뉴얼 목차
본 메뉴얼은 각 주제별로 별도 문서로 구성되어 있습니다. 아래 링크를 클릭하여 해당 문서로 이동하세요.
| 번호 | 문서 | 설명 |
|------|------|------|
| 1 | [시스템 개요 및 요구사항](01_시스템_개요.md) | 프로그램 소개, 주요 기능, 하드웨어/소프트웨어 요구사항, 장비 구성 |
| 2 | [초기 설정 가이드](02_초기_설정.md) | 설정 파일의 구성 요소 안내 및 최초 장비 가동 시 점검 순서 |
| 3 | [메인 화면 (Home View)](03_메인_화면.md) | 상단 정보 표시, 제품 정보 카드, 검사 결과 화면, 진행 상황 확인 및 버튼 제어 가이드 |
| 4 | [통신 설정 (Comm. Settings)](04_통신_설정.md) | 4251 센서 통신 채널 설정 및 PC 통신 포트 연동 방법 |
| 5 | [시험 설정 (Parameters)](05_시험_설정.md) | 제품 규격치(전압, 전류, 저항 범위) 및 레이저 마킹 도면 파일 연동 |
| 6 | [측정 데이터 조회 (Data View)](06_데이터_조회.md) | 생산 데이터베이스를 통한 이전 공정 이력 자동 조회 및 최종 결과 저장 원리 |
| 7 | [자동 시험 프로세스](07_시험_프로세스.md) | 자동 계측 시퀀스 기동, 마킹 수행 및 최종 합격 판정 흐름 |
| 8 | [로그 및 데이터 관리](08_로그_관리.md) | 시스템 로그 파일 확인 방법 및 장비 설정 파일 백업 방법 |
| 9 | [오류 대응 및 프로그램 종료](09_오류_대응.md) | 센서 통신 오류, DB 조회 에러, 마킹 불량 등 현장 장애 대응 및 안전한 프로그램 종료 방법 |
---
## 빠른 시작 가이드 (Quick Start)
처음 시스템을 가동하여 검사를 진행하는 경우 다음 순서에 따라 작업을 수행해 주십시오.
1. **프로그램 설치 (최초 1회)**
- 배포받은 단일 설치 프로그램 `MarkingSetup.exe`를 실행하여 클릭 한 번으로 `C:\marking` 경로 및 바탕화면 바로가기를 자동 생성합니다.
2. **하드웨어 연결 상태 확인**
- 계측 장비들과 제어용 PC 간의 네트워크(LAN)선 및 센서 리더용 시리얼 포트 케이블 연결을 확인합니다.
3. **프로그램 실행 및 로그인**
- 바탕화면의 `Marking System` (`marking_gui`) 프로그램을 실행한 뒤, 작업자 계정 정보를 입력하여 로그인합니다. (데이터베이스 점검 중인 경우 설정 파일을 통해 오프라인 모드로의 진입이 가능합니다.)
3. **통신 상태 확인 및 채널 설정**
- 우측 상단의 `통신 설정` 메뉴에서 센서가 올바른 포트와 채널에 연결되었는지 확인합니다.
4. **검사 규격값 검증**
- 우측 상단의 `시험 설정` 메뉴를 열고, 전압/전류/저항 검사 기준치와 각인 템플릿 도면 파일의 경로가 올바른지 점검합니다.
5. **성능 검사 및 마킹 시작**
- 안착 지그에 제품을 바르게 장착한 뒤 메인 화면 하단의 `검사 시작 (START)` 버튼을 눌러 계측 및 마킹을 수행합니다.
---
*본 문서는 Performance Inspect & Laser Marking System v1.0.0 기준으로 작성되었습니다.*

BIN
marking_gui/Marking.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

70
marking_gui/Models/MarkingRequest.cs

@ -0,0 +1,70 @@
using System;
namespace marking_gui.Models
{
public class MarkingRequest
{
/// <summary>
/// 제품 일련번호 (SERIAL 태그용)
/// </summary>
public string Serial { get; set; }
/// <summary>
/// 센서 IC S/N
/// </summary>
public string IcSerial { get; set; }
/// <summary>
/// LOT 번호 (YYMMDD 태그용)
/// </summary>
public string LotNo { get; set; }
/// <summary>
/// 제품 고유 번호 (PRODUCT 태그용, 예: 35981-M5000)
/// </summary>
public string ProductNo { get; set; }
/// <summary>
/// 전체 QR 코드 문자열 (QRCODETAG 태그용)
/// </summary>
public string QrString { get; set; }
/// <summary>
/// 생산 라인 코드 (LCODE 태그용)
/// </summary>
public string LineCode { get; set; }
/// <summary>
/// 근무조 정보 (BAN 태그용)
/// </summary>
public string ShiftCode { get; set; }
/// <summary>
/// 도면 품번 (DRAWING_NO 태그용)
/// </summary>
public string DrawingNo { get; set; }
/// <summary>
/// HKMC 품번 (HKMC_NO 태그용)
/// </summary>
public string HkmcNo { get; set; }
/// <summary>
/// 마킹에 사용할 EZD 도안 파일명 (예: AIC1.ezd)
/// </summary>
public string EzdFileName { get; set; }
public MarkingRequest()
{
Serial = string.Empty;
LotNo = string.Empty;
ProductNo = string.Empty;
QrString = string.Empty;
LineCode = string.Empty;
ShiftCode = string.Empty;
DrawingNo = string.Empty;
HkmcNo = string.Empty;
EzdFileName = string.Empty;
}
}
}

35
marking_gui/Models/MarkingResult.cs

@ -0,0 +1,35 @@
using System;
namespace marking_gui.Models
{
public class MarkingResult
{
/// <summary>
/// 마킹 작업의 성공 여부
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 에러 코드 (0 이면 성공, 그 외에는 각 에러 상태 코드)
/// </summary>
public int ErrorCode { get; set; }
/// <summary>
/// 에러 또는 상세 결과 메시지
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// 마킹이 실제 실행 및 완료된 시간
/// </summary>
public DateTime MarkedAt { get; set; }
public MarkingResult()
{
Success = false;
ErrorCode = -1;
ErrorMessage = "Uninitialized";
MarkedAt = DateTime.MinValue;
}
}
}

127
marking_gui/Models/MeasurementLimits.cs

@ -1,4 +1,5 @@
using System; using System;
using System.IO;
namespace marking_gui.Models namespace marking_gui.Models
{ {
@ -51,5 +52,131 @@ namespace marking_gui.Models
return true; return true;
} }
public static MeasurementLimits LoadFromIni(string iniPath)
{
var limits = new MeasurementLimits();
if (!File.Exists(iniPath)) return limits;
try
{
var lines = File.ReadAllLines(iniPath);
string currentSection = string.Empty;
var dict = new System.Collections.Generic.Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in lines)
{
string trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith(";")) continue;
if (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
{
currentSection = trimmed.Substring(1, trimmed.Length - 2).Trim();
continue;
}
if (currentSection.Equals("Limits", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
dict[parts[0].Trim()] = parts[1].Trim();
}
}
}
if (dict.TryGetValue("MinVoltage", out string minVStr) && double.TryParse(minVStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double minV))
limits.MinVoltage = minV;
if (dict.TryGetValue("MaxVoltage", out string maxVStr) && double.TryParse(maxVStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double maxV))
limits.MaxVoltage = maxV;
if (dict.TryGetValue("MinCurrent", out string minCStr) && double.TryParse(minCStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double minC))
limits.MinCurrent = minC;
if (dict.TryGetValue("MaxCurrent", out string maxCStr) && double.TryParse(maxCStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double maxC))
limits.MaxCurrent = maxC;
// 기존 Setting.ini에 uA 단위(8000 등)로 저장되어 있던 데이터를 mA 단위(8.00)로 자동 보정
if (limits.MinCurrent > 100) limits.MinCurrent /= 1000.0;
if (limits.MaxCurrent > 100) limits.MaxCurrent /= 1000.0;
if (dict.TryGetValue("MinResistance", out string minRStr) && double.TryParse(minRStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double minR))
limits.MinResistance = minR;
if (dict.TryGetValue("MaxResistance", out string maxRStr) && double.TryParse(maxRStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double maxR))
limits.MaxResistance = maxR;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[Limits] ini 로드 실패: " + ex.Message);
}
return limits;
}
public void SaveToIni(string iniPath)
{
try
{
var kvMap = new System.Collections.Generic.Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["MinVoltage"] = MinVoltage.ToString("F2", System.Globalization.CultureInfo.InvariantCulture),
["MaxVoltage"] = MaxVoltage.ToString("F2", System.Globalization.CultureInfo.InvariantCulture),
["MinCurrent"] = MinCurrent.ToString("F2", System.Globalization.CultureInfo.InvariantCulture),
["MaxCurrent"] = MaxCurrent.ToString("F2", System.Globalization.CultureInfo.InvariantCulture),
["MinResistance"] = MinResistance.ToString("F3", System.Globalization.CultureInfo.InvariantCulture),
["MaxResistance"] = MaxResistance.ToString("F3", System.Globalization.CultureInfo.InvariantCulture),
};
System.Collections.Generic.List<string> lines = File.Exists(iniPath) ? new System.Collections.Generic.List<string>(File.ReadAllLines(iniPath)) : new System.Collections.Generic.List<string>();
int limitsSectionIndex = -1;
for (int i = 0; i < lines.Count; i++)
{
if (lines[i].Trim().Equals("[Limits]", StringComparison.OrdinalIgnoreCase))
{
limitsSectionIndex = i;
break;
}
}
if (limitsSectionIndex >= 0)
{
int insertPos = limitsSectionIndex + 1;
while (insertPos < lines.Count && !lines[insertPos].Trim().StartsWith("["))
{
string line = lines[insertPos].Trim();
int eq = line.IndexOf('=');
if (eq > 0)
{
string key = line.Substring(0, eq).Trim();
if (kvMap.ContainsKey(key))
{
lines[insertPos] = $"{key}={kvMap[key]}";
kvMap.Remove(key);
}
}
insertPos++;
}
foreach (var kv in kvMap)
{
lines.Insert(insertPos, $"{kv.Key}={kv.Value}");
insertPos++;
}
}
else
{
lines.Add("");
lines.Add("[Limits]");
foreach (var kv in kvMap)
{
lines.Add($"{kv.Key}={kv.Value}");
}
}
File.WriteAllLines(iniPath, lines);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[Limits] ini 저장 실패: " + ex.Message);
}
}
} }
} }

6
marking_gui/Models/Product.cs

@ -8,12 +8,16 @@ namespace marking_gui.Models
public string HousingResult { get; set; } public string HousingResult { get; set; }
public string CalResult { get; set; } public string CalResult { get; set; }
public string EolResult { get; set; } public string EolResult { get; set; }
public string PcbBarcode { get; set; }
public bool IsPreviousStepsPassed public bool IsPreviousStepsPassed
{ {
get get
{ {
return HousingResult == "PASS" && CalResult == "PASS" && EolResult == "PASS"; bool housingOk = HousingResult == "PASS" || HousingResult == "OK";
bool calOk = CalResult == "PASS" || CalResult == "OK";
bool eolOk = EolResult == "PASS" || EolResult == "OK";
return housingOk && calOk && eolOk;
} }
} }

82
marking_gui/PortSettingsWindow.xaml

@ -0,0 +1,82 @@
<Window x:Class="marking_gui.PortSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="포트 설정" Height="280" Width="380"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
Background="#F3F4F6" ShowInTaskbar="False">
<Window.Resources>
<!-- 버튼 스타일 -->
<Style x:Key="DialogButtonStyle" TargetType="Button">
<Setter Property="Background" Value="#3B82F6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="15,6"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2563EB"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="CancelButtonStyle" TargetType="Button" BasedOn="{StaticResource DialogButtonStyle}">
<Setter Property="Background" Value="#9CA3AF"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#4B5563"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="🔌 4251 센서 통신 포트 설정" FontSize="16" FontWeight="Bold" Foreground="#1F2937" Margin="0,0,0,15"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 포트 선택 -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="통신 포트 (Port):" FontSize="14" VerticalAlignment="Center" Foreground="#4B5563" FontWeight="SemiBold"/>
<ComboBox x:Name="cbPort" Grid.Row="0" Grid.Column="2" FontSize="14" Padding="4" VerticalContentAlignment="Center"/>
<!-- BaudRate 선택 -->
<TextBlock Grid.Row="2" Grid.Column="0" Text="통신 속도 (Baud):" FontSize="14" VerticalAlignment="Center" Foreground="#4B5563" FontWeight="SemiBold"/>
<ComboBox x:Name="cbBaudRate" Grid.Row="2" Grid.Column="2" FontSize="14" Padding="4" VerticalContentAlignment="Center"/>
<!-- 채널 선택 -->
<TextBlock Grid.Row="4" Grid.Column="0" Text="통신 채널 (Channel):" FontSize="14" VerticalAlignment="Center" Foreground="#4B5563" FontWeight="SemiBold"/>
<ComboBox x:Name="cbChannel" Grid.Row="4" Grid.Column="2" FontSize="14" Padding="4" VerticalContentAlignment="Center"/>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
<Button x:Name="btnSave" Content="저장" Style="{StaticResource DialogButtonStyle}" Width="70" Height="32" Click="btnSave_Click" Margin="0,0,10,0"/>
<Button x:Name="btnCancel" Content="취소" Style="{StaticResource CancelButtonStyle}" Width="70" Height="32" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
</Window>

156
marking_gui/PortSettingsWindow.xaml.cs

@ -0,0 +1,156 @@
using System;
using System.IO;
using System.IO.Ports;
using System.Windows;
namespace marking_gui
{
/// <summary>
/// PortSettingsWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class PortSettingsWindow : Window
{
private string _iniPath;
public PortSettingsWindow()
{
InitializeComponent();
_iniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sensor.ini");
Loaded += PortSettingsWindow_Loaded;
}
private void PortSettingsWindow_Loaded(object sender, RoutedEventArgs e)
{
// 1. 시스템에 유효한 COM 포트 목록 조회
string[] ports = SerialPort.GetPortNames();
cbPort.Items.Clear();
foreach (var port in ports)
{
cbPort.Items.Add(port);
}
// 2. BaudRate 목록 추가
cbBaudRate.Items.Clear();
cbBaudRate.Items.Add("9600");
cbBaudRate.Items.Add("19200");
cbBaudRate.Items.Add("38400");
cbBaudRate.Items.Add("57600");
cbBaudRate.Items.Add("115200");
// 2-2. 채널 목록 추가 (1~4 채널)
cbChannel.Items.Clear();
cbChannel.Items.Add("1");
cbChannel.Items.Add("2");
// 3. Sensor.ini 에서 현재 설정 로드
string currentPort = "COM3";
string currentBaud = "115200";
string currentChannel = "1";
if (File.Exists(_iniPath))
{
try
{
var lines = File.ReadAllLines(_iniPath);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("Port", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2) currentPort = parts[1].Trim();
}
else if (trimmed.StartsWith("BaudRate", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2) currentBaud = parts[1].Trim();
}
else if (trimmed.StartsWith("Channel", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2) currentChannel = parts[1].Trim();
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[PortSettings] 설정 로딩 중 오류: " + ex.Message);
}
}
// 4. 조회한 포트 목록에 현재 설정된 포트가 없으면 임시 추가
if (!cbPort.Items.Contains(currentPort))
{
cbPort.Items.Add(currentPort);
}
// 5. 선택 상태 반영
cbPort.SelectedItem = currentPort;
if (cbBaudRate.Items.Contains(currentBaud))
{
cbBaudRate.SelectedItem = currentBaud;
}
else
{
cbBaudRate.SelectedItem = "115200";
}
if (cbChannel.Items.Contains(currentChannel))
{
cbChannel.SelectedItem = currentChannel;
}
else
{
cbChannel.SelectedItem = "1";
}
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (cbPort.SelectedItem == null || cbBaudRate.SelectedItem == null || cbChannel.SelectedItem == null)
{
MessageBox.Show("포트, 통신 속도 및 채널을 선택해 주십시오.", "알림", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string port = cbPort.SelectedItem.ToString();
string baud = cbBaudRate.SelectedItem.ToString();
string channel = cbChannel.SelectedItem.ToString();
try
{
// Sensor.ini 에 저장
using (var sw = new StreamWriter(_iniPath, false))
{
sw.WriteLine("; =================================================================");
sw.WriteLine("; 성능 검사 모니터링 시스템 - 4251 센서 하드웨어 통신 설정");
sw.WriteLine("; =================================================================");
sw.WriteLine();
sw.WriteLine("[Sensor]");
sw.WriteLine("; 연결할 장치 센서 시리얼(COM) 포트명");
sw.WriteLine("Port=" + port);
sw.WriteLine();
sw.WriteLine("; 시리얼 통신 속도 (Baud Rate, 기본값: 115200)");
sw.WriteLine("BaudRate=" + baud);
sw.WriteLine();
sw.WriteLine("; 4251 센서 통신 채널 (기본값: 1, 범위: 1~4)");
sw.WriteLine("Channel=" + channel);
}
DialogResult = true;
Close();
}
catch (Exception ex)
{
MessageBox.Show("설정을 저장하는 중 오류가 발생했습니다: " + ex.Message, "저장 실패", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}

15
marking_gui/Properties/PublishProfiles/FolderProfile.pubxml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>C:\Users\COMPUTER1\Downloads</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net9.0-windows</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>

389
marking_gui/Services/Board4251Service.cs

@ -0,0 +1,389 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace marking_gui.Services
{
/// <summary>
/// 제품 ID 센서에서 읽은 제품 ID 정보.
/// </summary>
public class SensorIdData
{
public int Year { get; set; }
public int Month { get; set; }
public int Day { get; set; }
public string Serial { get; set; } = "";
public string McLine { get; set; } = "";
public string LineNo { get; set; } = "";
public string Item { get; set; } = "";
public string ID { get; set; } = "";
}
/// <summary>
/// 제품 ID 센서 서비스의 추상 인터페이스.
/// </summary>
public interface IIdSensorService : IDisposable
{
event EventHandler<string> ProgressMessage;
event EventHandler<string> ErrorMessage;
event EventHandler<bool> ConnectionChanged;
bool Connect();
void Disconnect();
Task<SensorIdData> ReadSensorAsync();
}
/// <summary>
/// 신규 4251 보드와의 통신을 관리하는 서비스.
/// </summary>
public class Board4251Service : IDisposable
{
private readonly ICommunication _communication;
private readonly StringBuilder _receiveBuffer = new StringBuilder();
private readonly object _bufferLock = new object();
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private TaskCompletionSource<string> _currentTcs;
public int TimeoutMs { get; set; } = 5000;
private bool _shouldBeConnected = false;
private System.Timers.Timer _reconnectTimer;
public event EventHandler<string> ErrorOccurred;
public event EventHandler<bool> ConnectionChanged;
public string LastResponse { get; private set; } = "";
public string GetLastBuffer()
{
string currentBuf;
lock (_bufferLock)
{
currentBuf = _receiveBuffer.ToString().Trim();
}
return string.IsNullOrEmpty(currentBuf) ? LastResponse : currentBuf;
}
public Board4251Service(ICommunication communication)
{
_communication = communication;
_communication.DataReceived += OnDataReceived;
_communication.ConnectionStatusChanged += (s, isConnected) => {
ConnectionChanged?.Invoke(this, isConnected);
if (!isConnected && _shouldBeConnected) StartReconnectTimer();
};
_reconnectTimer = new System.Timers.Timer(1000);
_reconnectTimer.AutoReset = false;
_reconnectTimer.Elapsed += (s, e) => {
if (_shouldBeConnected && !_communication.IsOpen)
{
if (!_communication.Open())
{
if (_shouldBeConnected) _reconnectTimer.Start();
}
}
else if (_shouldBeConnected)
{
_reconnectTimer.Start();
}
};
}
private void StartReconnectTimer()
{
if (_reconnectTimer != null && !_reconnectTimer.Enabled) _reconnectTimer.Start();
}
public bool Connect()
{
_shouldBeConnected = true;
bool opened = _communication.Open();
if (!opened) StartReconnectTimer();
return opened;
}
public void Disconnect()
{
_shouldBeConnected = false;
_reconnectTimer?.Stop();
_communication.Close();
}
public async Task<bool> CheckStatusAsync(int channel = 1)
{
string response = await SendCommandAsync($"x00c_00{channel}101:owt28006727ea97c7801\r\n");
if (response == null) return false;
if (response.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0) return true;
if (response.IndexOf("Fail", StringComparison.OrdinalIgnoreCase) >= 0) return false;
return false;
}
public async Task<string> ReadIdAsync(int channel = 1)
{
string response = await SendCommandAsync($"x00c_00{channel}101:ow2800326003e\r\n");
if (response == null) return null;
// 16자리 16진수 또는 ID 형태 파싱 추출
// 보드 응답 예: "x00c_001101:ow2800326003e:Success:1234567890ABCDEF" 형태
// 혹은 단순히 "1234567890ABCDEF" 형태의 데이터가 수신됨
try
{
string[] parts = response.Split(':');
if (parts.Length >= 4)
{
return parts[parts.Length - 1].Trim();
}
return response.Trim();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[Board4251] ReadIdAsync 파싱 예외: " + ex.Message);
return response.Trim();
}
}
private string _activeCommand = null;
public async Task<string> SendCommandAsync(string command)
{
await _lock.WaitAsync();
try
{
lock (_bufferLock)
{
_receiveBuffer.Clear();
}
var tcs = new TaskCompletionSource<string>();
_currentTcs = tcs;
_activeCommand = command;
_communication.ClearBuffer();
if (!_communication.Write(command))
{
return null;
}
using (var cts = new CancellationTokenSource(TimeoutMs))
{
// 타임아웃 발생 시 TCS 취소 상태 전달
cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false);
try
{
string response = await tcs.Task;
if (response != null)
{
response = response.Replace("<end>", "").Trim();
}
return response;
}
catch (TaskCanceledException)
{
LoggerService.Warn($"[Board4251] 명령 전송 타임아웃 ({TimeoutMs}ms): {command.Trim()}");
lock (_bufferLock)
{
LastResponse = _receiveBuffer.ToString().Trim();
}
return null;
}
}
}
catch (Exception ex)
{
ErrorOccurred?.Invoke(this, "명령 전송 예외: " + ex.Message);
return null;
}
finally
{
_currentTcs = null;
_activeCommand = null;
_lock.Release();
}
}
private void OnDataReceived(object sender, string data)
{
if (string.IsNullOrEmpty(data)) return;
string currentData;
lock (_bufferLock)
{
_receiveBuffer.Append(data);
currentData = _receiveBuffer.ToString();
}
var targetTcs = _currentTcs;
if (targetTcs == null || targetTcs.Task.IsCompleted) return;
// <end> 기반 완료 확인 (시스템상 종단 확인 메커니즘 우선 적용)
if (_activeCommand != null && _activeCommand.Contains("x00c_"))
{
if (currentData.Contains("<end>"))
{
targetTcs.TrySetResult(currentData.Trim());
}
return;
}
// <end>가 명시되지 않는 기타 일반 패킷은 개행(\n) 또는 Success/Fail 기반으로 완료 판정
if (currentData.Contains("<end>") || currentData.Contains("\n") || currentData.Contains("Success") || currentData.Contains("Fail"))
{
targetTcs.TrySetResult(currentData.Trim());
}
}
public void Dispose()
{
Disconnect();
_reconnectTimer?.Dispose();
_lock.Dispose();
}
}
/// <summary>
/// 신규 4251 보드를 사용하여 제품 ID를 읽는 센서 서비스.
/// </summary>
public class Board4251SensorService : IIdSensorService
{
private readonly Board4251Service _service;
private readonly int _sensorIndex;
private readonly EventHandler<bool> _connectionChangedHandler;
public event EventHandler<string> ProgressMessage;
public event EventHandler<string> ErrorMessage;
public event EventHandler<bool> ConnectionChanged;
public Board4251SensorService(Board4251Service service, int sensorIndex)
{
_service = service;
_sensorIndex = sensorIndex;
_connectionChangedHandler = (s, isConnected) => ConnectionChanged?.Invoke(this, isConnected);
_service.ConnectionChanged += _connectionChangedHandler;
}
public bool Connect() => _service.Connect();
public void Disconnect() => _service.Disconnect();
public async Task<SensorIdData> ReadSensorAsync()
{
try
{
int channel = _sensorIndex + 1;
ProgressMessage?.Invoke(this, "4251 보드 초기화 중...");
_ = _service.SendCommandAsync("x00o\r\n");
await Task.Delay(350);
ProgressMessage?.Invoke(this, "4251 보드 데이터 읽는 중...");
int extendedTimeout = 15000;
string statusCmd = $"x00c_00{channel}101:owt28006727ea97c7801";
bool statusResult = false;
using (var cts = new CancellationTokenSource(extendedTimeout))
{
var statusTask = _service.CheckStatusAsync(channel);
var delayTask = Task.Delay(extendedTimeout, cts.Token);
var completedTask = await Task.WhenAny(statusTask, delayTask);
if (completedTask == delayTask)
{
string buf = _service.GetLastBuffer();
ErrorMessage?.Invoke(this, $"CH{channel} 상태 타임아웃\r\n송신: {statusCmd}\r\n수신: {buf}");
return null;
}
cts.Cancel();
statusResult = await statusTask;
}
if (!statusResult)
{
string buf = _service.GetLastBuffer();
ErrorMessage?.Invoke(this, $"CH{channel} 상태 실패\r\n송신: {statusCmd}\r\n수신: {buf}");
return null;
}
// ID 읽기 (최대 3회 시도)
string idCmd = $"x00c_00{channel}101:ow2800326003e";
string rawId = null;
int maxAttempts = 3;
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
using (var cts = new CancellationTokenSource(extendedTimeout))
{
var idTask = _service.ReadIdAsync(channel);
var delayTask = Task.Delay(extendedTimeout, cts.Token);
var completedTask = await Task.WhenAny(idTask, delayTask);
if (completedTask == delayTask)
{
if (attempt == maxAttempts)
{
string buf = _service.GetLastBuffer();
ErrorMessage?.Invoke(this, $"CH{channel} ID 타임아웃\r\n송신: {idCmd}\r\n수신: {buf}");
return null;
}
continue;
}
cts.Cancel();
rawId = await idTask;
}
if (string.IsNullOrEmpty(rawId))
{
if (attempt == maxAttempts)
{
string buf = _service.GetLastBuffer();
ErrorMessage?.Invoke(this, $"CH{channel} ID 응답 없음\r\n송신: {idCmd}\r\n수신: {buf}");
return null;
}
continue;
}
if (rawId.EndsWith("F", StringComparison.OrdinalIgnoreCase))
{
if (attempt < maxAttempts)
{
ProgressMessage?.Invoke(this, $"ID 읽기 재시도 중... ({attempt}/{maxAttempts})");
await Task.Delay(350);
continue;
}
}
break;
}
// Step 7: 보드 OFF/후처리 (x00o)
ProgressMessage?.Invoke(this, "4251 보드 OFF/후처리 중...");
await _service.SendCommandAsync("x00o\r\n");
await Task.Delay(350);
// Step 8: 보드 CAL 기본값 (x00c_00{channel}001:owt28006727ea97c7801)
ProgressMessage?.Invoke(this, "4251 보드 CAL 기본값 설정 중...");
string calCmd = $"x00c_00{channel}001:owt28006727ea97c7801\r\n";
await _service.SendCommandAsync(calCmd);
await Task.Delay(350);
var data = new SensorIdData
{
ID = rawId,
Serial = "",
Item = "N/A"
};
ProgressMessage?.Invoke(this, "ID 읽기 성공: " + rawId);
return data;
}
catch (Exception ex)
{
ErrorMessage?.Invoke(this, $"4251 읽기 예외: {ex.Message}");
return null;
}
}
public void Dispose()
{
if (_service != null && _connectionChangedHandler != null)
{
_service.ConnectionChanged -= _connectionChangedHandler;
}
}
}
}

142
marking_gui/Services/CommunicationBase.cs

@ -0,0 +1,142 @@
using System;
using System.IO.Ports;
namespace marking_gui.Services
{
/// <summary>
/// 하드웨어 통신을 위한 추상화 인터페이스.
/// 시리얼(RS232), 이더넷(TCP/IP) 등 통신 방식에 상관없이
/// 상위 서비스에서 동일한 방식으로 하드웨어에 접근할 수 있도록 정의함.
/// </summary>
public interface ICommunication
{
/// <summary> 통신 채널 식별 이름 (예: COM1, TCP_192.168.0.1) </summary>
string Name { get; }
/// <summary> 현재 통신 채널이 열려 있는지 여부 </summary>
bool IsOpen { get; }
/// <summary> 통신 채널을 연결함 </summary>
bool Open();
/// <summary> 통신 채널 연결을 해제함 </summary>
void Close();
/// <summary> 데이터를 하드웨어로 전송함 </summary>
bool Write(string data);
/// <summary> 수신 버퍼를 비워 잔류 데이터를 제거함 </summary>
void ClearBuffer();
/// <summary> 하드웨어로부터 데이터를 수신했을 때 발생하는 이벤트 </summary>
event System.EventHandler<string> DataReceived;
/// <summary> 연결 상태가 변경되었을 때 발생하는 이벤트 (connected, disconnected) </summary>
event System.EventHandler<bool> ConnectionStatusChanged;
}
/// <summary>
/// RS232 시리얼 통신을 담당하는 구현체.
/// 연결 안정성 강화를 위한 예외 처리 및 상태 감지 로직 포함.
/// </summary>
public class SerialProvider : ICommunication, IDisposable
{
private SerialPort _serialPort;
private bool _disposed = false;
public string Name { get; private set; }
public bool IsOpen => _serialPort != null && !_disposed && _serialPort.IsOpen;
public event EventHandler<string> DataReceived;
public event EventHandler<bool> ConnectionStatusChanged;
public SerialProvider(string portName, int baudRate)
{
Name = portName;
_serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
_serialPort.Handshake = Handshake.None;
_serialPort.DtrEnable = true; // 터미널 프로그램(포트몬)과 동일하게 제어 신호 활성화
_serialPort.RtsEnable = true;
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
_serialPort.DataReceived += (s, e) => {
try {
string data = _serialPort.ReadExisting();
if (!string.IsNullOrEmpty(data))
DataReceived?.Invoke(this, data);
} catch (Exception ex) {
Console.WriteLine($"[Serial Error] Read error on {Name}: {ex.Message}");
}
};
// 기기가 끊겼을 때 감지 (일부 환경에서는 PinChanged 등으로 감지 가능)
_serialPort.ErrorReceived += (s, e) => {
ConnectionStatusChanged?.Invoke(this, false);
};
}
public bool Open()
{
if (_disposed) return false;
try {
if (IsOpen) return true;
_serialPort.Open();
ConnectionStatusChanged?.Invoke(this, true);
return true;
} catch (Exception ex) {
Console.WriteLine($"[Serial Error] Failed to open {Name}: {ex.Message}");
ConnectionStatusChanged?.Invoke(this, false);
return false;
}
}
public void Close()
{
try {
if (IsOpen) {
_serialPort.Close();
ConnectionStatusChanged?.Invoke(this, false);
}
} catch (Exception ex) {
Console.WriteLine($"[Serial Error] Error closing {Name}: {ex.Message}");
}
}
public bool Write(string data)
{
if (!IsOpen) return false;
try {
_serialPort.Write(data);
return true;
} catch (Exception ex) {
Console.WriteLine($"[Serial Error] Write error on {Name}: {ex.Message}");
return false;
}
}
public void ClearBuffer()
{
if (IsOpen) {
try {
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.ReadExisting();
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine($"[CommunicationBase ClearBuffer Error] {ex.Message}");
}
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
Close();
_serialPort?.Dispose();
_serialPort = null;
}
}
}
}

406
marking_gui/Services/DatabaseService.cs

@ -0,0 +1,406 @@
using System;
using System.Configuration;
using marking_gui.Models;
namespace marking_gui.Services
{
public class DatabaseService
{
private readonly string _connectionString;
private readonly bool _offlineMode = false;
public bool OfflineMode => _offlineMode;
public DatabaseService()
{
try
{
string dbServer = string.Empty;
string dbName = string.Empty;
string dbUser = string.Empty;
string dbPassword = string.Empty;
string rawConnStr = string.Empty;
// AppDomain.CurrentDomain.BaseDirectory 에서 Database.ini 로드 시도
string iniPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database.ini");
if (System.IO.File.Exists(iniPath))
{
var lines = System.IO.File.ReadAllLines(iniPath);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith(";") || trimmed.StartsWith("#"))
{
continue;
}
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
rawConnStr = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Server", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
dbServer = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Name", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
dbName = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_User", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
dbUser = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Password", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
dbPassword = parts[1].Trim();
}
}
else if (trimmed.StartsWith("OfflineMode", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
bool.TryParse(parts[1].Trim(), out _offlineMode);
}
}
}
}
// 개별 설정이 있는 경우 조합해서 사용
if (!string.IsNullOrWhiteSpace(dbServer) &&
!string.IsNullOrWhiteSpace(dbName) &&
!string.IsNullOrWhiteSpace(dbUser) &&
!string.IsNullOrWhiteSpace(dbPassword))
{
_connectionString = $"Server={dbServer};Database={dbName};User Id={dbUser};Password={dbPassword};";
}
else
{
_connectionString = rawConnStr;
}
// Database.ini 에 없을 경우 App.config 백업 사용
if (string.IsNullOrEmpty(_connectionString))
{
var connSettings = ConfigurationManager.ConnectionStrings["MssqlConnection"];
if (connSettings != null)
{
_connectionString = connSettings.ConnectionString;
}
else
{
_connectionString = "Server=localhost;Database=MarkingDb;Trusted_Connection=True;";
}
}
// SSL 인증서 체인 신뢰성 오류(TrustServerCertificate=True 누락) 방지 조치 추가
if (!string.IsNullOrWhiteSpace(_connectionString) && _connectionString.IndexOf("TrustServerCertificate", StringComparison.OrdinalIgnoreCase) < 0)
{
if (!_connectionString.EndsWith(";")) _connectionString += ";";
_connectionString += "TrustServerCertificate=True;";
}
// DB 서버 오프라인 시 지연 방지를 위해 연결 타임아웃(Connection Timeout)을 3초로 강제 조정
if (!string.IsNullOrWhiteSpace(_connectionString) &&
_connectionString.IndexOf("Connect Timeout", StringComparison.OrdinalIgnoreCase) < 0 &&
_connectionString.IndexOf("Connection Timeout", StringComparison.OrdinalIgnoreCase) < 0)
{
if (!_connectionString.EndsWith(";")) _connectionString += ";";
_connectionString += "Connect Timeout=3;";
}
}
catch (Exception ex)
{
_connectionString = string.Empty;
System.Diagnostics.Debug.WriteLine("설정 로드 중 예외 발생: " + ex.Message);
}
}
public bool CheckConnection()
{
if (_offlineMode)
return true;
if (string.IsNullOrWhiteSpace(_connectionString))
return false;
try
{
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
conn.Open();
return true;
}
}
catch
{
return false;
}
}
public Product GetProductBySerial(string serial)
{
if (string.IsNullOrEmpty(serial))
return null;
string searchSerial = serial.Trim().ToUpper();
// 오프라인 모드인 경우 데이터베이스가 없으므로 항상 이전 공정 통과(PASS)로 가정하여 가상 데이터 반환
if (_offlineMode)
{
System.Diagnostics.Debug.WriteLine("[OfflineMode] 제품 조회 우회 (오프라인 모드 활성화). S/N: " + searchSerial);
return new Product(searchSerial, "PASS", "PASS", "PASS");
}
try
{
// 이전 3개 공정 테이블(Housing_Assembly, Cal_EOL_Result, EOL)에서 IC_SN 기준으로 Result 각각 조회
// Housing_Assembly 테이블에서 PCB_Barcode 가져옴
string query = @"
SELECT
(SELECT TOP 1 [Result] FROM [dbo].[Housing_Assembly] WHERE UPPER([IC_SN]) = @Serial) AS HousingResult,
(SELECT TOP 1 [Final_Result] FROM [dbo].[Cal_EOL_Result] WHERE UPPER([IC_SN]) = @Serial) AS CalResult,
(SELECT TOP 1 [Result] FROM [dbo].[EOL] WHERE UPPER([IC_SN]) = @Serial) AS EolResult,
(SELECT TOP 1 [PCB_Barcode] FROM [dbo].[Housing_Assembly] WHERE UPPER([IC_SN]) = @Serial) AS PcbBarcode";
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
cmd.Parameters.AddWithValue("@Serial", searchSerial);
conn.Open();
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
// 데이터베이스 값이 NULL 이거나 빈 값일 경우 "대기"로 처리
string housingRes = reader["HousingResult"] != DBNull.Value ? reader["HousingResult"].ToString().Trim() : "대기";
string calRes = reader["CalResult"] != DBNull.Value ? reader["CalResult"].ToString().Trim() : "대기";
string eolRes = reader["EolResult"] != DBNull.Value ? reader["EolResult"].ToString().Trim() : "대기";
string pcbBarcode = reader["PcbBarcode"] != DBNull.Value ? reader["PcbBarcode"].ToString().Trim() : string.Empty;
// 세 공정 중 하나라도 데이터 흔적이 있는 경우 Product 객체 생성 반환
if (housingRes != "대기" || calRes != "대기" || eolRes != "대기")
{
return new Product(searchSerial, housingRes, calRes, eolRes)
{
PcbBarcode = pcbBarcode
};
}
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[DB GetProduct] 이전 공정 결과 조회 중 에러: " + ex.Message);
}
return null;
}
/// <summary>
/// 최종 성능 검출 값을 데이터베이스에 저장합니다.
/// 전압과 전류 수치는 3,2 포맷(DECIMAL(3, 2) 등)에 대응하기 위해 소수점 이하 2자리로 반올림하여 처리합니다.
/// </summary>
public bool SaveProductResult(
string serial,
string maker,
string model,
string variant1,
string variant2,
string operatorId,
string line,
string lotNo,
string jigNo,
double voltage,
double current,
double resistance,
string result,
DateTime startTime,
DateTime endTime,
string markedSerial,
string pcbBarcode = null,
string drawingNo = "",
string hkmcNo = "")
{
// 전압과 전류, 저항을 소수점 이하 2자리(3,2 포맷 규격)로 반올림 처리
decimal roundedVoltage = Math.Round((decimal)voltage, 2);
decimal roundedCurrent = Math.Round((decimal)current, 2);
decimal roundedResistance = Math.Round((decimal)resistance, 3);
// IC_SN 및 PCB_Barcode 컬럼 규격(NVARCHAR(30), NOT NULL) 방어 처리
if (string.IsNullOrWhiteSpace(serial))
{
serial = "UNKNOWN_" + DateTime.Now.ToString("HHmmss");
}
if (serial.Length > 30)
{
serial = serial.Substring(0, 30);
}
// PCB_Barcode: Housing_Assembly 테이블에서 가져온 값 우선 사용, 없으면 IC_SN으로 대체
string safePcbBarcode = !string.IsNullOrWhiteSpace(pcbBarcode) ? pcbBarcode : serial;
if (safePcbBarcode.Length > 30) safePcbBarcode = safePcbBarcode.Substring(0, 30);
string safeProcessLabel = "Marking";
string safeProcessLabelScan = string.IsNullOrEmpty(result) ? "OK" : (result.Length > 5 ? result.Substring(0, 5) : result);
string safeResult = string.IsNullOrEmpty(result) ? "NG" : (result.Length > 5 ? result.Substring(0, 5) : result);
string safeErrorCode = (safeResult == "OK" || safeResult == "PASS") ? "0" : "E01";
// DB 컬럼 길이 한계에 맞춰서 문자열 안전하게 자르기 (Truncate)
string safeMaker = string.IsNullOrEmpty(maker) ? "" : (maker.Length > 10 ? maker.Substring(0, 10) : maker);
string safeModel = string.IsNullOrEmpty(model) ? "" : (model.Length > 10 ? model.Substring(0, 10) : model);
string safeVariant1 = string.IsNullOrEmpty(variant1) ? "" : (variant1.Length > 10 ? variant1.Substring(0, 10) : variant1);
string safeVariant2 = string.IsNullOrEmpty(variant2) ? "" : (variant2.Length > 10 ? variant2.Substring(0, 10) : variant2);
string safeOperator = string.IsNullOrEmpty(operatorId) ? "" : (operatorId.Length > 10 ? operatorId.Substring(0, 10) : operatorId);
string safeLine = string.IsNullOrEmpty(line) ? "" : (line.Length > 10 ? line.Substring(0, 10) : line);
string safeLotNo = string.IsNullOrEmpty(lotNo) ? "" : (lotNo.Length > 10 ? lotNo.Substring(0, 10) : lotNo);
string safeJigNo = string.IsNullOrEmpty(jigNo) ? "" : (jigNo.Length > 10 ? jigNo.Substring(0, 10) : jigNo);
DateTime prodDate = endTime;
// QR Code 조합 포맷 생성 ([IC S/N];[도면품번];[HKMC품번];[제품 S/N];) (Marking_QR: NVARCHAR(60))
string markingQr = string.Format("{0};{1};{2};{3};", serial, drawingNo ?? "", hkmcNo ?? "", markedSerial);
if (markingQr.Length > 60)
{
markingQr = markingQr.Substring(0, 60);
}
// 오프라인 모드인 경우 실제 DB 저장을 생략하고 정상 통과 처리
if (_offlineMode)
{
System.Diagnostics.Debug.WriteLine(string.Format(
"[OfflineMode] DB 저장 생략 (오프라인 모드 활성화) S/N: {0} | 각인번호: {1} | QR: {2} | Maker: {3} | Model: {4} | Line: {5} | LotNo: {6} | Vol_1: {7:F2}V | Cur_1: {8:F2}mA | Res_1: {9:F3}kΩ | 결과: {10} | 시작:{11} | 종료:{12}",
serial, markedSerial, markingQr, safeMaker, safeModel, safeLine, safeLotNo, roundedVoltage, roundedCurrent, roundedResistance, result,
startTime.ToString("HH:mm:ss"), endTime.ToString("HH:mm:ss")));
return true;
}
// 실제 MSSQL DB 적재 실행 (동일 IC_SN 재검사 시 데이터가 덮어씌워지도록 MERGE 문 도입)
try
{
string query = @"MERGE INTO [dbo].[Marking] AS target
USING (SELECT @Serial AS IC_SN) AS source
ON (target.[IC_SN] = source.IC_SN)
WHEN MATCHED THEN
UPDATE SET
[Maker] = @Maker,
[Model] = @Model,
[Variant_1] = @Var1,
[Variant_2] = @Var2,
[Operator] = @Operator,
[Production_Date] = @ProdDate,
[Line] = @Line,
[Lot_No] = @LotNo,
[JIG_No] = @JigNo,
[PCB_Barcode] = @PcbBarcode,
[Process_Label] = @ProcessLabel,
[Process_Label_Scan] = @ProcessLabelScan,
[PT_Vol_1] = @Vol1,
[PT_Current_1] = @Cur1,
[Marking_QR] = @Qr,
[Result] = @Result,
[Error_Code] = @ErrorCode
WHEN NOT MATCHED THEN
INSERT ([Maker], [Model], [Variant_1], [Variant_2], [Operator], [Production_Date], [Line], [Lot_No], [JIG_No], [IC_SN], [PCB_Barcode], [Process_Label], [Process_Label_Scan], [PT_Vol_1], [PT_Current_1], [Marking_QR], [Result], [Error_Code])
VALUES (@Maker, @Model, @Var1, @Var2, @Operator, @ProdDate, @Line, @LotNo, @JigNo, @Serial, @PcbBarcode, @ProcessLabel, @ProcessLabelScan, @Vol1, @Cur1, @Qr, @Result, @ErrorCode);";
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
cmd.Parameters.AddWithValue("@Maker", safeMaker);
cmd.Parameters.AddWithValue("@Model", safeModel);
cmd.Parameters.AddWithValue("@Var1", safeVariant1);
cmd.Parameters.AddWithValue("@Var2", safeVariant2);
cmd.Parameters.AddWithValue("@Operator", safeOperator);
cmd.Parameters.AddWithValue("@ProdDate", prodDate);
cmd.Parameters.AddWithValue("@Line", safeLine);
cmd.Parameters.AddWithValue("@LotNo", safeLotNo);
cmd.Parameters.AddWithValue("@JigNo", safeJigNo);
cmd.Parameters.AddWithValue("@Serial", serial);
cmd.Parameters.AddWithValue("@PcbBarcode", safePcbBarcode);
cmd.Parameters.AddWithValue("@ProcessLabel", safeProcessLabel);
cmd.Parameters.AddWithValue("@ProcessLabelScan", safeProcessLabelScan);
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@Vol1", System.Data.SqlDbType.Decimal) { Precision = 5, Scale = 2, Value = roundedVoltage });
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@Cur1", System.Data.SqlDbType.Decimal) { Precision = 5, Scale = 2, Value = roundedCurrent });
cmd.Parameters.AddWithValue("@Qr", markingQr);
cmd.Parameters.AddWithValue("@Result", safeResult);
cmd.Parameters.AddWithValue("@ErrorCode", safeErrorCode);
conn.Open();
cmd.ExecuteNonQuery();
}
}
// 성공 로그 출력 및 true 반환
System.Diagnostics.Debug.WriteLine(string.Format(
"[DB 저장 로그] S/N: {0} | 각인번호: {1} | QR: {2} | Maker: {3} | Model: {4} | Line: {5} | LotNo: {6} | Vol_1: {7:F2}V | Cur_1: {8:F2}mA | Res_1: {9:F3}kΩ | 결과: {10} | 시작:{11} | 종료:{12}",
serial, markedSerial, markingQr, safeMaker, safeModel, safeLine, safeLotNo, roundedVoltage, roundedCurrent, roundedResistance, result,
startTime.ToString("HH:mm:ss"), endTime.ToString("HH:mm:ss")));
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[DB SaveResult] DB 저장 실패 (메시지: " + ex.Message + ")");
throw; // 예외 원인을 상위로 전파하여 UI 상태창에 구체적인 오류 원인(ex.Message)이 그대로 노출되도록 함
}
}
/// <summary>
/// 지정한 LotNo로 기존에 저장 완료(PASS)된 제품들의 개수를 카운트하여 다음 각인 일련번호를 구합니다.
/// </summary>
public int GetNextMarkingSequence(string lotNo)
{
if (_offlineMode)
{
return 1;
}
try
{
string query = "SELECT COUNT(*) FROM [dbo].[Marking] WHERE [Lot_No] = @LotNo AND ([Result] = 'PASS' OR [Result] = 'OK')";
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
cmd.Parameters.AddWithValue("@LotNo", lotNo);
conn.Open();
int count = (int)cmd.ExecuteScalar();
return count + 1;
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[DB GetNextMarkingSequence] 에러: " + ex.Message);
throw; // 예외를 상위로 전파하여 DB 장애 시 중복 순번 발급 방지
}
}
}
}

417
marking_gui/Services/EquipmentMeasurementService.cs

@ -0,0 +1,417 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace marking_gui.Services
{
/// <summary>
/// DMM(전압+저항, .10) + DMM(전류, .11) + Power Supply(출력제어, .12) SCPI 계측기 연동 서비스.
/// Setting.ini 의 [DMM] / [CurrentDMM] / [PowerSupply] 섹션에서 연결 설정을 읽습니다.
/// </summary>
public sealed class EquipmentMeasurementService : IDisposable
{
// ── 설정 내부 데이터 클래스 ──────────────────────────────────────
private sealed class ScpiChannelSettings
{
public string Connection { get; set; } = string.Empty;
public string Host { get; set; } = string.Empty;
public int Port { get; set; } = 5025;
public string IdnMatch { get; set; } = string.Empty;
public string PortName { get; set; } = string.Empty;
public int BaudRate { get; set; } = 9600;
public string Terminator { get; set; } = "LF";
public string SetupCommand { get; set; } = string.Empty;
public string CleanupCommand { get; set; } = string.Empty;
public string ReadCommand { get; set; } = string.Empty;
public bool IsDisabled =>
string.IsNullOrWhiteSpace(Connection) ||
(Connection.ToUpperInvariant() is not ("LAN" or "TCP" or "ETHERNET" or "SERIAL" or "COM") &&
string.IsNullOrWhiteSpace(Host));
public string[] GetIdnMatches() =>
string.IsNullOrWhiteSpace(IdnMatch)
? Array.Empty<string>()
: IdnMatch.Split(',', StringSplitOptions.RemoveEmptyEntries);
public char GetTerminator() =>
Terminator.ToUpperInvariant() == "CR" ? '\r' : '\n';
}
// ── 필드 ────────────────────────────────────────────────────────
private int _timeoutMs = 5000;
private int _settleMs = 300;
private string _preBoardCommand = string.Empty;
private int _preBoardDelayMs = 2000;
private string _logoutCommand = string.Empty;
private string _resistanceReadCommand = "MEAS:RES?";
private ScpiChannelSettings _voltageDmm; // 전압 측정 전용 DMM 계측기
private ScpiChannelSettings _currentDmm; // 전류 측정 전용 DMM 계측기
private ScpiChannelSettings _resistanceDmm; // 저항 측정 전용 DMM 계측기
private ScpiChannelSettings _psu; // 출력 제어 Power Supply (전원 및 상시전원 인가)
private IScpiClient _voltageDmmClient;
private IScpiClient _currentDmmClient;
private IScpiClient _resistanceDmmClient;
private IScpiClient _psuClient;
private bool _disposed;
// ── 생성자 ──────────────────────────────────────────────────────
public EquipmentMeasurementService(string iniPath)
{
LoadSettings(iniPath);
}
private IScpiClient GetPsuClient()
{
if (_psuClient == null && !_psu.IsDisabled)
{
_psuClient = CreateClient("PSU", _psu);
}
return _psuClient;
}
private IScpiClient GetVoltageClient()
{
if (_voltageDmmClient == null && !_voltageDmm.IsDisabled)
{
_voltageDmmClient = CreateClient("VOLTAGE_DMM", _voltageDmm);
}
return _voltageDmmClient;
}
private IScpiClient GetCurrentDmmClient()
{
if (_currentDmmClient == null && !_currentDmm.IsDisabled)
{
_currentDmmClient = CreateClient("CURRENT_DMM", _currentDmm);
}
return _currentDmmClient;
}
private IScpiClient GetResistanceDmmClient()
{
if (_resistanceDmmClient == null && !_resistanceDmm.IsDisabled)
{
_resistanceDmmClient = CreateClient("RESISTANCE_DMM", _resistanceDmm);
}
// 저항 전용 DMM이 비활성화되어 있는 경우, 전압 DMM 클라이언트를 대체 사용
return _resistanceDmmClient ?? GetVoltageClient();
}
// ── 설정 로드 ────────────────────────────────────────────────────
private void LoadSettings(string iniPath)
{
var values = ParseIni(iniPath);
_timeoutMs = GetInt(values, "Timeout", 5000);
_settleMs = GetInt(values, "SettleMilliseconds", 300);
_preBoardCommand = GetString(values, "PreBoardCommand", string.Empty);
_preBoardDelayMs = GetInt(values, "PreBoardDelayMilliseconds", 2000);
_logoutCommand = GetString(values, "LogoutCommand", string.Empty);
_resistanceReadCommand = GetString(values, "ResistanceReadCommand", "MEAS:RES?");
_voltageDmm = LoadChannel(values, "Voltage", "MEAS:VOLT:DC?", "34460,34461,34465,34470");
_currentDmm = LoadChannel(values, "CurrentDMM", "MEAS:CURR:DC?", "34460,34461,34465,34470");
_resistanceDmm = LoadChannel(values, "ResistanceDMM", "MEAS:RES?", "34460,34461,34465,34470");
_psu = LoadChannel(values, "Current", string.Empty, "E362");
}
private static ScpiChannelSettings LoadChannel(
Dictionary<string, string> values,
string prefix,
string defaultReadCommand,
string defaultIdnMatch)
{
return new ScpiChannelSettings
{
Connection = GetString(values, prefix + "Connection", string.Empty),
Host = GetString(values, prefix + "Host", string.Empty),
Port = GetInt(values, prefix + "Port", 5025),
IdnMatch = GetString(values, prefix + "IdnMatch", defaultIdnMatch),
PortName = GetString(values, prefix + "PortName", string.Empty),
BaudRate = GetInt(values, prefix + "BaudRate", 9600),
Terminator = GetString(values, prefix + "Terminator", "LF"),
SetupCommand = GetString(values, prefix + "SetupCommand", string.Empty),
CleanupCommand = GetString(values, prefix + "CleanupCommand", string.Empty),
ReadCommand = GetString(values, prefix + "ReadCommand", defaultReadCommand),
};
}
// ── Public API ───────────────────────────────────────────────────
/// <summary>
/// 전압 DMM + 저항 DMM + 전류 DMM(또는 PSU) 독립 계측기 3-장비 측정 시퀀스.
/// </summary>
public async Task<(double Voltage, double Current, double Resistance)> MeasureAsync(Action<double?, double?, double?> onUpdate = null)
{
var log = new StringBuilder();
IScpiClient psuClient = GetPsuClient();
IScpiClient voltageClient = GetVoltageClient();
IScpiClient resistanceClient = GetResistanceDmmClient();
IScpiClient currentDmmClient = GetCurrentDmmClient();
try
{
if (psuClient == null)
throw new InvalidOperationException("PSU 파워서플라이 장치가 설정되지 않았거나 비활성화 상태입니다.");
if (voltageClient == null)
throw new InvalidOperationException("전압 측정 DMM 장치가 설정되지 않았거나 비활성화 상태입니다.");
// 1. 파워서플라이(PSU) CH1 출력 ON
log.AppendLine($"> PSU CONNECT ({_psu.Host})");
string psuSetup = !string.IsNullOrWhiteSpace(_psu.SetupCommand) ? _psu.SetupCommand : "OUTP ON, (@1)";
log.AppendLine($"> PSU SETUP: {psuSetup}");
await SendCommandListAsync(psuClient, psuSetup);
// settle 지연 대기
await Task.Delay(_settleMs).ConfigureAwait(false);
// 2. 전압 측정 (전압 전용 계측기에서 MEAS:VOLT:DC? 로 측정)
log.AppendLine($"> 전압 DMM CONNECT ({_voltageDmm.Host})");
if (!string.IsNullOrWhiteSpace(_voltageDmm.SetupCommand))
{
log.AppendLine($"> 전압 DMM SETUP: {_voltageDmm.SetupCommand}");
await SendCommandListAsync(voltageClient, _voltageDmm.SetupCommand);
}
string voltageReadCmd = !string.IsNullOrWhiteSpace(_voltageDmm.ReadCommand) ? _voltageDmm.ReadCommand : "MEAS:VOLT:DC?";
log.AppendLine($"> 전압 DMM SEND: {voltageReadCmd}");
string vResponse = await voltageClient.QueryAsync(voltageReadCmd);
log.AppendLine($" 응답: {vResponse.Trim()}");
double voltage = (double)ExtractFirstDecimal(vResponse, "VOLTAGE");
// 실시간 전압 업데이트
onUpdate?.Invoke(voltage, null, null);
// 3. 저항 측정 (저항 전용 계측기에서 MEAS:RES? 로 측정)
log.AppendLine($"> 저항 DMM CONNECT ({_resistanceDmm.Host ?? _voltageDmm.Host})");
if (resistanceClient != null && !string.IsNullOrWhiteSpace(_resistanceDmm.SetupCommand))
{
log.AppendLine($"> 저항 DMM SETUP: {_resistanceDmm.SetupCommand}");
await SendCommandListAsync(resistanceClient, _resistanceDmm.SetupCommand);
}
string resReadCmd = !string.IsNullOrWhiteSpace(_resistanceDmm.ReadCommand) ? _resistanceDmm.ReadCommand : _resistanceReadCommand;
log.AppendLine($"> 저항 DMM SEND: {resReadCmd}");
string rResponse = await (resistanceClient ?? voltageClient).QueryAsync(resReadCmd);
log.AppendLine($" 응답: {rResponse.Trim()}");
double rawResistance = (double)ExtractFirstDecimal(rResponse, "RESISTANCE");
double resistance = rawResistance / 1000.0; // Ohms -> kOhms 변환 (kΩ 단위 표출)
// 실시간 저항 업데이트
onUpdate?.Invoke(voltage, null, resistance);
// 4. 전류 측정 (파워서플라이 CH1에서 MEAS:CURR? (@1) 로 측정)
log.AppendLine($"> PSU 전류 측정 CONNECT ({_psu.Host})");
string currentReadCmd = !string.IsNullOrWhiteSpace(_psu.ReadCommand) ? _psu.ReadCommand : "MEAS:CURR? (@1)";
log.AppendLine($"> PSU SEND: {currentReadCmd}");
string aResponse = await psuClient.QueryAsync(currentReadCmd);
log.AppendLine($" 응답: {aResponse.Trim()}");
double rawCurrent = (double)ExtractFirstDecimal(aResponse, "CURRENT");
double current = rawCurrent * 1000.0; // Amperes -> mA 변환
// 실시간 전류 및 최종 업데이트
onUpdate?.Invoke(voltage, current, resistance);
return (voltage, current, resistance);
}
finally
{
// 5. 파워서플라이 CH1 출력 OFF
if (psuClient != null)
{
try
{
string psuCleanup = !string.IsNullOrWhiteSpace(_psu.CleanupCommand) ? _psu.CleanupCommand : "OUTP OFF, (@1)";
log.AppendLine($"> PSU CLEANUP: {psuCleanup}");
await SendCommandListAsync(psuClient, psuCleanup);
}
catch (Exception ex)
{
log.AppendLine($"> PSU OUTP OFF FAILED: {ex.Message}");
}
}
LoggerService.Info("[Equipment] 독립 계측기 측정 시퀀스 로그:\n" + log);
}
}
/// <summary>검사 전 보드 초기화 커맨드 실행 (PreBoardCommand).</summary>
public async Task RunPreBoardCommandAsync()
{
if (_psu.IsDisabled)
{
LoggerService.Warn("[Equipment] PreBoard 커맨드 실행 불가: Power Supply 설정이 비활성화되어 있거나 올바르지 않습니다.");
throw new InvalidOperationException("Power Supply 장치가 비활성화 상태이거나 [PowerSupply] 설정이 올바르지 않습니다.");
}
if (string.IsNullOrWhiteSpace(_preBoardCommand))
{
LoggerService.Warn("[Equipment] PreBoard 커맨드 실행 불가: Setting.ini 에 PreBoardCommand 설정이 없습니다.");
throw new InvalidOperationException("Setting.ini [Equipment] 섹션의 PreBoardCommand 설정이 비어있습니다.");
}
var client = GetPsuClient();
if (client == null)
{
LoggerService.Error("[Equipment] PreBoard 커맨드 실행 불가: Power Supply 클라이언트 생성 실패");
throw new InvalidOperationException("Power Supply 클라이언트를 생성할 수 없습니다 (연동 설식을 확인하세요).");
}
LoggerService.Info($"[Equipment] PreBoard 커맨드 실행 시작: {_preBoardCommand}");
await SendCommandListAsync(client, _preBoardCommand);
await Task.Delay(_preBoardDelayMs).ConfigureAwait(false);
LoggerService.Info("[Equipment] PreBoard 커맨드(상시전원 ON) 실행 완료");
}
/// <summary>로그아웃 시 보드 전원 차단 (LogoutCommand).</summary>
public async Task RunLogoutCommandAsync()
{
if (_psu.IsDisabled || string.IsNullOrWhiteSpace(_logoutCommand))
{
LoggerService.Warn("[Equipment] Logout 커맨드 실행 스킵: Power Supply 비활성화 상태이거나 LogoutCommand 설정이 없습니다.");
return;
}
var client = GetPsuClient();
if (client == null)
{
LoggerService.Error("[Equipment] Logout 커맨드 실행 실패: Power Supply 클라이언트 생성 불가");
return;
}
LoggerService.Info($"[Equipment] Logout 커맨드 실행: {_logoutCommand}");
await SendCommandListAsync(client, _logoutCommand);
}
// ── 내부 측정 헬퍼 ──────────────────────────────────────────────
private async Task<double> ReadChannelAsync(
string label,
ScpiChannelSettings ch,
StringBuilder log)
{
if (ch.IsDisabled)
{
log.AppendLine($"> {label} SKIP: Setting.ini 에서 비활성화됨");
return 0.0;
}
if (string.IsNullOrWhiteSpace(ch.ReadCommand))
throw new InvalidOperationException($"Setting.ini [{label}] ReadCommand 값을 확인하세요.");
var client = label == "VOLTAGE" ? GetVoltageClient() : GetPsuClient();
if (client == null) throw new InvalidOperationException($"{label} 장치 클라이언트를 생성할 수 없습니다.");
return (double)await ReadChannelWithClientAsync(label, ch, client, log);
}
private static async Task<decimal> ReadChannelWithClientAsync(
string label,
ScpiChannelSettings ch,
IScpiClient client,
StringBuilder log,
bool skipSetupCommand = false)
{
log.AppendLine($"> {label} CONNECT OK");
if (!skipSetupCommand && !string.IsNullOrWhiteSpace(ch.SetupCommand))
{
log.AppendLine($"> {label} SETUP: {ch.SetupCommand}");
await SendCommandListAsync(client, ch.SetupCommand);
}
log.AppendLine($"> {label} READ: {ch.ReadCommand}");
string response = await client.QueryAsync(ch.ReadCommand);
log.AppendLine($" 응답: {response.Trim()}");
return ExtractFirstDecimal(response, label);
}
private IScpiClient CreateClient(string label, ScpiChannelSettings ch)
{
string conn = ch.Connection.ToUpperInvariant();
if (conn is "LAN" or "TCP" or "ETHERNET")
{
return new TcpScpiClient(ch.Host, ch.Port, _timeoutMs, ch.GetIdnMatches(), ch.GetTerminator());
}
throw new InvalidOperationException(
$"{label} 장비 연결 방식 '{ch.Connection}' 은 지원되지 않습니다. LAN/TCP 만 지원됩니다.");
}
private static async Task SendCommandListAsync(IScpiClient client, string commands)
{
foreach (var cmd in commands.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
string trimmed = cmd.Trim();
if (string.IsNullOrWhiteSpace(trimmed)) continue;
// (@2) 채널에 OUTP OFF 명령은 보호 대상 — 실수로 보드 전원 차단 방지
if (Regex.IsMatch(trimmed, @"^\s*OUTP(?:UT)?\s+OFF\s*,?\s*\(@2\)\s*$", RegexOptions.IgnoreCase))
continue;
await client.SendAsync(trimmed);
await Task.Delay(100).ConfigureAwait(false);
}
}
private static decimal ExtractFirstDecimal(string response, string label)
{
if (string.IsNullOrWhiteSpace(response))
throw new InvalidOperationException($"[{label}] 계측기 응답이 비어있습니다 (응답 수신 실패).");
var match = Regex.Match(response, @"[-+]?\d+(?:\.\d+)?(?:[Ee][-+]?\d+)?");
if (!match.Success)
throw new InvalidOperationException($"[{label}] 수치 파싱 실패. 원본 응답: [{response.Trim()}]");
if (!decimal.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal val))
throw new InvalidOperationException($"[{label}] 수치 변환 실패 (값: '{match.Value}'). 원본 응답: [{response.Trim()}]");
return val;
}
// ── INI 파서 ────────────────────────────────────────────────────
private static Dictionary<string, string> ParseIni(string path)
{
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (!File.Exists(path)) return result;
foreach (var raw in File.ReadAllLines(path))
{
string line = raw.Trim();
if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("[")) continue;
int eq = line.IndexOf('=');
if (eq <= 0) continue;
string key = line.Substring(0, eq).Trim();
string value = line.Substring(eq + 1).Trim();
result[key] = value;
}
return result;
}
private static string GetString(Dictionary<string, string> d, string key, string def)
=> d.TryGetValue(key, out var v) && !string.IsNullOrWhiteSpace(v) ? v : def;
private static int GetInt(Dictionary<string, string> d, string key, int def)
=> d.TryGetValue(key, out var v) && int.TryParse(v, out int n) ? n : def;
// ── IDisposable ──────────────────────────────────────────────────
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try { _voltageDmmClient?.Dispose(); } catch { }
try { _currentDmmClient?.Dispose(); } catch { }
try { _resistanceDmmClient?.Dispose(); } catch { }
try { _psuClient?.Dispose(); } catch { }
_voltageDmmClient = null;
_currentDmmClient = null;
_resistanceDmmClient = null;
_psuClient = null;
}
}
}

336
marking_gui/Services/EzdMarkingService.cs

@ -0,0 +1,336 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using marking_gui.Models;
namespace marking_gui.Services
{
public class EzdMarkingService : IMarkingService
{
private bool _isInitialized = false;
private IntPtr _markingHandle = IntPtr.Zero; // 마킹 장치 기기 핸들 포인터
private string _ezdFolderPath = string.Empty;
private MarkingConfigService _config;
private string _lastInitErrorMessage = "초기화가 진행되지 않았습니다.";
public string LastInitErrorMessage => _lastInitErrorMessage;
// ==========================================
// MarkEzdWrapper.dll P/Invoke 선언부
// ==========================================
[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr InitMarkEzd();
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int ChangeTextByNameW(IntPtr hHandle, string strTextName, string strTextNew);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int ChangeTextByNameA(IntPtr hHandle, string strTextName, string strTextNew);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "GetTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int GetTextByNameW(IntPtr hHandle, string strTextName, System.Text.StringBuilder strText);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "GetTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int GetTextByNameA(IntPtr hHandle, string strTextName, System.Text.StringBuilder strText);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)]
private static extern int Mark(IntPtr hHandle);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ReadPort", CallingConvention = CallingConvention.Cdecl)]
private static extern int ReadPort(IntPtr hHandle, ref ushort Data);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "WritePort", CallingConvention = CallingConvention.Cdecl)]
private static extern int WritePort(IntPtr hHandle, ushort Data);
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)]
private static extern int Close(IntPtr hHandle);
public bool Initialize(string configPath, IntPtr windowHandle)
{
try
{
_lastInitErrorMessage = string.Empty;
_config = new MarkingConfigService();
_ezdFolderPath = _config.EzdFolderPath;
if (string.IsNullOrEmpty(_ezdFolderPath))
{
_lastInitErrorMessage = "Marking.ini 내 EzdFolderPath 경로가 비어 있습니다.";
_isInitialized = false;
return false;
}
if (!Directory.Exists(_ezdFolderPath))
{
_lastInitErrorMessage = string.Format("EzCad 폴더가 존재하지 않습니다: {0} (Marking.ini 설정 확인 필요)", _ezdFolderPath);
_isInitialized = false;
return false;
}
System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 LMC 초기화 시도. EzCadPath: " + _ezdFolderPath + ", WindowHandle: " + windowHandle);
// 1. InitMarkEzd() 호출로 디바이스 전역 제어 핸들 획득
_markingHandle = InitMarkEzd();
System.Diagnostics.Debug.WriteLine(string.Format("[EzdMarking] InitMarkEzd() 완료. 생성된 핸들 포인터: 0x{0:X}", _markingHandle.ToInt64()));
if (_markingHandle == IntPtr.Zero)
{
_isInitialized = false;
_lastInitErrorMessage = "InitMarkEzd() 실행 결과 생성된 핸들 포인터가 Zero(Null)입니다. 마킹 보드 및 드라이버 연결 상태를 점검하세요.";
return false;
}
// 2. 획득된 핸들을 이용해 Init() 장치 매핑 수행
int result = Init(_markingHandle, _ezdFolderPath, windowHandle);
if (result == 0) // 0 이 성공 코드
{
_isInitialized = true;
System.Diagnostics.Debug.WriteLine("[EzdMarking] 실서버 LMC 컨트롤 보드 장치 연결 및 초기화 성공.");
}
else
{
_isInitialized = false;
_lastInitErrorMessage = string.Format("LMC 컨트롤 카드 통신 실패 (Init 에러코드: {0})", result);
System.Diagnostics.Debug.WriteLine("[EzdMarking] LMC 장치 연결 실패. 에러 코드: " + result);
try { Close(_markingHandle); } catch { }
_markingHandle = IntPtr.Zero;
}
return _isInitialized;
}
catch (DllNotFoundException ex)
{
_isInitialized = false;
_lastInitErrorMessage = "MarkEzdWrapper.dll 또는 하위 C++ DLL 로드 실패 (x86/32비트 아키텍처 타겟팅 컴파일 여부 확인 필요)";
System.Diagnostics.Debug.WriteLine("[EzdMarking] MarkEzdWrapper.dll 또는 하위 DLL(Lmc1.dll 등)을 로드할 수 없습니다. 실행 폴더에 복사했는지 확인하세요. 상세: " + ex.Message);
return false;
}
catch (Exception ex)
{
_isInitialized = false;
_lastInitErrorMessage = "마킹 컨트롤 보드 연동 예외 발생: " + ex.Message;
System.Diagnostics.Debug.WriteLine("[EzdMarking] 초기화 예외 발생: " + ex.Message);
return false;
}
}
[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]
[System.Security.SecurityCritical]
public MarkingResult RequestMarking(MarkingRequest request)
{
if (!_isInitialized || _markingHandle == IntPtr.Zero)
{
return new MarkingResult
{
Success = false,
ErrorCode = -10,
ErrorMessage = "LMC 마킹 디바이스가 연결 및 초기화되지 않았습니다. 상세 원인: " + _lastInitErrorMessage,
MarkedAt = DateTime.Now
};
}
try
{
// 1. 도안 파일 경로 확인
string ezdFile = request.EzdFileName;
string ezdPath = ezdFile;
if (!File.Exists(ezdPath))
{
ezdPath = Path.Combine(_ezdFolderPath, ezdFile);
}
if (!File.Exists(ezdPath))
{
ezdPath = Path.Combine(_ezdFolderPath, "EasyCfg", ezdFile);
}
if (!File.Exists(ezdPath))
{
// 최종 확인 실패 시 에러
return new MarkingResult
{
Success = false,
ErrorCode = -11,
ErrorMessage = string.Format("도안 파일(.ezd)이 존재하지 않습니다: {0}", ezdFile),
MarkedAt = DateTime.Now
};
}
System.Diagnostics.Debug.WriteLine("[EzdMarking] 도안 파일 로드 시작: " + ezdPath);
string absoluteEzdPath = Path.GetFullPath(ezdPath);
int loadResult = LoadEzdFile(_markingHandle, absoluteEzdPath); // 획득한 핸들 전달
if (loadResult != 0)
{
return new MarkingResult
{
Success = false,
ErrorCode = loadResult,
ErrorMessage = string.Format("도안 파일(.ezd) 로드 실패. (경로: {0}, LoadEzdFile 에러코드: {1})", absoluteEzdPath, loadResult),
MarkedAt = DateTime.Now
};
}
// 1-1. 도안 파일(.ezd) 내부의 고정 텍스트 오브젝트(도면품번, HKMC 품번)를 직접 읽어오기
string ezdDrawingNo = GetTextFromEzd(_config.DrawingNoTag);
string ezdHkmcNo = GetTextFromEzd(_config.HkmcNoTag);
if (!string.IsNullOrWhiteSpace(ezdDrawingNo))
{
request.DrawingNo = ezdDrawingNo;
}
if (!string.IsNullOrWhiteSpace(ezdHkmcNo))
{
request.HkmcNo = ezdHkmcNo;
}
// EZD 고정값 기반으로 QR 코드 데이터 문자열 최종 재조합 ([IC S/N];[도면품번];[HKMC품번];[제품 S/N];)
string icSerial = string.IsNullOrWhiteSpace(request.IcSerial) ? request.Serial : request.IcSerial;
request.QrString = string.Format("{0};{1};{2};{3};", icSerial, request.DrawingNo, request.HkmcNo, request.Serial);
// 2. 도안 내 변수 태그 치환 (사양서 4대 태그: S/N, DMC, 도면품번, HKMC품번)
ChangeTextInEzd(_config.SerialTag, request.Serial);
ChangeTextInEzd(_config.QrCodeTag, request.QrString);
ChangeTextInEzd(_config.DrawingNoTag, request.DrawingNo);
ChangeTextInEzd(_config.HkmcNoTag, request.HkmcNo);
System.Diagnostics.Debug.WriteLine(string.Format(
"[EzdMarking] 도안 텍스트 치환 완료. Serial:{0}, LotNo:{1}, Product:{2}, Drawing:{3}, Hkmc:{4}, Qr:{5}",
request.Serial, request.LotNo, request.ProductNo, request.DrawingNo, request.HkmcNo, request.QrString));
// 3. 레이저 마킹 시작 (획득한 핸들 전달)
System.Diagnostics.Debug.WriteLine("[EzdMarking] 레이저 마킹 기계 동작 시작...");
int markResult = Mark(_markingHandle);
if (markResult == 0)
{
System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 동작 완료 (성공)");
return new MarkingResult
{
Success = true,
ErrorCode = 0,
ErrorMessage = "마킹 완료",
MarkedAt = DateTime.Now
};
}
else
{
System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 동작 에러 발생: " + markResult);
return new MarkingResult
{
Success = false,
ErrorCode = markResult,
ErrorMessage = string.Format("레이저 각인 하드웨어 동작 에러 (에러코드: {0})", markResult),
MarkedAt = DateTime.Now
};
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[EzdMarking] RequestMarking 실행 중 예외 발생: " + ex.Message);
return new MarkingResult
{
Success = false,
ErrorCode = -99,
ErrorMessage = "마킹 처리 중 예외 발생: " + ex.Message,
MarkedAt = DateTime.Now
};
}
}
private bool _disposed = false;
private string GetTextFromEzd(string tagName)
{
if (string.IsNullOrWhiteSpace(tagName) || _markingHandle == IntPtr.Zero)
return string.Empty;
try
{
// 1차: Unicode (UTF-16) 시도
var sbW = new System.Text.StringBuilder(512);
int resW = GetTextByNameW(_markingHandle, tagName, sbW);
if (resW == 0 && sbW.Length > 0)
{
return sbW.ToString().Trim();
}
// 2차: Ansi (CP949/EUC-KR) 시도 (C++ Wrapper가 ANSI인 경우 대비)
var sbA = new System.Text.StringBuilder(512);
int resA = GetTextByNameA(_markingHandle, tagName, sbA);
if (resA == 0 && sbA.Length > 0)
{
return sbA.ToString().Trim();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[EzdMarking] GetTextByName({tagName}) 읽기 예외: {ex.Message}");
}
return string.Empty;
}
private void ChangeTextInEzd(string tagName, string textNew)
{
if (string.IsNullOrWhiteSpace(tagName) || _markingHandle == IntPtr.Zero) return;
try
{
int resW = ChangeTextByNameW(_markingHandle, tagName, textNew);
if (resW != 0)
{
ChangeTextByNameA(_markingHandle, tagName, textNew);
}
}
catch
{
// Fallback
}
}
public void Shutdown()
{
try
{
if (_isInitialized && _markingHandle != IntPtr.Zero)
{
Close(_markingHandle);
_markingHandle = IntPtr.Zero;
_isInitialized = false;
System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 컨트롤 디바이스 정상 종료 완료.");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[EzdMarking] 디바이스 종료 중 예외: " + ex.Message);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
Shutdown();
_disposed = true;
}
}
~EzdMarkingService()
{
Dispose(false);
}
}
}

32
marking_gui/Services/IMarkingService.cs

@ -0,0 +1,32 @@
using System;
using marking_gui.Models;
namespace marking_gui.Services
{
public interface IMarkingService : IDisposable
{
/// <summary>
/// 마킹 서비스 및 하드웨어를 초기화합니다.
/// </summary>
/// <param name="configPath">설정 파일(Config.ini 등)의 경로</param>
/// <returns>초기화 성공 여부</returns>
bool Initialize(string configPath, IntPtr windowHandle);
/// <summary>
/// 지정한 매개변수로 마킹 작업을 요청합니다.
/// </summary>
/// <param name="request">마킹 파라미터 묶음</param>
/// <returns>마킹 처리 결과</returns>
MarkingResult RequestMarking(MarkingRequest request);
/// <summary>
/// 마지막 초기화 실패 시의 상세 에러 메시지
/// </summary>
string LastInitErrorMessage { get; }
/// <summary>
/// 마킹 드라이버 및 리소스를 정상적으로 해제합니다.
/// </summary>
void Shutdown();
}
}

17
marking_gui/Services/IScpiClient.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
namespace marking_gui.Services
{
/// <summary>
/// SCPI 계측기와 명령을 주고받는 클라이언트 인터페이스.
/// LAN(TCP), Serial 등 다양한 연결 방식에 대응합니다.
/// </summary>
public interface IScpiClient : System.IDisposable
{
/// <summary>계측기에 SCPI 명령을 전송합니다 (응답 없음).</summary>
Task SendAsync(string command);
/// <summary>계측기에 SCPI 명령을 전송하고 응답 문자열을 반환합니다.</summary>
Task<string> QueryAsync(string command);
}
}

96
marking_gui/Services/LoggerService.cs

@ -0,0 +1,96 @@
using System;
using System.IO;
namespace marking_gui.Services
{
public static class LoggerService
{
private static readonly string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
private static readonly object LockObj = new object();
static LoggerService()
{
try
{
if (!Directory.Exists(LogDirectory))
{
Directory.CreateDirectory(LogDirectory);
}
// 오래된 로그 정리 태스크 백그라운드 구동 (애플리케이션 시작 차단 방지)
System.Threading.Tasks.Task.Run(() => CleanUpOldLogs(30));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Log directory creation failed: " + ex.Message);
}
}
private static void CleanUpOldLogs(int retentionDays)
{
try
{
var dirInfo = new DirectoryInfo(LogDirectory);
if (!dirInfo.Exists) return;
DateTime threshold = DateTime.Now.AddDays(-retentionDays);
foreach (var file in dirInfo.GetFiles("*.log"))
{
try
{
if (file.LastWriteTime < threshold)
{
file.Delete();
System.Diagnostics.Debug.WriteLine($"[SRE Logger] 만료된 로그 파일 삭제 완료: {file.Name}");
}
}
catch (Exception fileEx)
{
System.Diagnostics.Debug.WriteLine($"[SRE Logger] 로그 파일 삭제 중 예외 (파일명: {file.Name}): {fileEx.Message}");
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[SRE Logger] 만료 로그 정리 중 예외 발생: " + ex.Message);
}
}
public static void Info(string message)
{
WriteLog("INFO", message);
}
public static void Warn(string message)
{
WriteLog("WARN", message);
}
public static void Error(string message, Exception ex = null)
{
string detail = message;
if (ex != null)
{
detail += string.Format("\n[Exception] {0}\n[StackTrace]\n{1}", ex.Message, ex.StackTrace);
}
WriteLog("ERROR", detail);
}
private static void WriteLog(string level, string message)
{
lock (LockObj)
{
try
{
string logFile = Path.Combine(LogDirectory, DateTime.Now.ToString("yyyy-MM-dd") + ".log");
string logLine = string.Format("[{0}] [{1}] {2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), level, message) + Environment.NewLine;
File.AppendAllText(logFile, logLine);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Writing log failed: " + ex.Message);
}
}
}
}
}

171
marking_gui/Services/LoginServicesStub.cs

@ -9,7 +9,51 @@ namespace Housing.Services
// 1. DatabaseSettings // 1. DatabaseSettings
public class DatabaseSettings public class DatabaseSettings
{ {
public string ConnectionString { get; set; } = string.Empty; public string Db_Server { get; set; } = string.Empty;
public string Db_Name { get; set; } = string.Empty;
public string Db_User { get; set; } = string.Empty;
public string Db_Password { get; set; } = string.Empty;
private string _rawConnectionString = string.Empty;
public string ConnectionString
{
get
{
string connStr;
if (!string.IsNullOrWhiteSpace(Db_Server) &&
!string.IsNullOrWhiteSpace(Db_Name) &&
!string.IsNullOrWhiteSpace(Db_User) &&
!string.IsNullOrWhiteSpace(Db_Password))
{
connStr = $"Server={Db_Server};Database={Db_Name};User Id={Db_User};Password={Db_Password};";
}
else
{
connStr = _rawConnectionString;
}
if (!string.IsNullOrWhiteSpace(connStr) && connStr.IndexOf("TrustServerCertificate", StringComparison.OrdinalIgnoreCase) < 0)
{
if (!connStr.EndsWith(";")) connStr += ";";
connStr += "TrustServerCertificate=True;";
}
if (!string.IsNullOrWhiteSpace(connStr) &&
connStr.IndexOf("Connect Timeout", StringComparison.OrdinalIgnoreCase) < 0 &&
connStr.IndexOf("Connection Timeout", StringComparison.OrdinalIgnoreCase) < 0)
{
if (!connStr.EndsWith(";")) connStr += ";";
connStr += "Connect Timeout=3;";
}
return connStr;
}
set
{
_rawConnectionString = value;
}
}
public static DatabaseSettings Load(string path) public static DatabaseSettings Load(string path)
{ {
@ -22,6 +66,12 @@ namespace Housing.Services
foreach (var line in lines) foreach (var line in lines)
{ {
var trimmed = line.Trim(); var trimmed = line.Trim();
if (trimmed.StartsWith(";") || trimmed.StartsWith("#"))
{
continue;
}
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase)) if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
{ {
var parts = trimmed.Split(new[] { '=' }, 2); var parts = trimmed.Split(new[] { '=' }, 2);
@ -30,6 +80,38 @@ namespace Housing.Services
settings.ConnectionString = parts[1].Trim(); settings.ConnectionString = parts[1].Trim();
} }
} }
else if (trimmed.StartsWith("Db_Server", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Server = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Name", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Name = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_User", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_User = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Password", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Password = parts[1].Trim();
}
}
} }
} }
} }
@ -273,6 +355,7 @@ namespace Housing.Services
{ {
using (var cmd = new System.Data.SqlClient.SqlCommand(_loginSettings.Procedure, conn)) using (var cmd = new System.Data.SqlClient.SqlCommand(_loginSettings.Procedure, conn))
{ {
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Operator", operatorId); cmd.Parameters.AddWithValue("@Operator", operatorId);
cmd.Parameters.AddWithValue("@Password", password); cmd.Parameters.AddWithValue("@Password", password);
@ -304,6 +387,7 @@ namespace Housing.Services
const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw"; const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw";
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn)) using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{ {
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
cmd.Parameters.AddWithValue("@Id", operatorId); cmd.Parameters.AddWithValue("@Id", operatorId);
cmd.Parameters.AddWithValue("@Pw", password); cmd.Parameters.AddWithValue("@Pw", password);
@ -354,4 +438,89 @@ namespace Housing.Services
return false; return false;
} }
} }
// 5. LoginAccount (For LoginOptions.json)
public class LoginAccount
{
public string Id { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
// 6. LoginSelectionOptions (For LoginOptions.json)
public class LoginSelectionOptions
{
public List<LoginAccount> LoginAccounts { get; set; } = new List<LoginAccount>();
public List<string> Makers { get; set; } = new List<string>();
public List<string> Models { get; set; } = new List<string>();
public List<string> Variant1Values { get; set; } = new List<string>();
public List<string> Variant2Values { get; set; } = new List<string>();
public List<string> LineValues { get; set; } = new List<string>();
public List<string> JigNoValues { get; set; } = new List<string>();
public static LoginSelectionOptions Load(string path)
{
try
{
if (File.Exists(path))
{
var json = File.ReadAllText(path);
var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var loaded = System.Text.Json.JsonSerializer.Deserialize<LoginSelectionOptions>(json, options);
if (loaded != null)
{
return loaded;
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Failed to load LoginOptions: " + ex.Message);
}
return new LoginSelectionOptions();
}
public static void CreateDefaultFile(string path)
{
try
{
var dir = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var defaultContent = @"{
""loginAccounts"": [
{
""id"": ""su"",
""password"": ""su""
}
],
""makers"": [
""AMO"",
""ABC""
],
""models"": [
""MODEL-1""
],
""variant1Values"": [],
""variant2Values"": [],
""lineValues"": [
""LINE-1""
],
""jigNoValues"": [
""JIG-1""
]
}";
File.WriteAllText(path, defaultContent);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Failed to create default LoginOptions file: " + ex.Message);
}
}
}
} }

259
marking_gui/Services/MarkingConfigService.cs

@ -0,0 +1,259 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace marking_gui.Services
{
public class MarkingConfigService
{
private readonly Dictionary<string, string> _settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, string> _tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string EzdFolderPath
{
get
{
string path = GetSetting("EzdFolderPath", @"c:\EzCad\EasyCfg");
// 1. C:\EzCad\EasyCfg 및 설정 경로 체크, 없으면 실행 파일 기준 폴더 사용
string defaultFallback = Directory.Exists(@"c:\EzCad\EasyCfg") ? @"c:\EzCad\EasyCfg" : AppDomain.CurrentDomain.BaseDirectory;
// 1. 설정값이 비어있을 시 실행 파일 기준 폴더 사용
if (string.IsNullOrWhiteSpace(path))
{
return defaultFallback;
}
// 2. 상대 경로일 시 실행 파일 디렉토리 기준으로 절대 경로 병합
if (!Path.IsPathRooted(path))
{
return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path));
}
// 3. 절대 경로이지만 타깃 경로가 실재하지 않는 경우 실행 파일 폴더로 Fallback
if (!Directory.Exists(path))
{
return defaultFallback;
}
return path;
}
}
public string LastSelectedEzdFile => GetSetting("LastSelectedEzdFile", string.Empty);
public void SaveLastSelectedEzdFile(string filePath)
{
try
{
_settings["LastSelectedEzdFile"] = filePath;
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string iniPath = Path.Combine(baseDir, "Setting.ini");
if (File.Exists(iniPath))
{
var lines = new List<string>(File.ReadAllLines(iniPath));
bool found = false;
for (int i = 0; i < lines.Count; i++)
{
string trimmed = lines[i].Trim();
if (trimmed.StartsWith("LastSelectedEzdFile=", StringComparison.OrdinalIgnoreCase))
{
lines[i] = "LastSelectedEzdFile=" + filePath;
found = true;
break;
}
}
if (!found)
{
// [Settings] 섹션 아래에 추가
for (int i = 0; i < lines.Count; i++)
{
if (lines[i].Trim().Equals("[Settings]", StringComparison.OrdinalIgnoreCase))
{
lines.Insert(i + 1, "LastSelectedEzdFile=" + filePath);
found = true;
break;
}
}
}
if (found)
{
File.WriteAllLines(iniPath, lines);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[MarkingConfig] LastSelectedEzdFile 저장 중 에러: " + ex.Message);
}
}
public string SerialTag => GetTag("SerialTag", "SERIAL");
public string LotNoTag => GetTag("LotNoTag", "YYMMDD");
public string ProductTag => GetTag("ProductTag", "PRODUCT");
public string QrCodeTag => GetTag("QrCodeTag", "QRCODETAG");
public string LineTag => GetTag("LineTag", "LCODE");
public string ShiftTag => GetTag("ShiftTag", "BAN");
public string DrawingNoTag => GetTag("DrawingNoTag", "DRAWING_NO");
public string HkmcNoTag => GetTag("HkmcNoTag", "HKMC_NO");
public string DrawingNo => GetSetting("DrawingNo", string.Empty);
public string HkmcNo => GetSetting("HkmcNo", string.Empty);
public bool IsTestMode
{
get
{
string val = GetSetting("test", "false");
return bool.TryParse(val, out bool result) && result;
}
}
public MarkingConfigService()
{
LoadConfig();
}
private void LoadConfig()
{
try
{
// BaseDirectory에서 Setting.ini 로드 시도
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string iniPath = Path.Combine(baseDir, "Setting.ini");
// BaseDirectory에 없으면 한 단계 위 프로젝트 폴더에서도 탐색 (개발 편의성)
if (!File.Exists(iniPath))
{
string parentDir = Directory.GetParent(baseDir)?.Parent?.Parent?.FullName;
if (parentDir != null)
{
string devPath = Path.Combine(parentDir, "marking_gui", "Setting.ini");
if (File.Exists(devPath))
{
iniPath = devPath;
}
}
}
if (File.Exists(iniPath))
{
byte[] bytes = File.ReadAllBytes(iniPath);
string fileText;
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
{
fileText = System.Text.Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3);
}
else
{
try
{
var strictUtf8 = new System.Text.UTF8Encoding(false, true);
fileText = strictUtf8.GetString(bytes);
}
catch
{
fileText = System.Text.Encoding.Default.GetString(bytes);
}
}
var lines = fileText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
string currentSection = string.Empty;
foreach (var line in lines)
{
var trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith(";"))
continue;
if (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
{
currentSection = trimmed.Substring(1, trimmed.Length - 2).Trim();
continue;
}
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
var key = parts[0].Trim();
var val = parts[1].Trim();
if (currentSection.Equals("Settings", StringComparison.OrdinalIgnoreCase))
{
_settings[key] = val;
}
else if (currentSection.Equals("Tags", StringComparison.OrdinalIgnoreCase))
{
_tags[key] = val;
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[MarkingConfig] 설정 로드 중 에러: " + ex.Message);
}
}
private string GetSetting(string key, string defaultVal)
{
return _settings.TryGetValue(key, out string val) ? val : defaultVal;
}
private string GetTag(string key, string defaultVal)
{
return _tags.TryGetValue(key, out string val) ? val : defaultVal;
}
public int GetLastSequence(string lotNo)
{
if (string.IsNullOrWhiteSpace(lotNo)) return 0;
string key = "Seq_" + lotNo.Trim();
if (_settings.TryGetValue(key, out string valStr) && int.TryParse(valStr, out int seq))
{
return seq;
}
return 0;
}
public void SaveLastSequence(string lotNo, int sequence)
{
if (string.IsNullOrWhiteSpace(lotNo)) return;
try
{
string key = "Seq_" + lotNo.Trim();
_settings[key] = sequence.ToString();
string iniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Setting.ini");
if (File.Exists(iniPath))
{
var lines = new List<string>(File.ReadAllLines(iniPath));
bool found = false;
for (int i = 0; i < lines.Count; i++)
{
string trimmed = lines[i].Trim();
if (trimmed.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase))
{
lines[i] = string.Format("{0}={1}", key, sequence);
found = true;
break;
}
}
if (!found)
{
lines.Add(string.Format("{0}={1}", key, sequence));
}
File.WriteAllLines(iniPath, lines);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[MarkingConfig] 순번 저장 실패: " + ex.Message);
}
}
}
}

30
marking_gui/Services/MockDatabase.cs

@ -1,30 +0,0 @@
using System;
using marking_gui.Models;
namespace marking_gui.Services
{
public class MockDatabase
{
public Product GetProductBySerial(string serial)
{
if (string.IsNullOrEmpty(serial))
return null;
string searchSerial = serial.Trim().ToUpper();
if (searchSerial == "PASS1234")
{
// 이전 검사 모두 합격
return new Product(searchSerial, "PASS", "PASS", "PASS");
}
else if (searchSerial == "FAIL5678")
{
// 이전 검사 중 Calibration 단계 실패
return new Product(searchSerial, "PASS", "FAIL", "PASS");
}
// 등록되지 않은 S/N
return null;
}
}
}

75
marking_gui/Services/MockMeasurer.cs

@ -1,75 +0,0 @@
using System;
using marking_gui.Models;
namespace marking_gui.Services
{
public class MockMeasurer
{
private readonly Random _random = new Random();
// 2초 동안 측정 중 요동칠 가상 데이터 생성
public void GenerateNoiseValues(MeasurementLimits limits, out double voltage, out double current, out double resistance)
{
double midVoltage = (limits.MinVoltage + limits.MaxVoltage) / 2.0;
double midCurrent = (limits.MinCurrent + limits.MaxCurrent) / 2.0;
double midResistance = (limits.MinResistance + limits.MaxResistance) / 2.0;
// 설정 범위 폭에 맞추어 ±50% 범위의 노이즈 생성
double voltageSpan = limits.MaxVoltage - limits.MinVoltage;
double currentSpan = limits.MaxCurrent - limits.MinCurrent;
double resistanceSpan = limits.MaxResistance - limits.MinResistance;
voltage = midVoltage + (_random.NextDouble() * voltageSpan - (voltageSpan / 2.0));
current = midCurrent + (_random.NextDouble() * currentSpan - (currentSpan / 2.0));
resistance = midResistance + (_random.NextDouble() * resistanceSpan - (resistanceSpan / 2.0));
}
// 최종 합/불 판정 측정값 결정 (80% 확률로 합격 범위, 20% 확률로 불합격 범위 값 생성)
public void GenerateFinalValues(MeasurementLimits limits, out double voltage, out double current, out double resistance)
{
double voltageSpan = limits.MaxVoltage - limits.MinVoltage;
double currentSpan = limits.MaxCurrent - limits.MinCurrent;
double resistanceSpan = limits.MaxResistance - limits.MinResistance;
if (_random.Next(0, 10) < 8)
{
// 합격 범위 내의 가상 측정값 생성
voltage = limits.MinVoltage + (_random.NextDouble() * voltageSpan);
current = limits.MinCurrent + (_random.NextDouble() * currentSpan);
resistance = limits.MinResistance + (_random.NextDouble() * resistanceSpan);
}
else
{
// 불합격 범위 유도
int errorType = _random.Next(0, 3);
if (errorType == 0)
{
// 전압 에러 유도 (범위 폭의 50% 정도 벗어나도록 가/감산하여 음수 방지)
voltage = _random.Next(0, 2) == 0
? (limits.MinVoltage - (voltageSpan * 0.5))
: (limits.MaxVoltage + (voltageSpan * 0.5));
current = limits.MinCurrent + (_random.NextDouble() * currentSpan);
resistance = limits.MinResistance + (_random.NextDouble() * resistanceSpan);
}
else if (errorType == 1)
{
// 전류 에러 유도
voltage = limits.MinVoltage + (_random.NextDouble() * voltageSpan);
current = _random.Next(0, 2) == 0
? (limits.MinCurrent - (currentSpan * 0.5))
: (limits.MaxCurrent + (currentSpan * 0.5));
resistance = limits.MinResistance + (_random.NextDouble() * resistanceSpan);
}
else
{
// 저항 에러 유도
voltage = limits.MinVoltage + (_random.NextDouble() * voltageSpan);
current = limits.MinCurrent + (_random.NextDouble() * currentSpan);
resistance = _random.Next(0, 2) == 0
? (limits.MinResistance - (resistanceSpan * 0.5))
: (limits.MaxResistance + (resistanceSpan * 0.5));
}
}
}
}
}

173
marking_gui/Services/MssqlDatabaseService.cs

@ -1,173 +0,0 @@
using System;
using System.Configuration;
using marking_gui.Models;
namespace marking_gui.Services
{
public class MssqlDatabaseService
{
private readonly string _connectionString;
public MssqlDatabaseService()
{
try
{
// AppDomain.CurrentDomain.BaseDirectory 에서 Database.ini 로드 시도
string iniPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database.ini");
if (System.IO.File.Exists(iniPath))
{
var lines = System.IO.File.ReadAllLines(iniPath);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
_connectionString = parts[1].Trim();
break;
}
}
}
}
// Database.ini 에 없을 경우 App.config 백업 사용
if (string.IsNullOrEmpty(_connectionString))
{
var connSettings = ConfigurationManager.ConnectionStrings["MssqlConnection"];
if (connSettings != null)
{
_connectionString = connSettings.ConnectionString;
}
else
{
_connectionString = "Server=localhost;Database=MarkingDb;Trusted_Connection=True;";
}
}
}
catch (Exception ex)
{
_connectionString = string.Empty;
System.Diagnostics.Debug.WriteLine("설정 로드 중 예외 발생: " + ex.Message);
}
}
public Product GetProductBySerial(string serial)
{
if (string.IsNullOrEmpty(serial))
return null;
string searchSerial = serial.Trim().ToUpper();
// TODO: 사용자의 지시에 따라 실제 MSSQL DB 질의 쿼리는 아직 작성하지 않고 대기합니다.
// 추후 실제 연동 시 아래 SQL Connection 및 SELECT 쿼리를 구현할 수 있도록 준비만 해둡니다.
/*
string query = "SELECT SerialNumber, HousingResult, CalResult, EolResult FROM ProductResults WHERE UPPER(SerialNumber) = @Serial";
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
...
}
*/
// 준비 단계 동안 임시로 기존 Mock 데이터를 반환하도록 구현 (Stub)
if (searchSerial == "PASS1234")
{
return new Product(searchSerial, "PASS", "PASS", "PASS");
}
else if (searchSerial == "FAIL5678")
{
return new Product(searchSerial, "PASS", "FAIL", "PASS");
}
return null;
}
/// <summary>
/// 최종 성능 검출 값을 데이터베이스에 저장합니다.
/// 전압과 전류 수치는 3,2 포맷(DECIMAL(3, 2) 등)에 대응하기 위해 소수점 이하 2자리로 반올림하여 처리합니다.
/// </summary>
public bool SaveProductResult(
string serial,
string maker,
string model,
string variant1,
string variant2,
string operatorId,
string line,
string lotNo,
string jigNo,
double voltage,
double current,
double resistance,
string result)
{
// 전압과 전류, 저항을 소수점 이하 2자리(3,2 포맷 규격)로 반올림 처리
decimal roundedVoltage = Math.Round((decimal)voltage, 2);
decimal roundedCurrent = Math.Round((decimal)current, 2);
decimal roundedResistance = Math.Round((decimal)resistance, 2);
// DB 컬럼 길이 한계에 맞춰서 문자열 안전하게 자르기 (Truncate)
string safeMaker = string.IsNullOrEmpty(maker) ? "" : (maker.Length > 10 ? maker.Substring(0, 10) : maker);
string safeModel = string.IsNullOrEmpty(model) ? "" : (model.Length > 10 ? model.Substring(0, 10) : model);
string safeVariant1 = string.IsNullOrEmpty(variant1) ? "" : (variant1.Length > 10 ? variant1.Substring(0, 10) : variant1);
string safeVariant2 = string.IsNullOrEmpty(variant2) ? "" : (variant2.Length > 10 ? variant2.Substring(0, 10) : variant2);
string safeOperator = string.IsNullOrEmpty(operatorId) ? "" : (operatorId.Length > 10 ? operatorId.Substring(0, 10) : operatorId);
string safeLine = string.IsNullOrEmpty(line) ? "" : (line.Length > 5 ? line.Substring(0, 5) : line);
string safeLotNo = string.IsNullOrEmpty(lotNo) ? "" : (lotNo.Length > 8 ? lotNo.Substring(0, 8) : lotNo);
string safeJigNo = string.IsNullOrEmpty(jigNo) ? "" : (jigNo.Length > 10 ? jigNo.Substring(0, 10) : jigNo);
DateTime prodDate = DateTime.Now;
// QR Code 조합 포맷 생성 (Maker;Model;Variant_1;Variant_2;Operator;Production_Date)
string markingQr = string.Format("{0};{1};{2};{3};{4};{5}",
safeMaker, safeModel, safeVariant1, safeVariant2, safeOperator, prodDate.ToString("yyyyMMddHHmmss"));
if (markingQr.Length > 100)
{
markingQr = markingQr.Substring(0, 100);
}
// TODO: 실제 MSSQL DB 적재를 위해 SQL 명령문 구현 템플릿 준비
/*
string query = @"INSERT INTO [dbo].[Marking]
([IC_SN], [Marking_QR], [Maker], [Model], [Variant_1], [Variant_2], [Operator], [Production_Date], [Line], [Lot_No], [Jig_No],
[PT_Vol_1], [PT_Vol_2], [PT_Vol_3], [PT_Current_1], [PT_Current_2], [PT_Current_3], [PT_Resistance_1], [PT_Resistance_2], [PT_Resistance_3], [Result])
VALUES
(@Serial, @Qr, @Maker, @Model, @Var1, @Var2, @Operator, @ProdDate, @Line, @LotNo, @JigNo,
@Vol1, 0.00, 0.00, @Cur1, 0.00, 0.00, @Res1, 0.00, 0.00, @Result)";
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@Serial", serial);
cmd.Parameters.AddWithValue("@Qr", markingQr);
cmd.Parameters.AddWithValue("@Maker", safeMaker);
cmd.Parameters.AddWithValue("@Model", safeModel);
cmd.Parameters.AddWithValue("@Var1", safeVariant1);
cmd.Parameters.AddWithValue("@Var2", safeVariant2);
cmd.Parameters.AddWithValue("@Operator", safeOperator);
cmd.Parameters.AddWithValue("@ProdDate", prodDate);
cmd.Parameters.AddWithValue("@Line", safeLine);
cmd.Parameters.AddWithValue("@LotNo", safeLotNo);
cmd.Parameters.AddWithValue("@JigNo", safeJigNo);
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@Vol1", System.Data.SqlDbType.Decimal) { Precision = 3, Scale = 2, Value = roundedVoltage });
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@Cur1", System.Data.SqlDbType.Decimal) { Precision = 3, Scale = 2, Value = roundedCurrent });
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@Res1", System.Data.SqlDbType.Decimal) { Precision = 3, Scale = 2, Value = roundedResistance });
cmd.Parameters.AddWithValue("@Result", result);
conn.Open();
cmd.ExecuteNonQuery();
}
}
*/
// 데모 단계 로그 출력 및 성공 반환
System.Diagnostics.Debug.WriteLine(string.Format(
"[DB 저장 로그] S/N: {0} | QR: {1} | Maker: {2} | Model: {3} | Line: {4} | LotNo: {5} | Vol_1: {6:F2}V | Cur_1: {7:F2}A | Res_1: {8:F2}Ω | 결과: {9}",
serial, markingQr, safeMaker, safeModel, safeLine, safeLotNo, roundedVoltage, roundedCurrent, roundedResistance, result));
return true;
}
}
}

200
marking_gui/Services/TcpScpiClient.cs

@ -0,0 +1,200 @@
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace marking_gui.Services
{
/// <summary>
/// TCP(LAN) 소켓을 사용하여 SCPI 계측기와 통신하는 클라이언트.
/// Keysight DMM 34465A, Power Supply E36233A 등 LAN 장비에 사용합니다.
/// </summary>
public sealed class TcpScpiClient : IScpiClient, IDisposable
{
private readonly string _host;
private readonly int _port;
private readonly int _timeoutMs;
private readonly string[] _idnMatches;
private readonly byte _terminator;
private TcpClient _tcp;
private NetworkStream _stream;
private readonly SemaphoreSlim _networkLock = new SemaphoreSlim(1, 1);
private bool _disposed = false;
public TcpScpiClient(string host, int port, int timeoutMs, string[] idnMatches, char terminator = '\n')
{
_host = host;
_port = port;
_timeoutMs = timeoutMs;
_idnMatches = idnMatches ?? Array.Empty<string>();
_terminator = (byte)terminator;
}
// ── 연결 (필요 시 자동) ────────────────────────────────────────────
private async Task EnsureConnectedAsync()
{
if (_tcp != null && _tcp.Connected) return;
await _networkLock.WaitAsync().ConfigureAwait(false);
try
{
if (_tcp != null && _tcp.Connected) return;
CleanUpSocket();
_tcp = new TcpClient();
_tcp.SendTimeout = _timeoutMs;
_tcp.ReceiveTimeout = _timeoutMs;
// 연결 시도 시 CancellationToken을 사용해 OS 타임아웃 대기를 피함
using (var cts = new CancellationTokenSource(_timeoutMs))
{
var connectTask = _tcp.ConnectAsync(_host, _port);
var delayTask = Task.Delay(_timeoutMs, cts.Token);
var completedTask = await Task.WhenAny(connectTask, delayTask).ConfigureAwait(false);
if (completedTask == delayTask)
{
CleanUpSocket();
throw new TimeoutException($"SCPI 계측기 연결 대기 시간 초과 ({_host}:{_port})");
}
cts.Cancel(); // delayTask 취소
await connectTask; // 예외 전파 확인
}
_stream = _tcp.GetStream();
// IDN 검증
if (_idnMatches.Length > 0)
{
string idn = await QueryRawAsync("*IDN?").ConfigureAwait(false);
bool matched = false;
foreach (var m in _idnMatches)
{
if (!string.IsNullOrWhiteSpace(m) &&
idn.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0)
{
matched = true;
break;
}
}
if (!matched)
throw new InvalidOperationException(
$"SCPI IDN 불일치: 기대=[{string.Join(",", _idnMatches)}] 실제=[{idn.Trim()}] ({_host}:{_port})");
}
LoggerService.Info($"[TcpScpi] 연결 완료 및 IDN 검증 성공: {_host}:{_port}");
}
catch (Exception ex)
{
CleanUpSocket();
LoggerService.Error($"[TcpScpi] 연결 중 예외 발생: {_host}:{_port}", ex);
throw;
}
finally
{
_networkLock.Release();
}
}
// ── IScpiClient ───────────────────────────────────────────────────
public async Task SendAsync(string command)
{
await EnsureConnectedAsync().ConfigureAwait(false);
await _networkLock.WaitAsync().ConfigureAwait(false);
try
{
string trimmedCommand = (command ?? "").TrimEnd('\r', '\n');
byte[] data = Encoding.ASCII.GetBytes(trimmedCommand + (char)_terminator);
await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
catch (Exception)
{
CleanUpSocket();
throw;
}
finally
{
_networkLock.Release();
}
}
public async Task<string> QueryAsync(string command)
{
await EnsureConnectedAsync().ConfigureAwait(false);
await _networkLock.WaitAsync().ConfigureAwait(false);
try
{
return await QueryRawAsync(command).ConfigureAwait(false);
}
catch (Exception)
{
CleanUpSocket();
throw;
}
finally
{
_networkLock.Release();
}
}
private async Task<string> QueryRawAsync(string command)
{
string trimmedCommand = (command ?? "").TrimEnd('\r', '\n');
byte[] data = Encoding.ASCII.GetBytes(trimmedCommand + (char)_terminator);
await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
// 응답 수신 (터미네이터 LF가 검출될 때까지 안전하게 수신)
using (var cts = new CancellationTokenSource(_timeoutMs))
{
var sb = new StringBuilder();
var buf = new byte[1024];
try
{
while (true)
{
int n = await _stream.ReadAsync(buf, 0, buf.Length, cts.Token).ConfigureAwait(false);
if (n == 0) break;
sb.Append(Encoding.ASCII.GetString(buf, 0, n));
// 버퍼의 마지막 바이트가 터미네이터(종단문자)인지 체크
if (buf[n - 1] == _terminator)
{
break;
}
}
}
catch (OperationCanceledException)
{
throw new TimeoutException($"SCPI 명령 '{trimmedCommand}' 응답 수신 타임아웃 ({_timeoutMs}ms) ({_host}:{_port})");
}
return sb.ToString();
}
}
private void CleanUpSocket()
{
try { _stream?.Dispose(); } catch { }
try { _tcp?.Dispose(); } catch { }
_stream = null;
_tcp = null;
}
// ── IDisposable ───────────────────────────────────────────────────
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
CleanUpSocket();
_networkLock.Dispose();
}
}
}
}

113
marking_gui/Setting.ini

@ -0,0 +1,113 @@
; =================================================================
; 성능 검사 모니터링 시스템 - 레이저 마킹기 연동 설정 파일
; =================================================================
[Settings]
; 마킹 제어 라이브러리 및 드라이버(EzCad) 폴더 경로
EzdFolderPath=c:\EzCad\EasyCfg
; 테스트 모드 여부 (true 시 하단 테스트 마킹, 강제 검사 버튼 노출)
test=false
; 도면품번 (EZD 도안의 도면품번 태그에 채워질 값 — DrawingNoTag에 매핑)
DrawingNo=
; HKMC 품번 (EZD 도안의 HKMC 품번 태그에 채워질 값 — HkmcNoTag에 매핑)
HkmcNo=
[Tags]
; 도안(.ezd) 파일 내의 텍스트 오브젝트 치환용 태그명 매핑 리스트
; (도안에 정의된 오브젝트명과 동일하게 설정해야 정상 작동합니다)
; 일련번호 (Serial Number) 치환 태그
SerialTag=SERIAL
; 작업 년월일 (Lot No) 치환 태그
LotNoTag=YYMMDD
; 제품 모델명 (Product Name) 치환 태그
ProductTag=PRODUCT
; DMC 치환 태그
QrCodeTag=QRCODETAG
; 도면품번 치환 태그 (EZD 도안 내 도면품번 오브젝트명)
DrawingNoTag=DRAWING_NO
; HKMC 품번 치환 태그 (EZD 도안 내 HKMC 품번 오브젝트명)
HkmcNoTag=HKMC_NO
; =================================================================
; 계측기 연동 설정 (SCPI / LAN)
; =================================================================
[Equipment]
; 계측기 통신 타임아웃 (ms)
Timeout=5000
; 측정 안정화 대기 시간 (ms) — Power Supply ON 후 이 시간만큼 대기 후 읽음
SettleMilliseconds=2000
; 검사 전 보드 전원 공급 커맨드 (세미콜론으로 구분)
PreBoardCommand=VOLT 5, (@2); CURR 2, (@2); OUTP ON, (@2)
PreBoardDelayMilliseconds=2000
; 로그아웃 시 전원 차단 커맨드
LogoutCommand=OUTP OFF, (@1)
[DMM]
; Digital Multimeter — 전압(Voltage) 측정 전용 계측기
VoltageConnection=LAN
VoltageHost=192.168.10.14
VoltagePort=5025
VoltageIdnMatch=34465A
VoltageBaudRate=115200
VoltageTerminator=LF
VoltageSetupCommand=
VoltageReadCommand=MEAS:VOLT:DC?
[ResistanceDMM]
; Digital Multimeter — 저항(Resistance) 측정 전용 계측기
ResistanceDMMConnection=LAN
ResistanceDMMHost=192.168.10.13
ResistanceDMMPort=5025
ResistanceDMMIdnMatch=34465A
ResistanceDMMBaudRate=115200
ResistanceDMMTerminator=LF
ResistanceDMMReadCommand=MEAS:RES?
[PowerSupply]
; Power Supply — 전류(Current) 측정 및 상시전원(PreBoard)/테스트전원 출력 제어
CurrentConnection=LAN
CurrentHost=192.168.10.15
CurrentPort=5025
CurrentIdnMatch=E36233A
CurrentBaudRate=9600
CurrentTerminator=LF
CurrentSetupCommand=OUTP ON, (@1)
CurrentCleanupCommand=OUTP OFF, (@1)
CurrentReadCommand=MEAS:CURR? (@1)
; =================================================================
; 계측 합격/불합격 판정 기준 범위 설정 (Limits)
; =================================================================
[Limits]
; 전압 판정 기준 범위 (V)
MinVoltage=1.00
MaxVoltage=6.00
; 전류 판정 기준 범위 (mA)
MinCurrent=5.00
MaxCurrent=10.00
; 저항 판정 기준 범위 (kΩ)
MinResistance=8.000
MaxResistance=13.000

1
marking_gui/SettingsWindow.xaml

@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="기준 범위 설정" Height="290" Width="350" Title="기준 범위 설정" Height="290" Width="350"
Icon="pack://application:,,,/Marking.ico"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize" WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
Background="#F3F4F6" ShowInTaskbar="False"> Background="#F3F4F6" ShowInTaskbar="False">
<Window.Resources> <Window.Resources>

8
marking_gui/SettingsWindow.xaml.cs

@ -20,10 +20,10 @@ namespace marking_gui
txtMinVoltage.Text = Limits.MinVoltage.ToString("F1"); txtMinVoltage.Text = Limits.MinVoltage.ToString("F1");
txtMaxVoltage.Text = Limits.MaxVoltage.ToString("F1"); txtMaxVoltage.Text = Limits.MaxVoltage.ToString("F1");
txtMinCurrent.Text = Limits.MinCurrent.ToString("F1"); txtMinCurrent.Text = Limits.MinCurrent.ToString("F2");
txtMaxCurrent.Text = Limits.MaxCurrent.ToString("F1"); txtMaxCurrent.Text = Limits.MaxCurrent.ToString("F2");
txtMinResistance.Text = Limits.MinResistance.ToString("F1"); txtMinResistance.Text = Limits.MinResistance.ToString("F3");
txtMaxResistance.Text = Limits.MaxResistance.ToString("F1"); txtMaxResistance.Text = Limits.MaxResistance.ToString("F3");
} }
private void btnSave_Click(object sender, RoutedEventArgs e) private void btnSave_Click(object sender, RoutedEventArgs e)

220
marking_gui/StartupLoginWindow.xaml

@ -2,11 +2,17 @@
<Window x:Class="Housing.Login.StartupLoginWindow" <Window x:Class="Housing.Login.StartupLoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Marking HMI Login" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Housing HMI Login"
Icon="pack://application:,,,/Marking.ico"
Width="760" Width="760"
Height="760" Height="760"
MinWidth="560" MinWidth="560"
MinHeight="620" MinHeight="620"
d:DesignWidth="760"
d:DesignHeight="760"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
Background="{StaticResource HmiScreenBackgroundBrush}" Background="{StaticResource HmiScreenBackgroundBrush}"
UseLayoutRounding="True" UseLayoutRounding="True"
@ -121,7 +127,160 @@
</Setter> </Setter>
</Style> </Style>
<Style x:Key="LoginButtonStyle" TargetType="Button"> <Style x:Key="ComboInputItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="{StaticResource HmiTextBrush}"/>
<Setter Property="Background" Value="{StaticResource HmiInputBrush}"/>
<Setter Property="FontSize" Value="17"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Border x:Name="ItemChrome"
Background="{TemplateBinding Background}"
BorderBrush="#263D4F"
BorderThickness="0,0,0,1"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="ItemChrome" Property="Background" Value="{StaticResource HmiButtonHoverBrush}"/>
<Setter Property="Foreground" Value="{StaticResource HmiTextBrush}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="ItemChrome" Property="Background" Value="#163A4A"/>
<Setter Property="Foreground" Value="{StaticResource HmiAccentBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ItemChrome" Property="Opacity" Value="0.55"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboInputStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Height" Value="42"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="Background" Value="{StaticResource HmiInputBrush}"/>
<Setter Property="Foreground" Value="{StaticResource HmiTextBrush}"/>
<Setter Property="BorderBrush" Value="#3F6178"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="19"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="8,0,34,0"/>
<Setter Property="IsTextSearchEnabled" Value="True"/>
<Setter Property="MaxDropDownHeight" Value="118"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ComboInputItemStyle}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<Border x:Name="FieldChrome"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3"
SnapsToDevicePixels="True">
<Grid>
<Rectangle Height="2"
VerticalAlignment="Top"
Fill="{StaticResource HmiAccentBrush}"
Opacity="0.42"/>
<ContentPresenter x:Name="ContentSite"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
RecognizesAccessKey="True"
IsHitTestVisible="False"/>
<ToggleButton x:Name="DropDownToggle"
Background="Transparent"
BorderThickness="0"
ClickMode="Press"
Focusable="False"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent">
<Path x:Name="Arrow"
Width="12"
Height="7"
Margin="0,0,12,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Data="M 0 0 L 6 7 L 12 0 Z"
Fill="{StaticResource HmiAccentBrush}"
Stretch="Fill"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Arrow" Property="Fill" Value="#76EAFF"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Arrow" Property="Fill" Value="{StaticResource HmiRunBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
</Border>
<Popup x:Name="PART_Popup"
AllowsTransparency="True"
Focusable="False"
IsOpen="{TemplateBinding IsDropDownOpen}"
Placement="Bottom"
PopupAnimation="Slide">
<Border Width="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
Background="#07111C"
BorderBrush="{StaticResource HmiAccentBrush}"
BorderThickness="1">
<ScrollViewer MaxHeight="{TemplateBinding MaxDropDownHeight}"
SnapsToDevicePixels="True"
VerticalScrollBarVisibility="Auto">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained"/>
</ScrollViewer>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="FieldChrome" Property="BorderBrush" Value="#76EAFF"/>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter TargetName="FieldChrome" Property="BorderBrush" Value="{StaticResource HmiAccentBrush}"/>
<Setter TargetName="FieldChrome" Property="BorderThickness" Value="2"/>
</Trigger>
<Trigger Property="IsDropDownOpen" Value="True">
<Setter TargetName="FieldChrome" Property="BorderBrush" Value="{StaticResource HmiRunBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="FieldChrome" Property="Opacity" Value="0.55"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CommandButtonStyle" TargetType="Button">
<Setter Property="Height" Value="58"/> <Setter Property="Height" Value="58"/>
<Setter Property="Foreground" Value="{StaticResource HmiTextBrush}"/> <Setter Property="Foreground" Value="{StaticResource HmiTextBrush}"/>
<Setter Property="Background" Value="{StaticResource HmiButtonBrush}"/> <Setter Property="Background" Value="{StaticResource HmiButtonBrush}"/>
@ -129,7 +288,6 @@
<Setter Property="BorderThickness" Value="2"/> <Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontSize" Value="23"/> <Setter Property="FontSize" Value="23"/>
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/> <Setter Property="Cursor" Value="Hand"/>
@ -200,7 +358,9 @@
<Border HorizontalAlignment="Stretch" <Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{StaticResource HmiPanelBrush}"> Background="{StaticResource HmiPanelBrush}"
BorderBrush="{StaticResource HmiPanelInnerBorderBrush}"
BorderThickness="0">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -221,7 +381,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel> <StackPanel>
<TextBlock Text="MARKING" <TextBlock Text="AMO HOUSING ASSEMBLY"
Foreground="{StaticResource HmiTextBrush}" Foreground="{StaticResource HmiTextBrush}"
FontSize="27" FontSize="27"
FontWeight="Bold" FontWeight="Bold"
@ -293,7 +453,7 @@
Fill="#304454" Fill="#304454"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBlock Text="Operator verification required before marking operation" <TextBlock Text="Operator verification required before line operation"
Foreground="{StaticResource HmiMutedTextBrush}" Foreground="{StaticResource HmiMutedTextBrush}"
FontSize="12" FontSize="12"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -343,10 +503,9 @@
<TextBlock Text="Maker" <TextBlock Text="Maker"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="MakerTextBox" <ComboBox x:Name="MakerComboBox"
Grid.Row="1" Grid.Row="1"
Text="MOBI" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -361,10 +520,9 @@
<TextBlock Text="Model" <TextBlock Text="Model"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="ModelTextBox" <ComboBox x:Name="ModelComboBox"
Grid.Row="1" Grid.Row="1"
Text="MOBI-EV-01" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -379,10 +537,9 @@
<TextBlock Text="Variant 1" <TextBlock Text="Variant 1"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="Variant1TextBox" <ComboBox x:Name="Variant1ComboBox"
Grid.Row="1" Grid.Row="1"
Text="V1-01" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -397,10 +554,9 @@
<TextBlock Text="Variant 2" <TextBlock Text="Variant 2"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="Variant2TextBox" <ComboBox x:Name="Variant2ComboBox"
Grid.Row="1" Grid.Row="1"
Text="V2-01" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
@ -428,7 +584,6 @@
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="OperatorTextBox" <TextBox x:Name="OperatorTextBox"
Grid.Row="1" Grid.Row="1"
Text="test"
Style="{StaticResource InputStyle}"/> Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -451,7 +606,7 @@
</Grid> </Grid>
<TextBlock Grid.Row="6" <TextBlock Grid.Row="6"
Text="TRACEABILITY INFORMATION" Text="LINE INFORMATION"
Style="{StaticResource SectionCaptionStyle}"/> Style="{StaticResource SectionCaptionStyle}"/>
<Grid Grid.Row="7"> <Grid Grid.Row="7">
@ -473,10 +628,9 @@
<TextBlock Text="Line" <TextBlock Text="Line"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="LineNoTextBox" <ComboBox x:Name="LineNoComboBox"
Grid.Row="1" Grid.Row="1"
Text="LINE-3" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -492,7 +646,6 @@
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="LotNoTextBox" <TextBox x:Name="LotNoTextBox"
Grid.Row="1" Grid.Row="1"
Text="LOT-20260605"
Style="{StaticResource InputStyle}"/> Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
@ -507,10 +660,9 @@
<TextBlock Text="Jig No" <TextBlock Text="Jig No"
Style="{StaticResource FieldLabelStyle}"/> Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="JigNoTextBox" <ComboBox x:Name="JigNoComboBox"
Grid.Row="1" Grid.Row="1"
Text="JIG-12" Style="{StaticResource ComboInputStyle}"/>
Style="{StaticResource InputStyle}"/>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
@ -518,7 +670,7 @@
<Button x:Name="LoginButton" <Button x:Name="LoginButton"
Grid.Row="9" Grid.Row="9"
Content="ACCESS / LOG IN" Content="ACCESS / LOG IN"
Style="{StaticResource LoginButtonStyle}" Style="{StaticResource CommandButtonStyle}"
Click="LogInButton_Click"/> Click="LogInButton_Click"/>
</Grid> </Grid>
</Grid> </Grid>

649
marking_gui/StartupLoginWindow.xaml.cs

@ -1,6 +1,9 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
@ -9,327 +12,501 @@ using Housing.Services;
namespace Housing.Login namespace Housing.Login
{ {
public sealed class StartupLoginWindowResult public sealed class StartupLoginWindowResult
{
public string Maker { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
public string Variant1 { get; set; } = string.Empty;
public string Variant2 { get; set; } = string.Empty;
public string Operator { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string LineNo { get; set; } = string.Empty;
public string LotNo { get; set; } = string.Empty;
public string JigNo { get; set; } = string.Empty;
}
public partial class StartupLoginWindow : Window
{
private const string OperatorHistoryFieldName = "Operator";
private const string SuppressedOperatorHistoryValue = "su";
private readonly LoginHistoryStore _loginHistoryStore;
private Popup? _openHistoryPopup;
public StartupLoginWindowResult Result { get; private set; } = new();
public StartupLoginWindow()
{ {
_loginHistoryStore = LoginHistoryStore.Load(GetLoginHistoryPath()); public string Maker { get; set; } = string.Empty;
RemoveSuppressedOperatorHistory(); public string Model { get; set; } = string.Empty;
InitializeComponent(); public string Variant1 { get; set; } = string.Empty;
AttachLoginHistoryMenus(); public string Variant2 { get; set; } = string.Empty;
this.PasswordBox.GotKeyboardFocus += (_, _) => CloseHistoryPopup(); public string Operator { get; set; } = string.Empty;
LoginButton.GotKeyboardFocus += (_, _) => CloseHistoryPopup(); public string Password { get; set; } = string.Empty;
OperatorTextBox.Focus(); public string LineNo { get; set; } = string.Empty;
public string LotNo { get; set; } = string.Empty;
public string JigNo { get; set; } = string.Empty;
} }
private async void LogInButton_Click(object sender, RoutedEventArgs e) public partial class StartupLoginWindow : Window
{ {
CloseHistoryPopup(); private const string OperatorHistoryFieldName = "Operator";
private const string SuppressedOperatorHistoryValue = "su";
private readonly LoginHistoryStore _loginHistoryStore;
private readonly LoginSelectionOptions _loginOptions;
private Popup? _openHistoryPopup;
if (!TryReadRequiredFields(out var loginResult)) public StartupLoginWindowResult Result { get; private set; } = new();
{
return;
}
if (sender is Button button) public StartupLoginWindow()
{ {
button.IsEnabled = false; _loginHistoryStore = LoginHistoryStore.Load(GetLoginHistoryPath());
_loginOptions = LoginSelectionOptions.Load(GetLoginOptionsPath());
RemoveSuppressedOperatorHistory();
InitializeComponent();
ApplyLoginOptions();
AttachLoginHistoryMenus();
AttachSelectionDropDowns();
PasswordBox.GotKeyboardFocus += (_, _) => CloseHistoryPopup();
LoginButton.GotKeyboardFocus += (_, _) => CloseHistoryPopup();
MakerComboBox.Focus();
} }
try private async void LogInButton_Click(object sender, RoutedEventArgs e)
{ {
var loginSettings = LoginSettings.Load(GetDatabaseIniPath()); CloseHistoryPopup();
if (loginSettings.OfflinePreview)
if (!TryReadRequiredFields(out var loginResult, out var password))
{
return;
}
if (sender is Button button)
{ {
button.IsEnabled = false;
}
try
{
if (!await IsValidLoginAsync(loginResult.Operator, password))
{
MessageBox.Show(this, "등록되지 않은 ID/PW입니다.", "Login", MessageBoxButton.OK, MessageBoxImage.Error);
PasswordBox.Clear();
PasswordBox.Focus();
return;
}
loginResult.Password = password; // 비밀번호를 결과 객체에도 담아둠
Result = loginResult; Result = loginResult;
SaveLoginHistory(loginResult); SaveLoginHistory(loginResult);
DialogResult = true; DialogResult = true;
Close(); Close();
return; }
catch (Exception ex)
{
MessageBox.Show(this, $"로그인 확인 실패: {ex.Message}", "Login", MessageBoxButton.OK, MessageBoxImage.Error);
OperatorTextBox.Focus();
OperatorTextBox.SelectAll();
}
finally
{
if (sender is Button loginButton)
{
loginButton.IsEnabled = true;
}
}
}
private async Task<bool> IsValidLoginAsync(string loginId, string password)
{
// 1. LoginOptions.json의 로컬 계정 정보에서 먼저 찾기
if (_loginOptions.LoginAccounts != null && _loginOptions.LoginAccounts.Count > 0)
{
var matched = _loginOptions.LoginAccounts.Any(account =>
string.Equals(account.Id, loginId, StringComparison.OrdinalIgnoreCase) &&
string.Equals(account.Password, password, StringComparison.Ordinal));
if (matched)
{
return true;
}
}
// 2. 일치하는 계정이 없다면 기존 DB 조회 로직 태우기
var loginSettings = LoginSettings.Load(GetDatabaseIniPath());
if (loginSettings.OfflinePreview)
{
return true;
} }
var repository = new LoginAccountRepository( var repository = new LoginAccountRepository(
DatabaseSettings.Load(GetDatabaseIniPath()), DatabaseSettings.Load(GetDatabaseIniPath()),
loginSettings); loginSettings);
if (!await repository.ExistsAsync(loginResult.Operator, loginResult.Password)) return await repository.ExistsAsync(loginId, password);
}
private bool TryReadRequiredFields(out StartupLoginWindowResult result, out string password)
{
result = new StartupLoginWindowResult();
password = string.Empty;
if (!TryReadRequiredComboBox(MakerComboBox, "Maker", out var maker) ||
!TryReadRequiredComboBox(ModelComboBox, "Model", out var model) ||
!TryReadTextBox(OperatorTextBox, "ID", out var loginId) ||
!TryReadPasswordBox(PasswordBox, "PW", out password) ||
!TryReadRequiredComboBox(LineNoComboBox, "Line", out var lineNo) ||
!TryReadTextBox(LotNoTextBox, "Lot No", out var lotNo) ||
!TryReadRequiredComboBox(JigNoComboBox, "Jig No", out var jigNo))
{
return false;
}
if (!string.IsNullOrWhiteSpace(lotNo) && lotNo.Length < 2)
{ {
MessageBox.Show(this, "DB에 등록된 ID/PW가 아닙니다.", "Login", MessageBoxButton.OK, MessageBoxImage.Error); lotNo = lotNo.PadLeft(2, '0');
this.PasswordBox.Clear();
this.PasswordBox.Focus();
return;
} }
Result = loginResult; var variant1 = ReadOptionalComboBox(Variant1ComboBox);
SaveLoginHistory(loginResult); var variant2 = ReadOptionalComboBox(Variant2ComboBox);
DialogResult = true;
Close(); result = new StartupLoginWindowResult
{
Maker = maker,
Model = model,
Variant1 = variant1,
Variant2 = variant2,
Operator = loginId,
LineNo = lineNo,
LotNo = lotNo,
JigNo = jigNo
};
return true;
} }
catch (Exception ex)
private void ApplyLoginOptions()
{ {
MessageBox.Show(this, $"로그인 DB 확인 실패: {ex.Message}", "Login", MessageBoxButton.OK, MessageBoxImage.Error); SetComboBoxOptions(MakerComboBox, _loginOptions.Makers, allowBlank: false, historyFieldName: "Maker");
OperatorTextBox.Focus(); SetComboBoxOptions(ModelComboBox, _loginOptions.Models, allowBlank: false, historyFieldName: "Model");
OperatorTextBox.SelectAll(); SetComboBoxOptions(Variant1ComboBox, _loginOptions.Variant1Values, allowBlank: true, historyFieldName: "Variant1");
SetComboBoxOptions(Variant2ComboBox, _loginOptions.Variant2Values, allowBlank: true, historyFieldName: "Variant2");
SetComboBoxOptions(LineNoComboBox, _loginOptions.LineValues, allowBlank: false, historyFieldName: "LineNo");
SetComboBoxOptions(JigNoComboBox, _loginOptions.JigNoValues, allowBlank: false, historyFieldName: "JigNo");
} }
finally
private void SetComboBoxOptions(
ComboBox comboBox,
IReadOnlyList<string> values,
bool allowBlank,
string historyFieldName)
{ {
if (sender is Button loginButton) var items = new List<LoginOptionItem>();
if (allowBlank)
{ {
loginButton.IsEnabled = true; items.Add(new LoginOptionItem("(None)", string.Empty));
} }
}
}
private bool TryReadRequiredFields(out StartupLoginWindowResult result) foreach (var value in values.Where(value => !string.IsNullOrWhiteSpace(value)))
{ {
result = new StartupLoginWindowResult(); if (items.Any(item => string.Equals(item.Value, value, StringComparison.OrdinalIgnoreCase)))
{
if (!TryReadTextBox(MakerTextBox, "Maker", out var maker) || continue;
!TryReadTextBox(ModelTextBox, "Model", out var model) || }
!TryReadTextBox(Variant1TextBox, "Variant 1", out var variant1) ||
!TryReadTextBox(Variant2TextBox, "Variant 2", out var variant2) || items.Add(new LoginOptionItem(value, value));
!TryReadTextBox(OperatorTextBox, "ID", out var loginId) || }
!TryReadPasswordBox(this.PasswordBox, "PW", out var password) ||
!TryReadTextBox(LineNoTextBox, "Line", out var lineNo) || // 옵션 항목 개수를 최대 3개로 제한
!TryReadTextBox(LotNoTextBox, "Lot No", out var lotNo) || items = items.Take(3).ToList();
!TryReadTextBox(JigNoTextBox, "Jig No", out var jigNo))
{ comboBox.ItemsSource = items;
return false; comboBox.DisplayMemberPath = nameof(LoginOptionItem.DisplayText);
var rememberedValue = _loginHistoryStore.GetValues(historyFieldName).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(rememberedValue) &&
TrySelectComboBoxValue(comboBox, rememberedValue))
{
return;
}
if (allowBlank)
{
comboBox.SelectedIndex = 0;
}
else if (items.Count == 1)
{
comboBox.SelectedIndex = 0;
}
} }
result = new StartupLoginWindowResult private static bool TrySelectComboBoxValue(ComboBox comboBox, string value)
{ {
Maker = maker, for (var index = 0; index < comboBox.Items.Count; index++)
Model = model, {
Variant1 = variant1, if (comboBox.Items[index] is LoginOptionItem item &&
Variant2 = variant2, string.Equals(item.Value, value, StringComparison.OrdinalIgnoreCase))
Operator = loginId, {
Password = password, comboBox.SelectedIndex = index;
LineNo = lineNo, return true;
LotNo = lotNo, }
JigNo = jigNo }
};
return true;
}
private void AttachLoginHistoryMenus() return false;
{ }
AttachHistoryMenu(MakerTextBox, "Maker", ModelTextBox);
AttachHistoryMenu(ModelTextBox, "Model", Variant1TextBox);
AttachHistoryMenu(Variant1TextBox, "Variant1", Variant2TextBox);
AttachHistoryMenu(Variant2TextBox, "Variant2", OperatorTextBox);
AttachHistoryMenu(OperatorTextBox, OperatorHistoryFieldName, this.PasswordBox);
AttachHistoryMenu(LineNoTextBox, "LineNo", LotNoTextBox);
AttachHistoryMenu(LotNoTextBox, "LotNo", JigNoTextBox);
AttachHistoryMenu(JigNoTextBox, "JigNo", LoginButton);
}
private void AttachHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl) private void AttachSelectionDropDowns()
{
textBox.GotKeyboardFocus += (_, _) => ShowHistoryMenu(textBox, fieldName, nextFocusControl);
textBox.PreviewMouseLeftButtonDown += (_, _) =>
{ {
if (textBox.IsKeyboardFocusWithin) foreach (var comboBox in new[]
{
MakerComboBox,
ModelComboBox,
Variant1ComboBox,
Variant2ComboBox,
LineNoComboBox,
JigNoComboBox
})
{ {
ShowHistoryMenu(textBox, fieldName, nextFocusControl); comboBox.GotKeyboardFocus += (_, _) => CloseHistoryPopup();
comboBox.DropDownOpened += (_, _) => CloseHistoryPopup();
} }
}; }
}
private void ShowHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl) private void AttachLoginHistoryMenus()
{
var values = _loginHistoryStore.GetValues(fieldName);
if (values.Count == 0)
{ {
CloseHistoryPopup(); AttachHistoryMenu(OperatorTextBox, OperatorHistoryFieldName, PasswordBox);
return; AttachHistoryMenu(LotNoTextBox, "LotNo", JigNoComboBox);
} }
CloseHistoryPopup(); private void AttachHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl)
var menuWidth = Math.Max(160, textBox.ActualWidth);
var stackPanel = new StackPanel();
var popup = new Popup
{
PlacementTarget = textBox,
Placement = PlacementMode.Bottom,
HorizontalOffset = 0,
StaysOpen = true,
AllowsTransparency = true,
Focusable = false
};
foreach (var value in values)
{ {
var item = new Button textBox.GotKeyboardFocus += (_, _) => ShowHistoryMenu(textBox, fieldName, nextFocusControl);
textBox.PreviewMouseLeftButtonDown += (_, _) =>
{ {
Content = value, if (textBox.IsKeyboardFocusWithin)
MinWidth = menuWidth, {
Padding = new Thickness(10, 6, 10, 6), ShowHistoryMenu(textBox, fieldName, nextFocusControl);
BorderThickness = new Thickness(0), }
Background = textBox.Background,
Foreground = textBox.Foreground,
HorizontalContentAlignment = HorizontalAlignment.Left
}; };
item.Click += (_, _) => }
private void ShowHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl)
{
// 최근 기록 히스토리 항목 개수 최대 3개로 제한
var values = _loginHistoryStore.GetValues(fieldName).Take(3).ToList();
if (values.Count == 0)
{ {
textBox.Text = value;
textBox.CaretIndex = textBox.Text.Length;
CloseHistoryPopup(); CloseHistoryPopup();
FocusControl(nextFocusControl); return;
}
CloseHistoryPopup();
var menuWidth = Math.Max(160, textBox.ActualWidth);
var stackPanel = new StackPanel();
var popup = new Popup
{
PlacementTarget = textBox,
Placement = PlacementMode.Bottom,
HorizontalOffset = 0,
StaysOpen = true,
AllowsTransparency = true,
Focusable = false
}; };
stackPanel.Children.Add(item);
foreach (var value in values)
{
var item = new Button
{
Content = value,
MinWidth = menuWidth,
Padding = new Thickness(10, 6, 10, 6),
BorderThickness = new Thickness(0),
Background = textBox.Background,
Foreground = textBox.Foreground,
HorizontalContentAlignment = HorizontalAlignment.Left
};
item.Click += (_, _) =>
{
textBox.Text = value;
textBox.CaretIndex = textBox.Text.Length;
CloseHistoryPopup();
FocusControl(nextFocusControl);
};
stackPanel.Children.Add(item);
}
popup.Child = new Border
{
Width = menuWidth,
MaxHeight = 180,
Background = textBox.Background,
BorderBrush = textBox.BorderBrush,
BorderThickness = new Thickness(1),
Child = new ScrollViewer
{
MaxHeight = 180,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Content = stackPanel
}
};
_openHistoryPopup = popup;
popup.IsOpen = true;
} }
popup.Child = new Border private void CloseHistoryPopup()
{ {
Width = menuWidth, if (_openHistoryPopup is not null)
MaxHeight = 180,
Background = textBox.Background,
BorderBrush = textBox.BorderBrush,
BorderThickness = new Thickness(1),
Child = new ScrollViewer
{ {
MaxHeight = 180, _openHistoryPopup.IsOpen = false;
VerticalScrollBarVisibility = ScrollBarVisibility.Auto, _openHistoryPopup = null;
Content = stackPanel
} }
}; }
_openHistoryPopup = popup; private void FocusControl(Control control)
popup.IsOpen = true; {
} Dispatcher.BeginInvoke(new Action(() =>
{
control.Focus();
if (control is TextBox textBox)
{
textBox.CaretIndex = textBox.Text.Length;
}
else if (control is PasswordBox passwordBox)
{
passwordBox.Focus();
}
}), DispatcherPriority.Input);
}
private void CloseHistoryPopup() private void SaveLoginHistory(StartupLoginWindowResult result)
{
if (_openHistoryPopup is not null)
{ {
_openHistoryPopup.IsOpen = false; _loginHistoryStore.Remember("Maker", result.Maker);
_openHistoryPopup = null; _loginHistoryStore.Remember("Model", result.Model);
_loginHistoryStore.Remember("Variant1", result.Variant1);
_loginHistoryStore.Remember("Variant2", result.Variant2);
RememberOperatorHistory(result.Operator);
_loginHistoryStore.Remember("LineNo", result.LineNo);
_loginHistoryStore.Remember("LotNo", result.LotNo);
_loginHistoryStore.Remember("JigNo", result.JigNo);
_loginHistoryStore.Save();
} }
}
private void FocusControl(Control control) private void RememberOperatorHistory(string operatorId)
{
Dispatcher.BeginInvoke(new Action(() =>
{ {
control.Focus(); if (IsSuppressedOperatorHistoryValue(operatorId))
{
_loginHistoryStore.Forget(OperatorHistoryFieldName, operatorId);
return;
}
_loginHistoryStore.Remember(OperatorHistoryFieldName, operatorId);
}
if (control is TextBox textBox) private void RemoveSuppressedOperatorHistory()
{
try
{ {
textBox.CaretIndex = textBox.Text.Length; if (_loginHistoryStore.Forget(OperatorHistoryFieldName, SuppressedOperatorHistoryValue))
{
_loginHistoryStore.Save();
}
} }
else if (control is PasswordBox passwordBox) catch
{ {
passwordBox.Focus(); // Login history cleanup is optional and should not block the login window.
} }
}), DispatcherPriority.Input); }
}
private void SaveLoginHistory(StartupLoginWindowResult result)
{
_loginHistoryStore.Remember("Maker", result.Maker);
_loginHistoryStore.Remember("Model", result.Model);
_loginHistoryStore.Remember("Variant1", result.Variant1);
_loginHistoryStore.Remember("Variant2", result.Variant2);
RememberOperatorHistory(result.Operator);
_loginHistoryStore.Remember("LineNo", result.LineNo);
_loginHistoryStore.Remember("LotNo", result.LotNo);
_loginHistoryStore.Remember("JigNo", result.JigNo);
_loginHistoryStore.Save();
}
private void RememberOperatorHistory(string operatorId) private static bool IsSuppressedOperatorHistoryValue(string value)
{
if (IsSuppressedOperatorHistoryValue(operatorId))
{ {
_loginHistoryStore.Forget(OperatorHistoryFieldName, operatorId); return string.Equals(value.Trim(), SuppressedOperatorHistoryValue, StringComparison.OrdinalIgnoreCase);
return;
} }
_loginHistoryStore.Remember(OperatorHistoryFieldName, operatorId); private bool TryReadRequiredComboBox(ComboBox comboBox, string fieldName, out string value)
}
private void RemoveSuppressedOperatorHistory()
{
try
{ {
if (_loginHistoryStore.Forget(OperatorHistoryFieldName, SuppressedOperatorHistoryValue)) value = ReadComboBoxValue(comboBox);
if (!string.IsNullOrWhiteSpace(value))
{ {
_loginHistoryStore.Save(); return true;
} }
MessageBox.Show(
this,
$"{fieldName} value is required. Edit LoginOptions.json and select a value.",
"Login",
MessageBoxButton.OK,
MessageBoxImage.Warning);
comboBox.Focus();
comboBox.IsDropDownOpen = true;
return false;
} }
catch
private static string ReadOptionalComboBox(ComboBox comboBox)
{ {
// Login history cleanup is optional and should not block the login window. return ReadComboBoxValue(comboBox);
} }
}
private static bool IsSuppressedOperatorHistoryValue(string value) private static string ReadComboBoxValue(ComboBox comboBox)
{ {
return string.Equals(value.Trim(), SuppressedOperatorHistoryValue, StringComparison.OrdinalIgnoreCase); return comboBox.SelectedItem is LoginOptionItem item
} ? item.Value.Trim()
: comboBox.Text.Trim();
}
private bool TryReadTextBox(TextBox textBox, string fieldName, out string value) private bool TryReadTextBox(TextBox textBox, string fieldName, out string value)
{
value = textBox.Text.Trim();
if (!string.IsNullOrWhiteSpace(value))
{ {
return true; value = textBox.Text.Trim();
if (!string.IsNullOrWhiteSpace(value))
{
return true;
}
MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning);
textBox.Focus();
textBox.SelectAll();
return false;
} }
MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning); private bool TryReadPasswordBox(PasswordBox passwordBox, string fieldName, out string value)
textBox.Focus(); {
textBox.SelectAll(); value = passwordBox.Password;
return false; if (!string.IsNullOrWhiteSpace(value))
} {
return true;
}
private bool TryReadPasswordBox(PasswordBox passwordBox, string fieldName, out string value) MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning);
{ passwordBox.Focus();
value = passwordBox.Password; passwordBox.SelectAll();
if (!string.IsNullOrWhiteSpace(value)) return false;
}
private static string GetDatabaseIniPath()
{ {
return true; return Path.Combine(AppContext.BaseDirectory, "Database.ini");
} }
MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning); private static string GetLoginOptionsPath()
passwordBox.Focus(); {
passwordBox.SelectAll(); // 1. 실행 파일과 동일한 디렉토리에 있는 LoginOptions.json을 최우선적으로 직접 사용
return false; var bundledPath = Path.Combine(AppContext.BaseDirectory, "LoginOptions.json");
} if (File.Exists(bundledPath))
{
return bundledPath;
}
private static string GetDatabaseIniPath() // 2. 실행 디렉토리에 존재하지 않는 경우, 로컬 AppData 폴백 처리 (하위 호환성)
{ var directory = Path.Combine(
return Path.Combine(AppContext.BaseDirectory, "Database.ini"); Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
} "Housing");
var filePath = Path.Combine(directory, "LoginOptions.json");
private static string GetLoginHistoryPath() if (!File.Exists(filePath))
{ {
return Path.Combine( Directory.CreateDirectory(directory);
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), LoginSelectionOptions.CreateDefaultFile(filePath);
"Housing", }
"LoginHistory.json");
return filePath;
}
private static string GetLoginHistoryPath()
{
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Housing",
"LoginHistory.json");
}
private sealed class LoginOptionItem
{
public string DisplayText { get; }
public string Value { get; }
public LoginOptionItem(string displayText, string value)
{
DisplayText = displayText;
Value = value;
}
}
} }
} }
}

BIN
marking_gui/bin/Debug/marking_gui.exe

Binary file not shown.

11
marking_gui/bin/Debug/marking_gui.exe.config

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MssqlConnection"
connectionString="Server=localhost;Database=MarkingDb;Trusted_Connection=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

BIN
marking_gui/bin/Debug/marking_gui.pdb

Binary file not shown.

262
marking_gui/marking_gui.csproj

@ -1,125 +1,163 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9E96D5BE-9375-4ED3-82DE-F200F83EBC2F}</ProjectGuid>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<RootNamespace>marking_gui</RootNamespace> <TargetFramework>net9.0-windows</TargetFramework>
<AssemblyName>marking_gui</AssemblyName> <UseWPF>true</UseWPF>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<!-- AssemblyInfo 중복 방지 설정 -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- 32비트 런타임 및 아키텍처 고정 설정 -->
<PlatformTarget>x86</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
<!-- 응용 프로그램 아이콘 설정 -->
<ApplicationIcon>Marking.ico</ApplicationIcon>
<!-- 단일 파일 배포 및 자립 실행(Self-Contained) 설정 -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <!-- .NET 9.0에 필요한 시리얼 통신 및 SQL 클라이언트 NuGet 패키지 추가 -->
<Reference Include="System.Data" /> <PackageReference Include="System.IO.Ports" Version="9.0.0" />
<Reference Include="System.Xml" /> <PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Configuration" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ApplicationDefinition Include="App.xaml"> <Resource Include="Marking.ico" />
<Generator>MSBuild:Compile</Generator> <None Update="Database.ini">
<SubType>Designer</SubType> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="StartupLoginWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="SettingsWindow.xaml.cs">
<DependentUpon>SettingsWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="StartupLoginWindow.xaml.cs">
<DependentUpon>StartupLoginWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Models\Product.cs" />
<Compile Include="Models\MeasurementLimits.cs" />
<Compile Include="Services\MssqlDatabaseService.cs" />
<Compile Include="Services\MockMeasurer.cs" />
<Compile Include="Services\MemoryReaderService.cs" />
<Compile Include="Services\LoginServicesStub.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None> </None>
</ItemGroup> <None Update="Setting.ini">
<ItemGroup> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<None Include="App.config" /> </None>
<None Include="Database.ini"> <None Update="LoginOptions.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 빌드 후 EzCad DLL을 빌드 결과 디렉토리에 복사 (DLL은 libs 하위 폴더로 격리 복사) -->
<Target Name="CopyEzCadFiles" AfterTargets="Build">
<ItemGroup>
<EzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
</ItemGroup>
<MakeDir Directories="$(TargetDir)libs" Condition="!Exists('$(TargetDir)libs')" />
<Copy SourceFiles="@(EzCadDlls)" DestinationFolder="$(TargetDir)libs\" SkipUnchangedFiles="true" />
</Target>
<!-- Publish 단계 완료 후 작동하는 EzCad 파일 복사 Target (libs 하위 폴더로 복사) -->
<Target Name="CopyEzCadFilesToPublish" AfterTargets="Publish">
<ItemGroup>
<PublishEzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
</ItemGroup>
<MakeDir Directories="$(PublishDir)libs" Condition="!Exists('$(PublishDir)libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(PublishDir)libs\" SkipUnchangedFiles="true" />
<MakeDir Directories="$(TargetDir)publish\libs" Condition="!Exists('$(TargetDir)publish\libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(TargetDir)publish\libs\" SkipUnchangedFiles="true" />
</Target>
<!-- C:\marking\ 설치 및 단일 설치 EXE(marking_setup.exe) 제작 전용 Target -->
<!-- C:\marking\ 설치 및 단일 설치 EXE(MarkingSetup.exe) 제작 전용 Target -->
<Target Name="FolderPublish" DependsOnTargets="Publish" AfterTargets="Publish">
<PropertyGroup>
<!-- VS GUI에서 지정한 최종 게시 대상 경로 추출 (기본값은 C:\marking_setup\) -->
<TargetPublishDir Condition="'$(PublishDir)' != ''">$(PublishDir)</TargetPublishDir>
<TargetPublishDir Condition="'$(PublishDir)' == ''">C:\marking_setup\</TargetPublishDir>
<DeployTargetFolder>C:\marking\</DeployTargetFolder>
<PublishBackupDir>$(MSBuildProjectDirectory)\bin\Release\net9.0-windows\win-x86\publish_backup\</PublishBackupDir>
</PropertyGroup>
<Message Text="Deploying clean publish output to $(DeployTargetFolder)..." Importance="high" />
<!-- 1. 기존 대상 디렉토리 클린업 및 재생성 (지저분한 기존 dll 파일들 소거, 실패 시 스킵) -->
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<MakeDir Directories="$(DeployTargetFolder)" />
<MakeDir Directories="$(DeployTargetFolder)libs" />
<!-- 2. 실행 파일, 구성 파일 복사 (VS 게시 출력 폴더로부터 격리 복사) -->
<Copy SourceFiles="$(PublishDir)marking_gui.exe" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(TargetDir)marking_gui.dll.config" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 설정 파일 및 아이콘 복사 (프로젝트 소스 루트에서 직접 이관) -->
<Copy SourceFiles="$(MSBuildProjectDirectory)\Database.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Setting.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\LoginOptions.json" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Marking.ico" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 3. 모든 DLL 라이브러리는 C:\marking\libs\ 폴더로 격리 복사 -->
<ItemGroup>
<EzCadSourceDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
<DeployLibFiles Include="$(PublishDir)libs\*.dll" />
<TargetLibFiles Include="$(TargetDir)libs\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(EzCadSourceDlls)" DestinationFolder="$(DeployTargetFolder)libs\" SkipUnchangedFiles="true" />
<Copy SourceFiles="@(DeployLibFiles)" DestinationFolder="$(DeployTargetFolder)libs\" SkipUnchangedFiles="true" />
<Copy SourceFiles="@(TargetLibFiles)" DestinationFolder="$(DeployTargetFolder)libs\" SkipUnchangedFiles="true" />
<!-- [WiX Toolset 단일 MSI 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer MSI via WiX Toolset..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Product.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.msi&quot;" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- [WiX Toolset 단일 EXE 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer EXE via WiX Toolset Bundle..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Bundle.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.exe&quot; -ext WixToolset.BootstrapperApplications.wixext/5.0.2" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- 빌드 중간 산출물 .wixpdb 및 MSI 파일 정리 -->
<Delete Files="$(DeployTargetFolder)MarkingSetup.wixpdb" />
<Delete Files="$(DeployTargetFolder)MarkingSetup.msi" />
<!-- 비주얼 스튜디오 게시 출력 폴더(TargetPublishDir 및 PublishDir) 정화 작업 -->
<Message Text="Purging publish output directories and leaving only MarkingSetup.exe..." Importance="high" />
<!-- 1. MarkingSetup.exe 파일을 임시 대피 경로로 복사 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<MakeDir Directories="$(PublishBackupDir)" />
<Copy SourceFiles="$(DeployTargetFolder)MarkingSetup.exe" DestinationFolder="$(PublishBackupDir)" />
<!-- 2. 게시 폴더(TargetPublishDir 및 내부 PublishDir) 하위의 모든 자원 삭제 (dll, config, json, exe, libs 폴더 등) -->
<ItemGroup>
<FilesToClean Include="$(TargetPublishDir)*.dll" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.exe" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.config" />
<FilesToClean Include="$(TargetPublishDir)*.json" />
<FilesToClean Include="$(TargetPublishDir)*.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.ini" />
<!-- 내부 PublishDir 하위 찌꺼기 파일 클린업 (중요!) -->
<FilesToClean Include="$(PublishDir)*.dll" />
<FilesToClean Include="$(PublishDir)marking_gui.exe" />
<FilesToClean Include="$(PublishDir)marking_gui.pdb" />
<FilesToClean Include="$(PublishDir)*.config" />
<FilesToClean Include="$(PublishDir)*.json" />
<FilesToClean Include="$(PublishDir)*.pdb" />
<FilesToClean Include="$(PublishDir)*.ini" />
</ItemGroup>
<Delete Files="@(FilesToClean)" ContinueOnError="true" />
<!-- libs 및 기타 번역 리소스 디렉토리 완전 소거 (TargetPublishDir 및 PublishDir 양쪽 모두) -->
<RemoveDir Directories="$(TargetPublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(TargetPublishDir)cs;$(TargetPublishDir)de;$(TargetPublishDir)es;$(TargetPublishDir)fr;$(TargetPublishDir)it;$(TargetPublishDir)ja;$(TargetPublishDir)ko;$(TargetPublishDir)pl;$(TargetPublishDir)pt-BR;$(TargetPublishDir)ru;$(TargetPublishDir)tr;$(TargetPublishDir)zh-Hans;$(TargetPublishDir)zh-Hant" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)cs;$(PublishDir)de;$(PublishDir)es;$(PublishDir)fr;$(PublishDir)it;$(PublishDir)ja;$(PublishDir)ko;$(PublishDir)pl;$(PublishDir)pt-BR;$(PublishDir)ru;$(PublishDir)tr;$(PublishDir)zh-Hans;$(PublishDir)zh-Hant" ContinueOnError="true" />
<!-- 3. 대피시켰던 단일 설치 EXE 파일만 다시 게시 폴더(TargetPublishDir 및 PublishDir)로 원복 이관 -->
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(TargetPublishDir)" />
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(PublishDir)" />
<!-- 4. 대피 임시 폴더 및 조립 폴더 C:\marking 삭제 정리 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<Message Text="Single Installer successfully compiled at both C:\marking\MarkingSetup.exe and $(TargetPublishDir)MarkingSetup.exe" Importance="high" />
</Target>
</Project> </Project>

363
marking_gui/marking_gui_1wcr445j_wpftmp.csproj

@ -0,0 +1,363 @@
<Project>
<PropertyGroup>
<AssemblyName>marking_gui</AssemblyName>
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath>C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\</MSBuildProjectExtensionsPath>
<_TargetAssemblyProjectName>marking_gui</_TargetAssemblyProjectName>
<RootNamespace>marking_gui</RootNamespace>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<LangVersion>10.0</LangVersion>
<Deterministic>true</Deterministic>
<!-- AssemblyInfo 중복 방지 설정 -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- 32비트 런타임 및 아키텍처 고정 설정 -->
<PlatformTarget>x86</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
<!-- 단일 파일 배포 및 자립 실행(Self-Contained) 설정 -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<!-- .NET 9.0에 필요한 시리얼 통신 및 SQL 클라이언트 NuGet 패키지 추가 -->
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
</ItemGroup>
<ItemGroup>
<None Update="Database.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Setting.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<!-- 빌드 후 EzCad DLL 및 EasyCfg 폴더를 빌드 결과 디렉토리에 복사 (DLL은 libs 하위 폴더로 격리 복사) -->
<Target Name="CopyEzCadFiles" AfterTargets="Build">
<ItemGroup>
<EzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
<EzCadEasyCfg Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\EasyCfg\**\*.*" />
</ItemGroup>
<MakeDir Directories="$(TargetDir)libs" Condition="!Exists('$(TargetDir)libs')" />
<Copy SourceFiles="@(EzCadDlls)" DestinationFolder="$(TargetDir)libs\" SkipUnchangedFiles="true" />
<Copy SourceFiles="@(EzCadEasyCfg)" DestinationFiles="@(EzCadEasyCfg-&gt;'$(TargetDir)EasyCfg\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<!-- Publish 단계 완료 후 작동하는 EzCad 파일 복사 Target (libs 하위 폴더로 복사) -->
<Target Name="CopyEzCadFilesToPublish" AfterTargets="Publish">
<ItemGroup>
<PublishEzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
</ItemGroup>
<MakeDir Directories="$(PublishDir)libs" Condition="!Exists('$(PublishDir)libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(PublishDir)libs\" SkipUnchangedFiles="true" />
<MakeDir Directories="$(TargetDir)publish\libs" Condition="!Exists('$(TargetDir)publish\libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(TargetDir)publish\libs\" SkipUnchangedFiles="true" />
</Target>
<!-- C:\marking\ 설치 및 단일 설치 EXE(marking_setup.exe) 제작 전용 Target -->
<!-- C:\marking\ 설치 및 단일 설치 EXE(MarkingSetup.exe) 제작 전용 Target -->
<Target Name="FolderPublish" DependsOnTargets="Publish" AfterTargets="Publish">
<PropertyGroup>
<!-- VS GUI에서 지정한 최종 게시 대상 경로 추출 (기본값은 C:\marking_setup\) -->
<TargetPublishDir Condition="'$(PublishDir)' != ''">$(PublishDir)</TargetPublishDir>
<TargetPublishDir Condition="'$(PublishDir)' == ''">C:\marking_setup\</TargetPublishDir>
<DeployTargetFolder>C:\marking\</DeployTargetFolder>
<PublishBackupDir>$(MSBuildProjectDirectory)\bin\Release\net9.0-windows\win-x86\publish_backup\</PublishBackupDir>
</PropertyGroup>
<Message Text="Deploying clean publish output to $(DeployTargetFolder)..." Importance="high" />
<!-- 1. 기존 대상 디렉토리 클린업 및 재생성 (지저분한 기존 dll 파일들 소거, 실패 시 스킵) -->
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<MakeDir Directories="$(DeployTargetFolder)" />
<MakeDir Directories="$(DeployTargetFolder)libs" />
<!-- 2. 실행 파일, 구성 파일 복사 (VS 게시 출력 폴더로부터 격리 복사) -->
<Copy SourceFiles="$(PublishDir)marking_gui.exe" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(TargetDir)marking_gui.dll.config" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 설정 파일 복사 (프로젝트 소스 루트에서 직접 이관) -->
<Copy SourceFiles="$(MSBuildProjectDirectory)\Database.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Setting.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 3. 모든 DLL 라이브러리는 C:\marking\libs\ 폴더로 격리 복사 -->
<ItemGroup>
<DeployLibFiles Include="$(PublishDir)libs\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(DeployLibFiles)" DestinationFolder="$(DeployTargetFolder)libs\" SkipUnchangedFiles="true" />
<!-- 4. EasyCfg 폴더 및 도안 파일 복사 -->
<ItemGroup>
<DeployEasyCfg Include="$(TargetDir)EasyCfg\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(DeployEasyCfg)" DestinationFiles="@(DeployEasyCfg-&gt;'$(DeployTargetFolder)EasyCfg\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
<!-- [WiX Toolset 단일 MSI 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer MSI via WiX Toolset..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Product.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.msi&quot;" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- [WiX Toolset 단일 EXE 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer EXE via WiX Toolset Bundle..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Bundle.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.exe&quot; -ext WixToolset.BootstrapperApplications.wixext/5.0.2" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- 빌드 중간 산출물 .wixpdb 및 MSI 파일 정리 -->
<Delete Files="$(DeployTargetFolder)MarkingSetup.wixpdb" />
<Delete Files="$(DeployTargetFolder)MarkingSetup.msi" />
<!-- 비주얼 스튜디오 게시 출력 폴더(TargetPublishDir 및 PublishDir) 정화 작업 -->
<Message Text="Purging publish output directories and leaving only MarkingSetup.exe..." Importance="high" />
<!-- 1. MarkingSetup.exe 파일을 임시 대피 경로로 복사 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<MakeDir Directories="$(PublishBackupDir)" />
<Copy SourceFiles="$(DeployTargetFolder)MarkingSetup.exe" DestinationFolder="$(PublishBackupDir)" />
<!-- 2. 게시 폴더(TargetPublishDir 및 내부 PublishDir) 하위의 모든 자원 삭제 (dll, config, json, exe, libs 폴더 등) -->
<ItemGroup>
<FilesToClean Include="$(TargetPublishDir)*.dll" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.exe" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.config" />
<FilesToClean Include="$(TargetPublishDir)*.json" />
<FilesToClean Include="$(TargetPublishDir)*.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.ini" />
<!-- 내부 PublishDir 하위 찌꺼기 파일 클린업 (중요!) -->
<FilesToClean Include="$(PublishDir)*.dll" />
<FilesToClean Include="$(PublishDir)marking_gui.exe" />
<FilesToClean Include="$(PublishDir)marking_gui.pdb" />
<FilesToClean Include="$(PublishDir)*.config" />
<FilesToClean Include="$(PublishDir)*.json" />
<FilesToClean Include="$(PublishDir)*.pdb" />
<FilesToClean Include="$(PublishDir)*.ini" />
</ItemGroup>
<Delete Files="@(FilesToClean)" ContinueOnError="true" />
<!-- libs 및 기타 번역 리소스 디렉토리 완전 소거 (TargetPublishDir 및 PublishDir 양쪽 모두) -->
<RemoveDir Directories="$(TargetPublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(TargetPublishDir)cs;$(TargetPublishDir)de;$(TargetPublishDir)es;$(TargetPublishDir)fr;$(TargetPublishDir)it;$(TargetPublishDir)ja;$(TargetPublishDir)ko;$(TargetPublishDir)pl;$(TargetPublishDir)pt-BR;$(TargetPublishDir)ru;$(TargetPublishDir)tr;$(TargetPublishDir)zh-Hans;$(TargetPublishDir)zh-Hant" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)cs;$(PublishDir)de;$(PublishDir)es;$(PublishDir)fr;$(PublishDir)it;$(PublishDir)ja;$(PublishDir)ko;$(PublishDir)pl;$(PublishDir)pt-BR;$(PublishDir)ru;$(PublishDir)tr;$(PublishDir)zh-Hans;$(PublishDir)zh-Hant" ContinueOnError="true" />
<!-- 3. 대피시켰던 단일 설치 EXE 파일만 다시 게시 폴더(TargetPublishDir 및 PublishDir)로 원복 이관 -->
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(TargetPublishDir)" />
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(PublishDir)" />
<!-- 4. 대피 임시 폴더 및 조립 폴더 C:\marking 삭제 정리 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<Message Text="Single Installer successfully compiled at both C:\marking\MarkingSetup.exe and $(TargetPublishDir)MarkingSetup.exe" Importance="high" />
</Target>
<ItemGroup>
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Accessibility.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.CSharp.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.VisualBasic.Core.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.VisualBasic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Registry.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Registry.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.SystemEvents.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\mscorlib.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\netstandard.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationCore.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Aero.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Aero2.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.AeroLite.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Classic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Luna.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Royale.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationUI.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\ReachFramework.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.AppContext.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Buffers.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.CodeDom.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Concurrent.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Immutable.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.NonGeneric.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Specialized.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.Annotations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.DataAnnotations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.EventBasedAsync.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.TypeConverter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Configuration.ConfigurationManager.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Configuration.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Console.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Core.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.Common.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.DataSetExtensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.dll" />
<ReferencePath Include="C:\Users\COMPUTER1\.nuget\packages\system.data.sqlclient\4.9.0\lib\net8.0\System.Data.SqlClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Contracts.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Debug.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.DiagnosticSource.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.EventLog.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.FileVersionInfo.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.PerformanceCounter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Process.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.StackTrace.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.TextWriterTraceListener.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Tools.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.TraceSource.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Tracing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.DirectoryServices.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Drawing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Drawing.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Dynamic.Runtime.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Formats.Asn1.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Formats.Nrbf.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Formats.Tar.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.Calendars.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.Brotli.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.FileSystem.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.ZipFile.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.DriveInfo.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.Watcher.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.IsolatedStorage.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.MemoryMappedFiles.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.IO.Packaging.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipelines.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipes.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipes.dll" />
<ReferencePath Include="C:\Users\COMPUTER1\.nuget\packages\system.io.ports\9.0.0\lib\net9.0\System.IO.Ports.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.UnmanagedMemoryStream.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Expressions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Parallel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Queryable.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Memory.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Http.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Http.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.HttpListener.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Mail.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.NameResolution.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.NetworkInformation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Ping.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Quic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Requests.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Security.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.ServicePoint.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Sockets.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebHeaderCollection.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebProxy.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebSockets.Client.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebSockets.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Numerics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Numerics.Vectors.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ObjectModel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Printing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.DispatchProxy.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.ILGeneration.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.Lightweight.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Metadata.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.TypeExtensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Resources.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.Reader.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.ResourceManager.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.Writer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.CompilerServices.Unsafe.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.CompilerServices.VisualC.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Handles.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.JavaScript.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.RuntimeInformation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Intrinsics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Loader.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Numerics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Formatters.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Claims.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Algorithms.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Cng.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Csp.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Encoding.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.OpenSsl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Pkcs.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.ProtectedData.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.X509Certificates.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Permissions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Principal.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Principal.Windows.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.SecureString.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ServiceModel.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ServiceProcess.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.CodePages.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encodings.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.RegularExpressions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Threading.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Channels.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Overlapped.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Dataflow.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Parallel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Thread.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.ThreadPool.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Timer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Transactions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Transactions.Local.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ValueTuple.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Web.HttpUtility.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Controls.Ribbon.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Windows.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Input.Manipulations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Presentation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Xaml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.Linq.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.ReaderWriter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.Serialization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XmlDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XmlSerializer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XPath.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XPath.XDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationClientSideProviders.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationProvider.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationTypes.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\WindowsBase.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\MainWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\PortSettingsWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\SettingsWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\StartupLoginWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\App.g.cs" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll" />
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.NetAnalyzers.dll" />
<Analyzer Include="C:\Users\COMPUTER1\.nuget\packages\microsoft.net.illink.tasks\9.0.17\analyzers\dotnet\cs\ILLink.CodeFixProvider.dll" />
<Analyzer Include="C:\Users\COMPUTER1\.nuget\packages\microsoft.net.illink.tasks\9.0.17\analyzers\dotnet\cs\ILLink.RoslynAnalyzer.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.ComInterfaceGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.SourceGeneration.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

BIN
marking_gui/marking_gui_TemporaryKey.pfx

Binary file not shown.

363
marking_gui/marking_gui_colf0m00_wpftmp.csproj

@ -0,0 +1,363 @@
<Project>
<PropertyGroup>
<AssemblyName>marking_gui</AssemblyName>
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath>C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\</MSBuildProjectExtensionsPath>
<_TargetAssemblyProjectName>marking_gui</_TargetAssemblyProjectName>
<RootNamespace>marking_gui</RootNamespace>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<LangVersion>10.0</LangVersion>
<Deterministic>true</Deterministic>
<!-- AssemblyInfo 중복 방지 설정 -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- 32비트 런타임 및 아키텍처 고정 설정 -->
<PlatformTarget>x86</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
<!-- 단일 파일 배포 및 자립 실행(Self-Contained) 설정 -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<!-- .NET 9.0에 필요한 시리얼 통신 및 SQL 클라이언트 NuGet 패키지 추가 -->
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
</ItemGroup>
<ItemGroup>
<None Update="Database.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Setting.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<!-- 빌드 후 EzCad DLL 및 EasyCfg 폴더를 빌드 결과 디렉토리에 복사 (DLL은 libs 하위 폴더로 격리 복사) -->
<Target Name="CopyEzCadFiles" AfterTargets="Build">
<ItemGroup>
<EzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
<EzCadEasyCfg Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\EasyCfg\**\*.*" />
</ItemGroup>
<MakeDir Directories="$(TargetDir)libs" Condition="!Exists('$(TargetDir)libs')" />
<Copy SourceFiles="@(EzCadDlls)" DestinationFolder="$(TargetDir)libs\" SkipUnchangedFiles="true" />
<Copy SourceFiles="@(EzCadEasyCfg)" DestinationFiles="@(EzCadEasyCfg-&gt;'$(TargetDir)EasyCfg\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<!-- Publish 단계 완료 후 작동하는 EzCad 파일 복사 Target (libs 하위 폴더로 복사) -->
<Target Name="CopyEzCadFilesToPublish" AfterTargets="Publish">
<ItemGroup>
<PublishEzCadDlls Include="C:\Users\COMPUTER1\Desktop\mobi\marking\EzCad\*.dll" />
</ItemGroup>
<MakeDir Directories="$(PublishDir)libs" Condition="!Exists('$(PublishDir)libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(PublishDir)libs\" SkipUnchangedFiles="true" />
<MakeDir Directories="$(TargetDir)publish\libs" Condition="!Exists('$(TargetDir)publish\libs')" />
<Copy SourceFiles="@(PublishEzCadDlls)" DestinationFolder="$(TargetDir)publish\libs\" SkipUnchangedFiles="true" />
</Target>
<!-- C:\marking\ 설치 및 단일 설치 EXE(marking_setup.exe) 제작 전용 Target -->
<!-- C:\marking\ 설치 및 단일 설치 EXE(MarkingSetup.exe) 제작 전용 Target -->
<Target Name="FolderPublish" DependsOnTargets="Publish" AfterTargets="Publish">
<PropertyGroup>
<!-- VS GUI에서 지정한 최종 게시 대상 경로 추출 (기본값은 C:\marking_setup\) -->
<TargetPublishDir Condition="'$(PublishDir)' != ''">$(PublishDir)</TargetPublishDir>
<TargetPublishDir Condition="'$(PublishDir)' == ''">C:\marking_setup\</TargetPublishDir>
<DeployTargetFolder>C:\marking\</DeployTargetFolder>
<PublishBackupDir>$(MSBuildProjectDirectory)\bin\Release\net9.0-windows\win-x86\publish_backup\</PublishBackupDir>
</PropertyGroup>
<Message Text="Deploying clean publish output to $(DeployTargetFolder)..." Importance="high" />
<!-- 1. 기존 대상 디렉토리 클린업 및 재생성 (지저분한 기존 dll 파일들 소거, 실패 시 스킵) -->
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<MakeDir Directories="$(DeployTargetFolder)" />
<MakeDir Directories="$(DeployTargetFolder)libs" />
<!-- 2. 실행 파일, 구성 파일 복사 (VS 게시 출력 폴더로부터 격리 복사) -->
<Copy SourceFiles="$(PublishDir)marking_gui.exe" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(TargetDir)marking_gui.dll.config" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 설정 파일 복사 (프로젝트 소스 루트에서 직접 이관) -->
<Copy SourceFiles="$(MSBuildProjectDirectory)\Database.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Setting.ini" DestinationFolder="$(DeployTargetFolder)" SkipUnchangedFiles="true" />
<!-- 3. 모든 DLL 라이브러리는 C:\marking\libs\ 폴더로 격리 복사 -->
<ItemGroup>
<DeployLibFiles Include="$(PublishDir)libs\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(DeployLibFiles)" DestinationFolder="$(DeployTargetFolder)libs\" SkipUnchangedFiles="true" />
<!-- 4. EasyCfg 폴더 및 도안 파일 복사 -->
<ItemGroup>
<DeployEasyCfg Include="$(TargetDir)EasyCfg\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(DeployEasyCfg)" DestinationFiles="@(DeployEasyCfg-&gt;'$(DeployTargetFolder)EasyCfg\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
<!-- [WiX Toolset 단일 MSI 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer MSI via WiX Toolset..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Product.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.msi&quot;" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- [WiX Toolset 단일 EXE 설치 파일 자동 빌드 스텝] -->
<Message Text="Compiling single installer EXE via WiX Toolset Bundle..." Importance="high" />
<Exec Command="wix build &quot;$(MSBuildProjectDirectory)\..\marking_gui.Setup\Bundle.wxs&quot; -o &quot;$(DeployTargetFolder)MarkingSetup.exe&quot; -ext WixToolset.BootstrapperApplications.wixext/5.0.2" WorkingDirectory="$(MSBuildProjectDirectory)" />
<!-- 빌드 중간 산출물 .wixpdb 및 MSI 파일 정리 -->
<Delete Files="$(DeployTargetFolder)MarkingSetup.wixpdb" />
<Delete Files="$(DeployTargetFolder)MarkingSetup.msi" />
<!-- 비주얼 스튜디오 게시 출력 폴더(TargetPublishDir 및 PublishDir) 정화 작업 -->
<Message Text="Purging publish output directories and leaving only MarkingSetup.exe..." Importance="high" />
<!-- 1. MarkingSetup.exe 파일을 임시 대피 경로로 복사 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<MakeDir Directories="$(PublishBackupDir)" />
<Copy SourceFiles="$(DeployTargetFolder)MarkingSetup.exe" DestinationFolder="$(PublishBackupDir)" />
<!-- 2. 게시 폴더(TargetPublishDir 및 내부 PublishDir) 하위의 모든 자원 삭제 (dll, config, json, exe, libs 폴더 등) -->
<ItemGroup>
<FilesToClean Include="$(TargetPublishDir)*.dll" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.exe" />
<FilesToClean Include="$(TargetPublishDir)marking_gui.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.config" />
<FilesToClean Include="$(TargetPublishDir)*.json" />
<FilesToClean Include="$(TargetPublishDir)*.pdb" />
<FilesToClean Include="$(TargetPublishDir)*.ini" />
<!-- 내부 PublishDir 하위 찌꺼기 파일 클린업 (중요!) -->
<FilesToClean Include="$(PublishDir)*.dll" />
<FilesToClean Include="$(PublishDir)marking_gui.exe" />
<FilesToClean Include="$(PublishDir)marking_gui.pdb" />
<FilesToClean Include="$(PublishDir)*.config" />
<FilesToClean Include="$(PublishDir)*.json" />
<FilesToClean Include="$(PublishDir)*.pdb" />
<FilesToClean Include="$(PublishDir)*.ini" />
</ItemGroup>
<Delete Files="@(FilesToClean)" ContinueOnError="true" />
<!-- libs 및 기타 번역 리소스 디렉토리 완전 소거 (TargetPublishDir 및 PublishDir 양쪽 모두) -->
<RemoveDir Directories="$(TargetPublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(TargetPublishDir)cs;$(TargetPublishDir)de;$(TargetPublishDir)es;$(TargetPublishDir)fr;$(TargetPublishDir)it;$(TargetPublishDir)ja;$(TargetPublishDir)ko;$(TargetPublishDir)pl;$(TargetPublishDir)pt-BR;$(TargetPublishDir)ru;$(TargetPublishDir)tr;$(TargetPublishDir)zh-Hans;$(TargetPublishDir)zh-Hant" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)libs" ContinueOnError="true" />
<RemoveDir Directories="$(PublishDir)cs;$(PublishDir)de;$(PublishDir)es;$(PublishDir)fr;$(PublishDir)it;$(PublishDir)ja;$(PublishDir)ko;$(PublishDir)pl;$(PublishDir)pt-BR;$(PublishDir)ru;$(PublishDir)tr;$(PublishDir)zh-Hans;$(PublishDir)zh-Hant" ContinueOnError="true" />
<!-- 3. 대피시켰던 단일 설치 EXE 파일만 다시 게시 폴더(TargetPublishDir 및 PublishDir)로 원복 이관 -->
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(TargetPublishDir)" />
<Copy SourceFiles="$(PublishBackupDir)MarkingSetup.exe" DestinationFolder="$(PublishDir)" />
<!-- 4. 대피 임시 폴더 및 조립 폴더 C:\marking 삭제 정리 -->
<RemoveDir Directories="$(PublishBackupDir)" ContinueOnError="true" />
<RemoveDir Directories="$(DeployTargetFolder)" ContinueOnError="true" />
<Message Text="Single Installer successfully compiled at both C:\marking\MarkingSetup.exe and $(TargetPublishDir)MarkingSetup.exe" Importance="high" />
</Target>
<ItemGroup>
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Accessibility.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.CSharp.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.VisualBasic.Core.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.VisualBasic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Registry.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.Registry.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\Microsoft.Win32.SystemEvents.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\mscorlib.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\netstandard.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationCore.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Aero.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Aero2.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.AeroLite.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Classic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Luna.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationFramework.Royale.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\PresentationUI.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\ReachFramework.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.AppContext.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Buffers.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.CodeDom.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Concurrent.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Immutable.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.NonGeneric.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Collections.Specialized.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.Annotations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.DataAnnotations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.EventBasedAsync.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ComponentModel.TypeConverter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Configuration.ConfigurationManager.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Configuration.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Console.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Core.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.Common.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.DataSetExtensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Data.dll" />
<ReferencePath Include="C:\Users\COMPUTER1\.nuget\packages\system.data.sqlclient\4.9.0\lib\net8.0\System.Data.SqlClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Contracts.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Debug.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.DiagnosticSource.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.EventLog.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.FileVersionInfo.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.PerformanceCounter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Process.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.StackTrace.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.TextWriterTraceListener.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Tools.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.TraceSource.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Diagnostics.Tracing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.DirectoryServices.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Drawing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Drawing.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Dynamic.Runtime.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Formats.Asn1.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Formats.Nrbf.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Formats.Tar.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.Calendars.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Globalization.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.Brotli.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.FileSystem.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Compression.ZipFile.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.DriveInfo.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.FileSystem.Watcher.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.IsolatedStorage.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.MemoryMappedFiles.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.IO.Packaging.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipelines.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipes.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.Pipes.dll" />
<ReferencePath Include="C:\Users\COMPUTER1\.nuget\packages\system.io.ports\9.0.0\lib\net9.0\System.IO.Ports.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.IO.UnmanagedMemoryStream.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Expressions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Parallel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Linq.Queryable.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Memory.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Http.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Http.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.HttpListener.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Mail.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.NameResolution.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.NetworkInformation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Ping.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Quic.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Requests.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Security.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.ServicePoint.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.Sockets.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebHeaderCollection.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebProxy.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebSockets.Client.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Net.WebSockets.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Numerics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Numerics.Vectors.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ObjectModel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Printing.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.DispatchProxy.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.ILGeneration.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Emit.Lightweight.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Metadata.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Reflection.TypeExtensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Resources.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.Reader.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.ResourceManager.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Resources.Writer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.CompilerServices.Unsafe.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.CompilerServices.VisualC.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Handles.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.JavaScript.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.InteropServices.RuntimeInformation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Intrinsics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Loader.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Numerics.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Formatters.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Runtime.Serialization.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Claims.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Algorithms.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Cng.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Csp.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Encoding.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.OpenSsl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Pkcs.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Primitives.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.ProtectedData.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.X509Certificates.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Cryptography.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Security.Permissions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Principal.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.Principal.Windows.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Security.SecureString.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ServiceModel.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ServiceProcess.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.CodePages.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encoding.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Encodings.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.Json.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Text.RegularExpressions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Threading.AccessControl.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Channels.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Overlapped.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Dataflow.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Tasks.Parallel.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Thread.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.ThreadPool.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Threading.Timer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Transactions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Transactions.Local.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.ValueTuple.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Web.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Web.HttpUtility.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Controls.Ribbon.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Windows.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Extensions.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Input.Manipulations.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Windows.Presentation.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\System.Xaml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.Linq.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.ReaderWriter.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.Serialization.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XmlDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XmlSerializer.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XPath.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\ref\net9.0\System.Xml.XPath.XDocument.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationClient.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationClientSideProviders.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationProvider.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\UIAutomationTypes.dll" />
<ReferencePath Include="C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\9.0.17\ref\net9.0\WindowsBase.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\MainWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\PortSettingsWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\SettingsWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\StartupLoginWindow.g.cs" />
<Compile Include="C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\net9.0-windows\win-x86\App.g.cs" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll" />
<Analyzer Include="C:\Program Files\dotnet\sdk\10.0.301\Sdks\Microsoft.NET.Sdk\targets\..\analyzers\Microsoft.CodeAnalysis.NetAnalyzers.dll" />
<Analyzer Include="C:\Users\COMPUTER1\.nuget\packages\microsoft.net.illink.tasks\9.0.17\analyzers\dotnet\cs\ILLink.CodeFixProvider.dll" />
<Analyzer Include="C:\Users\COMPUTER1\.nuget\packages\microsoft.net.illink.tasks\9.0.17\analyzers\dotnet\cs\ILLink.RoslynAnalyzer.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.ComInterfaceGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/Microsoft.Interop.SourceGeneration.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll" />
<Analyzer Include="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.17\analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

4
marking_gui/obj/Debug/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs

@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]

BIN
marking_gui/obj/Debug/App.baml

Binary file not shown.

77
marking_gui/obj/Debug/App.g.cs

@ -1,77 +0,0 @@
#pragma checksum "..\..\App.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "7901E81250AFB6085C580D5F91E3114E104640F12456214E674506A1451A61E4"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using marking_gui;
namespace marking_gui {
/// <summary>
/// App
/// </summary>
public partial class App : System.Windows.Application {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/app.xaml", System.UriKind.Relative);
#line 1 "..\..\App.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public static void Main() {
marking_gui.App app = new marking_gui.App();
app.InitializeComponent();
app.Run();
}
}
}

77
marking_gui/obj/Debug/App.g.i.cs

@ -1,77 +0,0 @@
#pragma checksum "..\..\App.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "7901E81250AFB6085C580D5F91E3114E104640F12456214E674506A1451A61E4"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using marking_gui;
namespace marking_gui {
/// <summary>
/// App
/// </summary>
public partial class App : System.Windows.Application {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/app.xaml", System.UriKind.Relative);
#line 1 "..\..\App.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public static void Main() {
marking_gui.App app = new marking_gui.App();
app.InitializeComponent();
app.Run();
}
}
}

BIN
marking_gui/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache

Binary file not shown.

BIN
marking_gui/obj/Debug/MainWindow.baml

Binary file not shown.

387
marking_gui/obj/Debug/MainWindow.g.cs

@ -1,387 +0,0 @@
#pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "AD4A4C06745EFEC612E5A40E8825C3B16CE481532342C19F0C009E1B56E7E5F4"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using marking_gui;
namespace marking_gui {
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 138 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrentTime;
#line default
#line hidden
#line 139 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtSystemStatus;
#line default
#line hidden
#line 146 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtOperatorInfo;
#line default
#line hidden
#line 153 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtModelInfo;
#line default
#line hidden
#line 160 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtLineInfo;
#line default
#line hidden
#line 163 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnLogout;
#line default
#line hidden
#line 170 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnMaximize;
#line default
#line hidden
#line 171 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtMaximizeIcon;
#line default
#line hidden
#line 201 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtSerial;
#line default
#line hidden
#line 232 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevHousing;
#line default
#line hidden
#line 243 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevCal;
#line default
#line hidden
#line 254 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevEol;
#line default
#line hidden
#line 289 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtVoltage;
#line default
#line hidden
#line 293 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtVoltageRangeGuide;
#line default
#line hidden
#line 308 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrent;
#line default
#line hidden
#line 312 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrentRangeGuide;
#line default
#line hidden
#line 327 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtResistance;
#line default
#line hidden
#line 331 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtResistanceRangeGuide;
#line default
#line hidden
#line 337 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border borderFinalResult;
#line default
#line hidden
#line 346 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtFinalResult;
#line default
#line hidden
#line 347 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtFinalSerial;
#line default
#line hidden
#line 357 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnStart;
#line default
#line hidden
#line 366 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnOpenSettings;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
#line 11 "..\..\MainWindow.xaml"
((marking_gui.MainWindow)(target)).ContentRendered += new System.EventHandler(this.Window_ContentRendered);
#line default
#line hidden
return;
case 2:
#line 129 "..\..\MainWindow.xaml"
((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown);
#line default
#line hidden
return;
case 3:
this.txtCurrentTime = ((System.Windows.Controls.TextBlock)(target));
return;
case 4:
this.txtSystemStatus = ((System.Windows.Controls.TextBlock)(target));
return;
case 5:
this.txtOperatorInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 6:
this.txtModelInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 7:
this.txtLineInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 8:
this.btnLogout = ((System.Windows.Controls.Button)(target));
#line 163 "..\..\MainWindow.xaml"
this.btnLogout.Click += new System.Windows.RoutedEventHandler(this.btnLogout_Click);
#line default
#line hidden
return;
case 9:
#line 167 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click);
#line default
#line hidden
return;
case 10:
this.btnMaximize = ((System.Windows.Controls.Button)(target));
#line 170 "..\..\MainWindow.xaml"
this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click);
#line default
#line hidden
return;
case 11:
this.txtMaximizeIcon = ((System.Windows.Controls.TextBlock)(target));
return;
case 12:
#line 173 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnClose_Click);
#line default
#line hidden
return;
case 13:
this.txtSerial = ((System.Windows.Controls.TextBox)(target));
return;
case 14:
this.txtPrevHousing = ((System.Windows.Controls.TextBlock)(target));
return;
case 15:
this.txtPrevCal = ((System.Windows.Controls.TextBlock)(target));
return;
case 16:
this.txtPrevEol = ((System.Windows.Controls.TextBlock)(target));
return;
case 17:
this.txtVoltage = ((System.Windows.Controls.TextBlock)(target));
return;
case 18:
this.txtVoltageRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 19:
this.txtCurrent = ((System.Windows.Controls.TextBlock)(target));
return;
case 20:
this.txtCurrentRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 21:
this.txtResistance = ((System.Windows.Controls.TextBlock)(target));
return;
case 22:
this.txtResistanceRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 23:
this.borderFinalResult = ((System.Windows.Controls.Border)(target));
return;
case 24:
this.txtFinalResult = ((System.Windows.Controls.TextBlock)(target));
return;
case 25:
this.txtFinalSerial = ((System.Windows.Controls.TextBlock)(target));
return;
case 26:
this.btnStart = ((System.Windows.Controls.Button)(target));
#line 358 "..\..\MainWindow.xaml"
this.btnStart.Click += new System.Windows.RoutedEventHandler(this.btnStart_Click);
#line default
#line hidden
return;
case 27:
this.btnOpenSettings = ((System.Windows.Controls.Button)(target));
#line 367 "..\..\MainWindow.xaml"
this.btnOpenSettings.Click += new System.Windows.RoutedEventHandler(this.btnOpenSettings_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

387
marking_gui/obj/Debug/MainWindow.g.i.cs

@ -1,387 +0,0 @@
#pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "AD4A4C06745EFEC612E5A40E8825C3B16CE481532342C19F0C009E1B56E7E5F4"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using marking_gui;
namespace marking_gui {
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 138 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrentTime;
#line default
#line hidden
#line 139 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtSystemStatus;
#line default
#line hidden
#line 146 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtOperatorInfo;
#line default
#line hidden
#line 153 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtModelInfo;
#line default
#line hidden
#line 160 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtLineInfo;
#line default
#line hidden
#line 163 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnLogout;
#line default
#line hidden
#line 170 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnMaximize;
#line default
#line hidden
#line 171 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtMaximizeIcon;
#line default
#line hidden
#line 201 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtSerial;
#line default
#line hidden
#line 232 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevHousing;
#line default
#line hidden
#line 243 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevCal;
#line default
#line hidden
#line 254 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtPrevEol;
#line default
#line hidden
#line 289 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtVoltage;
#line default
#line hidden
#line 293 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtVoltageRangeGuide;
#line default
#line hidden
#line 308 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrent;
#line default
#line hidden
#line 312 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtCurrentRangeGuide;
#line default
#line hidden
#line 327 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtResistance;
#line default
#line hidden
#line 331 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtResistanceRangeGuide;
#line default
#line hidden
#line 337 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border borderFinalResult;
#line default
#line hidden
#line 346 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtFinalResult;
#line default
#line hidden
#line 347 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtFinalSerial;
#line default
#line hidden
#line 357 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnStart;
#line default
#line hidden
#line 366 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnOpenSettings;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
#line 11 "..\..\MainWindow.xaml"
((marking_gui.MainWindow)(target)).ContentRendered += new System.EventHandler(this.Window_ContentRendered);
#line default
#line hidden
return;
case 2:
#line 129 "..\..\MainWindow.xaml"
((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown);
#line default
#line hidden
return;
case 3:
this.txtCurrentTime = ((System.Windows.Controls.TextBlock)(target));
return;
case 4:
this.txtSystemStatus = ((System.Windows.Controls.TextBlock)(target));
return;
case 5:
this.txtOperatorInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 6:
this.txtModelInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 7:
this.txtLineInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 8:
this.btnLogout = ((System.Windows.Controls.Button)(target));
#line 163 "..\..\MainWindow.xaml"
this.btnLogout.Click += new System.Windows.RoutedEventHandler(this.btnLogout_Click);
#line default
#line hidden
return;
case 9:
#line 167 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click);
#line default
#line hidden
return;
case 10:
this.btnMaximize = ((System.Windows.Controls.Button)(target));
#line 170 "..\..\MainWindow.xaml"
this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click);
#line default
#line hidden
return;
case 11:
this.txtMaximizeIcon = ((System.Windows.Controls.TextBlock)(target));
return;
case 12:
#line 173 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnClose_Click);
#line default
#line hidden
return;
case 13:
this.txtSerial = ((System.Windows.Controls.TextBox)(target));
return;
case 14:
this.txtPrevHousing = ((System.Windows.Controls.TextBlock)(target));
return;
case 15:
this.txtPrevCal = ((System.Windows.Controls.TextBlock)(target));
return;
case 16:
this.txtPrevEol = ((System.Windows.Controls.TextBlock)(target));
return;
case 17:
this.txtVoltage = ((System.Windows.Controls.TextBlock)(target));
return;
case 18:
this.txtVoltageRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 19:
this.txtCurrent = ((System.Windows.Controls.TextBlock)(target));
return;
case 20:
this.txtCurrentRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 21:
this.txtResistance = ((System.Windows.Controls.TextBlock)(target));
return;
case 22:
this.txtResistanceRangeGuide = ((System.Windows.Controls.TextBlock)(target));
return;
case 23:
this.borderFinalResult = ((System.Windows.Controls.Border)(target));
return;
case 24:
this.txtFinalResult = ((System.Windows.Controls.TextBlock)(target));
return;
case 25:
this.txtFinalSerial = ((System.Windows.Controls.TextBlock)(target));
return;
case 26:
this.btnStart = ((System.Windows.Controls.Button)(target));
#line 358 "..\..\MainWindow.xaml"
this.btnStart.Click += new System.Windows.RoutedEventHandler(this.btnStart_Click);
#line default
#line hidden
return;
case 27:
this.btnOpenSettings = ((System.Windows.Controls.Button)(target));
#line 367 "..\..\MainWindow.xaml"
this.btnOpenSettings.Click += new System.Windows.RoutedEventHandler(this.btnOpenSettings_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

BIN
marking_gui/obj/Debug/SettingsWindow.baml

Binary file not shown.

177
marking_gui/obj/Debug/SettingsWindow.g.cs

@ -1,177 +0,0 @@
#pragma checksum "..\..\SettingsWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "3B54A611CD6FF472DF4AA312133DE53F7310470DDD5247B79B8465A5C8B8AF60"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace marking_gui {
/// <summary>
/// SettingsWindow
/// </summary>
public partial class SettingsWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 67 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinVoltage;
#line default
#line hidden
#line 69 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxVoltage;
#line default
#line hidden
#line 73 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinCurrent;
#line default
#line hidden
#line 75 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxCurrent;
#line default
#line hidden
#line 79 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinResistance;
#line default
#line hidden
#line 81 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxResistance;
#line default
#line hidden
#line 85 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnSave;
#line default
#line hidden
#line 86 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnCancel;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/settingswindow.xaml", System.UriKind.Relative);
#line 1 "..\..\SettingsWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.txtMinVoltage = ((System.Windows.Controls.TextBox)(target));
return;
case 2:
this.txtMaxVoltage = ((System.Windows.Controls.TextBox)(target));
return;
case 3:
this.txtMinCurrent = ((System.Windows.Controls.TextBox)(target));
return;
case 4:
this.txtMaxCurrent = ((System.Windows.Controls.TextBox)(target));
return;
case 5:
this.txtMinResistance = ((System.Windows.Controls.TextBox)(target));
return;
case 6:
this.txtMaxResistance = ((System.Windows.Controls.TextBox)(target));
return;
case 7:
this.btnSave = ((System.Windows.Controls.Button)(target));
#line 85 "..\..\SettingsWindow.xaml"
this.btnSave.Click += new System.Windows.RoutedEventHandler(this.btnSave_Click);
#line default
#line hidden
return;
case 8:
this.btnCancel = ((System.Windows.Controls.Button)(target));
#line 86 "..\..\SettingsWindow.xaml"
this.btnCancel.Click += new System.Windows.RoutedEventHandler(this.btnCancel_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

177
marking_gui/obj/Debug/SettingsWindow.g.i.cs

@ -1,177 +0,0 @@
#pragma checksum "..\..\SettingsWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "3B54A611CD6FF472DF4AA312133DE53F7310470DDD5247B79B8465A5C8B8AF60"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace marking_gui {
/// <summary>
/// SettingsWindow
/// </summary>
public partial class SettingsWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 67 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinVoltage;
#line default
#line hidden
#line 69 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxVoltage;
#line default
#line hidden
#line 73 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinCurrent;
#line default
#line hidden
#line 75 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxCurrent;
#line default
#line hidden
#line 79 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMinResistance;
#line default
#line hidden
#line 81 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox txtMaxResistance;
#line default
#line hidden
#line 85 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnSave;
#line default
#line hidden
#line 86 "..\..\SettingsWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnCancel;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/settingswindow.xaml", System.UriKind.Relative);
#line 1 "..\..\SettingsWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.txtMinVoltage = ((System.Windows.Controls.TextBox)(target));
return;
case 2:
this.txtMaxVoltage = ((System.Windows.Controls.TextBox)(target));
return;
case 3:
this.txtMinCurrent = ((System.Windows.Controls.TextBox)(target));
return;
case 4:
this.txtMaxCurrent = ((System.Windows.Controls.TextBox)(target));
return;
case 5:
this.txtMinResistance = ((System.Windows.Controls.TextBox)(target));
return;
case 6:
this.txtMaxResistance = ((System.Windows.Controls.TextBox)(target));
return;
case 7:
this.btnSave = ((System.Windows.Controls.Button)(target));
#line 85 "..\..\SettingsWindow.xaml"
this.btnSave.Click += new System.Windows.RoutedEventHandler(this.btnSave_Click);
#line default
#line hidden
return;
case 8:
this.btnCancel = ((System.Windows.Controls.Button)(target));
#line 86 "..\..\SettingsWindow.xaml"
this.btnCancel.Click += new System.Windows.RoutedEventHandler(this.btnCancel_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

BIN
marking_gui/obj/Debug/StartupLoginWindow.baml

Binary file not shown.

193
marking_gui/obj/Debug/StartupLoginWindow.g.cs

@ -1,193 +0,0 @@
#pragma checksum "..\..\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "722E0887EE8192805D77793F6780CDB03B22E7B72F70E2D394ECFCEBCA0B9C94"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace Housing.Login {
/// <summary>
/// StartupLoginWindow
/// </summary>
public partial class StartupLoginWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 346 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox MakerTextBox;
#line default
#line hidden
#line 364 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox ModelTextBox;
#line default
#line hidden
#line 382 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant1TextBox;
#line default
#line hidden
#line 400 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant2TextBox;
#line default
#line hidden
#line 429 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox OperatorTextBox;
#line default
#line hidden
#line 446 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.PasswordBox PasswordBox;
#line default
#line hidden
#line 476 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LineNoTextBox;
#line default
#line hidden
#line 493 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LotNoTextBox;
#line default
#line hidden
#line 510 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox JigNoTextBox;
#line default
#line hidden
#line 518 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button LoginButton;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/startuploginwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\StartupLoginWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.MakerTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 2:
this.ModelTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 3:
this.Variant1TextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 4:
this.Variant2TextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 5:
this.OperatorTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 6:
this.PasswordBox = ((System.Windows.Controls.PasswordBox)(target));
return;
case 7:
this.LineNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 8:
this.LotNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 9:
this.JigNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 10:
this.LoginButton = ((System.Windows.Controls.Button)(target));
#line 522 "..\..\StartupLoginWindow.xaml"
this.LoginButton.Click += new System.Windows.RoutedEventHandler(this.LogInButton_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

193
marking_gui/obj/Debug/StartupLoginWindow.g.i.cs

@ -1,193 +0,0 @@
#pragma checksum "..\..\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "722E0887EE8192805D77793F6780CDB03B22E7B72F70E2D394ECFCEBCA0B9C94"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace Housing.Login {
/// <summary>
/// StartupLoginWindow
/// </summary>
public partial class StartupLoginWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 346 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox MakerTextBox;
#line default
#line hidden
#line 364 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox ModelTextBox;
#line default
#line hidden
#line 382 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant1TextBox;
#line default
#line hidden
#line 400 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant2TextBox;
#line default
#line hidden
#line 429 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox OperatorTextBox;
#line default
#line hidden
#line 446 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.PasswordBox PasswordBox;
#line default
#line hidden
#line 476 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LineNoTextBox;
#line default
#line hidden
#line 493 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LotNoTextBox;
#line default
#line hidden
#line 510 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox JigNoTextBox;
#line default
#line hidden
#line 518 "..\..\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button LoginButton;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/marking_gui;component/startuploginwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\StartupLoginWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.MakerTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 2:
this.ModelTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 3:
this.Variant1TextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 4:
this.Variant2TextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 5:
this.OperatorTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 6:
this.PasswordBox = ((System.Windows.Controls.PasswordBox)(target));
return;
case 7:
this.LineNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 8:
this.LotNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 9:
this.JigNoTextBox = ((System.Windows.Controls.TextBox)(target));
return;
case 10:
this.LoginButton = ((System.Windows.Controls.Button)(target));
#line 522 "..\..\StartupLoginWindow.xaml"
this.LoginButton.Click += new System.Windows.RoutedEventHandler(this.LogInButton_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}

BIN
marking_gui/obj/Debug/marking_gui.Properties.Resources.resources

Binary file not shown.

BIN
marking_gui/obj/Debug/marking_gui.csproj.AssemblyReference.cache

Binary file not shown.

1
marking_gui/obj/Debug/marking_gui.csproj.CoreCompileInputs.cache

@ -1 +0,0 @@
786424e28f57daf62f6180d8da5730790c336df832876ad7e4dec61838b897e5

41
marking_gui/obj/Debug/marking_gui.csproj.FileListAbsolute.txt

@ -1,41 +0,0 @@
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\bin\Debug\marking_gui.exe.config
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\bin\Debug\marking_gui.exe
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\bin\Debug\marking_gui.pdb
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.AssemblyReference.cache
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\SettingsWindow.baml
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\StartupLoginWindow.baml
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\MainWindow.g.cs
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\SettingsWindow.g.cs
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\StartupLoginWindow.g.cs
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\App.g.cs
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui_MarkupCompile.cache
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui_MarkupCompile.lref
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\MainWindow.baml
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.g.resources
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.Properties.Resources.resources
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.GenerateResource.cache
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.CoreCompileInputs.cache
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.exe
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\marking_gui.pdb
C:\Users\wjddn\Desktop\amosense\marking_gui\marking_gui\obj\Debug\App.baml
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.AssemblyReference.cache
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.Properties.Resources.resources
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.GenerateResource.cache
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.csproj.CoreCompileInputs.cache
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\bin\Debug\marking_gui.exe.config
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\bin\Debug\marking_gui.exe
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\bin\Debug\marking_gui.pdb
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\SettingsWindow.baml
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\StartupLoginWindow.baml
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\MainWindow.g.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\SettingsWindow.g.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\StartupLoginWindow.g.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\App.g.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui_MarkupCompile.cache
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui_MarkupCompile.lref
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\App.baml
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\MainWindow.baml
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.g.resources
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.exe
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\marking_gui.pdb
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\bin\Debug\Database.ini

BIN
marking_gui/obj/Debug/marking_gui.csproj.GenerateResource.cache

Binary file not shown.

BIN
marking_gui/obj/Debug/marking_gui.exe

Binary file not shown.

BIN
marking_gui/obj/Debug/marking_gui.g.resources

Binary file not shown.

BIN
marking_gui/obj/Debug/marking_gui.pdb

Binary file not shown.

20
marking_gui/obj/Debug/marking_gui_MarkupCompile.cache

@ -1,20 +0,0 @@
marking_gui
winexe
C#
.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\
marking_gui
none
false
DEBUG;TRACE
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\App.xaml
31238780151
13495465398
141066371409
MainWindow.xaml;SettingsWindow.xaml;StartupLoginWindow.xaml;
False

20
marking_gui/obj/Debug/marking_gui_MarkupCompile.i.cache

@ -1,20 +0,0 @@
marking_gui
winexe
C#
.cs
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\obj\Debug\
marking_gui
none
false
DEBUG;TRACE
C:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\App.xaml
3-1210742192
141215079075
14-207034072
MainWindow.xaml;SettingsWindow.xaml;StartupLoginWindow.xaml;
True

4
marking_gui/obj/Debug/marking_gui_MarkupCompile.i.lref

@ -1,4 +0,0 @@

FC:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\MainWindow.xaml;;

4
marking_gui/obj/Debug/marking_gui_MarkupCompile.lref

@ -1,4 +0,0 @@

FC:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\App.xaml;;
FC:\Users\COMPUTER1\Desktop\mobi\marking_gui\marking_gui\MainWindow.xaml;;

15
marking_installer/Empty.resx

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<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>

161
marking_installer/Program.cs

@ -0,0 +1,161 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Windows.Forms;
using System.Drawing;
namespace MarkingInstaller
{
public class Program
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string targetDir = @"C:\marking";
// 설치 의사 확인
var confirm = MessageBox.Show(
"Marking 시스템을 컴퓨터에 설치하시겠습니까?\n\n" +
"설치 경로: " + targetDir + "\n" +
"바탕화면에 실행 바로가기 아이콘이 생성됩니다.",
"Marking 설치 관리자",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (confirm != DialogResult.Yes) return;
// 설치 상태 진행 폼
Form installForm = new Form
{
Text = "Marking 설치 진행 중...",
Size = new Size(400, 150),
StartPosition = FormStartPosition.CenterScreen,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false,
BackColor = Color.FromArgb(240, 243, 244)
};
Label lblStatus = new Label
{
Text = "설치 파일 압축을 푸는 중입니다...",
Location = new Point(20, 20),
Size = new Size(360, 25),
Font = new Font("Malgun Gothic", 10, FontStyle.Bold)
};
ProgressBar pb = new ProgressBar
{
Location = new Point(20, 55),
Size = new Size(345, 23),
Style = ProgressBarStyle.Marquee
};
installForm.Controls.Add(lblStatus);
installForm.Controls.Add(pb);
installForm.Shown += async (s, ev) =>
{
try
{
await System.Threading.Tasks.Task.Run(() =>
{
// 1. 기존 디렉토리 안전 클린업
if (Directory.Exists(targetDir))
{
try
{
Directory.Delete(targetDir, true);
}
catch
{
// 프로세스가 잠긴 경우 대비하여 모든 파일 삭제 시도
foreach (var file in Directory.GetFiles(targetDir, "*.*", SearchOption.AllDirectories))
{
try { File.Delete(file); } catch { }
}
}
}
Directory.CreateDirectory(targetDir);
// 2. 리소스에서 payload.zip 추출 및 압축 해제
var assembly = Assembly.GetExecutingAssembly();
using (Stream resourceStream = assembly.GetManifestResourceStream("payload.zip"))
{
if (resourceStream == null)
{
throw new Exception("설치 리소스를 찾을 수 없습니다. (payload.zip)");
}
string tempZip = Path.Combine(Path.GetTempPath(), "marking_payload.zip");
using (FileStream fs = new FileStream(tempZip, FileMode.Create, FileAccess.Write))
{
resourceStream.CopyTo(fs);
}
ZipFile.ExtractToDirectory(tempZip, targetDir);
try { File.Delete(tempZip); } catch { }
}
// 3. 바탕화면에 프로그램 바로가기 단축 아이콘 생성
CreateShortcut(targetDir);
});
installForm.Close();
MessageBox.Show(
"설치가 성공적으로 완료되었습니다!\n" +
"바탕화면의 [Marking System] 아이콘을 클릭하여 앱을 사용하십시오.",
"설치 완료",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
// 설치 완료 후 즉시 프로그램 실행
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = Path.Combine(targetDir, "marking_gui.exe"),
WorkingDirectory = targetDir
});
}
catch { }
}
catch (Exception ex)
{
installForm.Close();
MessageBox.Show("설치 중 예기치 못한 오류가 발생했습니다:\n" + ex.Message, "설치 실패", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
Application.Run(installForm);
}
private static void CreateShortcut(string targetDir)
{
try
{
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string shortcutPath = Path.Combine(desktopPath, "Marking System.lnk");
// Windows Script Host 리플렉션을 활용해 무참조 단축 바로가기 아이콘 생성
Type shellType = Type.GetTypeFromProgID("WScript.Shell");
object shell = Activator.CreateInstance(shellType);
object shortcut = shellType.InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, shell, new object[] { shortcutPath });
shellType.InvokeMember("TargetPath", BindingFlags.SetProperty, null, shortcut, new object[] { Path.Combine(targetDir, "marking_gui.exe") });
shellType.InvokeMember("WorkingDirectory", BindingFlags.SetProperty, null, shortcut, new object[] { targetDir });
shellType.InvokeMember("Description", BindingFlags.SetProperty, null, shortcut, new object[] { "Marking System Application" });
shellType.InvokeMember("Save", BindingFlags.InvokeMethod, null, shortcut, null);
}
catch (Exception ex)
{
Console.WriteLine("바탕화면 바로가기 등록 실패: " + ex.Message);
}
}
}
}

12
marking_installer/app.manifest

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MarkingInstaller.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- 관리자 권한 요구 속성 설정 (C드라이브 설치를 위해 필수) -->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

38
marking_installer/marking_installer.csproj

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>10.0</LangVersion>
<ApplicationManifest>app.manifest</ApplicationManifest>
<!-- 와일드카드 소스 및 리소스 자동 수집 완전 차단 -->
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<EnableDefaultItems>false</EnableDefaultItems>
<!-- 단일 파일 설치 바이너리(Setup EXE) 출력 설정 -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<!-- 명시적으로 소스 파일 등록하여 와일드카드 매치 오류 해결 -->
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<!-- 구체적인 리소스만 수동 명시하여 MSB3552 에러 원천 차단 -->
<EmbeddedResource Include="Empty.resx">
<LogicalName>Empty.resx</LogicalName>
</EmbeddedResource>
<!-- payload.zip 파일이 빌드 타임에 동적 생성되어 포함됨 -->
<EmbeddedResource Include="payload.zip" Condition="Exists('payload.zip')">
<LogicalName>payload.zip</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Project>

116
program_flow.md

@ -1,48 +1,48 @@
# 양산형 테스트 기기 모니터링 시스템 사용자 설명서 (User Manual) # 양산형 성능 검사 & 레이저 마킹 시스템 사용자 설명서 (User Manual)
본 문서는 **양산형 테스트 기기 모니터링 시스템**을 사용하는 작업자 및 검사자를 위한 운영 설명서입니다. 프로그램의 시작부터 제품 조회, 성능 검사 수행, 판정 기준 설정까지 내부 프로그램 코드를 몰라도 누구나 쉽게 작동법을 이해할 수 있도록 사용자 화면 관점에서 기술되었습니다. 본 문서는 **양산형 테스트 기기 모니터링 및 레이저 마킹 시스템**을 사용하는 현장 작업자 및 검사자를 위한 운영 설명서입니다. 프로그램의 시작부터 제품 조회, 3대 전기 파라미터(전압, 전류, 저항) 성능 검사 수행, 레이저 각인, 판정 기준 설정까지 내부 코드를 몰라도 누구나 쉽게 작동법을 이해할 수 있도록 사용자 화면 관점에서 기술되었습니다.
--- ---
## 1. 시스템 개요 (Introduction) ## 1. 시스템 개요 (Introduction)
본 시스템은 제조 라인의 최종 단계에서 제품의 전기적 성능(측정 전압 및 전류)을 검사하고 최종 합격/불합격 여부를 판정하는 모니터링 프로그램입니다. 본 시스템은 제조 라인의 최종 단계에서 완제품의 3대 전기적 성능(전압, 전류, 저항)을 통합 검사하고, 적합 제품에 한해 표면에 2D QR 코드 및 일련번호를 레이저 마킹하는 자동화 모니터링 프로그램입니다.
- **이전 단계 확인:** 제품의 바코드(S/N)를 조회하여 이전 주요 공정(Housing, Calibration, EOL)을 모두 통과했는지 자동으로 검증합니다. - **선행 공정 교차 검증:** 제품 바코드(S/N)를 수집하여 이전 주요 공정(Housing, Calibration, EOL)을 모두 통과했는지 생산 데이터베이스에서 자동 조회합니다.
- **실시간 계측 및 판정:** 검사를 시작하면 전기적 신호가 측정되며, 설정된 범위 이내인 경우 최종 `PASS`(합격), 범위를 벗어나면 `FAIL`(불합격) 판정을 내립니다. - **실시간 3대 파라미터 계측:** 전원 공급 장치 및 DMM 계측기와 연동하여 전압(V), 전류(mA), 저항(kΩ/Ω) 수치를 실시간 수집 및 반올림 정리합니다.
- **파라미터 조율:** 생산 제품 사양에 맞춰 합격 판정 기준 범위(최소/최대 전압 및 전류)를 간편하게 변경할 수 있습니다. - **레이저 각인 연동 (LMC1):** 최종 종합 합격(`PASS`) 건에 한하여 2D QR 코드 및 제품 시퀀스 코드를 레이저로 마킹합니다.
- **규격 파라미터 조율:** 생산 제품 사양 변경 시 최소/최대 임계치(전압, 전류, 저항 범위)를 간편하게 변경 적용할 수 있습니다.
--- ---
## 2. 전체 동작 흐름도 (Operational Flow) ## 2. 전체 동작 프로세스 흐름도 (Operational Flowchart)
검사자가 프로그램을 구동하여 최종 검사를 완료하기까지의 전체 프로세스는 다음과 같습니다. 검사자가 프로그램을 구동하여 최종 성능 검사 및 레이저 각인을 완료하기까지의 전체 프로세스는 다음과 같습니다.
```mermaid ```mermaid
graph TD graph TD
A([1. 프로그램 실행]) --> B[메인 모니터링 화면 기동] A([1. 프로그램 실행]) --> B[메인 모니터링 화면 기동]
B --> C[화면 중앙 로그인 팝업 활성화] B --> C[화면 중앙 로그인 팝업 활성화]
C --> D{로그인} C --> D{작업자 로그인}
D -- 취소/닫기 --> E([프로그램 종료]) D -- 취소/닫기 --> E([프로그램 종료 및 전원 차단])
D -- 로그인 성공 --> F[상단바 우측에 작업자/모델/라인 정보 표출 및 메인 활성화] D -- 로그인 성공 --> F[상단바 작업자/모델/라인 정보 표출 & 상시전원 인가]
F --> G[▶ 검사 시작 START 버튼 클릭] F --> G[▶ 검사 시작 START 버튼 클릭]
G --> H[UI 화면 상태 및 측정 수치 초기화] G --> H[UI 화면 수치 및 판정 상태 초기화]
H --> I[메모리로부터 S/N를 자동으로 읽어서 입력받음] H --> I[센서 판독기 메모리로부터 제품 S/N 자동 추출]
I --> J[데이터베이스에서 이전 검사 결과 조회] I --> J[생산 DB에서 선행 공정 이력 교차 조회]
J --> K{이전 검사 결과 확인} J --> K{선행 공정 결과 검증}
K -- 이전 단계 중 FAIL 불합격 존재 --> L[경고 팝업 창 출력 및 검사 중단] K -- 불합격/미진행 존재 --> L[경고 팝업 출력 및 검사 중단]
L --> G L --> M{강제 검사 버튼 클릭?}
K -- 이전 단계 모두 PASS 합격 --> M[자동으로 검사 시작 및 2초간 수치 측정] M -- 아니오 --> G
M --> N[측정 완료 및 최종값 소수점 둘째 자리 표시] M -- 예: 스킵 실행 --> N[자동 계측 15단계 시퀀스 기동]
N --> O{측정 전압/전류가 기준범위 이내인가?} K -- 선행 공정 모두 PASS --> N
O -- 합격 범위 내 PASS --> P[대형 화면 PASS 표시 및 최종 합격 판정] N --> O[전압, 저항, 전류 2초간 실시간 계측 완료]
O -- 범위 이탈 FAIL --> Q[대형 화면 FAIL 표시 및 최종 불합격 판정] O --> P{전압 / 전류 / 저항이 모두 기준범위 이내인가?}
P --> R[데이터베이스에 측정값 3,2 포맷팅 반올림 및 PASS 저장] P -- 1개 이상 이탈 FAIL --> Q[대형 FAIL 표시 & 마킹 제외 & DB 불합격 적재]
Q --> S[데이터베이스에 측정값 3,2 포맷팅 반올림 및 FAIL 저장] P -- 모두 규격 내 PASS --> R[대형 PASS 표시 & DB 적재 & LMC1 레이저 QR 각인 실행]
R --> T[최종 설비에 SUCCESS 신호 전달] Q --> S[시스템 자동 대기 복구 및 다음 제품 검사 준비]
S --> U[시스템 대기 상태 복구 및 다음 검사 준비] R --> S
T --> U S --> G
U --> G
``` ```
--- ---
@ -51,54 +51,40 @@ graph TD
### 3.1. 시스템 시작 설정 및 로그인 ### 3.1. 시스템 시작 설정 및 로그인
1. **메인 화면 로드 및 로그인 팝업:** 프로그램을 실행하면 백그라운드에 메인 모니터링 화면이 먼저 켜져서 깔리며, 그 위에 **[시스템 시작 설정 & 로그인]** 화면이 화면 정중앙에 팝업(모달) 형태로 활성화됩니다. 1. **메인 화면 로드 및 로그인 팝업:** 프로그램을 실행하면 백그라운드에 메인 모니터링 화면이 깔리며, 정중앙에 **[시스템 시작 설정 & 로그인]** 모달 창이 활성화됩니다.
2. **설정값 기입:** 제조사, 모델명, 색상 코드, 라인 번호, LOT 번호, 지그 번호를 확인 및 기입하고, 담당 **작업자명**과 **비밀번호**를 입력합니다. (빠른 실행을 위해 테스트 계정 `test` / `test`가 미리 기입되어 있습니다.) 2. **작업 정보 기입:** 라인 번호, LOT 번호, 지그 번호, 담당 **작업자 ID** 및 **비밀번호**를 입력합니다.
3. **팝업 취소 시 앱 종료:** 최초 로그인 시 창을 그냥 닫거나 취소를 누르면 프로그램 전체가 안전하게 종료됩니다. 3. **상시전원 자동 인가:** 로그인 성공 시 로그인 창이 닫히며 상단 헤더 영역에 작업 정보 배지가 갱신되고, 검사 장비 구동을 위한 상시전원이 자동으로 인가됩니다.
4. **정보 연동 및 로그아웃:** 로그인이 완료되면 로그인 창이 닫히며 메인 화면 상단바 우측 영역에 `[작업자: test] [모델: MOBI-EV-01 | 라인: LINE-3]` 형태로 정돈되어 정보가 표시됩니다. 또한 바로 우측에 빨간색 **[로그아웃]** 버튼이 생성됩니다. 4. **로그아웃 및 안전 차단:** 상단 우측의 **[로그아웃]** 버튼을 클릭하면 현재 수치들이 초기화되고 로그인 창으로 이동하며, 안전을 위해 주전원 및 상시전원 출력이 자동으로 차단됩니다.
- [로그아웃] 버튼을 클릭하면 현재 화면의 수치들이 초기화되고 로그인 팝업창이 다시 정중앙에 호출됩니다.
--- ---
### 3.2. 제품 조회 및 이전 공정 이력 검증 ### 3.2. 제품 조회 및 선행 공정 이력 검증
1. **검사 시작 및 UI 초기화:** [▶ 검사 시작 (START)] 버튼(기본 활성화 상태)을 클릭합니다. 클릭 즉시 이전 제품의 수치와 결과가 오인되는 것을 방지하기 위해 화면의 전압/전류 수치(`-.--`), 최종 판정(`대 기 중`), 이전 공정 단계(`대기`)가 깨끗이 초기화됩니다. 1. **검사 시작 (START):** 메인 화면 하단의 **[▶ 검사 시작 (START)]** 버튼을 클릭합니다. 잔여 수치 오인을 방지하기 위해 화면의 전압/전류/저항 수치(`-.--`) 및 판정 카드(`대기중`)가 초기화됩니다.
2. **메모리 자동 읽기:** UI 초기화가 끝나면 시스템은 메모리로부터 바코드(S/N) 정보를 자동으로 읽어와 `S/N 입력` 창에 자동으로 입력합니다. (조회 버튼은 완전히 제거되었으며 수동 입력을 거치지 않습니다.) 2. **S/N 자동 수집:** 안착 지그에 제품을 장착하면 센서 판독기를 통해 제품 고유 일련번호(IC_SN)를 자동으로 로드합니다.
3. **이전 단계 자동 검증:** 조회된 S/N으로 데이터베이스를 즉시 탐색하여 이전 공정 단계 결과를 확인합니다. 3. **이력 교차 검증:**
- **합격 제품 테스트 (S/N: `PASS1234`):** Housing, Calibration, EOL 단계가 모두 초록색 `PASS`로 채워지며, 별도 조작 대기 없이 **최종 성능 검사를 자동으로 바로 가동**합니다. - **Housing, Calibration, EOL** 3개 선행 단계가 모두 초록색 `PASS`인 경우 즉시 **자동 계측 시퀀스를 기동**합니다.
- **불합격 제품 테스트 (S/N: `FAIL5678`):** 이전 단계 중 실패한 항목이 빨간색 `FAIL`로 표시되며, 즉시 경고 팝업 창이 출력되고 검사 시작을 전면 차단합니다. - 선행 단계 중 불합격이 존재하거나 이력이 없으면 빨간색 `FAIL` 또는 경고 팝업이 출력되며 검사가 차단됩니다. (단, 특수 분석 시 최하단 **`강제 검사 (스킵)`** 버튼으로 검증을 우회할 수 있습니다.)
--- ---
### 3.3. 최종 성능 검사 및 합불 판정 ### 3.3. 15단계 자동 성능 계측 및 레이저 각인
1. **자동 계측 시작:** 이전 공정 결과가 모두 합격인 경우, 상태창이 파란색 **'측정 중'**으로 자동 변경되고 전압 및 전류 측정 영역의 수치가 소수점 둘째 자리(`-.--`) 상태에서 약 2초간 흔들리며 측정 동작을 연출합니다. 1. **15단계 자동 시퀀스 기동:**
2. **측정 완료 및 판정:** 2초가 지나면 측정이 끝나고 최종 검출된 전압과 전류 수치가 소수점 둘째 자리 포맷(`F2`)으로 화면에 표시됩니다. - 사전 전원 체크 ➔ S/N 수집 및 보정 ➔ 주 테스트 전원 인가 ➔ **전압(V), 저항(kΩ/Ω), 전류(mA)** 순차 실측 ➔ 테스트 전원 안전 차단까지 총 15단계 과정이 백그라운드에서 비동기로 자동 진행됩니다.
- 측정된 전압(디폴트 `4.00V ~ 6.00V`)과 전류(디폴트 `8.00A ~ 10.00A`)가 모두 가이드 범위 이내이면 대형 판정 영역이 초록색 배경의 **`PASS`**로 표시됩니다. 범위를 벗어날 경우 빨간색 배경의 **`FAIL`** 판정을 내립니다. 2. **합부 판정 (PASS / FAIL):**
3. **데이터베이스 적재 (3,2 포맷팅):** 최종 판정이 이루어지는 순간, 측정된 전압과 전류 수치를 **소수점 이하 2자리(3,2 포맷에 대응)로 반올림 처리**하여 최종 판정 결과(PASS/FAIL) 및 S/N 정보와 함께 데이터베이스에 안전하게 기록합니다. - 수집된 3개 측정값이 모두 지정된 최소/최대 규격치 이내인 경우 대형 판정 카드가 초록색 **`PASS`**로 변경됩니다. 단 하나라도 벗어나면 빨간색 **`FAIL`**이 출력됩니다.
4. **SUCCESS 완료 신호 전달:** 최종 합격(`PASS`) 판정 건에 한하여, 데이터베이스 저장이 끝난 후 외부 설비 제어기(PLC 등) 측 메모리에 **최종 SUCCESS 완료 신호**를 송신하여 제품 이동 및 라인 흐름을 자동 제어하도록 구성했습니다. 3. **데이터베이스 적재 및 레이저 각인 (LMC1):**
5. **자동 대기 복구:** 모든 저장 및 신호 전달 프로세스가 완료되면 시스템은 자동으로 대기 상태로 복구되어 다음 제품 검사를 진행할 준비를 마칩니다. - 측정값은 소수점 규격에 맞춰 DB에 안전하게 보관됩니다.
- **PASS 합격품에 한하여** LMC1 레이저 각인기를 통해 2D QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다. (FAIL 제품은 마킹을 스킵하여 제품을 보호합니다.)
--- ---
### 3.4. 판정 기준 파라미터 설정 ### 3.4. 판정 기준 파라미터 및 통신 설정
1. **설정창 열기:** 최하단 바 좌측에 있는 파란색 **[⚙ 파라미터 설정]** 버튼을 클릭합니다. 1. **시험 설정 (파라미터 변경):** 상단 우측 **[시험 설정]** 메뉴에서 전압, 전류, 저항의 최소/최대 합격 기준치를 입력하여 변경할 수 있습니다. 저장 즉시 메인 화면 하단 가이드 문구가 갱신되어 새로운 기준이 적용됩니다.
2. **기준값 수정:** 화면에 팝업창이 나타나며 전압의 최소/최대값 및 전류의 최소/최대 기준 범위를 키보드로 입력하여 수정할 수 있습니다. 2. **통신 설정 (포트/채널 변경):** 상단 우측 **[통신 설정]** 메뉴에서 센서 판독기의 COM 포트 및 통신 채널(CH1/CH2)을 지정할 수 있습니다.
3. **유효성 체크 및 저장:**
- 입력한 값은 음수여서는 안 되며, 최소값이 최대값보다 작아야 저장할 수 있습니다.
- **[저장]**을 누르면 변경 사항이 적용되며, 메인 화면 전압/전류 카드 하단에 표시되는 가이드 문구(`기준범위: X.XXV ~ Y.YYV`)가 실시간으로 자동 변경되어 즉시 새로운 판정 기준으로 적용됩니다.
--- ---
### 4. GUI *본 문서는 Performance Inspect & Laser Marking System v1.0.0 표준 운영 사양을 기준으로 작성되었습니다.*
#### 메인 화면
![[Pasted image 20260605142629.png]]
#### 작동화면
![[Pasted image 20260605142747.png]]
#### 이전 시험 단계 실패 화면
![[Pasted image 20260605142804.png]]
#### 파라미터 설정 화면
![[Pasted image 20260605142845.png]]

14
docs/database_setup.sql → sql/database_setup.sql

@ -71,18 +71,12 @@ BEGIN
[Variant_2] NVARCHAR(10) NOT NULL, [Variant_2] NVARCHAR(10) NOT NULL,
[Operator] NVARCHAR(10) NOT NULL, [Operator] NVARCHAR(10) NOT NULL,
[Production_Date] DATETIME2(0) NOT NULL, [Production_Date] DATETIME2(0) NOT NULL,
[Line] NVARCHAR(5) NOT NULL, [Line] NVARCHAR(10) NOT NULL,
[Lot_No] NVARCHAR(8) NOT NULL, [Lot_No] NVARCHAR(8) NOT NULL,
[Jig_No] NVARCHAR(10) NOT NULL, [Jig_No] NVARCHAR(10) NOT NULL,
[PT_Vol_1] DECIMAL(3, 2) NOT NULL, [PT_Vol_1] DECIMAL(4, 2) NOT NULL,
[PT_Vol_2] DECIMAL(3, 2) NOT NULL, [PT_Current_1] DECIMAL(4, 2) NOT NULL,
[PT_Vol_3] DECIMAL(3, 2) NOT NULL, [PT_Resistance_1] DECIMAL(5, 3) NOT NULL,
[PT_Current_1] DECIMAL(3, 2) NOT NULL,
[PT_Current_2] DECIMAL(3, 2) NOT NULL,
[PT_Current_3] DECIMAL(3, 2) NOT NULL,
[PT_Resistance_1] DECIMAL(3, 2) NOT NULL,
[PT_Resistance_2] DECIMAL(3, 2) NOT NULL,
[PT_Resistance_3] DECIMAL(3, 2) NOT NULL,
[Result] NVARCHAR(5) NOT NULL, [Result] NVARCHAR(5) NOT NULL,
[Spare_1] NVARCHAR(30) NULL, [Spare_1] NVARCHAR(30) NULL,
[Spare_2] NVARCHAR(30) NULL, [Spare_2] NVARCHAR(30) NULL,
Loading…
Cancel
Save