2026-08-06 11:29:00 +09:00

356 lines
22 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 자동 생성 스크립트.
실행:
PYTHONUTF8=1 py .claude/skills/ubisam-deliverables/references/build_02설계_pptx.py
출력: docs/[Ubisam산출물]/02.설계/설계리뷰_DR/SW_설계서.pptx
"""
import os, io, 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")
# 프로젝트 메타의 유일 진실원은 _meta/project.yaml 이다 (ubisam-format-map §5).
# 이 스크립트에 조직·담당자를 하드코딩하면 다른 프로젝트로 재사용할 때 타사 값이 그대로 새어 나간다.
_PROJECT_YAML = os.path.join(REPO, "docs", "[Ubisam산출물]", "_meta", "project.yaml")
_META_CACHE = None
def _meta(path, default="미정 — 확인 필요"):
"""project.yaml에서 점 경로(예: "people.pl")를 읽는다.
파일·키가 없거나 값이 미치환 토큰("{PL}")이면 default를 그대로 렌더링해
'미정'임이 산출물에 드러나게 한다 — 조용히 남의 값이 찍히는 것보다 낫다.
"""
global _META_CACHE
if _META_CACHE is None:
try:
import yaml
with io.open(_PROJECT_YAML, encoding="utf-8") as f:
_META_CACHE = yaml.safe_load(f) or {}
except Exception:
_META_CACHE = {} # 파일 부재·파싱 실패는 정상 — default가 쓰인다
cur = _META_CACHE
for k in path.split("."):
if not isinstance(cur, dict) or k not in cur:
return default
cur = cur[k]
v = str(cur).strip() if cur is not None else ""
return default if (not v or v.startswith("{")) else v
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 · Digital IO(보드타입 런타임 설정 — 미확정)"
],
# 조직·담당자는 docs/[Ubisam산출물]/_meta/project.yaml 이 유일 진실원이다.
# 파일이 없으면 아래 자리표시자를 그대로 렌더링해 "미정"임이 드러나게 한다.
"team": _meta("org.team"),
"contacts": [_meta("people.pl")]
},
{
"type": "history",
"no": "00.",
"title": "변경 이력",
"subtitle": "Revision History",
"rows": [
["01", "0.1", _meta("schedule.contract_date", "미정"), _meta("people.registrant"),
"소스 기반 최초 작성 (기능명세·시퀀스·신호정의·IO맵·TactTime)"],
["02", "0.2", _meta("schedule.contract_date", "미정"), _meta("people.registrant"),
"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", "(제안) IOUnitConfig.BoardType 확인 필요", "필드버스", "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 축", "(제안) AuroraMotionBoard", "보드", "1축 (제안)"],
["Vision", "카메라/측정", "MIL 기반 AuroraVision", "—", "(현재 미사용)"],
["상/하위(MES)", "호스트 연동", "Stream(LBAD/RPC)", "TCP", "옵션"]
]
},
{
"type": "table",
"no": "04.", "title": "모션축 정의",
"subtitle": "ACS SPiiPlus(EtherCAT) · AuroraMotionBoard(보드타입 미확정) — 파라미터 미확정(제안)",
"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", "포커스/높이", "(제안) AuroraMotionBoard", "(제안)", "(제안)", "(제안)"],
["—", "Scanner θ/XY", "빔 편향(갈바노)", "RTC LaserScanner", "List 기반 Jump/Mark", "—", "보정테이블"]
]
},
{
"type": "table",
"no": "04.", "title": "I/O 맵 — DI / DO",
"subtitle": "PAIX_PTOP.ePrismaticDIAddress / ePrismaticDOAddress (보드타입 런타임 설정)",
"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)", "미측정", "—", "—", "런타임 값 — 실측 필요(로그 TactTime, 라인수 의존)"],
["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": _meta("org.team"),
"contacts": [_meta("people.pl")]
}
]
}
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()