NohDoyeon241104 0cddd5d5b3 feat: ubisam-ppt-make and ubisam-deliverables PPTX integration (Step 2/2) - SW 설계서 자동 생성
- build_02설계_pptx.py 추가: 2.설계 산출물(기능명세·시퀀스·신호정의·IO맵·TactTime) → SW_설계서.pptx 22슬라이드 한 줄 재생산
  - cover / history / agenda / divider × 5 / table × 12 / flow × 2 / closing 구성
  - tempfile spec JSON → build_deck.py → pptx, spec 자동 삭제
  - 실행: set PYTHONUTF8=1 && py .claude/skills/ubisam-deliverables/references/build_02설계_pptx.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 16:04:07 +09:00

323 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
ILSOS PrismaticRD PTOP — 2.설계 SW_설계서.pptx 자동 생성 스크립트.
실행:
set PYTHONUTF8=1 && py .claude/skills/ubisam-deliverables/references/build_02설계_pptx.py
출력: docs/[Ubisam산출물]/02.설계/설계리뷰_DR/SW_설계서.pptx
"""
import os, json, subprocess, sys, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.abspath(os.path.join(HERE, "..", "..", "..", ".."))
BUILDER = os.path.join(REPO, ".claude", "skills", "ubisam-ppt-make", "build_deck.py")
OUTPUT = os.path.join(REPO, "docs", "[Ubisam산출물]", "02.설계", "설계리뷰_DR", "SW_설계서.pptx")
SPEC = {
"doc_title": "ILSOS PrismaticRD PTOP SW 설계서",
"slides": [
{
"type": "cover",
"tag": "SW DESIGN DOCUMENT",
"title": "ILSOS PrismaticRD PTOP\nSW 설계서",
"subtitles": [
"LGES OC5 PtoP Welder — Prismatic 전지 점 용접 자동화 SW",
"Aurora 2.0 / .NET 4.8.1 / WPF · MX Component · ACS EtherCAT · Ajin IO"
],
"team": "로봇자동화사업부",
"contacts": ["노도연 대리 otteryeonny@ubisam.com"]
},
{
"type": "history",
"no": "00.",
"title": "변경 이력",
"subtitle": "Revision History",
"rows": [
["01", "0.1", "2026-06-29", "노도연", "소스 기반 최초 작성 (기능명세·시퀀스·신호정의·IO맵·TactTime)"],
["02", "0.2", "2026-06-30", "노도연", "SW_설계서.pptx 최초 생성 (build_02설계_pptx.py)"]
]
},
{
"type": "agenda",
"title": "Contents",
"subtitle": "목차",
"items": [
{"no": "01", "title": "기능명세서", "sub": "메뉴 구조 · View별 기능"},
{"no": "02", "title": "제어 시퀀스 / 인터록", "sub": "SeqAutoRunPTOP · 인터록 정의"},
{"no": "03", "title": "PLC-PC 신호 정의", "sub": "통신 사양 · PC↔PLC 신호 목록"},
{"no": "04", "title": "모션축 / IO 맵", "sub": "축 정의 · DI/DO 맵"},
{"no": "05", "title": "TactTime 산정서", "sub": "자동 1사이클 단위동작별 T/T"}
]
},
# ── 01. 기능명세서 ──────────────────────────────────────────────
{"type": "divider", "no": "01", "title": "기능명세서", "subtitle": "UI / Function Specification"},
{
"type": "table",
"no": "01.", "title": "메뉴 구조",
"subtitle": "RegisterViews — 메인 메뉴 · 권한 · 서브뷰 · 기능 요약",
"headers": ["메인 메뉴", "권한", "서브뷰", "기능"],
"widths": [1.6, 1.2, 3.4, 5.2], "center_cols": [0, 1],
"rows": [
["AutoRun", "NONE", "UCMainAutoRun", "자동 운전 메인(SeqAutoRun 연동), 페이지/상태 표시"],
["Recipe", "NONE", "UCRecipe / UCRecipeChangeHistory / UCModel", "레시피 편집·변경이력·모델"],
["Manual", "NONE", "UCManualScannerOperation / UCManualModelFinder", "스캐너 수동조작·모델 파인더"],
["Calibration","NONE", "UCScannerCalibration / UCCameraPixelResolution", "스캐너 보정·픽셀 해상도"],
["Monitoring", "NONE", "UCPLCBit / UCPLCWord / UCIOInterfaceView", "PLC 비트(M)·워드(D)·IO 모니터링"],
["Device", "LEVEL3", "DeviceSettingView", "디바이스 설정"],
["Setting", "LEVEL1", "EquipSettingView / LogSettingView / AutoBackupView2", "장비·로그·자동백업 설정"],
["Alarm", "NONE", "AlarmHistoryView / AlarmDescView", "알람 이력·설명"]
]
},
{
"type": "table",
"no": "01.", "title": "View별 기능 (1/2)",
"subtitle": "MainView · RecipeView · ManualView",
"headers": ["View", "no", "기능 영역", "기능명", "연동"],
"widths": [1.6, 0.6, 1.8, 2.8, 4.6], "center_cols": [0, 1],
"rows": [
["MainView", "1", "운전", "StartSequence / Stop", "SeqAutoRunPTOP"],
["MainView", "2", "상태", "UpdatePageNum", "EVENT_AUTO_RUN_UPDATE_PAGENUM"],
["MainView", "3", "상태", "WorkingPosition", "eWorkingPosition"],
["MainView", "4", "운전", "Auto→Manual+Resume", "bit15(PLC_WELD_READY_REQ)"],
["MainView", "5", "표시", "ScannerCanvasCtrlTeaching", "레이어/오브젝트"],
["RecipeView", "1", "레시피", "ActivateRecipe", "RecipeManager"],
["RecipeView", "2", "레시피", "RecipeChangeHistory", "RchHandler"],
["RecipeView", "3", "모델", "UCModel / EquipmentSpecificModel", "EquipmentSpecificModel_PTOP"],
["ManualView", "1", "수동", "ScannerOperation", "CommonDeviceIds.Scanner"],
["ManualView", "2", "수동", "ModelFinder", "UCManualModelFinder"]
]
},
{
"type": "table",
"no": "01.", "title": "View별 기능 (2/2)",
"subtitle": "MonitoringView · CalibrationView · SettingView · AlarmView",
"headers": ["View", "no", "기능 영역", "기능명", "연동"],
"widths": [1.8, 0.6, 1.8, 2.8, 4.4], "center_cols": [0, 1],
"rows": [
["MonitoringView", "1", "모니터링", "PLCBitMonitoring", "ePLCDevice.M"],
["MonitoringView", "2", "모니터링", "PLCWordMonitoring", "ePLCDevice.D"],
["MonitoringView", "3", "모니터링", "IOInterfaceView", "IODevice"],
["CalibrationView", "1", "보정", "UCScannerCalibration", "스캐너 보정"],
["CalibrationView", "2", "보정", "UCCameraPixelResolution","카메라 픽셀 해상도"],
["SettingView", "1", "설정", "EquipSettingView", "장비 설정"],
["SettingView", "2", "설정", "LogSettingView / AutoBackupView2", "로그·자동백업"],
["AlarmView", "1", "알람", "AlarmHistoryView", "AlarmCode_PTOP"],
["AlarmView", "2", "알람", "AlarmDescView", "AlarmManager_PTOP"]
]
},
# ── 02. 제어 시퀀스 / 인터록 ────────────────────────────────────
{"type": "divider", "no": "02", "title": "제어 시퀀스 / 인터록", "subtitle": "Control Sequence & Interlock"},
{
"type": "flow",
"no": "02.", "title": "SeqAutoRunPTOP — 자동 시퀀스 (초기화 ~ 업로드)",
"subtitle": "Sequences/SeqAutoRunPTOP.cs — Builder() 노드 흐름 (1/2)",
"steps": [
{"title": "Sequence_Enter", "code": "레시피/장비 초기화 · PC_AUTO_STATUS ON", "kind": "proc"},
{"title": "CheckInitCondition", "code": "초기 조건 판단", "kind": "dec"},
{"title": "CheckPageNo", "code": "PLC_PAGE_CHANGE_REQ(0x08) 대기", "kind": "proc"},
{"title": "CheckRecipeNoComp", "code": "PLC_RECIPE_NO 확정/전환 · PC_RECIPE_NO 에코", "kind": "proc"},
{"title": "CheckPageNoComp", "code": "PLC_PAGE_NO 3회 검증 · PC_PAGE_CHANGE_COMPLETE ACK", "kind": "proc"},
{"title": "SeqUploadWeldingList","code": "레이어 선택 → 용접리스트 업로드 · offset(D70510) 적용", "kind": "proc"}
],
"branch": {"title": "Resume", "sub": "핸드셰이크 스킵"}
},
{
"type": "flow",
"no": "02.", "title": "SeqAutoRunPTOP — 자동 시퀀스 (발진 ~ 완료)",
"subtitle": "Sequences/SeqAutoRunPTOP.cs — Builder() 노드 흐름 (2/2)",
"steps": [
{"title": "WeldingStartReadBit", "code": "PLC_WELD_TRIGGER_REQ(0x10) 대기", "kind": "proc"},
{"title": "PreScoutCheck", "code": "M70509 ON → PreScout 실행", "kind": "dec"},
{"title": "WaitWeldingStart", "code": "PC_WELD_TRIGGER_ACK ON / WELD_COMPLETE OFF", "kind": "proc"},
{"title": "ExecuteWelding", "code": "HeadTheta 인터락 → Gate ON(15ms) → ExecuteList → Gate OFF", "kind": "proc"},
{"title": "WriteWeldComplete", "code": "PC_WELD_TRIGGER_ACK OFF · PC_WELD_COMPLETE ON", "kind": "proc"},
{"title": "CheckCycleStop", "code": "루프백 or SaveResult → WriteCompletionPLCBit", "kind": "dec"}
],
"branch": {"title": "PostScout", "sub": "M70509 ON 시 실행"}
},
{
"type": "table",
"no": "02.", "title": "인터록 정의",
"subtitle": "LaserInterlockSetting.cs · SeqAutoRunPTOP.cs",
"headers": ["#", "인터록", "조건", "동작"],
"widths": [0.5, 2.3, 4.0, 4.6], "center_cols": [0],
"rows": [
["1", "Head Theta 발진 게이트", "LaserInterlockSetting.HeadThetaInterlockEnabled ON · Front/Down ±tol 벗어남", "발진 차단 (ALARM_LASER_HEAD_THETA_INTERLOCK). PLC 미연결 시 fail-closed"],
["2", "레시피별 방향 차단", "RecipeLaserInterlock.Front/Down 체크", "해당 방향 구역이면 발진 차단"],
["3", "중알람(Heavy)", "PLC_HEAVY_ALARM_STATUS(0x05)", "AlarmManager_PTOP.StopSequenceByAlarm → 시퀀스 정지"],
["4", "Door / EMO", "PLC_DOOR_INTERLOCK(0x06) / PLC_EMO_STATUS(0x07)", "안전 정지"],
["5", "업로드 실패", "용접 오브젝트 0개 / Upload Success=false", "시퀀스 중단 (return null)"]
]
},
# ── 03. PLC-PC 신호 정의서 ──────────────────────────────────────
{"type": "divider", "no": "03", "title": "PLC-PC 신호 정의서", "subtitle": "PLC Interface Signal Definition"},
{
"type": "table",
"no": "03.", "title": "통신 사양",
"subtitle": "EquipmentSetup.cs · PLCController.Address.cs",
"headers": ["항목", "내용"],
"widths": [2.8, 8.6], "center_cols": [],
"rows": [
["PLC 기종", "Mitsubishi Melsec"],
["통신 방식", "MX Component (ePLCNetType.MXComponent) — EquipmentSetup.RegisterDevices()"],
["맵 파일", "CSV (PLCMMapFileType.CSV)"],
["데이터 갱신", "폴링(10ms 주기 ReadBit/ReadWord) + 이벤트 비트(REQ/ACK)"],
["디바이스 영역", "비트=M, 워드=D (입력 M70500/D70500 · 출력 M70000/D70000 — 제안)"],
["핸드셰이크 규칙", "요청(REQ)→처리→완료(ACK/COMPLETE). 단일 트리거 PLC_PAGE_CHANGE_REQ(0x08)"],
["인터락", "Door(0x06)·EMO(0x07)·Heavy알람(0x05) → 시퀀스 정지. HeadTheta(D70510) 발진 게이트"],
["Watchdog", "PLC_LIVE_STATUS(0x00) ↔ PC_LIVE_STATUS(0x00) Alive 토글"]
]
},
{
"type": "table",
"no": "03.", "title": "PC → PLC 신호 목록",
"subtitle": "eBitOutput / eWordOutput (Write) — 주요 신호",
"headers": ["No", "신호명(enum)", "디바이스", "Type", "의미"],
"widths": [0.7, 3.3, 1.4, 0.8, 5.2], "center_cols": [0, 2, 3],
"rows": [
["0x00", "PC_LIVE_STATUS", "M70000", "BIT", "PC Alive 토글 (Watchdog)"],
["0x01", "PC_AUTO_STATUS", "M70001", "BIT", "Auto 상태 — Enter 시 ON"],
["0x05", "PC_HEAVY_ALARM_STATUS", "M70005", "BIT", "중알람"],
["0x06", "PC_DOOR_INTERLOCK", "M70006", "BIT", "Door 인터락"],
["0x07", "PC_EMO_STATUS", "M70007", "BIT", "EMO"],
["0x08", "PC_PAGE_CHANGE_COMPLETE", "M70008", "BIT", "페이지 확정 ACK — PageNo 일치 시 ON"],
["0x0F", "PC_WELD_READY_ACK", "M70015", "BIT", "Resume ACK (bit15, 260621)"],
["0x10", "PC_WELD_TRIGGER_ACK", "M70016", "BIT", "용접 트리거 ACK"],
["0x11", "PC_WELD_BUSY", "M70017", "BIT", "Laser On 표시(발진중)"],
["0x12", "PC_WELD_COMPLETE", "M70018", "BIT", "용접 완료"],
["D0x01", "PC_PAGE_NO", "D70001", "INT", "페이지 번호 에코"],
["D0x02", "PC_RECIPE_NO", "D70002", "INT", "레시피 번호 에코"]
]
},
{
"type": "table",
"no": "03.", "title": "PLC → PC 신호 목록",
"subtitle": "eBitInput / eWordInput (Read) — 주요 신호",
"headers": ["No", "신호명(enum)", "디바이스", "Type", "의미"],
"widths": [0.7, 3.3, 1.4, 0.8, 5.2], "center_cols": [0, 2, 3],
"rows": [
["0x00", "PLC_LIVE_STATUS", "M70500", "BIT", "PLC Alive (Watchdog)"],
["0x05", "PLC_HEAVY_ALARM_STATUS", "M70505", "BIT", "중알람 → 시퀀스 정지"],
["0x06", "PLC_DOOR_INTERLOCK", "M70506", "BIT", "Door 인터락"],
["0x07", "PLC_EMO_STATUS", "M70507", "BIT", "EMO"],
["0x08", "PLC_PAGE_CHANGE_REQ", "M70508", "BIT", "단일 게이트 트리거 — PageNo+RecipeNo 확정"],
["0x09", "PLC_SCOUT_ENABLE", "M70509", "BIT", "Scout 상시 ON → 용접 전/후 Scout"],
["0x0F", "PLC_WELD_READY_REQ", "M70515", "BIT", "Weld_01 Ready → Resume 트리거 (bit15)"],
["0x10", "PLC_WELD_TRIGGER_REQ", "M70516", "BIT", "용접 발진 요청"],
["D0x01", "PLC_PAGE_NO", "D70501", "INT", "페이지 번호 (1-base, 3회 검증)"],
["D0x02", "PLC_RECIPE_NO", "D70502", "INT", "레시피 번호 (11/21/22/31/32)"],
["D0x0A", "HEAD_THETA_POSITION", "D70510", "INT", "Laser1 Head Theta — 발진 인터락"]
]
},
# ── 04. 모션축 / IO 맵 ──────────────────────────────────────────
{"type": "divider", "no": "04", "title": "모션축 / IO 맵", "subtitle": "Motion Axis & I/O Map"},
{
"type": "table",
"no": "04.", "title": "통신·제어 구성",
"subtitle": "EquipmentSetup.cs · PAIX_PTOP.cs · architecture.md §4",
"headers": ["구분", "대상", "컨트롤러/디바이스", "통신/버스", "수량·범위"],
"widths": [1.8, 1.8, 2.8, 2.0, 3.0], "center_cols": [0],
"rows": [
["PLC통신", "외부 신호 제어", "Mitsubishi Melsec", "MX Component", "1"],
["I/O", "디지털 IO", "Ajin(AJINEXTEK)", "필드버스", "DI/DO"],
["Laser", "Core/Ring 레이저", "IO Gate(TruPulse nano 계열)", "DO 게이트", "2(Core+Ring)"],
["Scanner", "레이저 스캐너", "RTC LaserScanner", "RTC", "1"],
["모션(Servo)", "X/Y 축", "ACS SPiiPlus", "EtherCAT", "2축 (제안)"],
["모션(Servo)", "Z 축", "Ajin Motion Board", "보드", "1축 (제안)"],
["Vision", "카메라/측정", "MIL 기반 AuroraVision", "—", "(현재 미사용)"],
["상/하위(MES)", "호스트 연동", "Stream(LBAD/RPC)", "TCP", "옵션"]
]
},
{
"type": "table",
"no": "04.", "title": "모션축 정의",
"subtitle": "ACS SPiiPlus(EtherCAT) · Ajin Motion Board — 파라미터 미확정(제안)",
"headers": ["Axis", "축명", "용도", "컨트롤러/통신", "행정/범위", "최대속도", "Home방식"],
"widths": [0.7, 0.8, 1.7, 2.5, 1.7, 1.5, 2.5], "center_cols": [0, 1],
"rows": [
["0", "X", "셀 위치 X", "ACS SPiiPlus / EtherCAT", "(제안)", "(제안)", "(제안)"],
["1", "Y", "셀 위치 Y", "ACS SPiiPlus / EtherCAT", "(제안)", "(제안)", "(제안)"],
["2", "Z", "포커스/높이", "Ajin Motion Board", "(제안)", "(제안)", "(제안)"],
["—", "Scanner θ/XY", "빔 편향(갈바노)", "RTC LaserScanner", "List 기반 Jump/Mark", "—", "보정테이블"]
]
},
{
"type": "table",
"no": "04.", "title": "I/O 맵 — DI / DO",
"subtitle": "PAIX_PTOP.ePrismaticDIAddress / ePrismaticDOAddress (Ajin 필드버스)",
"headers": ["구분", "CH(No)", "신호명", "Active", "설명"],
"widths": [0.8, 1.0, 3.2, 1.0, 5.4], "center_cols": [0, 1, 3],
"rows": [
["DI", "9", "CoreLaser_Laser_Ready", "High", "Core 레이저 Ready"],
["DI", "10", "CoreLaser_Error_State", "High", "Core 레이저 에러"],
["DI", "11", "CoreLaser_Laser_Output_State", "High", "Core 발진 출력 상태"],
["DI", "12", "CoreLaser_Control_Status", "High", "Core 제어 상태"],
["DI", "13", "RingLaser_Laser_Ready", "High", "Ring 레이저 Ready"],
["DI", "14", "RingLaser_Error_State", "High", "Ring 레이저 에러"],
["DI", "15", "RingLaser_Laser_Output_State", "High", "Ring 발진 출력 상태"],
["DI", "23", "RingLaser_Control_Status", "High", "Ring 제어 상태"],
["DO", "9", "CoreLaser_Control", "High", "Core 레이저 게이트(발진 ON/OFF)"],
["DO", "10", "CoreLaser_ErrorClearing", "High", "Core 에러 클리어"],
["DO", "13", "RingLaser_Control", "High", "Ring 레이저 게이트(발진 ON/OFF)"],
["DO", "14", "RingLaser_ErrorClearing", "High", "Ring 에러 클리어"],
["DO", "17", "EX_Red_Control", "High", "EX Red(가이드/표시등) 제어"]
]
},
# ── 05. TactTime 산정서 ─────────────────────────────────────────
{"type": "divider", "no": "05", "title": "TactTime 산정서", "subtitle": "Tact Time Analysis"},
{
"type": "table",
"no": "05.", "title": "PC TactTime — 자동 1사이클 (음극 1페이지 기준)",
"subtitle": "SeqAutoRunPTOP.cs 지연·로그 상수 기준 (제안 — 현장 실측 시 확정)",
"headers": ["Unit", "단위동작", "T/T(ms)", "PC", "PLC", "출처"],
"widths": [0.7, 3.0, 1.3, 0.6, 0.6, 5.2], "center_cols": [0, 2, 3, 4],
"rows": [
["1", "Page Change 요청 대기", "가변", "●", "●", "CheckPageNo (폴링 10ms, timeout 60s)"],
["2", "Recipe 확정/전환", "~0~수백","●", "—", "CheckRecipeNoComp (동일이면 ~0, 전환 시 Activate+reload)"],
["3", "Page 3회 검증", "~600", "●", "●", "CheckPageNoComp (200ms×3)"],
["4", "용접리스트 업로드", "가변", "●", "—", "SeqUploadWeldingList (라인수 의존)"],
["5", "발진 요청 대기", "가변", "●", "●", "WeldingStartReadBit"],
["6", "준비 응답", "~205", "●", "●", "WaitWeldingStart (200+5ms)"],
["7", "Gate ON delay", "15", "●", "—", "laserEmissionGateDelayMs (TruPulse 매뉴얼 ≥10ms)"],
["8", "실제 발진(ExecuteList)", "~285", "—", "—", "로그 예시 'TactTime: [285ms]' (라인수 의존)"],
["9", "Gate OFF + BUSY OFF", "~50", "●", "—", "ExecuteWelding 후미"],
["10", "Jump+CorrectionTable 복귀", "~500", "—", "—", "Thread.Sleep(500)"],
["11", "완료비트 송신", "~55", "●", "●", "WriteWeldComplete (50+5ms)"],
["(옵션)","Pre/Post Scout", "+100/회","●","Vision","SCOUT_PROCEED_DELAY_MS, M70509 ON 시"]
]
},
{
"type": "closing",
"title": "Thank You",
"message": "본 설계서는 소스코드 분석 기반 0.2버전입니다. 파라미터 확정 후 갱신 예정.",
"team": "로봇자동화사업부",
"contacts": ["노도연 대리 otteryeonny@ubisam.com"]
}
]
}
def main():
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
fd, spec_path = tempfile.mkstemp(suffix=".json", prefix="sw_spec_")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(SPEC, f, ensure_ascii=False, indent=2)
result = subprocess.run(
[sys.executable, BUILDER, "--input", spec_path, "--output", OUTPUT],
capture_output=True, text=True, encoding="utf-8"
)
if result.stdout:
print(result.stdout.strip())
if result.returncode != 0:
print("[ERROR]", result.stderr.strip(), file=sys.stderr)
sys.exit(result.returncode)
finally:
if os.path.exists(spec_path):
os.remove(spec_path)
if __name__ == "__main__":
main()