From e34ed97a3ac098c20b212fe602a53d67a3d97985 Mon Sep 17 00:00:00 2001 From: NohDoyeon241104 Date: Tue, 30 Jun 2026 14:35:22 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=82=AC=20=EC=A0=84=EB=B6=80=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=5F=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20=EB=B8=8C=EB=9E=9C=EC=B9=98=EC=97=90=EC=84=9C=20=EB=84=A3?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=95=98=EB=8D=98=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=93=A4=20=EC=A0=84=EB=B6=80=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/skills/doyeon_note/SKILL.md | 42 ++ .claude/skills/ptop-deliverables/SKILL.md | 79 +++ .../references/design-system.md | 126 +++++ .../references/source-map.md | 105 ++++ .claude/skills/ubisam-deliverable/SKILL.md | 49 ++ .claude/skills/ubisam-deliverables/README.md | 36 ++ .../references/00-harness.md | 47 ++ .../ubisam-deliverables/references/01-tree.md | 41 ++ .../references/02-enums.md | 56 ++ .../references/03-schema.md | 39 ++ .../references/05-form-schemas.md | 94 ++++ .../references/07-fill-mapping.md | 40 ++ .../references/08-design-system.md | 126 +++++ .../references/build_02설계_xlsx.py | 479 ++++++++++++++++++ .../references/xlsxfill.py | 203 ++++++++ .../templates/_task.template.md | 32 ++ .../templates/project.yaml.template | 31 ++ .claude/skills/ubisam-format-map/SKILL.md | 78 +++ .../ubisam-ppt-make/assets/Ubisam_logo.gif | Bin 0 -> 6991 bytes .../assets/Ubisam_logo_transparent.png | Bin 0 -> 8251 bytes .../ubisam-ppt-make/examples/showcase.json | 394 ++++++++++++++ .../ubisam-ppt-make/생성파일/test_bg.pptx | Bin 0 -> 64326 bytes .../ubisam-ppt-make/생성파일/test_wm.pptx | Bin 0 -> 64410 bytes .claude/skills/ui_layout_image/SKILL.md | 68 +++ .../skills/ui_layout_image/render_helpers.py | 75 +++ 25 files changed, 2240 insertions(+) create mode 100644 .claude/skills/doyeon_note/SKILL.md create mode 100644 .claude/skills/ptop-deliverables/SKILL.md create mode 100644 .claude/skills/ptop-deliverables/references/design-system.md create mode 100644 .claude/skills/ptop-deliverables/references/source-map.md create mode 100644 .claude/skills/ubisam-deliverable/SKILL.md create mode 100644 .claude/skills/ubisam-deliverables/README.md create mode 100644 .claude/skills/ubisam-deliverables/references/00-harness.md create mode 100644 .claude/skills/ubisam-deliverables/references/01-tree.md create mode 100644 .claude/skills/ubisam-deliverables/references/02-enums.md create mode 100644 .claude/skills/ubisam-deliverables/references/03-schema.md create mode 100644 .claude/skills/ubisam-deliverables/references/05-form-schemas.md create mode 100644 .claude/skills/ubisam-deliverables/references/07-fill-mapping.md create mode 100644 .claude/skills/ubisam-deliverables/references/08-design-system.md create mode 100644 .claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py create mode 100644 .claude/skills/ubisam-deliverables/references/xlsxfill.py create mode 100644 .claude/skills/ubisam-deliverables/templates/_task.template.md create mode 100644 .claude/skills/ubisam-deliverables/templates/project.yaml.template create mode 100644 .claude/skills/ubisam-format-map/SKILL.md create mode 100644 .claude/skills/ubisam-ppt-make/assets/Ubisam_logo.gif create mode 100644 .claude/skills/ubisam-ppt-make/assets/Ubisam_logo_transparent.png create mode 100644 .claude/skills/ubisam-ppt-make/examples/showcase.json create mode 100644 .claude/skills/ubisam-ppt-make/생성파일/test_bg.pptx create mode 100644 .claude/skills/ubisam-ppt-make/생성파일/test_wm.pptx create mode 100644 .claude/skills/ui_layout_image/SKILL.md create mode 100644 .claude/skills/ui_layout_image/render_helpers.py diff --git a/.claude/skills/doyeon_note/SKILL.md b/.claude/skills/doyeon_note/SKILL.md new file mode 100644 index 0000000..5c04d26 --- /dev/null +++ b/.claude/skills/doyeon_note/SKILL.md @@ -0,0 +1,42 @@ +--- +name: doyeon_note +description: 도연님의 작업일지를 기록합니다. "/doyeon_note <내용>" 형태로 호출하면 docs/작업일지/yyyy_mm_dd.md 파일에 오늘 날짜로 내용을 추가합니다. 작업일지 기록, 일지 작성, 노트 추가 요청 시 사용. +--- + +# doyeon_note — 작업일지 기록 스킬 + +사용자가 말한 내용을 `docs/작업일지/yyyy_mm_dd.md` 파일에 기록합니다. + +## 동작 절차 + +1. **오늘 날짜 확인**: 시스템의 현재 날짜를 `yyyy_mm_dd` 형식으로 만든다. + (컨텍스트에 `currentDate`가 있으면 그 값을 사용. 없으면 `date +%Y_%m_%d` 실행) + +2. **대상 파일 경로**: `docs/작업일지/.md` + +3. **내용 정리**: 사용자가 `args`로 전달한 내용을 작업일지 항목으로 정리한다. + - 단순 한 줄이면 그대로 한 항목으로. + - 여러 작업/생각이 섞여 있으면 자연스럽게 불릿(`-`)으로 나눈다. + - 사용자가 적은 의도/뉘앙스는 보존하고, 임의로 내용을 지어내지 않는다. + +4. **파일 기록**: + - 파일이 **없으면** 아래 템플릿으로 새로 만든다. + - 파일이 **있으면** 기존 내용을 Read한 뒤, 맨 아래에 새 항목을 이어 붙인다(append). 기존 내용은 절대 지우지 않는다. + - 같은 날 여러 번 호출되면 시각(`HH:MM`) 소제목을 붙여 구분한다. + +5. 기록 후, 어떤 파일에 무엇을 추가했는지 한 줄로 사용자에게 알린다. + +## 새 파일 템플릿 + +```markdown +# 작업일지 — yyyy-mm-dd + +## HH:MM +- (항목) +``` + +## 주의 + +- `args`가 비어 있으면 무엇을 기록할지 사용자에게 한 줄로 물어본다. +- 날짜/파일명 형식은 반드시 `yyyy_mm_dd`를 지킨다. +- 작업일지는 사용자의 기록이므로, 내용을 요약·각색하지 말고 사용자의 표현을 최대한 살린다. diff --git a/.claude/skills/ptop-deliverables/SKILL.md b/.claude/skills/ptop-deliverables/SKILL.md new file mode 100644 index 0000000..27f8b9e --- /dev/null +++ b/.claude/skills/ptop-deliverables/SKILL.md @@ -0,0 +1,79 @@ +--- +name: ptop-deliverables +description: >- + LGES OC5 PtoP Welder 전용 산출물 자동 생성·갱신 스킬. + 프로젝트 소스를 분석해 docs/[Ubisam산출물]/ 하위 MD 산출물(색상 디자인 포함)을 + 생성하거나 최신화한다. 다른 PC에서 Git clone 후 이 스킬 한 번으로 전체 산출물 + 재생산 가능. "ptop 산출물", "산출물 업데이트", "/ptop-deliverables" 요청 시 사용. +--- + +# PTOP 산출물 자동 생성 SKILL + +LGES OC5 PtoP Welder(`ILSOS_PrismaticRD_PTOP`) 전용. +소스를 분석해 6단계 산출물 MD를 **Ubisam 디자인 시스템**(배너·뱃지·진행률 포함) 기준으로 생성한다. + +## 참조 맵 + +| # | 파일 | 용도 | +|---|---|---| +| D | [references/design-system.md](references/design-system.md) | 색상 팔레트·배너·뱃지 HTML 템플릿 | +| S | [references/source-map.md](references/source-map.md) | 소스 파일 → 산출물 필드 매핑 | + +## 소스 진실원 — 읽는 순서 + +다른 PC에서 실행 시 아래 파일을 순서대로 읽어 산출물을 채운다. + +| 순서 | 파일 경로 | 추출 정보 | +|---|---|---| +| 1 | `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` | BIT/WORD 신호 enum 전수 | +| 2 | `Machines/ILSOS_PrismaticRD_PTOP/EquipmentSetup.cs` | 디바이스 등록·메뉴·뷰 목록 | +| 3 | `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs` | 시퀀스 노드·인터락·TactTime | +| 4 | `Machines/ILSOS_PrismaticRD_PTOP/Device/IO/PAIX_PTOP.cs` | DI/DO 채널 맵 | +| 5 | `docs/architecture.md` | 플랫폼·모션·디바이스 개요 | +| 6 | `docs/[Ubisam산출물]/_meta/project.yaml` | 프로젝트 코드·팀 정보 | + +## 실행 절차 + +### STEP 0 — 진입 체크 +`docs/[Ubisam산출물]/` 폴더 존재 여부 확인. +없으면 `/ubisam-deliverables` 스캐폴딩을 먼저 실행 후 재진입. + +### STEP 1 — 소스 분석 +소스 진실원 6개를 순서대로 읽어 추출한다: +- PLC BIT/WORD 신호 목록 (eBitInput/Output, eWordInput/Output) +- 등록 디바이스 (PLC·IO·Laser·Scanner·Motion·Vision) +- 메뉴·뷰 트리 (RegisterViews — 권한 레벨 포함) +- 시퀀스 노드 흐름 + 인터락 조건 + 정지/에러 처리 +- TactTime 상수 (지연 ms, 타임아웃 s, 루프 슬립) +- DI/DO 채널 번호·신호명·설명 (PAIX_PTOP.cs) + +### STEP 2 — 산출물 MD 생성/갱신 + +[design-system.md](references/design-system.md) 배너·뱃지 템플릿을 적용해 +아래 파일을 **없으면 생성, 있으면 내용 갱신**한다. + +| 산출물 | 경로 | 채움 소스 | +|---|---|---| +| PLC-PC 신호정의서 | `02.설계/IO_DB설계/PLC_PC_인터페이스_신호정의서.md` | PLCController.Address.cs | +| 모션축·IO 맵 정의서 | `02.설계/IO_DB설계/모션축_IO맵_정의서.md` | EquipmentSetup + PAIX_PTOP | +| 기능명세서 | `02.설계/UIUX_기능설계/기능명세서.md` | EquipmentSetup.RegisterViews | +| 시퀀스·인터락 정의서 | `02.설계/시퀀스_인터록/제어시퀀스_인터락_정의서.md` | SeqAutoRunPTOP.Builder | +| TactTime 산정서 | `02.설계/시퀀스_인터록/TactTime_산정서.md` | SeqAutoRunPTOP 지연 상수 | + +각 파일 구조: **배너(진네이비) → 부제(파랑) → 내용 테이블/mermaid → enum 요약** + +### STEP 3 — _task 카드 갱신 +`01~06` 각 `_task.md` 상태·진행률을 현황 반영해 갱신. +적용 디자인: 상태 뱃지 + 유형 뱃지 + `` 진행률 바. + +### STEP 4 — 분석 로그 기록 +`[분석]/YYYY_MM_DD_PTOP설계산출물-분석.md` 작성. +내용: 소스→필드 매핑 표 + 미확정 항목 목록 + 진실원 인덱스. + +## 불변 규칙 +- **비침습**: `docs/[Ubisam산출물]/` 밖은 읽기만. 소스·CLAUDE.md 수정 금지. +- **MD 200줄 상한**: 초과 시 분할 + 상대경로 링크. +- **출처 명시**: 채운 값마다 `— 출처: 파일명:심볼` 주석. +- **미확정 표기**: 주소·행정·속도 등 미확정값 → `(제안)`. +- **멱등**: 재실행 시 배너·뱃지는 교체, 테이블 데이터는 행 추가/갱신. +- **Aurora 고정 문구**: "운영 SW는 LG PRI의 Aurora 플랫폼으로 개발한다." diff --git a/.claude/skills/ptop-deliverables/references/design-system.md b/.claude/skills/ptop-deliverables/references/design-system.md new file mode 100644 index 0000000..a2ec800 --- /dev/null +++ b/.claude/skills/ptop-deliverables/references/design-system.md @@ -0,0 +1,126 @@ +# PTOP 산출물 — Ubisam 디자인 시스템 + +> MD 산출물에 적용하는 HTML 색상 템플릿 모음. +> VS Code Markdown Preview / Typora / GitLab 렌더링 기준. + +--- + +## 색상 팔레트 + +| 구분 | 이름 | 색 코드 | 용도 | +|---|---|---|---| +| 배너 | 진네이비 | `#1F4E78` | 문서 최상단 제목 배너 | +| 헤더 | 파랑 | `#2E75B6` | 배너 하단 부제 바, 섹션 강조 | +| 연파랑 | — | `#DDEBF7` | 테이블 홀수 행 참조 (xlsx 전용, MD 생략 가능) | +| 상태·해야 할 일 | 회색 | `#757575` | 미착수 | +| 상태·분석 진행 | 청색 | `#1976D2` | 선택됨 | +| 상태·업무 진행중 | 파랑 | `#1565C0` | 진행 중 | +| 상태·진행 완료 | 연두 | `#689F38` | 완료 | +| 유형·오류수정 | 빨강 | `#D32F2F` | — | +| 유형·불합리 | 주황 | `#F57C00` | — | +| 유형·미팅 | 청록 | `#00796B` | 1.분석/협의 | +| 유형·설계 | 연두 | `#689F38` | 2.설계 | +| 유형·개발 | 파랑 | `#1976D2` | — | +| 유형·제작 | 남파랑 | `#283593` | 3.제작 | +| 유형·입고셋업 | 보라 | `#7B1FA2` | 4.셋업 | +| 유형·적용 및 모니터링 | 하늘 | `#0288D1` | 5.양산 | +| 유형·요청대응 | 남색 | `#1A237E` | 6.CS | + +--- + +## 1. 문서 배너 (최상단) + +산출물 MD 파일 1번째 줄부터 적용. + +```html +
+문서 제목 +
+
+근거: 소스 경로  ·  Aurora 2.0 / .NET 4.8.1 / WPF +
+``` + +--- + +## 2. 상태 뱃지 + +`_task.md` 상태 필드에 사용. 값에 맞춰 색 코드 교체. + +```html + +해야 할 일 + + +분석 진행 + + +업무 진행중 + + +진행 완료 +``` + +--- + +## 3. 유형 뱃지 + +```html +미팅 +설계 +제작 +입고셋업 +적용 및 모니터링 +요청대응 +개발 +``` + +--- + +## 4. 진행률 바 + +`` 태그 + 텍스트 병기 (렌더러 호환). + +```html + 85% +``` + +--- + +## 5. _task.md 전체 템플릿 + +```markdown +
+N.단계명 — LGES OC5 PtoP Welder +
+
+LGES_PTOP_01_20260605-XXXX +
+ +| 필드 | 값 | +|---|---| +| 유형 | 유형명 | +| 상태 | 상태명 | +| 담당자 / 참조자 | {미정} / {미정} | +| 진행률 | **N%** | +| Hashtag | 태그1 / 태그2 | +``` + +--- + +## 6. 산출물 섹션 헤더 + +산출물 MD 내 시트 구분 헤더. + +```markdown +## 시트① 제목 +``` + +--- + +## 7. 미확정·제안 강조 + +```markdown +> [!NOTE] +> 아래 값은 소스 enum offset 기반 `(제안)`. PLC 맵 CSV 입수 후 확정. +``` diff --git a/.claude/skills/ptop-deliverables/references/source-map.md b/.claude/skills/ptop-deliverables/references/source-map.md new file mode 100644 index 0000000..597a851 --- /dev/null +++ b/.claude/skills/ptop-deliverables/references/source-map.md @@ -0,0 +1,105 @@ +# PTOP 소스 → 산출물 필드 매핑 + +> 이 파일은 소스 파일과 산출물 필드 간 1:1 매핑 인덱스. +> 다른 PC에서 스킬 실행 시 이 파일을 보고 어떤 소스를 읽어야 할지 판단한다. + +--- + +## A. PLC-PC 인터페이스 신호정의서 + +출력: `02.설계/IO_DB설계/PLC_PC_인터페이스_신호정의서.md` + +| 시트 | 소스 파일 | 읽어야 할 심볼 | +|---|---|---| +| 통신 사양 | `Device/PLC/PLCContoller.Config.cs` | `ePLCNetType`, `PLCMMapFileType`, 베이스 주소 설정 | +| PC→PLC (Write) | `Device/PLC/PLCController.Address.cs` | `eBitOutput`, `eWordOutput` enum 전수 | +| PLC→PC (Read) | `Device/PLC/PLCController.Address.cs` | `eBitInput`, `eWordInput` enum 전수 | +| 핸드셰이크 시퀀스 | `Sequences/SeqAutoRunPTOP.cs` | `CheckPageNo`, `CheckRecipeNoComp`, `CheckPageNoComp`, `WaitWeldingStart`, `WriteWeldComplete` | + +**PLC 주소 규칙**: +- `eBitOutput` offset → `M70000 + offset` +- `eBitInput` offset → `M70500 + offset` +- `eWordOutput` offset → `D70000 + offset` +- `eWordInput` offset → `D70500 + offset` +- 두 번째 블록(0x65~, 양극 P) → `M70600~` (현재 미사용) + +--- + +## B. 모션축·IO 맵 정의서 + +출력: `02.설계/IO_DB설계/모션축_IO맵_정의서.md` + +| 시트 | 소스 파일 | 읽어야 할 심볼 | +|---|---|---| +| 통신·제어 구성 | `EquipmentSetup.cs` | `RegisterDevices()` 내 모든 `Register*Device` 호출 | +| 모션축 정의 | `docs/architecture.md §4` | AuroraACS(X/Y), AuroraMotionBoard(Z), RTC(Scanner) | +| I/O 맵 DI | `Device/IO/PAIX_PTOP.cs` | `ePrismaticDIAddress` enum — 채널 번호·신호명 | +| I/O 맵 DO | `Device/IO/PAIX_PTOP.cs` | `ePrismaticDOAddress` enum — 채널 번호·신호명 | + +**모션축 주의사항**: +- X/Y 행정·속도·Home 방식 = 소스에 없음 → 전부 `(제안)` 표기. +- `AuroraACS` = EtherCAT SPiiPlus. `AuroraMotionBoard` = Ajin 보드. + +--- + +## C. 기능명세서 / UI 설계 + +출력: `02.설계/UIUX_기능설계/기능명세서.md` + +| 탭 | 소스 파일 | 읽어야 할 심볼 | +|---|---|---| +| 메뉴 구조 | `EquipmentSetup.cs` | `RegisterViews()` 전수 (MenuId, ViewType, AuthLevel) | +| MainView | `Views/Main/UCMainAutoRun*.cs` | 버튼·이벤트·바인딩 목록 | +| RecipeView | `Views/Recipe/UCRecipe*.cs` | ActivateRecipe, RecipeChangeHistory, UCModel | +| ManualView | `Views/Manual/UCManualScannerOperation*.cs` | ScannerOperation, ModelFinder | +| MonitoringView | `Views/Monitoring/UCPLC*.cs`, `UCIOInterfaceView.cs` | PLCBit, PLCWord, IOInterface | +| Sequence 흐름 | `Sequences/SeqAutoRunPTOP.cs` | Builder() 노드 목록 순서대로 | + +**8탭 고정** (xlsx ref 호환): OverView / MainView / RecipeView / ManualView / DeviceView / SettingView / VisionView / Sequence 흐름 + +--- + +## D. 제어 시퀀스·인터락 정의서 + +출력: `02.설계/시퀀스_인터록/제어시퀀스_인터락_정의서.md` + +| 섹션 | 소스 파일 | 읽어야 할 심볼 | +|---|---|---| +| 메인 시퀀스 노드 흐름 | `SeqAutoRunPTOP.cs` | `Builder()` 내 모든 AddNode·AddTransition | +| 단계별 동작 요약 | `SeqAutoRunPTOP.cs` | 각 노드 메서드 구현 첫 줄 주석·로그 | +| 인터락 정의 | `Settings/LaserInterlockSetting.cs` | `HeadThetaInterlockEnabled`, `ThetaTolerance` | +| 인터락 정의 (추가) | `SeqAutoRunPTOP.cs` | `LaserInterlock`, `PLC_HEAVY_ALARM_STATUS` 처리 | +| 정지/에러 처리 | `SeqAutoRunPTOP.cs` | `OnStopped`, `OnForceError`, `ResumeMode` | + +**Resume(bit15) 흐름**: `PLC_WELD_READY_REQ(0x0F)` 수신 → 팝업 + Auto→Manual 전환 → `PC_WELD_READY_ACK(0x0F)` ON → 1사이클 후 OFF. + +--- + +## E. TactTime 산정서 + +출력: `02.설계/시퀀스_인터록/TactTime_산정서.md` + +| 단위동작 | 소스 파일 | 상수/값 | +|---|---|---| +| Page 3회 검증 | `SeqAutoRunPTOP.cs` | `PAGE_VERIFY_DELAY_MS` × 3 (≈200ms×3 = 600ms) | +| 준비 응답 | `SeqAutoRunPTOP.cs` | `WELD_START_DELAY_MS` (200ms) + 소신호(5ms) | +| Gate ON delay | `SeqAutoRunPTOP.cs` | `laserEmissionGateDelayMs` = 15ms | +| 실제 발진 | `SeqAutoRunPTOP.cs` | 로그 "TactTime: [Nms]" — 예시 285ms (라인 수 의존) | +| Jump 복귀 | `SeqAutoRunPTOP.cs` | `Thread.Sleep(500)` | +| 완료비트 | `SeqAutoRunPTOP.cs` | 50ms + 5ms | +| Pre/Post Scout | `SeqAutoRunPTOP.cs` | `SCOUT_PROCEED_DELAY_MS` = 100ms/회 (M70509 ON 시) | + +--- + +## F. _task.md 진행률 기준 (현황 기준) + +| 단계 | 상태 | 진행률 | 근거 | +|---|---|---|---| +| 1.분석/협의 | 해야 할 일 | 0% | 사양서 미입수 | +| 2.설계 | 업무 진행중 | 85% | xlsx 4종 완료, 모션 파라미터 미확정 | +| 3.제작 | 해야 할 일 | 0% | CheckList 미작성 | +| 4.셋업 | 해야 할 일 | 0% | 현장 셋업 미완료 | +| 5.양산 | 업무 진행중 | 40% | 운영매뉴얼 3종 완료, 나머지 미완료 | +| 6.CS | 해야 할 일 | 0% | 미착수 | + +> 이 값은 스킬 실행 시점 기준. 재실행 시 소스·폴더 현황 재확인 후 갱신. diff --git a/.claude/skills/ubisam-deliverable/SKILL.md b/.claude/skills/ubisam-deliverable/SKILL.md new file mode 100644 index 0000000..1540d5d --- /dev/null +++ b/.claude/skills/ubisam-deliverable/SKILL.md @@ -0,0 +1,49 @@ +--- +name: ubisam-deliverable +description: Ubisam 산출물 재생산 하네스의 오케스트레이터. 타사 양식 산출물(Doc/산출물문서의 xlsx·pptx)을 Ubisam 협업관리 양식(상태4·유형9·6단계 슬롯·필드 스키마)으로 분석·재배치·재생산·검증하는 분석가·작성가·검증가 3에이전트 팀을 구동한다. "Ubisam 산출물 만들어/재생산/재배치", "협업관리 양식으로", "산출물 채움", "이 문서 Ubisam 단계로", 그리고 후속 표현("다시 실행", "재실행", "업데이트", "수정", "보완", "OO만 다시", "이전 결과 기반") 시 반드시 이 스킬을 사용할 것. 단순 양식 질문은 ubisam-format-map만으로 답할 수 있다. +--- + +# ubisam-deliverable — Ubisam 산출물 재생산 오케스트레이터 + +타사 양식 산출물을 **Ubisam 양식으로 재생산**하는 3에이전트 팀(생성-검증 파이프라인)을 조율한다. + +- 실행 모드: **에이전트 팀** (분석 → 재생산 ↔ 검증) +- 팀원: `ubisam-analyst`, `ubisam-producer`, `ubisam-qa` (모두 `model: "opus"`) +- 공유 스킬: `ubisam-format-map` (양식 enum·스키마·슬롯 매핑 — 모든 팀원이 먼저 읽음) +- 작업공간: 대상별 `_workspace/`, 최종 산출물은 지정 경로 또는 `docs/[Ubisam산출물]/` 하위 + +## Phase 0: 컨텍스트 확인 +1. 대상 산출물과 작업 디렉토리를 정한다. 기본 작업공간: `docs/[Ubisam산출물]/_workspace/`. +2. 실행 모드 판별: + - `_workspace/` 없음 → **초기 실행**(Phase 1부터) + - `_workspace/` 있음 + 부분 수정 요청 → **부분 재실행**(해당 에이전트만) + - `_workspace/` 있음 + 새 입력 → **새 실행**(기존을 `_workspace_prev/`로 이동) +3. `ubisam-format-map` 스킬을 로드해 양식 기준을 확보한다. + +## Phase 1: 분석 (analyst) +- `ubisam-analyst`에게 대상 문서를 주고 구조 분석·타설비 잔재 식별·6단계 슬롯 매핑을 요청한다. +- 산출: `_workspace/01_analyst_{문서}.md`. + +## Phase 2: 재생산 ↔ 검증 (producer ↔ qa, 점진적) +- `ubisam-producer`가 매핑·채움 항목으로 산출물을 재생산한다(xlsx=openpyxl, pptx=`ubisam-ppt-make`, md). +- 각 문서 완성 직후 `ubisam-qa`가 **점진적으로** 검증(enum·슬롯·스키마·잔재·출처·자동화). +- Critical/Major는 producer가 수정 후 재검증. 통과까지 반복(최대 합리적 횟수, 1회 재시도 원칙). + +## Phase 3: 종합·보고 +- 최종 산출물 경로, qa 통과 여부, 미정/TBD 항목, 출처를 사용자에게 요약 보고한다. +- 중간 산출물(`_workspace/`)은 보존(감사 추적). + +## 데이터 전달 프로토콜 +- 태스크 기반(`TaskCreate`/`TaskUpdate`)으로 진행 추적, 파일 기반(`_workspace/`)으로 산출물 전달, 메시지 기반(`SendMessage`)으로 실시간 조율. +- 파일명: `{phase}_{agent}_{artifact}.{ext}` (예: `01_analyst_sw점검결과.md`). + +## 에러 핸들링 +- 에이전트 실패 시 1회 재시도, 재실패하면 해당 결과 없이 진행하고 보고서에 누락을 명시한다. +- 상충 데이터는 삭제하지 않고 출처를 병기한다. + +## 테스트 시나리오 +- **정상 흐름**: `23. SW 점검 결과` xlsx → analyst가 SW Version/Gantt를 6단계 슬롯(제작/양산 등)에 매핑 → producer가 Ubisam 양식 시트로 재배치 → qa가 enum/스키마 검증 → 통과 보고. +- **에러 흐름**: 대상 파일이 편집기 잠금 → producer가 잠금 경고·작업 사본 진행 → qa가 원본 미반영 표시 → 사용자에게 "원본 닫기 후 반영 필요" 보고. + +## 후속 작업 +- 부분 수정 요청("OO만 다시")은 해당 에이전트만 재호출하고, 이전 `_workspace/` 산출물을 기반으로 변경분만 갱신한다. diff --git a/.claude/skills/ubisam-deliverables/README.md b/.claude/skills/ubisam-deliverables/README.md new file mode 100644 index 0000000..baa8941 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/README.md @@ -0,0 +1,36 @@ +# ubisam-deliverables (Claude Code Skill) + +Ubisam 협업관리 구조로 **어떤 프로젝트에서든 동일한 산출물 트리·양식**을 생성·관리하는 스킬. +`ref/` 원본 파일 없이도 동작하도록 양식 골격을 MD에 내장(자기완결). + +## 설치 +대상 프로젝트에 폴더째 복사: +``` +<프로젝트>/.claude/skills/ubisam-deliverables/ +``` +(개인 전역으로 쓰려면 `~/.claude/skills/` 아래에 둬도 됨.) + +## 사용 +- "Ubisam 산출물 구조 만들어줘" / "산출물 트리 스캐폴딩" → 트리 생성(A). +- "이 양식 채워줘"(`산출물문서/양식/`에 파일 투입 후) → 양식 채움(B). + +## 구성 +``` +SKILL.md # 인덱스 + 오케스트레이션(0 하네스 / A 스캐폴딩 / B 채움) +references/ + 00-harness.md # 호스트 레포 맞물림·비침습 계약 + 01-tree.md # 목표 트리·배치 규칙 + 02-enums.md # 상태4·유형9·티켓코드 + 03-schema.md # 일감·프로젝트 스키마 + 04-stage-deliverables.md# 6단계↔슬롯 매핑 + 05-form-schemas.md # 설계 양식 시트·컬럼(ref 불필요) + 06-procedure.md # 분석→채움 절차 + 07-fill-mapping.md # 프로젝트 분석→산출물 필드 자동 채움 +templates/ + _task.template.md # 일감 카드 템플릿 + project.yaml.template # 프로젝트 메타 템플릿 +``` + +## 원칙 +- 모든 MD 200줄 이내, 상호 링크로 매핑. +- enum 고정(상태/유형) · 출처 명시 · 조건부 서식 보존 · 레포 루트 보호. diff --git a/.claude/skills/ubisam-deliverables/references/00-harness.md b/.claude/skills/ubisam-deliverables/references/00-harness.md new file mode 100644 index 0000000..4e5b241 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/00-harness.md @@ -0,0 +1,47 @@ +# 00 · 하네스 (호스트 프로젝트 맞물림 설계) + +> 이 스킬이 **이미 CLAUDE.md·.claude가 있는 임의의 소스 레포**에 들어가 기존 에이전트와 +> 충돌 없이 맞물려 동작하기 위한 계약. SKILL.md가 가장 먼저 참조한다. + +## 비침습 계약 (절대 규칙) +- **읽기만**: 호스트의 `CLAUDE.md`, `.claude/`, `RULES.md`, `src/`·소스코드는 입력으로만 읽는다. +- **쓰기 격리**: 모든 생성/수정은 `docs/[Ubisam산출물]/` 하위로 한정. +- **우선순위**: 호스트 루트 `RULES.md`/`CLAUDE.md` > 본 산출물 규칙. 충돌 시 호스트 우선. +- **멱등**: 폴더·파일은 "없으면 생성, 있으면 유지". 재실행이 기존 산출물을 덮어쓰지 않는다. + +## 입력 (호스트에서 읽는 진실원) +| 입력 | 용도 | +|---|---| +| 루트 `CLAUDE.md` | 설비·플랫폼·디바이스·시퀀스 등 프로젝트 사실 | +| 소스코드(`src/`,`Machines/` 등) | PLC/모션/IO/Vision 구성, 메인 시퀀스 추출 | +| `README*`, 설계 문서 | 보조 컨텍스트 | +| git 원격/폴더명 | 프로젝트 코드·명 자동 추정 | + +추출 사실 → 산출물 필드 매핑은 [07-fill-mapping.md](07-fill-mapping.md). + +## 데이터 흐름 (호스트 에이전트와 맞물림) +``` +[호스트 CLAUDE.md + 소스] ──읽기──▶ (분석) ──▶ 사실 추출 + │ │ + ▼ ▼ + docs/[Ubisam산출물]/ 트리 생성 ◀── 매핑(07) ── 산출물 필드 채움 + │ + ▼ + _task.md 진행률 갱신 + [작업일지] 기록 +``` +- 스킬은 **온디맨드** 호출(트리거 문구). 호스트 에이전트 루프를 가로채지 않는다. +- 분석/검색은 호스트 에이전트의 도구(Grep/Read)를 그대로 활용. + +## 프로젝트 자동 인식 +1. `project_code`: git 원격명·폴더명·`CLAUDE.md`에서 추정 → 실패 시 사용자에게 질문. +2. `project_name`: `CLAUDE.md` 제목/설비명에서 추정. +3. 추정값은 `_meta/project.yaml`에 기록하고 `(추정)` 표기. 확정 전까지 문서에 `(제안)` 유지. + +## 트리거 문구 +- 생성: "산출물 구조 만들어줘", "Ubisam 트리 스캐폴딩". +- 채움: "설계 산출물 채워줘", "IO 맵 정의서 생성", "프로젝트 분석해서 산출물 채움". +- 갱신: "2.설계 진행률 갱신", "양산 단계 산출물 추가". + +## 안전장치 +- 호스트 파일 수정 요청이 들어오면 거부하고 `docs/` 내 대안 제시. +- 양식 엑셀은 원본 템플릿 복제 후 값만 덮어써 조건부 서식 보존([06-procedure.md](06-procedure.md)). diff --git a/.claude/skills/ubisam-deliverables/references/01-tree.md b/.claude/skills/ubisam-deliverables/references/01-tree.md new file mode 100644 index 0000000..ec09993 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/01-tree.md @@ -0,0 +1,41 @@ +# 01 · 목표 트리 & 배치 규칙 + +> 이 스킬만으로(ref 없이) 어떤 프로젝트에서도 동일 트리를 생성한다. + +## 배치 원칙 +- 산출물은 **소스 루트가 아니라 `docs/[Ubisam산출물]/` 하위**에 둔다. +- 루트의 `.claude/`·`CLAUDE.md`·`RULES.md`는 손대지 않는다. +- 우선순위: 레포 루트 `RULES.md` > 본 산출물 `RULES.MD`(스코프 하위). + +## 트리 + +``` +<소스레포루트>/ +├─ .claude/ CLAUDE.md RULES.md # (기존) 손대지 않음 +└─ docs/ + └─ [Ubisam산출물]/ + ├─ RULES.MD # 산출물 영역 규칙(이 스킬이 생성) + ├─ README.md # 트리 한눈에 + 시작하기 + ├─ _meta/ + │ ├─ project.yaml # → templates/project.yaml.template + │ ├─ status.enum.md # → 02-enums §상태 + │ ├─ task-type.enum.md # → 02-enums §유형 + │ ├─ task-schema.md # → 03-schema §일감 + │ └─ ticket-code.rule.md # → 02-enums §티켓코드 + ├─ 01.분석협의/ _task.md + 사양서/ 회의록_CR/ 마스터플랜/ + ├─ 02.설계/ _task.md + UIUX_기능설계/ 시퀀스_인터록/ IO_DB설계/ 설계리뷰_DR/ + ├─ 03.제작/ _task.md + 테스트결과_CheckList/ + ├─ 04.셋업/ _task.md + HW_IO모션점검/ 파라미터_레시피백업/ 이슈로그_IssueLog/ SAT_인수검사보고서/ + ├─ 05.양산/ _task.md + 운영매뉴얼_HMI/ 최종SW_마스터백업/ 프로젝트완료보고서/ + ├─ 06.CS/ _task.md + ├─ 산출물문서/ README.md + 양식/(빈 양식 드롭존) + ├─ ref/ # 참고자료(읽기전용) — 비어 있어도 무방 + ├─ [분석]/ # 에이전트 분석 산출물(날짜_주제.md) + └─ [작업일지]/ # 일자별 로그(yyyy_mm_dd.md) +``` + +산출물 슬롯 폴더는 비어 있으면 `.gitkeep`을 둔다. 슬롯 상세 매핑은 [04-stage-deliverables.md](04-stage-deliverables.md). + +## 폴더명 규칙 +- 단계: `NN.단계명`(2자리 zero-pad). 협업관리 일감 제목과 동일 어휘. +- 산출물 슬롯: 한글_영문 혼합 가능, 공백 대신 `_`. diff --git a/.claude/skills/ubisam-deliverables/references/02-enums.md b/.claude/skills/ubisam-deliverables/references/02-enums.md new file mode 100644 index 0000000..397b9aa --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/02-enums.md @@ -0,0 +1,56 @@ +# 02 · enum & 티켓코드 (회사 공통 마스터) + +> 임의 신규값 금지. 새 값 발견 시 이 파일을 먼저 갱신 후 사용. +> 스캐폴딩 시 §상태→`status.enum.md`, §유형→`task-type.enum.md`, §티켓코드→`ticket-code.rule.md`로 복사. + +## §상태(Status) — 4종 (전이 순서) + +``` +해야 할 일 → 분석 진행 → 업무 진행중 → 진행 완료 +``` + +| 코드(한글) | 색 | HEX | 칸반 컬럼 | +|---|---|---|---| +| 해야 할 일 | 회색 | `#757575` | Todo | +| 분석 진행 | 청색 | `#1976D2` | Select For dev | +| 업무 진행중 | 파랑 | `#1565C0` | InProgress | +| 진행 완료 | 연두 | `#689F38` | Done | + +컬럼 옵션: "상태 없으면 게시판 비표시", "WIP 초과 강조". + +## §유형(Type) — 9종 + +| 유형 | 색 | HEX | 6단계 매핑(관례) | +|---|---|---|---| +| 오류수정 | 빨강 | `#D32F2F` | — | +| 불합리 | 주황 | `#F57C00` | — | +| 미팅 | 청록 | `#00796B` | 1.분석/협의 | +| 설계 | 연두 | `#689F38` | 2.설계 | +| 개발 | 파랑 | `#1976D2` | — | +| 제작 | 남파랑 | `#283593` | 3.제작 | +| 입고셋업 | 보라 | `#7B1FA2` | 4.셋업 | +| 적용 및 모니터링 | 하늘 | `#0288D1` | 5.양산 | +| 요청대응 | 남색 | `#1A237E` | 6.CS | + +> 뱃지 HTML 템플릿은 [08-design-system.md](08-design-system.md) § 2·3 참조. + +## §우선순위 +- 확인값: `보통`(Normal). 그 외 등급 미확인 — 발견 시 보강. + +## §티켓코드 규칙 + +``` +{프로젝트코드}-{일련번호} +``` +- 프로젝트코드: `_meta/project.yaml › project_code` (예: `LGIT_SI_01_20260601`) +- 일련번호: 4자리 권장, 생성 순 증가. +- 전체 예: `LGIT_SI_01_20260601-1024` + +| 일감 | 일련번호(예) | +|---|---| +| 1.분석/협의 | 1023 | +| 2.설계 | 1024 | +| 3.제작 | 1025 | +| 4.셋업 | 1026 | +| 5.양산 | 1027 | +| 6.CS | 1028 | diff --git a/.claude/skills/ubisam-deliverables/references/03-schema.md b/.claude/skills/ubisam-deliverables/references/03-schema.md new file mode 100644 index 0000000..66a1b3c --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/03-schema.md @@ -0,0 +1,39 @@ +# 03 · 일감 & 프로젝트 스키마 + +> 스캐폴딩 시 §일감→`task-schema.md`, 프로젝트 메타→`project.yaml`(템플릿 사용). + +## §일감(Task) 필드 + +| 필드 | 비고 | +|---|---| +| 제목 | `N.단계명` (예: `2.설계`) | +| 티켓번호 | `{프로젝트코드}-{일련번호}` → [02-enums §티켓코드](02-enums.md) | +| 유형 | [02-enums §유형](02-enums.md) (9종) | +| 상태 | [02-enums §상태](02-enums.md) (4종) | +| 우선순위 | `보통` 등 | +| 담당자 / 참조자 | 이름(직급) 표기 (예: 홍길동(사원)) | +| 진행률 | % | +| Hashtag | 자유 태그 (예: 분석/검토/협의) | +| 등록일 / 수정일 / 종료일 | `yyyy/mm/dd` | +| 예상시간 | `2w 4d 6h 45m` 형식 | +| 수정 버전 | None 가능 | +| 첨부파일 | 최대 2.93GB | +| 체크리스트 | 의견글 / 시간변경이력 / 타임 트래킹 | + +`_task.md` 카드 양식은 [templates/_task.template.md](../templates/_task.template.md). + +## §프로젝트 메타 + +| 필드 | 예시/형식 | +|---|---| +| 프로젝트명 | `{사업부}_{고객사}_{설비명} 운영SW` | +| 종류 | Software / Hardware | +| 프로젝트 형식 | Kanban | +| 코드명 | `{CUST}_{TYPE}_{NN}_{YYYYMMDD}` | +| 사업부 / 팀 | `{사업부}` / `{팀}` | +| PL / 등록자 | `{이름}({직급})` | +| 계약일 / 납기 | `yyyy-mm-dd` | +| 권한 | 책임자 → 참여자 → 열람자 (3단계) | +| 알람 | 실시간 티켓 / 메일 / 댓글 | + +전체 템플릿(토큰 포함): [templates/project.yaml.template](../templates/project.yaml.template). diff --git a/.claude/skills/ubisam-deliverables/references/05-form-schemas.md b/.claude/skills/ubisam-deliverables/references/05-form-schemas.md new file mode 100644 index 0000000..356b8d5 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/05-form-schemas.md @@ -0,0 +1,94 @@ +# 05 · 설계 양식 시트·컬럼 스키마 + +> **재생산 방식 (2026-06-29 확정): 완전 독립 (ref 파일 불필요)** +> xlsx는 `xlsxfill.py` 의 하드코딩 스타일 상수와 `build_02설계_xlsx.py` 의 PtoP 데이터만으로 처음부터(`from scratch`) 생성한다. ref 원본 xlsx 없이도 동일한 외형을 재현한다. +> +> **재생산 도구 (영구 보존)** +> - `xlsxfill.py` — 같은 폴더. LG PRI 스타일 상수 + `make_workbook / write_banner / write_header_row / write_data_rows / group_merge` scratch-build API. +> - `build_02설계_xlsx.py` — 같은 폴더. PtoP 데이터 내장, 단독 실행으로 4개 xlsx 재생산. +> ``` +> cd E:\Work\GIT\LGES-OC5-PtoP-Welder +> set PYTHONUTF8=1 +> py .claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py +> ``` +> - 전제조건: Python 3.9+, openpyxl >= 3.1 (`pip install openpyxl`) +> +> **공통 관례**: ① 배너 B2=제목, B3=근거/부제 ② 미확정 값은 `(제안)` 표기 ③ Aurora 고정 문구 "운영 SW는 LG PRI의 Aurora 플랫폼으로 개발" + +--- + +## 스타일 상수 (xlsxfill.py 하드코딩값) + +| 구분 | 폰트 | 색상(fgColor) | 테두리 | +|---|---|---|---| +| 배너 행(B2) | 맑은 고딕 13pt Bold White(FFFFFF) | FF1F4E78 (진네이비) | medium all | +| 부제 행(B3) | 맑은 고딕 9pt | 없음 | medium L/R/T | +| 헤더 행 | 맑은 고딕 11pt Bold White(FFFFFF) | FF2E75B6 (파랑) | medium 좌우, thin 상하 | +| 데이터 행(홀수) | 맑은 고딕 10pt | FFDDEBF7 (연파랑) | medium 좌우, thin 상하 | +| 데이터 행(짝수) | 맑은 고딕 10pt | FFFFFFFF (흰색) | medium 좌우, thin 상하 | +| 기능명세서 배너 | 맑은 고딕 14pt Bold | 없음 | medium all | +| 기능명세서 헤더 | 맑은 고딕 11pt Bold | **없음** | thin all | +| 기능명세서 데이터 | 맑은 고딕 11pt | 없음 | thin all | +| TactTime 헤더 | 맑은 고딕 11pt | **없음** | thin all | +| TactTime 데이터 | 맑은 고딕 11pt | 없음 | thin all | + +> `write_header_row(fill=None)` — 배경 없음(기능명세서·TactTime 계열). 인자 생략 시 파랑(HEADER_FILL) 기본값. `fill=_UNSET` sentinel으로 구분. +> `TODAY = datetime.date.today()` — 실행일 자동 반영. 이력 셀(Update 컬럼)에 삽입. + +--- + +## A. 모션축·IO 맵 정의서 (슬롯 `02.설계/IO_DB설계/모션축_IO맵_정의서.xlsx`) +**3탭 고정** + +| 탭명 | 헤더(B4~) | 데이터 시작행 | PtoP 특이사항 | +|---|---|---|---| +| 통신·제어 구성 | 구분\|대상\|컨트롤러/디바이스\|통신/버스\|수량·범위\|비고 | B5 | PLC=MXComponent, IO=Ajin, Laser=IOGate, Scanner=RTC | +| 모션축 정의(ACS) | Axis\|축명(논리)\|용도\|컨트롤러/통신\|구동\|행정/범위\|최대속도\|Home방식\|Soft Limit\|동기/단축\|비고 | B5 | X/Y=ACS EtherCAT, Z=Ajin, Scanner θ/XY=RTC. 행정·속도·Home=(제안) | +| I-O 맵(Ajin) | No\|신호명\|Type\|권장모듈\|슬롯/CH\|Active\|설명/인터락\|비고 | B5 | Ajin DI 9~23 / DO 9~17 (`PAIX_PTOP.cs`) | + +## B. PLC-PC 인터페이스 신호 정의서 (슬롯 `02.설계/IO_DB설계/PLC_PC_인터페이스_신호정의서.xlsx`) +**4탭 고정** + +| 탭명 | 헤더(B4~) | 데이터 시작행 | +|---|---|---| +| 통신 사양 | 항목\|내용 | B5 | +| PC→PLC (Write) | No\|Seq/구분\|신호명\|방향\|디바이스(제안)\|Type\|의미 / 트리거\|응답·완료\|비고 | B5 | +| PLC→PC (Read) | 동일 | B5 | +| 핸드셰이크 시퀀스 | No\|Seq\|Step\|주체\|동작 / 신호\|디바이스(제안)\|비고 | B5 | + +- Seq 구분 enum: Status · Alarm · Process · Request · Info · Control · Model · Interlock +- 디바이스: 비트 `M70000~`(PC Write), `M70500~`(PLC Read). 워드 `D70000~`/`D70500~`. +- PtoP 핸드셰이크: PAGE_REQ 단일 게이트 → Recipe 확정 → Page 3회 검증 → 리스트 업로드 → 발진 → 완료. + +## C. 기능명세서 / UI 설계 (슬롯 `02.설계/UIUX_기능설계/기능명세서.xlsx`) +**8탭 고정** (우리 설비에 없는 탭도 양식 유지) + +| 탭명 | 헤더(행5~) | G3 배너 | PtoP 내용 | +|---|---|---|---| +| OverView | no.\|Content | — | SW 개요(Aurora·Win11·.NET·설비·모션·레이저·PLC·비전) | +| MainView | Update\|Funtion Area\|Funtion Name\|NO\|Sub\|Content | "다음은 Main 화면…" | AutoRun 시작/정지·페이지·Resume(bit15)·스캐너캔버스 | +| RecipeView | 동일 | "다음은 Recipe 화면…" | ActivateRecipe·변경이력·모델 편집 | +| ManualView | 동일 | "다음은 Manual 화면…" | ScannerOperation·ModelFinder·Calibration | +| DeviceView | 동일 | "다음은 Device 화면…" | DeviceSetting·PLCBit/Word·IOInterface | +| SettingView | 동일 | "다음은 Setting 화면…" | Equipment·Log·AutoBackup·Alarm·Stream | +| VisionView | 동일 | "다음은 Vision 화면…" | `(해당없음)` — AuroraVision 미사용 | +| Sequence 흐름 | Update\|Funtion Area\|Processing Name\|NO\|Content | — | PAGE_REQ게이트→Recipe→Page→ListUpload→발진→완료→루프백 | + +## D. TactTime 산정서 (슬롯 `02.설계/시퀀스_인터록/TactTime_산정서.xlsx`) +**2탭 고정** + +| 탭명 | 좌측 컬럼 | 우측 컬럼 | PtoP 내용 | +|---|---|---|---| +| PC TactTime | 공정\|Unit\|단위동작\|T/T | Working Process\|상세합계\|기준/참조합계 | SeqAutoRunPTOP.cs 타이밍 (Page gate·Recipe·발진·완료) | +| PLC TactTime | 동일 구조 | — | 미입수 → `(제안)` | + +## E. Flow Chart (슬롯 `02.설계/시퀀스_인터록/`) +- 형식: PPTX 흐름도. 메인 시퀀스: PAGE_REQ 단일게이트 → 리스트 업로드 → 발진 → 완료비트. 분기·인터락 표현. + +## enum 요약(양식 고정값) +- I/O Type: DI · DO · AI · AO +- 신호 Type: BIT · INT · INT32 +- PLC Seq 구분: Status · Alarm · Process · Request · Info · Control · Model · Interlock +- 운영 모드: 수동 · 반자동 · 자동 + +> 재생산 시 항상 `build_02설계_xlsx.py` 를 실행할 것. 스크립트는 cwd=repo root 기준으로 실행. diff --git a/.claude/skills/ubisam-deliverables/references/07-fill-mapping.md b/.claude/skills/ubisam-deliverables/references/07-fill-mapping.md new file mode 100644 index 0000000..01dc077 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/07-fill-mapping.md @@ -0,0 +1,40 @@ +# 07 · 자동 채움 매핑 (프로젝트 분석 → 산출물 필드) + +> "산출물 양식 필드 ← 호스트 레포의 진실원" 규칙. 프로젝트 비종속(휴리스틱) + PTOP 예시. +> 양식 시트·컬럼 골격은 [05-form-schemas.md](05-form-schemas.md), 절차는 [06-procedure.md](06-procedure.md). + +## 추출 휴리스틱 (어떤 프로젝트든) +| 추출 대상 | 탐색 방법(예) | +|---|---| +| 플랫폼/프레임워크 | `CLAUDE.md`, `*.csproj`/`*.sln`, `*.config` | +| PLC 기종·통신 | `grep -ri "melsec\|mx ?component\|slmp\|modbus\|ethernet/ip"` | +| 모션 컨트롤러·축 | `grep -ri "acs\|ajin\|axis\|ethercat\|servo"` | +| I/O 디바이스 | `grep -ri "beckhoff\|EL1\|EL2\|digital ?io\|io ?gate"` | +| Vision | `grep -ri "matrox\|MIL\|halcon\|cognex\|keyence"` | +| 메인 시퀀스 | `grep -ri "Seq\|Sequence\|Recipe\|Auto ?Run"` → 클래스/메서드 | +| 주소/메모리맵 | `*Address*.cs`, `*Map*`, `*.csv` 주소표 | + +## 설계 산출물 채움 매핑 (02.설계) +| 양식·필드 | 진실원(호스트) | +|---|---| +| 통신·제어 구성: 컨트롤러/통신 | 위 휴리스틱(PLC/모션/IO/Vision/Laser) | +| 모션축 정의: Axis·용도·범위 | 모션 설정·축 enum·파라미터 파일 | +| I/O 맵: 신호명·Type·모듈 | IO 정의/매핑 소스, 주소표 | +| PLC-PC 신호: 디바이스(M/D)·트리거 | PLC 인터페이스 주소 클래스(`*PLCController*Address*`) | +| 기능명세서: View·Function | UI/뷰모델 구조, 화면 클래스 | +| TactTime: Unit·단위동작 | 시퀀스 단계·동작 메서드 | + +미발견 값은 `(제안)` 표기 후 "최종 합의/소스 확인 시 확정" 단서. + +## PTOP 예시 (참고값) +- 설비: 각형 배터리 레이저 용접기(ILSOS OC5 PtoP Welder), Aurora 2.0 / .NET 4.8.1 / WPF +- PLC: Mitsubishi Melsec(MX Component) · Laser Scanner: RTC · Laser: IO Gate · Ajin Digital IO +- 메인 시퀀스: `SeqAutoRunPTOP`(Recipe/Page 핸드셰이크 → 용접리스트 업로드 → 발진 → 완료비트) +- PLC 주소: `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` + +> 이 예시는 PTOP 한정. 다른 프로젝트에선 위 휴리스틱으로 동일 절차를 적용해 값만 교체. + +## 채움 원칙 +- 모든 채운 값에 **출처 주석**(파일·심볼 경로) 부기. +- 상태/유형은 [02-enums.md](02-enums.md) enum 고정. +- 새 디바이스 종류 발견 시 [05-form-schemas.md](05-form-schemas.md) enum에 추가 후 사용. diff --git a/.claude/skills/ubisam-deliverables/references/08-design-system.md b/.claude/skills/ubisam-deliverables/references/08-design-system.md new file mode 100644 index 0000000..95eee28 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/08-design-system.md @@ -0,0 +1,126 @@ +# 08 · Ubisam 산출물 디자인 시스템 + +> 모든 프로젝트의 산출물 MD에 공통 적용하는 HTML 색상 템플릿. +> VS Code Markdown Preview / Typora / GitLab 렌더링 기준. +> 스캐폴딩·채움 어느 단계에서든 이 파일을 참조해 배너·뱃지·진행률을 적용한다. + +--- + +## 색상 팔레트 + +| 구분 | 이름 | 색 코드 | 용도 | +|---|---|---|---| +| 배너 | 진네이비 | `#1F4E78` | 문서 최상단 제목 배너 | +| 헤더 | 파랑 | `#2E75B6` | 배너 하단 부제 바, 섹션 헤더 강조 | +| 상태·해야 할 일 | 회색 | `#757575` | 미착수 | +| 상태·분석 진행 | 청색 | `#1976D2` | 선택됨 | +| 상태·업무 진행중 | 파랑 | `#1565C0` | 진행 중 | +| 상태·진행 완료 | 연두 | `#689F38` | 완료 | +| 유형·오류수정 | 빨강 | `#D32F2F` | — | +| 유형·불합리 | 주황 | `#F57C00` | — | +| 유형·미팅 | 청록 | `#00796B` | 1.분석/협의 | +| 유형·설계 | 연두 | `#689F38` | 2.설계 | +| 유형·개발 | 파랑 | `#1976D2` | — | +| 유형·제작 | 남파랑 | `#283593` | 3.제작 | +| 유형·입고셋업 | 보라 | `#7B1FA2` | 4.셋업 | +| 유형·적용 및 모니터링 | 하늘 | `#0288D1` | 5.양산 | +| 유형·요청대응 | 남색 | `#1A237E` | 6.CS | + +> xlsx 색상과 동일 계열: 배너=`FF1F4E78`(진네이비), 헤더=`FF2E75B6`(파랑), 데이터 홀수=`FFDDEBF7`(연파랑). + +--- + +## 1. 문서 배너 (모든 산출물 MD 1번째 줄) + +```html +
+문서 제목 — 설비명 +
+
+근거: 소스 파일 경로  ·  플랫폼 정보 +
+``` + +--- + +## 2. 상태 뱃지 (값에 맞춰 색 코드 교체) + +```html +해야 할 일 +분석 진행 +업무 진행중 +진행 완료 +``` + +--- + +## 3. 유형 뱃지 + +```html +미팅 +설계 +개발 +제작 +입고셋업 +적용 및 모니터링 +요청대응 +오류수정 +불합리 +``` + +--- + +## 4. 진행률 바 + +```html + **0%** +``` + +--- + +## 5. 섹션 헤더 강조 (산출물 MD 내 시트 구분) + +```markdown +## 시트① 제목 +``` + +--- + +## 6. 미확정 경고 (GitHub Alerts) + +```markdown +> [!NOTE] +> 아래 값은 소스 분석 기반 `(제안)`. 현장 확인 후 확정. +``` + +--- + +## 7. _task.md 완성 템플릿 (디자인 포함) + +단계명·프로젝트명·코드·유형·상태·진행률 토큰 교체 후 사용. + +```markdown +
+{N}.{단계명} — {프로젝트명} +
+
+{프로젝트코드}-{일련번호} +
+ +| 필드 | 값 | +|---|---| +| 유형 | {유형명} | +| 상태 | 해야 할 일 | +| 담당자 / 참조자 | {담당자} / {참조자} | +| 진행률 | **0%** | +| Hashtag | {태그1} / {태그2} | +``` + +--- + +## 적용 규칙 +- **항상 배너 먼저**: 산출물 MD 생성 시 본문 앞에 배너 블록을 삽입한다. +- **뱃지 > 텍스트**: 상태·유형은 텍스트가 아닌 뱃지 HTML로 표기한다. +- **진행률 bar 병기**: `` 태그 + bold 숫자를 함께 쓴다(텍스트 렌더러 폴백). +- **섹션 헤더**: 산출물 내 시트 구분 `##` 제목은 `` 적용. +- **README·_meta enum**: 동일 색상 체계를 그대로 적용한다. diff --git a/.claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py b/.claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py new file mode 100644 index 0000000..e951477 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py @@ -0,0 +1,479 @@ +# -*- coding: utf-8 -*- +""" +build_02설계_xlsx.py — PtoP Welder(가접용접기) 02.설계 산출물 재생산 +──────────────────────────────────────────────────────────────────── +LG PRI 표준 양식(ref 파일 불필요 · 완전 독립 실행). +스타일은 xlsxfill.py 에 하드코딩된 상수를 사용. + +전제조건: + - Python 3.9+ + - openpyxl >= 3.1 (pip install openpyxl) + +실행: + cd # E:\\Work\\GIT\\LGES-OC5-PtoP-Welder + set PYTHONUTF8=1 + py .claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py + +출력: + docs/[Ubisam산출물]/02.설계/IO_DB설계/PLC_PC_인터페이스_신호정의서.xlsx + docs/[Ubisam산출물]/02.설계/IO_DB설계/모션축_IO맵_정의서.xlsx + docs/[Ubisam산출물]/02.설계/UIUX_기능설계/기능명세서.xlsx + docs/[Ubisam산출물]/02.설계/시퀀스_인터록/TactTime_산정서.xlsx +""" +import sys, os, datetime +sys.path.insert(0, os.path.dirname(__file__)) +from xlsxfill import ( + make_workbook, set_col_widths, set_row_height, + write_banner, write_header_row, write_data_rows, group_merge, + BANNER_FONT, BANNER_FILL, BANNER_BORDER, BANNER_ALIGN, + SUBBANNER_FONT, SUBBANNER_BORDER, SUBBANNER_ALIGN, + HEADER_FONT, HEADER_FILL, HEADER_BORDER_FIRST, HEADER_BORDER_MID, HEADER_BORDER_LAST, + DATA_FONT, DATA_FILL_ODD, DATA_FILL_EVEN, + DATA_BORDER_FIRST, DATA_BORDER_MID, DATA_BORDER_LAST, + DATA_ALIGN_L, DATA_ALIGN_C, + FUNC_BANNER_FONT, FUNC_HEADER_FONT, FUNC_HEADER_BORDER, + FUNC_DATA_FONT, FUNC_DATA_BORDER, FUNC_DATA_ALIGN, + TACT_FONT, TACT_BORDER, TACT_ALIGN, TACT_ALIGN_L, + _apply, _border, +) +from openpyxl.styles import Font, PatternFill, Alignment +from openpyxl.utils import get_column_letter as GL + +OUT_DIR = "docs/[Ubisam산출물]/02.설계/" +TODAY = datetime.date.today() # 실행일 자동 반영 + + +# ══════════════════════════════════════════════════════ +# 1. PLC-PC 인터페이스 신호 정의서 +# 탭: 통신 사양 | PC→PLC (Write) | PLC→PC (Read) | 핸드셰이크 시퀀스 +# ══════════════════════════════════════════════════════ +def build_plcpc(): + wb = make_workbook() + OUT = OUT_DIR + "IO_DB설계/PLC_PC_인터페이스_신호정의서.xlsx" + + # ── 통신 사양 ────────────────────────────────── + ws = wb.create_sheet("통신 사양") + set_col_widths(ws, {"A":"3.7","B":24,"C":106}) + write_banner(ws, 2, 2, 3, + "PtoP Welder(가접용접기) ↔ PLC 인터페이스 통신 사양", + "PtoP Welder(가접용접기) — MX Component / 단일 게이트(PAGE_REQ) 핸드셰이크 · 디바이스 주소=제안(M70000/M70500 베이스)") + write_header_row(ws, 4, 2, ["항목", "내용"]) + comm = [ + ["PLC 기종", "Mitsubishi Melsec"], + ["통신 방식", "MX Component (ePLCNetType.MXComponent)"], + ["맵 파일", "CSV (PLCMMapFileType.CSV)"], + ["데이터 갱신", "폴링(10ms) + 이벤트 비트(REQ/ACK)"], + ["디바이스 영역","비트=M, 워드=D (PC Write M70000~/D70000~, PLC Read M70500~/D70500~)"], + ["핸드셰이크", "REQ→처리→ACK/COMPLETE. 단일 트리거 PLC_PAGE_CHANGE_REQ"], + ["좌표·실수", "워드 INT(short). Offset/변위 INT. 스케일 ×1(필요시 ×1000)"], + ["Watchdog", "PLC_LIVE_STATUS(0x00) ↔ PC_LIVE_STATUS(0x00)"], + ["인터락", "Door(0x06)/EMO(0x07)/Heavy(0x05)→정지, HeadTheta(D70510) 발진 인터락"], + ["주의", "주소=제안. 2nd 블록(0x65~)은 양극(P) 예비, 현재 음극만 사용"], + ] + write_data_rows(ws, 5, 2, comm, center_cols={0}) + + # ── PC→PLC (Write) ───────────────────────────── + ws = wb.create_sheet("PC→PLC (Write)") + set_col_widths(ws, {"A":3.7,"B":8,"C":14,"D":28,"E":10,"F":14,"G":8,"H":36,"I":28,"J":22}) + write_banner(ws, 2, 2, 10, + "PC → PLC 신호 (PC Write)", + "PC(Aurora)가 PLC로 보내는 요청·결과·보정값") + HDR = ["No","Seq/구분","신호명","방향","디바이스(제안)","Type","의미 / 트리거","응답·완료","비고"] + write_header_row(ws, 4, 2, HDR) + write_rows = [ + ["0x00","Status","PC_LIVE_STATUS","PC→PLC","M70000","BIT","PC Alive","","Watchdog"], + ["0x01","Status","PC_AUTO_STATUS","PC→PLC","M70001","BIT","Auto 상태","","Enter시 true"], + ["0x02","Status","PC_AUTO_COMPLETE","PC→PLC","M70002","BIT","Auto 완료","",""], + ["0x03","Status","PC_MANUAL_COMPLETE","PC→PLC","M70003","BIT","Manual 완료","",""], + ["0x04","Alarm","PC_LIGHT_ALARM_STATUS","PC→PLC","M70004","BIT","경알람","",""], + ["0x05","Alarm","PC_HEAVY_ALARM_STATUS","PC→PLC","M70005","BIT","중알람","",""], + ["0x06","Status","PC_DOOR_INTERLOCK","PC→PLC","M70006","BIT","Door 인터락","",""], + ["0x07","Status","PC_EMO_STATUS","PC→PLC","M70007","BIT","EMO","",""], + ["0x08","Process","PC_PAGE_CHANGE_COMPLETE","PC→PLC","M70008","BIT","페이지 확정 ACK","","PageNo 일치시 ON"], + ["0x09","Process","PC_RECIPE_CHANGE_COMPLETE","PC→PLC","M70009","BIT","레시피 변경 완료(미감시)","","260618"], + ["0x0F","Process","PC_WELD_READY_ACK","PC→PLC","M70015","BIT","Resume ACK/run","","bit15,260621"], + ["0x10","Process","PC_WELD_TRIGGER_ACK","PC→PLC","M70016","BIT","용접 트리거 ACK","",""], + ["0x11","Status","PC_WELD_BUSY","PC→PLC","M70017","BIT","Laser On 표시","","260626"], + ["0x12","Process","PC_WELD_COMPLETE","PC→PLC","M70018","BIT","용접 완료","",""], + ["0x14","Process","PC_WELD_NG_JUDGE","PC→PLC","M70020","BIT","용접 NG 판정","",""], + ["0x25","Request","PC_RMS_PARAMETER_READ_REQUEST_ACK","PC→PLC","M70037","BIT","RMS read ACK","",""], + ["D0x00","Info","PC_ERROR_CODE","PC→PLC","D70000","INT","에러코드 에코","",""], + ["D0x01","Info","PC_PAGE_NO","PC→PLC","D70001","INT","페이지 에코","",""], + ["D0x02","Info","PC_RECIPE_NO","PC→PLC","D70002","INT","레시피 에코","","260618"], + ["D0x08","Process","PC_WELD_OFFSET_X","PC→PLC","D70008","INT","용접 보정 X","",""], + ["D0x0A","Process","PC_WELD_OFFSET_Y","PC→PLC","D70010","INT","용접 보정 Y","",""], + ["D0x0C","Process","PC_DISPLACEMENT1_MEASUREMENT","PC→PLC","D70012","INT","변위 측정1","",""], + ["D0x0E","Process","PC_DISPLACEMENT2_MEASUREMENT","PC→PLC","D70014","INT","변위 측정2","",""], + ["D0x14~","Info","PC_CELL_ID_01~10","PC→PLC","D70020~","INT(ASCII)","Cell ID 에코","",""], + ["D0x1E~","Info","PC_LOT_ID_01~08","PC→PLC","D70030~","INT(ASCII)","Lot ID 에코","",""], + ["D0x28~","Info","PC_FDC_PAGE1~3_POWER/VELOCITY","PC→PLC","D70040~","INT","FDC 파워/속도","",""], + ["D0x3C~","Info","PC_RMS_ALLOC_START~END","PC→PLC","D70060~97","INT","RMS 할당","",""], + ] + write_data_rows(ws, 5, 2, write_rows, center_cols={0,3,5}) + n = len(write_rows) + group_merge(ws, 3, 5, n, [r[1] for r in write_rows]) + ws.merge_cells(f"E5:E{4+n}") + + # ── PLC→PC (Read) ────────────────────────────── + ws = wb.create_sheet("PLC→PC (Read)") + set_col_widths(ws, {"A":3.7,"B":8,"C":14,"D":28,"E":10,"F":14,"G":8,"H":36,"I":28,"J":22}) + write_banner(ws, 2, 2, 10, + "PLC → PC 신호 (PC Read)", + "PLC가 PC로 보내는 요청·상태·완료") + write_header_row(ws, 4, 2, HDR) + read_rows = [ + ["0x00","Status","PLC_LIVE_STATUS","PLC→PC","M70500","BIT","PLC Alive","Watchdog",""], + ["0x01","Status","PLC_AUTO_STATUS","PLC→PC","M70501","BIT","Auto 상태","",""], + ["0x02","Request","PLC_AUTO_REQ","PLC→PC","M70502","BIT","Auto 요청","PC_AUTO_STATUS",""], + ["0x03","Request","PLC_MANUAL_REQ","PLC→PC","M70503","BIT","Manual 요청","",""], + ["0x04","Alarm","PLC_LIGHT_ALARM_STATUS","PLC→PC","M70504","BIT","경알람","",""], + ["0x05","Alarm","PLC_HEAVY_ALARM_STATUS","PLC→PC","M70505","BIT","중알람","시퀀스 정지",""], + ["0x06","Status","PLC_DOOR_INTERLOCK","PLC→PC","M70506","BIT","Door","인터락",""], + ["0x07","Status","PLC_EMO_STATUS","PLC→PC","M70507","BIT","EMO","인터락",""], + ["0x08","Request","PLC_PAGE_CHANGE_REQ","PLC→PC","M70508","BIT","단일 게이트 트리거","Page+Recipe 확정",""], + ["0x09","Request","PLC_SCOUT_ENABLE","PLC→PC","M70509","BIT","Scout Enable","","260622"], + ["0x0F","Request","PLC_WELD_READY_REQ","PLC→PC","M70515","BIT","Weld_01 Ready→Resume","","bit15,260621"], + ["0x10","Request","PLC_WELD_TRIGGER_REQ","PLC→PC","M70516","BIT","용접 발진 요청","READY_ACK/BUSY",""], + ["0x11","Status","PLC_WELD_WAIT","PLC→PC","M70517","BIT","용접 대기","",""], + ["0x12","Process","PLC_WELD_COMPLETE_ACK","PLC→PC","M70518","BIT","용접 완료 ACK","",""], + ["0x1E","Alarm","PLC_ALARM_RESET","PLC→PC","M70530","BIT","알람 리셋","",""], + ["0x25","Request","PLC_RMS_PARAMETER_READ_REQUEST","PLC→PC","M70537","BIT","RMS read 요청","ACK",""], + ["D0x00","Info","PLC_ERROR_CODE","PLC→PC","D70500","INT","에러코드","",""], + ["D0x01","Info","PLC_PAGE_NO","PLC→PC","D70501","INT","페이지(1-base)","3회 검증",""], + ["D0x02","Info","PLC_RECIPE_NO","PLC→PC","D70502","INT","레시피(11/21/22/31/32)","","260618"], + ["D0x03","Info","PLC_CURRENT_SLOT_NO","PLC→PC","D70503","INT","현재 슬롯","EL/CLEAN",""], + ["D0x0A","Process","HEAD_THETA_POSITION","PLC→PC","D70510","INT","Laser1 Head Theta","발진 인터락",""], + ["D0x14~","Info","PLC_CELL_ID_01~10","PLC→PC","D70520~","INT(ASCII)","Cell ID","",""], + ["D0x1E~","Info","PLC_LOT_ID_01~08","PLC→PC","D70530~","INT(ASCII)","Lot ID","",""], + ] + write_data_rows(ws, 5, 2, read_rows, center_cols={0,3,5}) + n = len(read_rows) + group_merge(ws, 3, 5, n, [r[1] for r in read_rows]) + ws.merge_cells(f"E5:E{4+n}") + + # ── 핸드셰이크 시퀀스 ───────────────────────── + ws = wb.create_sheet("핸드셰이크 시퀀스") + set_col_widths(ws, {"A":3.7,"B":6,"C":16,"D":6,"E":8,"F":48,"G":36,"H":40}) + write_banner(ws, 2, 2, 8, + "PLC ↔ PC 핸드셰이크 시퀀스 (Seq별)", + "요청 → 처리 → 완료/응답 흐름 │ PtoP Welder 단일 게이트(PLC_PAGE_CHANGE_REQ) 기준") + write_header_row(ws, 4, 2, ["No","Seq","Step","주체","동작 / 신호","디바이스(제안)","비고"]) + hs = [ + [1,"PageGate",1,"PLC","Page Change 요청(단일 게이트)","PLC_PAGE_CHANGE_REQ(M70508) Set","Page+Recipe 확정 트리거"], + [2,"PageGate",2,"PC","Recipe 확정/전환","PLC_RECIPE_NO(D70502)→ActivateRecipe→PC_RECIPE_NO(D70002) 에코","0/동일이면 전환 생략"], + [3,"PageGate",3,"PC","Page 3회 검증·확정","PLC_PAGE_NO(D70501) 3회→PC_PAGE_NO 에코→PC_PAGE_CHANGE_COMPLETE(M70008) Set","200ms×3"], + [4,"PageGate",4,"PC","용접 리스트 업로드","레이어 선택 + SeqUploadWeldingList","-"], + [5,"Weld",1,"PLC","발진 요청","PLC_WELD_TRIGGER_REQ(M70516) Set","PreScout(M70509) 옵션"], + [6,"Weld",2,"PC","발진 ACK · BUSY","PC_WELD_TRIGGER_ACK(M70016) Set → PC_WELD_BUSY(M70017) ON","HeadTheta(D70510) 인터락"], + [7,"Weld",3,"PC","실제 발진(Gate ON/OFF)","Scanner.ExecuteList → Gate ON/OFF","laserEmissionGateDelay 15ms"], + [8,"Weld",4,"PC","완료 통지","PC_WELD_BUSY OFF → PC_WELD_COMPLETE(M70018) Set","PostScout(M70509) 옵션"], + [9,"Weld",5,"양측","완료 ACK · Clear","PLC_WELD_COMPLETE_ACK(M70518) → TRIGGER_ACK OFF","루프백(PLC_PAGE_CHANGE_REQ) 대기"], + [10,"Resume",1,"PLC","Weld_01 Ready → Resume 요청","PLC_WELD_READY_REQ(M70515) Set","bit15, Auto→Manual+Resume(가변용접)"], + [11,"Resume",2,"PC","Resume ACK / run","PC_WELD_READY_ACK(M70015) Set","Resume 시 READY_ACK OFF"], + [12,"Interlock",1,"PLC","Door/EMO/Heavy 상태 공유","PLC_DOOR(M70506)/EMO(M70507)/HEAVY(M70505)","이상 시 시퀀스 정지"], + [13,"Status",1,"양측","Alive Watchdog 토글","PC_LIVE(M70000) ↔ PLC_LIVE(M70500)","단절 시 안전정지"], + [14,"Alarm",1,"PC","PC 알람 통지","PC_LIGHT_ALARM(M70004)/HEAVY_ALARM(M70005) Set","중알람 시 시퀀스 정지"], + ] + write_data_rows(ws, 5, 2, hs, center_cols={0,2,3}) + group_merge(ws, 3, 5, len(hs), [r[1] for r in hs]) + + wb.save(OUT); print("✓ PLC-PC:", OUT) + + +# ══════════════════════════════════════════════════════ +# 2. 모션축·IO 맵 정의서 +# 탭: 통신·제어 구성 | 모션축 정의(ACS) | I-O 맵(Ajin) +# ══════════════════════════════════════════════════════ +def build_motio(): + wb = make_workbook() + OUT = OUT_DIR + "IO_DB설계/모션축_IO맵_정의서.xlsx" + + # ── 통신·제어 구성 ───────────────────────────── + ws = wb.create_sheet("통신·제어 구성") + set_col_widths(ws, {"A":4.4,"B":16,"C":24.8,"D":26,"E":20,"F":22,"G":34}) + write_banner(ws, 2, 2, 7, + "PtoP Welder(가접용접기) 통신 · 제어 구성 (PC / Aurora)", + "근거: EquipmentSetup.RegisterDevices() / PAIX_PTOP.cs / architecture.md §4 │ 주소·수량=제안") + write_header_row(ws, 4, 2, ["구분","대상","컨트롤러 / 디바이스","통신 / 버스","수량·범위","비고"]) + write_data_rows(ws, 5, 2, [ + ["PLC통신","외부 신호 제어","Mitsubishi Melsec","MX Component","1","ePLCNetType.MXComponent"], + ["I/O","디지털 IO","Ajin(AJINEXTEK)","필드버스","DI/DO","PAIX_PTOP:IODevice"], + ["Laser","Core/Ring 레이저","IO Gate(TruPulse nano)","DO 게이트","2","ExecuteWelding Gate"], + ["Scanner","레이저 스캐너","RTC LaserScanner","RTC","1","Scanner.ExecuteList"], + ["모션(Servo)","X/Y 축","ACS SPiiPlus","EtherCAT","2축 (제안)","AuroraACS"], + ["모션(Servo)","Z 축","Ajin Motion Board","보드","1축 (제안)","AuroraMotionBoard"], + ["Vision","카메라/측정","MIL 기반","-","-","AuroraVision(현재 미사용)"], + ["상/하위(MES)","호스트 연동","Stream(LBAD/RPC)","TCP","옵션","UseStream 시"], + ], center_cols={0,4}) + + # ── 모션축 정의(ACS) ──────────────────────────── + ws = wb.create_sheet("모션축 정의(ACS)") + set_col_widths(ws, {"A":4.4,"B":7,"C":12,"D":18,"E":22,"F":8,"G":12,"H":10,"I":10,"J":10,"K":10,"L":20}) + write_banner(ws, 2, 2, 12, + "PtoP Welder(가접용접기) 모션축 구성표 (ACS · Ajin) — 제안", + "각 축 Init / 단축·동기축 / Jog·Relative·Absolute │ 행정·속도·Home은 모듈 소스 미보유 → (제안)") + write_header_row(ws, 4, 2, + ["Axis","축 명(논리)","용도","컨트롤러/통신","구동","행정/범위","최대속도","Home 방식","Soft Limit","동기/단축","비고"]) + write_data_rows(ws, 5, 2, [ + ["0","X","셀 위치 X","ACS SPiiPlus/EtherCAT","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], + ["1","Y","셀 위치 Y","ACS SPiiPlus/EtherCAT","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], + ["2","Z","포커스/높이","Ajin Motion Board","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], + ["-","Scanner θ/XY","빔 편향(갈바노)","RTC LaserScanner","Galvano","List Jump/Mark","-","-","보정테이블","-",""], + ], center_cols={0,1,4,5,6,7,8,9}) + ws.cell(12, 3).value = "※ ACS = PC가 EtherCAT으로 직접 제어하는 Servo 축(X/Y). Z는 Ajin Motion Board, Scanner θ/XY는 RTC 갈바노. 행정/속도/Home 미확정." + ws.cell(12, 3).font = Font(name="맑은 고딕", size=9, italic=True) + + # ── I-O 맵 (Ajin) ─────────────────────────────── + ws = wb.create_sheet("I-O 맵(Ajin)") + set_col_widths(ws, {"A":4.4,"B":6,"C":30,"D":6,"E":10,"F":10,"G":8,"H":36,"I":20}) + write_banner(ws, 2, 2, 9, + "PtoP Welder(가접용접기) I/O 맵 (Ajin DI/DO) — 제안", + "근거: PAIX_PTOP.cs ePrismaticDIAddress / ePrismaticDOAddress · DI 9~23 / DO 9~17") + write_header_row(ws, 4, 2, ["No","신호명","Type","권장 모듈","슬롯/CH","Active","설명/인터락","비고"]) + raw_io = [ + (9,"CoreLaser_Laser_Ready","DI","Ajin DI","High","Core 레이저 Ready"), + (10,"CoreLaser_Error_State","DI","Ajin DI","High","Core 레이저 에러"), + (11,"CoreLaser_Laser_Output_State","DI","Ajin DI","High","Core 발진 출력 상태"), + (12,"CoreLaser_Control_Status","DI","Ajin DI","High","Core 제어 상태"), + (13,"RingLaser_Laser_Ready","DI","Ajin DI","High","Ring 레이저 Ready"), + (14,"RingLaser_Error_State","DI","Ajin DI","High","Ring 레이저 에러"), + (15,"RingLaser_Laser_Output_State","DI","Ajin DI","High","Ring 발진 출력 상태"), + (23,"RingLaser_Control_Status","DI","Ajin DI","High","Ring 제어 상태"), + (9,"CoreLaser_Control","DO","Ajin DO","High","Core 레이저 게이트(발진)"), + (10,"CoreLaser_ErrorClearing","DO","Ajin DO","High","Core 에러 클리어"), + (13,"RingLaser_Control","DO","Ajin DO","High","Ring 레이저 게이트(발진)"), + (14,"RingLaser_ErrorClearing","DO","Ajin DO","High","Ring 에러 클리어"), + (17,"EX_Red_Control","DO","Ajin DO","High","EX Red 표시등 제어"), + ] + io_rows = [[i+1, name, typ, mod, f"{typ} Ch{ch}", act, desc, ""] + for i,(ch,name,typ,mod,act,desc) in enumerate(raw_io)] + write_data_rows(ws, 5, 2, io_rows, center_cols={0,2,3,4,5}) + + wb.save(OUT); print("✓ 모션축·IO:", OUT) + + +# ══════════════════════════════════════════════════════ +# 3. 기능명세서 +# 탭: OverView | MainView | RecipeView | ManualView | +# DeviceView | SettingView | VisionView | Sequence 흐름 +# ══════════════════════════════════════════════════════ +def build_funcspec(): + wb = make_workbook() + OUT = OUT_DIR + "UIUX_기능설계/기능명세서.xlsx" + + # FUNC 계열은 배경 없이 thin border만 사용 + tb = lambda: FUNC_DATA_BORDER + + # ── OverView ──────────────────────────────────── + ws = wb.create_sheet("OverView") + set_col_widths(ws, {"A":4.1,"B":7.8,"C":66,"D":18.2,"E":19.3}) + set_row_height(ws, 2, 43.5) + c = ws.cell(2, 2, "PtoP Welder(가접용접기) SW 기능명세서") + _apply(c, FUNC_BANNER_FONT, border=_border("medium","medium","medium","medium"), + alignment=Alignment(horizontal="center",vertical="center",wrap_text=True)) + ws.merge_cells("B2:E2") + # FUNC 헤더: 배경 없음(None), thin border + write_header_row(ws, 4, 2, ["no.","Content"], font=FUNC_HEADER_FONT, fill=None) + ov_items = [ + "운영 SW는 LG PRI의 Aurora 플랫폼으로 개발", + "운영 환경 : Windows 11, .NET Framework", + "개발 환경 : Windows 11, Visual Studio", + "설비 : PtoP Welder(가접용접기) — 셀 P2P 레이저 가접(Tack) 용접", + "모션 : ACS SPiiPlus(X/Y, EtherCAT) + Ajin(Z), 빔 편향 = RTC 갈바노 스캐너", + "레이저 : Core / Ring 2계통, IO Gate(TruPulse nano) 발진", + "PLC : Mitsubishi MELSEC / MX Component, 단일 게이트(PAGE_REQ) 핸드셰이크", + "비전 : MIL 기반(현재 AuroraVision 미사용)", + ] + for i, txt in enumerate(ov_items): + r = 5 + i + ws.cell(r, 2, i+1).font = FUNC_DATA_FONT + ws.cell(r, 2).border = tb() + ws.cell(r, 2).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(r, 3, txt).font = FUNC_DATA_FONT + ws.cell(r, 3).border = tb() + ws.cell(r, 3).alignment = Alignment(horizontal="left", vertical="center", wrap_text=True) + ws.merge_cells(f"C{r}:E{r}") + + # ── View 시트 공통 헬퍼 ────────────────────────── + # 헤더: Update|Funtion Area|Funtion Name|NO|Sub|Content + VIEW_HDR = ["Update","Funtion Area","Funtion Name","NO","Sub","Content"] + def make_view(name, banner_text, rows): + ws = wb.create_sheet(name) + set_col_widths(ws, {"A":4,"B":14,"C":16,"D":24,"E":6,"F":14,"G":48}) + set_row_height(ws, 3, 28) + ws.cell(3, 7, banner_text).font = FUNC_DATA_FONT + ws.cell(3, 7).alignment = Alignment(horizontal="left", vertical="center") + set_row_height(ws, 5, 20) + for j, h in enumerate(VIEW_HDR): + c = ws.cell(5, 2+j, h) + c.font = FUNC_HEADER_FONT + c.border = tb() + c.alignment = Alignment(horizontal="center", vertical="center") + for i, row in enumerate(rows): + r = 6 + i + vals = [TODAY] + row + for j, v in enumerate(vals): + c = ws.cell(r, 2+j, v) + c.font = FUNC_DATA_FONT + c.border = tb() + c.alignment = Alignment( + horizontal="center" if j in {0,3} else "left", + vertical="center", wrap_text=True) + + make_view("MainView","다음은 Main 화면에서 보여야 하는 기능이다.",[ + ["운전","StartSequence/Stop", 1,"SeqAutoRunPTOP", "자동운전 시작/정지"], + ["상태","UpdatePageNum", 2,"EVENT_AUTO_RUN_UPDATE_PAGENUM","현재 페이지 표시"], + ["상태","WorkingPosition", 3,"eWorkingPosition", "용접 진행 위치 표시"], + ["운전","Auto→Manual+Resume", 4,"bit15(PLC_WELD_READY_REQ)", "Resume(가변용접) 기능"], + ["표시","ScannerCanvasCtrlTeaching", 5,"레이어/오브젝트", "스캐너 캔버스"], + ]) + make_view("RecipeView","다음은 Recipe 화면에서 보여야 하는 기능이다.",[ + ["레시피","ActivateRecipe", 1,"RecipeManager", "레시피 선택/활성"], + ["레시피","RecipeChangeHistory", 2,"RchHandler", "변경 이력"], + ["모델", "UCModel", 3,"EquipmentSpecificModel_PTOP","모델 편집"], + ]) + make_view("ManualView","다음은 Manual 화면에서 보여야 하는 기능이다.",[ + ["Manual", "ScannerOperation", 1,"ScannerOperationView","스캐너 수동 조작(발진/Jump/Mark)"], + ["Manual", "ModelFinder", 2,"ModelFinderView", "모델 파인더"], + ["Calibration","ScannerCalibration",3,"(제안)", "스캐너 보정"], + ["Calibration","PixelResolution", 4,"(제안)", "픽셀 해상도"], + ]) + make_view("DeviceView","다음은 Device 화면에서 보여야 하는 기능이다.",[ + ["Device", "DeviceSettingView", 1,"LEVEL3", "디바이스 설정(활성/비활성)"], + ["Monitoring","PLCBitMonitoring", 2,"ePLCDevice.M","PLC 비트 모니터"], + ["Monitoring","PLCWordMonitoring", 3,"ePLCDevice.D","PLC 워드 모니터"], + ["Monitoring","IOInterfaceView", 4,"IODevice", "IO 인터페이스"], + ]) + make_view("SettingView","다음은 Setting 화면에서 보여야 하는 기능이다.",[ + ["Setting","Equipment", 1,"LEVEL1", "장비 설정"], + ["Setting","Log", 2,"-", "로그 설정"], + ["Setting","AutoBackup", 3,"-", "자동 백업"], + ["Alarm", "History/Desc", 4,"-", "알람 이력·설명"], + ["Stream", "ServiceSettingView",5,"UseStream","Stream(상/하위) 사용 시"], + ]) + make_view("VisionView","다음은 Vision 화면에서 보여야 하는 기능이다.",[ + ["Vision","(해당없음)",1,"AuroraVision 미사용","MIL 기반 — 현재 미사용(제안)"], + ]) + + # ── Sequence 흐름: Update|Funtion Area|Processing Name|NO|Content + ws = wb.create_sheet("Sequence 흐름") + set_col_widths(ws, {"A":4,"B":14,"C":16,"D":24,"E":6,"F":60}) + SEQ_HDR = ["Update","Funtion Area","Processing Name","NO","Content"] + set_row_height(ws, 5, 20) + for j,h in enumerate(SEQ_HDR): + c = ws.cell(5, 2+j, h) + c.font = FUNC_HEADER_FONT; c.border = tb() + c.alignment = Alignment(horizontal="center",vertical="center") + seq_data = [ + ["AutoRun","PAGE_REQ Gate", 1,"PLC_PAGE_CHANGE_REQ(M70508) 단일 게이트 트리거"], + ["AutoRun","Recipe 확정", 2,"PLC_RECIPE_NO→ActivateRecipe→PC_RECIPE_NO 에코(0/동일 생략)"], + ["AutoRun","Page 확정", 3,"PLC_PAGE_NO 3회 검증→PC_PAGE_CHANGE_COMPLETE"], + ["AutoRun","List Upload", 4,"레이어 선택 + SeqUploadWeldingList"], + ["AutoRun","발진 요청", 5,"PLC_WELD_TRIGGER_REQ→PC_WELD_TRIGGER_ACK/BUSY(HeadTheta 인터락)"], + ["AutoRun","발진", 6,"Gate ON(15ms)→Scanner.ExecuteList→Gate OFF"], + ["AutoRun","완료", 7,"PC_WELD_COMPLETE→PLC_WELD_COMPLETE_ACK"], + ["AutoRun","루프백", 8,"CheckCycleStop→PAGE_REQ 대기(Resume 시 READY_ACK OFF)"], + ] + for i, row in enumerate(seq_data): + r = 6 + i + vals = [TODAY] + row + for j,v in enumerate(vals): + c = ws.cell(r, 2+j, v) + c.font = FUNC_DATA_FONT; c.border = tb() + c.alignment = Alignment(horizontal="center" if j in {0,3} else "left", + vertical="center", wrap_text=True) + + wb.save(OUT); print("✓ 기능명세서:", OUT) + + +# ══════════════════════════════════════════════════════ +# 4. TactTime 산정서 +# 탭: PLC TactTime | PC TactTime +# ══════════════════════════════════════════════════════ +def build_tact(): + wb = make_workbook() + OUT = OUT_DIR + "시퀀스_인터록/TactTime_산정서.xlsx" + + def tact_cell(ws, r, c, v, align="center"): + cell = ws.cell(r, c, v) + cell.font = TACT_FONT; cell.border = TACT_BORDER + cell.alignment = Alignment(horizontal=align, vertical="center", wrap_text=True) + return cell + + # ── PLC TactTime (미입수) ────────────────────── + ws = wb.create_sheet("PLC TactTime") + set_col_widths(ws, {"B":18,"C":19.5,"D":49.6,"E":10,"F":10.8}) + set_row_height(ws, 3, 53) + tact_cell(ws, 3, 2, "PLC Main 동작 (PtoP Welder) — 미입수") + ws.merge_cells("B3:E3") + write_header_row(ws, 4, 2, ["공정","Unit","단위동작","T/T"], font=TACT_FONT, fill=None) + set_row_height(ws, 4, 22) + set_row_height(ws, 5, 40) + tact_cell(ws, 5, 2, "(미입수)", "center") + tact_cell(ws, 5, 3, "PLC 시퀀스 사양 입수 후 작성", "left") + tact_cell(ws, 5, 4, "셀 로딩 / 언로딩 / 페이지 인덱싱 T/T — PLC 측 단위동작 미보유(제안)", "left") + tact_cell(ws, 5, 5, "미입수", "center") + + # ── PC TactTime ──────────────────────────────── + ws = wb.create_sheet("PC TactTime") + set_col_widths(ws, {"A":11.7,"B":18,"C":38.1,"D":75.8,"E":8,"F":10.9,"G":12.2,"H":42,"I":12,"J":13}) + set_row_height(ws, 3, 53) + tact_cell(ws, 3, 2, "Main 동작 (PtoP Welder · 자동, 값=제안 / 출처: SeqAutoRunPTOP.cs)", "left") + ws.merge_cells("B3:E3") + tact_cell(ws, 3, 8, "구간 합계") + ws.merge_cells("H3:J3") + # 헤더 행 높이 + set_row_height(ws, 4, 22) + for col, h in zip([2,3,4,5], ["공정","Unit","단위동작","T/T"]): + tact_cell(ws, 4, col, h) + for col, h in zip([8,9,10], ["Working Process","상세합계","기준/참조합계"]): + tact_cell(ws, 4, col, h) + + L = [ + ("Page/Recipe 게이트","핸드셰이크","Page Change 요청 대기","가변"), + ("Page/Recipe 게이트","핸드셰이크","Recipe 확정/전환","~0~수백"), + ("Page/Recipe 게이트","핸드셰이크","Page 3회 검증 (200ms×3)","~600"), + ("Page/Recipe 게이트","핸드셰이크","용접리스트 업로드","가변"), + ("발진 준비","WeldingStart","발진 요청 대기","가변"), + ("발진 준비","WeldingStart","준비 응답 WaitWeldingStart(200+5)","~205"), + ("발진","ExecuteWelding","Gate ON delay(laserEmissionGateDelayMs)","15"), + ("발진","ExecuteWelding","실제 발진 Scanner.ExecuteList","~285"), + ("발진","ExecuteWelding","Gate OFF + BUSY OFF","~50"), + ("발진","ExecuteWelding","Jump+Correction 복귀 Thread.Sleep(500)","~500"), + ("완료","WeldComplete","완료비트 송신 WriteWeldComplete(50+5)","~55"), + ("옵션","Scout","Pre/Post Scout (M70509 ON 시)","+100/회"), + ] + R = [ + ("Page/Recipe 게이트","가변(폴링 10ms, timeout 60s)","-"), + ("발진 준비","~205",""), + ("발진(1발 기준)","~850 (15+285+50+500)",""), + ("완료","~55",""), + ("Scout(옵션)","+100/회",""), + ("1 사이클(가변 제외)","≈1110+","제안"), + ] + for i, (grp, unit, action, tt) in enumerate(L): + r = 5 + i + set_row_height(ws, r, 30) + tact_cell(ws, r, 2, grp, "left") + tact_cell(ws, r, 3, unit, "left") + tact_cell(ws, r, 4, action, "left") + tact_cell(ws, r, 5, tt, "center") + for i, (proc, detail, ref_) in enumerate(R): + r = 5 + i + tact_cell(ws, r, 8, proc, "left") + tact_cell(ws, r, 9, detail, "center") + tact_cell(ws, r, 10, ref_, "center") + # 공정 그룹 병합 + group_merge(ws, 2, 5, len(L), [r[0] for r in L]) + group_merge(ws, 3, 5, len(L), [r[0]+"|"+r[1] for r in L]) + + wb.save(OUT); print("✓ TactTime:", OUT) + + +# ══════════════════════════════════════════════════════ +if __name__ == "__main__": + build_plcpc() + build_motio() + build_funcspec() + build_tact() + print("\n✅ 02.설계 xlsx 4종 재생산 완료 (ref 파일 불필요).") diff --git a/.claude/skills/ubisam-deliverables/references/xlsxfill.py b/.claude/skills/ubisam-deliverables/references/xlsxfill.py new file mode 100644 index 0000000..1c0b213 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/references/xlsxfill.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +""" +xlsxfill.py — LG PRI 표준 양식 xlsx 헬퍼 (ref 파일 불필요 · 완전 독립) + +스타일 상수는 ref(공소수정기·FMM) 파일에서 직접 추출한 값으로 하드코딩됨. +모든 파일 구조(배너·헤더·데이터·그룹병합)를 openpyxl만으로 재현한다. + +사용법: + import sys + sys.path.insert(0, os.path.dirname(__file__)) + from xlsxfill import ( + make_workbook, set_col_widths, set_row_height, + write_banner, write_header_row, write_data_rows, group_merge, + BANNER_FONT, BANNER_FILL, HEADER_FONT, HEADER_FILL, + DATA_FONT, DATA_FILL_ODD, DATA_FILL_EVEN, + FUNC_BANNER_FONT, FUNC_HEADER_FONT, FUNC_DATA_FONT, + TACT_FONT, TACT_BORDER, _apply, _border, + ) + +전제조건: Python 3.9+, openpyxl >= 3.1 +실행: set PYTHONUTF8=1 && py build_02설계_xlsx.py +""" +import openpyxl +from openpyxl import Workbook +from openpyxl.styles import Font, PatternFill, Border, Side, Alignment, GradientFill +from openpyxl.utils import get_column_letter +from openpyxl.utils.cell import range_boundaries + +# ───────────────────────────────────────────── +# 스타일 상수 (ref 파일 직접 추출값) +# ───────────────────────────────────────────── +_F = Font +_P = PatternFill +_B = Border +_S = Side +_A = Alignment + +def _border(left=None, right=None, top=None, bottom=None): + def s(v): return _S(border_style=v) if v else _S() + return _B(left=s(left), right=s(right), top=s(top), bottom=s(bottom)) + +# 배너 행 (B2) — 진네이비 배경, 흰 굵은 글자 +BANNER_FONT = _F(name="맑은 고딕", size=13, bold=True, color="FFFFFFFF") +BANNER_FILL = _P("solid", fgColor="FF1F4E78") +BANNER_BORDER = _border("medium","medium","medium","medium") +BANNER_ALIGN = _A(horizontal="center", vertical="center", wrap_text=True) + +# 부제 행 (B3) — 테두리만 +SUBBANNER_FONT = _F(name="맑은 고딕", size=9) +SUBBANNER_BORDER = _border("medium","medium",None,None) +SUBBANNER_ALIGN = _A(horizontal="left", vertical="center", wrap_text=True) + +# 헤더 행 (B4/B5 등) — 파랑 배경, 흰 굵은 글자 +HEADER_FONT = _F(name="맑은 고딕", size=11, bold=True, color="FFFFFFFF") +HEADER_FILL = _P("solid", fgColor="FF2E75B6") +HEADER_BORDER_FIRST = _border("medium","thin","thin","thin") +HEADER_BORDER_MID = _border("thin","thin","thin","thin") +HEADER_BORDER_LAST = _border("thin","medium","thin","thin") +HEADER_ALIGN = _A(horizontal="center", vertical="center", wrap_text=True) + +# 데이터 행 — 교대 배경(연파랑) + 흰 배경 +DATA_FONT = _F(name="맑은 고딕", size=10) +DATA_FILL_ODD = _P("solid", fgColor="FFDDEBF7") # 연파랑 (홀수행) +DATA_FILL_EVEN = _P("solid", fgColor="FFFFFFFF") # 흰색 (짝수행) +DATA_BORDER_FIRST = _border("medium","thin","thin","thin") +DATA_BORDER_MID = _border("thin","thin","thin","thin") +DATA_BORDER_LAST = _border("thin","medium","thin","thin") +DATA_ALIGN_L = _A(horizontal="left", vertical="center", wrap_text=True) +DATA_ALIGN_C = _A(horizontal="center", vertical="center", wrap_text=True) + +# 기능명세서 전용 스타일 (FUNC ref: 맑은 고딕, 14pt 배너) +FUNC_BANNER_FONT = _F(name="맑은 고딕", size=14, bold=True) +FUNC_HEADER_FONT = _F(name="맑은 고딕", size=11, bold=True) +FUNC_HEADER_BORDER = _border("thin","thin","thin","thin") +FUNC_DATA_FONT = _F(name="맑은 고딕", size=11) +FUNC_DATA_BORDER = _border("thin","thin","thin","thin") +FUNC_DATA_ALIGN = _A(horizontal="left", vertical="center", wrap_text=True) + +# TactTime 전용 (단순 thin 테두리, 색상 없음) +TACT_FONT = _F(name="맑은 고딕", size=11) +TACT_BORDER = _border("thin","thin","thin","thin") +TACT_ALIGN = _A(horizontal="center", vertical="center", wrap_text=True) +TACT_ALIGN_L= _A(horizontal="left", vertical="center", wrap_text=True) + + +# ───────────────────────────────────────────── +# 헬퍼 함수 +# ───────────────────────────────────────────── + +_UNSET = object() # write_header_row fill/font 기본값 sentinel + + +def make_workbook(): + wb = Workbook() + wb.remove(wb.active) # 기본 Sheet 제거 + return wb + + +def set_col_widths(ws, widths: dict): + """widths: {'A':4.4, 'B':16.0, ...}""" + for col, w in widths.items(): + ws.column_dimensions[col].width = w + + +def set_row_height(ws, row, height): + ws.row_dimensions[row].height = height + + +def _apply(cell, font=None, fill=None, border=None, alignment=None): + if font: cell.font = font + if fill: cell.fill = fill + if border: cell.border = border + if alignment: cell.alignment = alignment + + +def write_banner(ws, row, col_start, col_end, title, subtitle=None, + banner_h=26, sub_h=24): + """배너 2행(제목+부제)을 쓰고 병합한다.""" + set_row_height(ws, row, banner_h) + # 제목 + c = ws.cell(row, col_start, title) + _apply(c, BANNER_FONT, BANNER_FILL, BANNER_BORDER, BANNER_ALIGN) + L = get_column_letter + if col_end > col_start: + ws.merge_cells(f"{L(col_start)}{row}:{L(col_end)}{row}") + # 우측 셀 border 유지 + ws.cell(row, col_end).border = _border(right="medium", top="medium", bottom="medium") + if subtitle is not None: + set_row_height(ws, row+1, sub_h) + c2 = ws.cell(row+1, col_start, subtitle) + _apply(c2, SUBBANNER_FONT, border=SUBBANNER_BORDER, alignment=SUBBANNER_ALIGN) + if col_end > col_start: + ws.merge_cells(f"{L(col_start)}{row+1}:{L(col_end)}{row+1}") + ws.cell(row+1, col_end).border = _border(right="medium") + + +def write_header_row(ws, row, col_start, headers, height=22, + font=_UNSET, fill=_UNSET): + """헤더 행 작성. 첫/마지막 열 medium 테두리. + fill=None 을 넘기면 배경 없음(흰색). 생략하면 HEADER_FILL(파랑) 적용. + font=None 을 넘기면 폰트 미설정. 생략하면 HEADER_FONT 적용. + """ + set_row_height(ws, row, height) + _font = HEADER_FONT if font is _UNSET else font + _fill = HEADER_FILL if fill is _UNSET else fill + n = len(headers) + for i, h in enumerate(headers): + col = col_start + i + c = ws.cell(row, col, h) + if i == 0: + border = HEADER_BORDER_FIRST + elif i == n-1: + border = HEADER_BORDER_LAST + else: + border = HEADER_BORDER_MID + _apply(c, _font, _fill, border, HEADER_ALIGN) + + +def write_data_rows(ws, data_start, col_start, rows, + center_cols=None, height=None, + font=None, odd_fill=True): + """ + rows: list[list] 각 행의 값. + center_cols: set of 0-based 인덱스 → center align. 나머지 left. + odd_fill: True=홀/짝 교대 배경, False=배경 없음. + """ + n_cols = max((len(r) for r in rows), default=0) + center_cols = center_cols or set() + for i, rowvals in enumerate(rows): + r = data_start + i + if height: + set_row_height(ws, r, height) + is_odd = (i % 2 == 0) + fill = (DATA_FILL_ODD if is_odd else DATA_FILL_EVEN) if odd_fill else None + for j in range(n_cols): + col = col_start + j + c = ws.cell(r, col) + c.value = rowvals[j] if j < len(rowvals) else None + if j == 0: + border = DATA_BORDER_FIRST + elif j == n_cols-1: + border = DATA_BORDER_LAST + else: + border = DATA_BORDER_MID + align = DATA_ALIGN_C if j in center_cols else DATA_ALIGN_L + _apply(c, font or DATA_FONT, fill, border, align) + + +def group_merge(ws, col, data_start, n, key_rows): + """동일 key_rows 값의 연속 행을 col열에서 수직 병합.""" + i = 0 + while i < n: + k = key_rows[i] + if k is None: + i += 1 + continue + j = i + 1 + while j < n and key_rows[j] == k: + j += 1 + if j - i > 1: + L = get_column_letter(col) + ws.merge_cells(f"{L}{data_start+i}:{L}{data_start+j-1}") + i = j diff --git a/.claude/skills/ubisam-deliverables/templates/_task.template.md b/.claude/skills/ubisam-deliverables/templates/_task.template.md new file mode 100644 index 0000000..35c1a14 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/templates/_task.template.md @@ -0,0 +1,32 @@ +
+{N}.{단계명} — {프로젝트명} +
+
+{프로젝트코드}-{일련번호} +
+ +| 필드 | 값 | +|---|---| +| 유형 | {유형명} | +| 상태 | 해야 할 일 | +| 담당자 / 참조자 | {담당자} / {참조자} | +| 진행률 | **0%** | +| Hashtag | {태그1} / {태그2} | + +## 산출물 정의 +> 세부 × 산출물 × 슬롯 표. 단계별 산출물은 04-stage-deliverables 참조. + +| 세부 | 산출물 | 슬롯 | +|---|---|---| +| {세부} | {산출물} | `{슬롯}/` | + +## 첨부 / 템플릿 +- {파일명 또는 양식 템플릿 위치} + + diff --git a/.claude/skills/ubisam-deliverables/templates/project.yaml.template b/.claude/skills/ubisam-deliverables/templates/project.yaml.template new file mode 100644 index 0000000..c732b59 --- /dev/null +++ b/.claude/skills/ubisam-deliverables/templates/project.yaml.template @@ -0,0 +1,31 @@ +# 프로젝트 메타 (템플릿) — {...} 토큰을 실제 값으로 교체 +# 출처: UBISAM 협업관리 › 프로젝트 설정 › 기본정보 / 대상자 + +project_name: "{사업부}_{고객사}_{설비명} 운영SW" +project_kind: "Software" # Software | Hardware +project_code: "{CUST}_{TYPE}_{NN}_{YYYYMMDD}" # 예: LGIT_SI_01_20260601 +board_type: "Kanban" +status: "진행중" + +org: + division: "{사업부}" + team: "{팀}" + +people: + registrant: "{등록자} ({직급})" + pl: "{PL} ({직급})" + +schedule: + contract_date: "{YYYY-MM-DD}" + due_date: "{YYYY-MM-DD}" + +# 권한 3단계: 책임자 → 참여자 → 열람자 +roles: + owners: ["{책임자}"] + participants: ["{참여자1}", "{참여자2}"] + viewers: ["{열람자…}"] + +alarm: + realtime_ticket: true + mail: true + comment: true diff --git a/.claude/skills/ubisam-format-map/SKILL.md b/.claude/skills/ubisam-format-map/SKILL.md new file mode 100644 index 0000000..94407d9 --- /dev/null +++ b/.claude/skills/ubisam-format-map/SKILL.md @@ -0,0 +1,78 @@ +--- +name: ubisam-format-map +description: Ubisam 협업관리 양식의 공통 enum·스키마·단계↔산출물 슬롯 매핑 레퍼런스. 상태(4종)·일감유형(9종)·진행 6단계(분석/협의·설계·제작·셋업·양산·CS)·일감/프로젝트 필드 스키마·티켓 코드 규칙을 제공. Ubisam 양식으로 산출물을 재생산·재배치·검증하거나, 타사 양식 문서를 Ubisam 단계로 매핑할 때 반드시 이 스킬을 먼저 읽을 것. "Ubisam 양식", "협업관리", "일감 6단계", "산출물 슬롯", "상태/유형 enum" 언급 시 사용. +--- + +# Ubisam 협업관리 양식 맵 (Format Map) + +타사 양식 산출물을 Ubisam 협업관리 양식으로 **재생산·재배치·검증**할 때의 단일 기준 사전이다. +근거 원본(스크린샷 분석)은 아래를 참조하며, 값이 갱신되면 **이 파일과 원본을 함께** 고친다. + +- `docs/[Ubisam산출물]/[분석]/_ubisam-협업관리-용어집(skill-ref).md` — enum 사전(원본) +- `docs/[Ubisam산출물]/[분석]/2026_06_29_협업관리-프로젝트관리-분석.md` — 단계별 산출물 정의 상세 +- `docs/[Ubisam산출물]/ref/` — 화면 캡처 14장 + +> 본문은 요약 기준만 담는다. 단계별 산출물 표 전문이 필요하면 위 분석 문서를 열어라. + +## 1. 상태(Status) enum — 4종 (전이 순서) + +``` +해야 할 일 → 분석 진행 → 업무 진행중 → 진행 완료 +``` +| 상태 | 색(ARGB) | 칸반 컬럼 | +|---|---|---| +| 해야 할 일 | FFD9D9D9 (회색) | Todo | +| 분석 진행 | FF8FAADC→청색 | Select For dev | +| 업무 진행중 | FF8FAADC (파랑) | InProgress | +| 진행 완료 | FFA9D08E (연두) | Done | + +상태 문자열은 위 4개만 사용한다. 임의 변형(예: "완료", "진행중") 금지 — 칸반/조건부서식 매칭이 깨진다. + +## 2. 일감 유형(Type) enum — 9종 + +오류수정 · 불합리 · 미팅 · 설계 · 개발 · 제작 · 입고셋업 · 적용 및 모니터링 · 요청대응 + +## 3. 진행 6단계 ↔ 기본 일감유형 ↔ 산출물 슬롯 + +| # | 단계 | 기본 유형 | 핵심 산출물(슬롯) | +|---|---|---|---| +| 1 | 분석/협의 | 미팅 | 사양서, CR회의록, 마스터플랜, URS 분석/제어 시나리오 검토 | +| 2 | 설계 | 설계 | HMI 화면정의서·UI 스토리보드·기능명세서 / 시퀀스 다이어그램·인터록 정의서 / 통신맵(Memory·IO·Register)·DB 스키마 / D/R 회의록 | +| 3 | 제작 | 제작 | 개발환경·핵심모듈·UI/메인 시퀀스·시뮬레이션 / 단위·통합 테스트 결과 보고서(Check-List) | +| 4 | 셋업 | 입고셋업 | HW IO/모션 점검 시트 / 파라미터·레시피 백업 / 이슈 로그(Dry Run·Tack Time) / MES 연동 / SAT 결과서 | +| 5 | 양산 | 적용 및 모니터링 | 운영 가이드·매뉴얼(HMI·알람조치) / S/W 최적화 / 최종 버전 동결·마스터 백업 / 프로젝트 완료 보고서 | +| 6 | CS | 요청대응 | 요청대응 이력(가동 후 CS) | + +**타사 양식 → 슬롯 매핑 원칙**: 원본 문서의 목적(요구분석/설계정의/테스트/셋업/운영매뉴얼)을 위 슬롯 키워드와 대조해 단계를 결정한다. 한 문서가 여러 단계에 걸치면 주(主) 목적 단계에 배치하고 비고에 보조 단계를 병기한다. + +## 4. 일감 필드 스키마 + +| 필드 | 규칙 | +|---|---| +| 제목 | `N.단계명` (예: `2.설계`) | +| 티켓번호 | `{프로젝트코드}-{일련번호}` (예: `LGIT_SI_01_20260601-1024`) | +| 유형 | §2 enum 1개 | +| 상태 | §1 enum 1개 | +| 우선순위 | 보통(=Normal) 기본 | +| 담당자/참조자 | `이름(직급)` | +| 진행률 | % | +| 등록일/수정일/종료일 | yyyy/mm/dd (또는 yyyy-mm-dd) | +| 예상시간 | `2w 4d 6h 45m` 형식 | +| Hashtag | 자유 태그 | +| 첨부파일 | 최대 2.93GB | + +## 5. 프로젝트 메타 스키마 + +프로젝트명 · 종류(Software) · 프로젝트형식(Kanban) · 코드명 · 사업부/팀 · PL · 등록자 · 계약일/납기 · 권한(책임자→참여자→열람자). + +이 프로젝트(시범 대상) 고정값: +- 프로젝트명 `LGIT_구미_FMM 공소수정기 운영SW`, 코드명 `LGIT_SI_01_20260601` +- 사업부/팀 `로봇자동화사업부 / 로봇팀`, PL `김영중 부장`, 계약일 `2026-03-25`, 납기 `2026-10-30` + +## 6. PTOP 채움 시 공통 사실 + +설비: 각형 배터리 레이저 용접기(ILSOS OC5 PtoP Welder), Aurora 2.0 / .NET 4.8.1 / WPF. +주요 디바이스: PLC(Mitsubishi Melsec·MX Component), Laser Scanner(RTC), Laser(IO Gate), Ajin Digital IO. +메인 시퀀스: `SeqAutoRunPTOP`. PLC 인터페이스: `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs`. + +> 재생산 산출물의 **출처(코드 경로/커밋)**를 항상 명시한다 — 검증가가 근거 추적에 사용한다. diff --git a/.claude/skills/ubisam-ppt-make/assets/Ubisam_logo.gif b/.claude/skills/ubisam-ppt-make/assets/Ubisam_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c224287499fe3da1b96a173021af9b7d2dc22c2 GIT binary patch literal 6991 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 QFTtpEwT-mOH5@|!1MwFtO#lD@ literal 0 HcmV?d00001 diff --git a/.claude/skills/ubisam-ppt-make/assets/Ubisam_logo_transparent.png b/.claude/skills/ubisam-ppt-make/assets/Ubisam_logo_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..42d42175a018df71cc3055b7565bd09269e28892 GIT binary patch literal 8251 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 literal 0 HcmV?d00001 diff --git a/.claude/skills/ubisam-ppt-make/examples/showcase.json b/.claude/skills/ubisam-ppt-make/examples/showcase.json new file mode 100644 index 0000000..4ca0c3c --- /dev/null +++ b/.claude/skills/ubisam-ppt-make/examples/showcase.json @@ -0,0 +1,394 @@ +{ + "doc_title": "P-PRJ BFHB SW 설계서", + "slides": [ + { + "type": "cover", + "tag": "SW DESIGN DOCUMENT", + "title": "P-PRJ BFHB SW 설계서", + "subtitles": [ + "인도향 P-Project · HFHB (Button Flex Hot Bar 열압착 본딩기)", + "A device that compresses a button flex by applying pressure and temperature to a point" + ], + "team": "시스템 사업팀", + "contacts": [ + "조정묵 과장 jojeongmuk@ubisam.com", + "남재근 대리 namjaegeun@ubisam.com" + ] + }, + { + "type": "history", + "rows": [ + [ + "1", + "1.0", + "2025.04.28", + "조정묵", + "초안 작성" + ], + [ + "2", + "1.1", + "2025.04.29", + "조정묵", + "UI 안, Class 내용 추가" + ], + [ + "3", + "1.2", + "2025.05.09", + "조정묵", + "250425_엑추에이터 정리.xlsx 내용 반영" + ], + [ + "4", + "1.3", + "2025.05.15", + "조정묵", + "[DFM]_LG PRI_V57_BFHB_1.2 내용 반영" + ], + [ + "5", + "1.4", + "2025.05.21", + "조정묵", + "[DFM]_LG PRI_V57_BFHB_1.7 내용 반영" + ] + ] + }, + { + "type": "agenda", + "items": [ + { + "no": "01", + "title": "System Overview", + "sub": "시스템 구성도" + }, + { + "no": "02", + "title": "UI Concept", + "sub": "Main / Teaching UI 구성" + }, + { + "no": "03", + "title": "Sequence Flowchart", + "sub": "Loading · Bonding · Unloading · PDCA" + }, + { + "no": "04", + "title": "Class 관계도", + "sub": "Class Diagram" + }, + { + "no": "05", + "title": "축 구성", + "sub": "Axis Configuration" + }, + { + "no": "06", + "title": "IO 구성", + "sub": "I/O Map" + } + ] + }, + { + "type": "divider", + "no": "01", + "title": "System Overview", + "subtitle": "시스템 구성도" + }, + { + "type": "content", + "no": "02.", + "title": "UI Concept", + "subtitle": "Main UI 구성 1", + "panels": [ + { + "title": "HotBar 측정 값", + "body": "선형 그래프 (최근 생산 100개 표시)" + }, + { + "title": "IO Monitoring", + "body": "조작 버튼 / 상태 표시" + }, + { + "title": "CPK", + "body": "막대형 그래프" + }, + { + "title": "OK 판정률", + "body": "도넛 그래프 (Bali 수신 + 현재공정 판정)" + } + ] + }, + { + "type": "flow", + "no": "03.", + "title": "Sequence Flowchart", + "subtitle": "[Loading]", + "steps": [ + { + "title": "Wait (Is Loader Empty)", + "code": "SendMachine_Ready", + "kind": "proc" + }, + { + "title": "Is UpStream Ready", + "code": "CHECK_UPSTREAM", + "kind": "dec" + }, + { + "title": "Wait (Is Board Available)", + "code": "BoardAvailable", + "kind": "proc" + }, + { + "title": "Loader Conveyor On", + "code": "SendStartTransport", + "kind": "proc" + }, + { + "title": "Is TransportError", + "code": "TransportError", + "kind": "dec" + } + ], + "branch": { + "title": "Conveyor Stop", + "sub": "Sequence Stop" + } + }, + { + "type": "table", + "no": "06.", + "title": "IO 구성", + "subtitle": "I/O Map", + "headers": [ + "No.", + "Type", + "Address", + "Signal Name", + "Description" + ], + "widths": [ + 0.8, + 1.4, + 1.7, + 3.3, + 4.2 + ], + "center_cols": [ + 0, + 1, + 2 + ], + "rows": [ + [ + "1", + "DI", + "X00.0", + "Start_Button", + "사이클 시작 입력" + ], + [ + "2", + "DI", + "X00.1", + "Stop_Button", + "사이클 정지 입력" + ], + [ + "3", + "DI", + "X00.2", + "EMG_Stop", + "비상 정지" + ], + [ + "4", + "DO", + "Y10.0", + "Tower_Lamp_R", + "경광등 적색" + ], + [ + "5", + "DO", + "Y10.1", + "Conveyor_Run", + "컨베이어 구동" + ], + [ + "6", + "DO", + "Y10.2", + "Stopper1_Up", + "스토퍼1 상승" + ] + ] + }, + { + "type": "divider", + "no": "07", + "title": "Operation Manual", + "subtitle": "조작 매뉴얼" + }, + { + "type": "screen_guide", + "no": "07.", + "title": "화면 설명", + "subtitle": "Main 운전 화면", + "caption": "Main 운전 화면 캡처", + "items": [ + { + "title": "운전 상태 표시", + "desc": "현재 사이클/공정 상태 램프", + "pos": [ + 0.5, + 0.5 + ] + }, + { + "title": "측정값 그래프", + "desc": "Load Cell · 온도 실시간 추이", + "pos": [ + 3.0, + 1.5 + ] + }, + { + "title": "판정 결과", + "desc": "OK/NG 도넛 그래프", + "pos": [ + 5.5, + 2.5 + ] + }, + { + "title": "조작 버튼", + "desc": "Start / Stop / Reset", + "pos": [ + 1.5, + 3.8 + ] + }, + { + "title": "알람 표시줄", + "desc": "최근 발생 알람 메시지", + "pos": [ + 4.0, + 4.3 + ] + } + ] + }, + { + "type": "procedure", + "no": "07.", + "title": "조작 절차", + "subtitle": "운전 시작 순서", + "steps": [ + { + "title": "전원 ON 및 초기화", + "desc": "메인 전원 인가 후 [Reset] 버튼으로 장비 초기화" + }, + { + "title": "원점 복귀", + "desc": "[Home] 버튼 선택 → 전 축 원점 복귀 완료 확인" + }, + { + "title": "레시피 선택", + "desc": "생산 모델에 맞는 레시피 로드 및 파라미터 확인" + }, + { + "title": "자동 운전 시작", + "desc": "[Auto] 모드 전환 후 [Start] 버튼으로 사이클 시작" + }, + { + "title": "운전 종료", + "desc": "[Stop] 후 사이클 완료 대기 → 전원 OFF" + } + ] + }, + { + "type": "icon_ref", + "no": "07.", + "title": "버튼 설명", + "subtitle": "Main UI 조작 버튼", + "items": [ + { + "icon": "▶", + "name": "Start", + "func": "자동 운전 시작" + }, + { + "icon": "■", + "name": "Stop", + "func": "사이클 정지" + }, + { + "icon": "↺", + "name": "Reset", + "func": "알람 해제 / 초기화" + }, + { + "icon": "⌂", + "name": "Home", + "func": "전 축 원점 복귀" + }, + { + "icon": "≡", + "name": "Recipe", + "func": "레시피 관리 화면" + }, + { + "icon": "⚙", + "name": "Setup", + "func": "설정 / 파라미터" + } + ] + }, + { + "type": "alarm", + "no": "07.", + "title": "알람 및 조치 방법", + "subtitle": "주요 알람 코드", + "notice": "알람 발생 시 원인 확인 후 아래 조치 방법을 순서대로 수행하십시오.", + "rows": [ + [ + "AL-001", + "비상정지 입력", + "EMG 버튼 눌림", + "안전 확인 후 EMG 해제 → Reset" + ], + [ + "AL-012", + "원점 복귀 실패", + "센서 미감지 / 간섭", + "축 위치 확인 후 수동 원점 재시도" + ], + [ + "AL-024", + "Load Cell 통신 이상", + "케이블 단선 / 노이즈", + "커넥터 체결 확인 후 전원 재투입" + ], + [ + "AL-031", + "온도 상한 초과", + "Heater 과열", + "설정 온도 확인 → 냉각 후 재가동" + ] + ] + }, + { + "type": "closing", + "title": "Thank You", + "message": "문의 사항은 아래 담당자에게 연락 바랍니다.", + "team": "시스템 사업팀", + "contacts": [ + "조정묵 과장 jojeongmuk@ubisam.com", + "남재근 대리 namjaegeun@ubisam.com" + ] + } + ] +} \ No newline at end of file diff --git a/.claude/skills/ubisam-ppt-make/생성파일/test_bg.pptx b/.claude/skills/ubisam-ppt-make/생성파일/test_bg.pptx new file mode 100644 index 0000000000000000000000000000000000000000..e2322d384e0c8b7980db0b89bce66006fdf0f45b GIT binary patch literal 64326 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 VY~T_F1nGdE0&pgFqJM7fe*n?ZJb(ZI literal 0 HcmV?d00001 diff --git a/.claude/skills/ubisam-ppt-make/생성파일/test_wm.pptx b/.claude/skills/ubisam-ppt-make/생성파일/test_wm.pptx new file mode 100644 index 0000000000000000000000000000000000000000..00c66d8f18db4e8fc4c68cb0c6146e06fa7f522b GIT binary patch literal 64410 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

F1_w}6i%><% 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 literal 0 HcmV?d00001 diff --git a/.claude/skills/ui_layout_image/SKILL.md b/.claude/skills/ui_layout_image/SKILL.md new file mode 100644 index 0000000..859f8a3 --- /dev/null +++ b/.claude/skills/ui_layout_image/SKILL.md @@ -0,0 +1,68 @@ +--- +name: ui_layout_image +description: UI 소스코드(예 C# WPF XAML, WinForms, Qt, HTML/CSS, Android/Flutter)를 읽어 화면의 레이아웃 구조를 분석하고, 스크린샷 없이 그리드/컨테이너 구조를 그대로 도식(wireframe mockup) 이미지로 생성해 저장한다. 필요 시 PPT/매뉴얼에 삽입. "화면 구성 이미지로 만들어줘", "XAML 보고 레이아웃 그려줘", "코드 보고 화면 도식/목업 만들어 매뉴얼에 넣어줘", "스크린샷 말고 코드로 화면 그려줘" 요청 시 사용. +--- + +# ui_layout_image — 코드 기반 화면 레이아웃 도식 생성 스킬 + +**실행 중인 앱 스크린샷이 아니라**, UI 소스코드의 레이아웃 정의(컨테이너/그리드/컨트롤)를 읽어 +화면 구성을 도식(wireframe mockup) 이미지로 렌더링한다. 매뉴얼·산출물·문서에 삽입하기 위함. + +## 언제 쓰나 +- 앱을 띄울 수 없거나(하드웨어/시뮬 불가), 스크린샷이 부적절할 때 +- 코드만으로 "이 화면이 이렇게 생겼다"를 문서/매뉴얼에 보여줄 때 +- 여러 화면을 일관된 스타일의 도식으로 빠르게 만들 때 + +## 입력(args) 해석 +- 대상 화면(들)의 소스 파일 경로 또는 화면 이름. 비어 있으면 무엇을 그릴지 한 줄로 물어본다. +- 선택 옵션: 스타일(dark HMI / light), 출력 경로, 삽입 대상(PPTX 경로·슬라이드). + +## 동작 절차 + +### 1) UI 소스 식별 및 레이아웃 파싱 (프레임워크별) +대상 언어/프레임워크를 판별하고 **레이아웃 트리**(중첩 컨테이너 + 컨트롤 + 라벨/바인딩)를 추출한다. + +| 프레임워크 | 읽을 것 | 핵심 레이아웃 단서 | +|-----------|--------|-------------------| +| **C# WPF** (이 프로젝트 기본) | `*.xaml` | `Grid`(RowDefinitions/ColumnDefinitions, `*`/`auto`/비율), `GroupBox.Header`, `StackPanel`, `UniformGrid(Rows/Columns)`, `Button.Content`, `TextBlock.Text`, `usercontrols:UCKeyValue KeyText/Unit`, `Grid.Row/Column/ColumnSpan` | +| C# WinForms | `*.Designer.cs` | `Controls.Add`, `.Location`, `.Size`, `.Dock`, `.Text` | +| Qt | `*.ui`(XML) | ``, ``, `` | +| HTML/CSS | `*.html`/`*.css`/JSX | div 구조, flex/grid, 라벨 텍스트 | +| Android | `res/layout/*.xml` | `LinearLayout`/`ConstraintLayout`, `android:text` | +| Flutter | `*.dart` | `Row`/`Column`/`Grid`/`Container`, `Text(...)` | + +- 비율(`2*:1*` 등), 행/열 수, 헤더 텍스트, 버튼/입력칸/표 컬럼을 **코드에서 그대로** 가져온다. 임의로 지어내지 않는다. +- 바인딩(`{Binding ...}`)은 라벨/예시값으로 치환(예: "Process Running", "1234")하되 의미를 보존한다. + +### 2) 도식 이미지 렌더링 (Python · matplotlib) +- 실행: Windows에선 `py`(파이썬 런처). 라이브러리: `matplotlib`, `Pillow` 사용 가능. +- 한글 폰트: `C:\Windows\Fonts\malgun.ttf` 를 `font_manager`로 등록. +- 좌표계: `ax.set_ylim(H, 0)` 로 좌상단 원점(화면 좌표계와 동일). +- 스타일 기본값 = **dark HMI** (이 프로젝트 UI와 일치): + - 배경 `#2D2D30`, 패널 `#3E3E42`, 테두리 `#6E6E72`, 헤더바 `#007ACC`, 글자 `#DDDDDD`, + 강조버튼 보라 `#7E57C2`/파랑 `#2D6CA2`, 입력칸 `#252528`. +- 컴포넌트 헬퍼: `panel(header)`(둥근 박스+헤더바), `kv(key,val)`(키-값 입력칸), `button(text,color)`, `label`. +- **그리드 구조를 그대로**: 컬럼 비율 → 폭 배분, 행 정의(auto/`*`) → 높이 배분, GroupBox → panel, UniformGrid → 균등 분할. +- 참고 구현(이 프로젝트 실제 사용): `UnTrackedLocalFiles/`의 `gen_mockups.py`, `gen_menubar.py` 패턴. + +### 3) 저장 +- 기본 출력: `UnTrackedLocalFiles/hmi_mockups/.png` (git 제외 위치). 다른 위치 지정 시 그곳에. +- 생성 후 Read 도구로 PNG를 열어 **시각 검증**(겹침/라벨 누락 확인). 문제 있으면 수정 후 재생성. + +### 4) (선택) 문서/PPT 삽입 +- PPTX: `python-pptx`로 대상 슬라이드의 기존 스크린샷(PICTURE)·번호 콜아웃(예 "타원*")을 제거하고 + 같은 위치/크기에 `add_picture`. 빈 콜아웃 박스도 정리. +- 검증: LibreOffice(`soffice --headless --convert-to pdf`) → PyMuPDF(`fitz`)로 PNG 렌더 후 Read로 확인. + +### 5) 보고 +- 생성한 이미지 경로, 반영한 화면/소스, 삽입 위치를 한 줄로 요약. 코드와 다르게 추정한 부분은 명시. + +## 주의 +- 반드시 **소스코드 기준**. 실제 값이 불명확하면 라벨/예시값으로 두고 "(예시)" 표기. +- 원본 문서(PPT/Excel) 수정 전 백업(`UnTrackedLocalFiles/...backup`). 서식 보존. +- 생성 스크립트·중간 렌더는 `UnTrackedLocalFiles/`(커밋 제외)에 둔다. 산출물(이미지가 삽입된 문서)만 커밋. + +## 호출 예시 +- `/ui_layout_image UCMainAutoRun.xaml 화면 도식 만들어줘` +- `/ui_layout_image Manual/Calibration 화면들 dark 스타일로 그려서 HMI Manual.pptx 슬라이드에 넣어줘` +- 자연어: "스크린샷 말고 XAML 그리드 구조 보고 화면 레이아웃 이미지로 만들어서 매뉴얼에 넣어줘" diff --git a/.claude/skills/ui_layout_image/render_helpers.py b/.claude/skills/ui_layout_image/render_helpers.py new file mode 100644 index 0000000..2c92727 --- /dev/null +++ b/.claude/skills/ui_layout_image/render_helpers.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +"""ui_layout_image 스킬용 렌더 헬퍼 (dark HMI 스타일). +사용 예: + py render_helpers.py # 데모 1장 생성 +실전에서는 import 하여 panel/kv/button/label 로 화면별 도식을 그린다. +""" +import os +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.patches import FancyBboxPatch, Rectangle +import matplotlib.font_manager as fm + +# ---- 한글 폰트 ---- +FONT = r"C:\Windows\Fonts\malgun.ttf" +try: + fp = fm.FontProperties(fname=FONT) + plt.rcParams["font.family"] = fp.get_name() +except Exception: + pass +plt.rcParams["axes.unicode_minus"] = False + +# ---- dark HMI 팔레트 (프로젝트 UI와 일치) ---- +BG = "#2D2D30"; PANEL = "#3E3E42"; BORDER = "#6E6E72"; HEADER = "#007ACC" +TXT = "#DDDDDD"; PURPLE = "#7E57C2"; BLUE = "#2D6CA2"; GREEN = "#2E7D32"; GRAY = "#555558" +INPUT = "#252528"; VALCOLOR = "#9CDCFE" + + +def new_canvas(w, h): + fig = plt.figure(figsize=(w / 100, h / 100), dpi=100) + ax = fig.add_axes([0, 0, 1, 1]) + ax.set_xlim(0, w); ax.set_ylim(h, 0); ax.axis("off") + ax.add_patch(Rectangle((0, 0), w, h, facecolor=BG, edgecolor="none")) + return fig, ax + + +def panel(ax, x, y, w, h, header=None, fc=PANEL): + ax.add_patch(FancyBboxPatch((x + 2, y + 2), w - 4, h - 4, boxstyle="round,pad=2", + facecolor=fc, edgecolor=BORDER, linewidth=1.2)) + if header: + ax.add_patch(Rectangle((x + 4, y + 4), w - 8, 22, facecolor=HEADER, edgecolor="none")) + ax.text(x + 12, y + 15, header, color="white", fontsize=9, fontweight="bold", va="center") + return x + 8, y + 30 + + +def label(ax, x, y, text, size=8.5, color=TXT, bold=False, ha="left"): + ax.text(x, y, text, color=color, fontsize=size, va="center", ha=ha, + fontweight=("bold" if bold else "normal")) + + +def kv(ax, x, y, w, key, val=""): + ax.text(x + w * 0.46, y, key, color=TXT, fontsize=8, va="center", ha="right") + ax.add_patch(Rectangle((x + w * 0.5, y - 9), w * 0.48, 18, facecolor=INPUT, edgecolor=BORDER, lw=0.6)) + if val: + ax.text(x + w * 0.52, y, val, color=VALCOLOR, fontsize=8, va="center", ha="left") + + +def button(ax, x, y, w, h, text, color=BLUE): + ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=1.5", facecolor=color, edgecolor="none")) + ax.text(x + w / 2, y + h / 2, text, color="white", fontsize=8.5, fontweight="bold", ha="center", va="center") + + +def save(fig, path): + os.makedirs(os.path.dirname(path), exist_ok=True) + fig.savefig(path, facecolor=BG) + plt.close(fig) + + +if __name__ == "__main__": + fig, ax = new_canvas(600, 300) + panel(ax, 10, 10, 580, 280, "Demo Panel") + kv(ax, 30, 60, 300, "Key", "Value") + button(ax, 30, 90, 120, 28, "Run", PURPLE) + save(fig, os.path.join(os.path.dirname(__file__), "_demo.png")) + print("demo saved")