Page to poster (페이지 투 포스터)
모험을 읽고 장소를 설명하는 다섯 개의 작은 파일을 작성합니다. 이 폴더의 코드는 이를 검증하고, 헤드리스 Blender에서 3D 모델을 빌드하며, 렌더링하고 포스터를 구성합니다.
adventure (PDF/텍스트) + 가이드라인
-> brief.md 텍스트가 장소에 대해 말하는 내용 (사용자 작성)
-> layout.json 방, 통로, 입구 (미터 단위) (사용자 작성; `check`가 검증 및 그림)
-> props.py 각 방에 있는 것 (사용자 작성; `build`가 모델링)
-> poster.json 제목, 레이블, 콜아웃, 텍스트 박스 (사용자 작성; `poster`가 렌더링 및 구성)
-> out/poster_gm.png, poster_player.png, poster_bare.png (+ .jpg)
p2p.py는 이 폴더에 있습니다. 사용자의 작업 디렉토리에서 전체 경로로 호출하여 프로젝트 폴더가 여기가 아니라 그곳에 생성되도록 하세요. 아래 명령어는 편의상 python p2p.py로 표기합니다. python이 Python 3가 아닌 경우 python3를 사용하세요.
모든 단계에 적용되는 규칙
- 모든 이미지를 직접 확인하세요. 각 단계마다 이미지가 생성됩니다. 이미지 읽기 도구로 열어보고 계속 진행하세요. 단계마다 파일 이름이 명시되어 있습니다. 대부분의 실수는 숫자보다 그림에서 더 명확하게 드러납니다.
- 단위는 미터(m), +x는 동쪽, +y는 북쪽, 바닥은 z = 0입니다.
props.py에서 각도는 라디안(radian)을 사용합니다. - 추정한 내용을 반드시 명시하세요. 모험에서는 크기나 정확한 위치를 거의 제공하지 않습니다. 추정했다면
brief.md에 "est."라고 기재하고, 결과 보고 시 추정치 목록을 함께 제시하세요. 절대 책에 없는 사실을 마치 원문에 있는 것처럼 제시하지 마십시오. - Blender는
p2p.py를 통해 헤드리스로 실행됩니다. Blender 창을 열거나 Blender MCP 서버를 사용하지 마세요. - 드래프트는 저렴하게 만드세요. 드래프트 포스터는
--dpi 100으로 생성하고, 드래프트가 올바르게 되면 최종 버전은--dpi 200으로 렌더링하세요. - 프로젝트 폴더에는 모험의 텍스트와 지도가 포함됩니다. 모험을 공개할 권한이 없는 한 공개 저장소에 보관하지 마세요.
0단계: 시스템 확인
python p2p.py doctor
Core pipeline: ready로 끝나야 합니다. Python 패키지가 없으면: pip install -r requirements.txt. Blender가 없으면 docs/setup.md를 따르세요. GPU는 필요하지 않으며(렌더링은 CPU로 폴백), ComfyUI는 선택 사항입니다. 사용자가 ComfyUI URL을 제공한 경우에만 --comfy URL을 전달하세요 (docs/comfyui.md 참조).
1단계: 프로젝트 시작 및 소스 읽기
python p2p.py new PROJECT --source adventure.pdf --pages 53-56
PROJECT는 이 지도를 위한 새 폴더입니다(예: 사용자 작업 디렉토리의 maps/the-site-name). --pages는 지도로 만들 위치에 해당하는 PDF 자체 페이지 번호(첫 페이지는 1)입니다. 짧은 PDF의 경우 생략 가능합니다. 텍스트 또는 Markdown 소스는 그대로 복사됩니다. 이 명령어는 다섯 개 파일의 템플릿을 작성하고, PDF의 경우 source/page_NNN.txt와 source/page_NNN.png도 생성합니다.
모든 텍스트를 읽으세요. 그런 다음 모든 페이지 이미지를 확인하세요: 지도, 다이어그램 및 사이드바는 그림에만 있습니다. 책에 지도가 있으면 그 지도가 방의 위치를 결정하고, 텍스트는 방 안에 무엇이 있는지 결정합니다.
사용자가 요청한 내용을 바탕으로 guidelines.md를 작성하세요. 사용자가 언급하지 않은 것은 기본값을 유지합니다. 어떤 위치를 지도로 만들지 판단할 수 없는 경우에만 사용자에게 질문하세요.
brief.md를 작성하세요: 방마다 한 행, 연결마다 한 행, 입구마다 한 행. 페이지 번호를 인용하세요. 이 단계에서 주어진 것과 추정할 것을 결정합니다.
텍스트에 크기가 없는 경우 기본 크기: 문은 1m, 복도는 2.53.5m, 감방이나 사무실은 4x5m, 일반 방은 6x8m, 홀은 15x25m, 동굴이나 격납고는 30x50m. 천장 높이: 일반 방 3m, 홀은 58m. 판타지 게임에서 지도 한 칸은 보통 5피트(1.5m)입니다. "걸음"은 약 1미터입니다.
2단계: layout.json
템플릿을 장소의 방, 통로 및 입구로 교체하세요. docs/layout.md에 모든 키가 설명되어 있습니다. 참고할 수 있는 완전한 프로젝트 두 개: examples/brine-chapel/(지도 없는 텍스트에서 만든 석조 던전)과 examples/kestrel-9/(스케치 맵이 있는 PDF에서 만든 조립식 기지). 핵심 사항:
- **방(room)**은 축 정렬된 직사각형의 바닥 공간입니다.
"min"은 남서쪽 모서리,"max"는 북동쪽 모서리입니다. 어떤 두 방 사이에도 최소 0.6m(벽 하나)의 간격을 두세요. - **연결(link)**은 점들의
"path"를 따라 두 방을 연결합니다. 첫 번째 점은 방a의 가장자리에, 마지막 점은 방b의 가장자리에 있습니다. 경로는 각 방을 벽에 직각으로 나갑니다."kind"는open,blocked(붕괴로 막힘), 또는vent(덕트나 기어다니는 통로, 방과 방 사이;"z"는 높이 설정) 중 하나입니다."doors": ["a", "b"]는 해당 끝에 문을 배치합니다."secret": true는 플레이어가 보지 말아야 할 연결을 표시합니다. 해당 문은 비밀 문이 되고, 플레이어 포스터에서는 경로 표시가 생략됩니다. - 세 개 이상의 통로가 만나는 지점은
"junctions"아래에 이름을 지정하고 경로를 그곳에서 끝내세요. - **입구(entrance)**는 지도 외부에서 들어오는 방법입니다: 벽에 있는
stairs,ramp,tunnel, 또는door("side": "N|S|E|W","at": 해당 벽을 따른 위치), 또는 방 안의 한 지점에 있는shaft. 지도를 벗어나서 붕괴된 통로는"blocked": true가 있는tunnel입니다. - 직사각형이 아닌 방: 경계 직사각형을 지정한 다음
props.py에서 모양을 정의하거나(cave_fill은 동굴이나 붕괴,partition은 내부 벽), 짧고 넓은 연결로 연결된 두 개의 방으로 분할하세요. - 하나의 포스터는 하나의 레벨(층)을 보여줍니다. 여러 레벨이 있는 장소의 경우 레벨당 하나의 프로젝트를 만들고 "Stairs to level 2"라고 표시된 입구로 연결하세요.
장소가 컴팩트하고 너비와 깊이가 비슷하게 배치되도록 하세요. 그래야 등척도 뷰에서 3:2 용지에 가장 잘 채워집니다. 첫 번째 방을 (0, 0) 근처에 두고 1m 그리드로 바깥쪽으로 작업하세요.
python p2p.py check PROJECT
모든 오류를 수정하세요(각 메시지는 무엇을 변경해야 하는지 알려줍니다). 그런 다음 PROJECT/build/layout_check.png를 열어 책의 지도 또는 작성한 brief와 비교하세요: 모든 방이 존재하고, 이웃이 올바른 쪽에 있으며, 모든 연결이 그려져 있고, 문과 입구가 올바른 벽에 있는지 확인하세요. 올바를 때까지 반복하세요. 확인하지 않은 레이아웃으로 진행하지 마십시오.
3단계: 텍스처
python p2p.py textures PROJECT
이 명령어는 코드로 표면 텍스처와 데칼을 몇 초 만에 그립니다. doctor --comfy URL을 통과한 ComfyUI가 있는 경우 --comfy URL을 추가하여 대신 페인팅할 수 있습니다(느리지만 더 풍부함). 첫 번째 빌드 전에 실행하세요. 나중에 텍스처를 다시 만들면 다시 빌드하세요.
PROJECT/textures/_preview/<texture>_tiled.png 중 두세 개를 열고(concrete_floor, stone_wall, rust_metal, planks, tile, ...) 장소에 맞는 바닥 및 벽 재질을 선택하세요. layout.json에서 장소 전체 또는 방별로 "floor" 및 "wall"로 설정하세요. docs/kit-reference.md의 재질 표는 모든 재질과 사용하는 텍스처를 나열합니다.
4단계: props.py, 그리고 빌드
props.py에는 방 ID별로 명명된 하나의 함수(room_<id>)가 있으며, brief에 해당 방에 대해 나열된 내용을 배치합니다. docs/props.md는 키트를 설명하고 모든 기성 소품을 보여줍니다. docs/kit-reference.md는 모든 호출을 나열합니다. examples/brine-chapel/props.py는 완전한 모듈입니다. 최소한의 방:
def room_3(r, rng, P):
mb = MB("r3_props") # 메시 빌더: 솔리드를 추가한 다음 하나의 오브젝트 생성
pillar(mb, r.minx + 2, r.cy, h=r.h) # 기성 소품은 x, y를 미터 단위로 받습니다...
altar(mb, T(r.cx, r.maxy - 2, 0, rz=0)) # ... 또는 변환 T(x, y, z, rz=각도)
box(mb, "wood", (r.cx, r.cy, 0.4), (2.0, 1.0, 0.8)) # 그 외: 상자, 원통, 스윕
mb.obj(P) # P는 이 방의 컬렉션
feat("3.altar", r.cx, r.maxy - 2, 1.1) # 포스터 콜아웃이 연결될 수 있는 명명된 지점
포스터에서 방이 읽히게 만드는 요소:
- 텍스트에서 먼저 언급된 것을 모델링하고 각각에
feat()을 부여하세요. 그런 다음 방의 용도를 알려주는 3~10개의 평범한 물건을 추가하세요. 빈 방은 실수처럼 보입니다. - 형태는 실루엣과 톤으로 읽힙니다. 밝은 재질(
tile,bone,ice,mushroom,glass)은 흰색으로 인쇄되고, 빨간색 재질(hazard,liquid_acid,emissive_red)은 강조 잉크로, 어두운 재질은 검정색으로 인쇄됩니다. 약 0.3m보다 작은 것은 사라집니다. - 각 출입구 앞에 1.5m를 비워두고 소품을 방 직사각형 안에 유지하세요.
- 모든 무작위 선택에
rng를 사용하여 각 빌드가 동일하도록 하세요. - 구덩이와 웅덩이는 네 번째 매개변수가 필요합니다:
def room_4(r, rng, P, cut).
python p2p.py build PROJECT
python p2p.py render PROJECT closeup --rooms 3,4
build는 몇 초 걸리며 등록한 기능 이름을 나열합니다. 빌드 후 PROJECT/build/iso_preview.png(전체 모델, 텍스처 없음)를 열고, 작업한 각 방에 대해 PROJECT/renders/closeup_<id>.png를 열어 확인하세요: 떠 있는 것, 벽을 통과하는 것, 출입구가 막힌 것, 방이 텍스트에서 인식 가능한지 확인하세요. props.py의 Python 오류는 줄 번호와 함께 출력됩니다. 전체 로그는 build/build.log입니다.
클로즈업은 poster.json의 뷰와 컷어웨이를 사용하며, 포스터와 동일합니다. 벽은 "cutaway": {"wall": 3.2}미터에서 잘리고, 뷰어에 가장 가까운 두 개의 벽은 그 높이만큼의 바닥 띠를 숨깁니다. 이는 홀에 적합합니다. 방이 작은 경우(약 8m 미만) 지금 이를 1.5 또는 2로 낮추세요. 그렇지 않으면 가까운 벽을 따라 있는 모든 것이 사라집니다.
5단계: poster.json, 그리고 포스터
템플릿을 편집하세요: 제목, 부제목, 바닥글, 주목할 만한 기능당 하나의 콜아웃, 텍스트 상자. docs/poster.md에 모든 키가 있습니다.
- **콜아웃(callout)**은
{"feature": "3.altar", "text": "Salt altar"}형식입니다. 기능 이름은feat()으로 등록한 것입니다. 플레이어 버전에서 제외하려면"gm_only": true를 추가하세요. 텍스트는 약 8단어 미만으로 유지하세요. - **
box**는 GM의 메모("gm": 조우 테이블, 타이머, 한 줄 진실)와 플레이어에게 알려주는 내용("player")을 담습니다. 각각 6~8개의 짧은 줄. - 플레이어 버전은
gm_only콜아웃과"secret"으로 표시된 연결을 제거합니다. 아트워크는 둘 다 동일하므로 비밀 방은 여전히 그려집니다."room_labels": {"5": {"gm": "Ossuary", "player": ""}}로 이름을 숨기고 사용자에게 방 자체는 표시된다고 알려주세요. - 장소에 맞는 **팔레트(palette)**를 선택하거나(종이, 잉크, 흰색, 강조), 기본 산성 황록색을 유지하세요.
- **
size_in**은 시트 크기(인치)이며, 사용자가 다른 크기를 요청하지 않는 한[36, 24]입니다. 레이블은 시트 크기에 따라 작아집니다. 작은 시트에서는 콜아웃을 읽기 편하게 하기 위해"label_scale"을 높이세요.
python p2p.py poster PROJECT --dpi 100
PROJECT/out/poster_gm.png와 poster_player.png를 여세요. 확인하고 poster.json에서 수정하세요:
| 문제점 | 수정 방법 |
|---|---|
| 방 레이블이 방에서 중요한 것을 가림 | "room_label_offsets": {"3": [dx, dy]} (미터 단위 동쪽 및 북쪽). 기본 뷰에서 동쪽은 시트에서 오른쪽 위, 북쪽은 왼쪽 위입니다: [d, -d]는 레이블을 오른쪽으로, [d, d]는 위로 이동 |
| 콜아웃 태그가 무언가 위에 있음 | 해당 콜아웃에 "side": "nw"를 추가하면 태그가 점의 해당 면에 배치됨(n은 시트 위쪽). 또는 콜아웃을 줄이거나 병합하거나 혼잡한 방에서 제거 |
| 가까운 벽을 따라 있는 것들이 숨겨짐 | "cutaway": {"wall": 1.6}을 낮추고 다시 렌더링 |
| 레이블이 장소에 비해 너무 작거나 큼 | "label_scale": 1.3 |
| 제목, 상자 또는 범례가 아트워크 위에 있음 | "corners": {"title": "tl", "box": "br", "legend": "bl"} 또는 "art_box"로 아트 축소 |
| 장소의 흥미로운 면이 반대쪽을 향함 | "view": {"azimuth": 135} (시도: -45, 45, 135, -135) 후 다시 렌더링 |
| 키가 큰 소품이 뒤에 있는 것을 가림 | "cutaway": {"props": {"3": 4.0}}은 방 3의 소품을 4m에서 절단 |
| 전체 인쇄물이 너무 어둡거나 창백함 | "exposure": 1.15 (밝게) 또는 0.9 (어둡게) |
| 콜아웃이 아무것도 가리키지 않음 | props.py의 feat() 좌표가 잘못됨. 수정 후 빌드 및 렌더링 |
텍스트나 레이블만 변경한 후에는 --reuse-render를 추가하여 렌더링을 건너뛰세요. layout.json, props.py, 뷰 또는 컷어웨이를 변경한 후에는 다시 빌드하고 렌더링하세요. 드래프트가 올바르게 되면:
python p2p.py poster PROJECT --dpi 200
이 명령어는 out/poster_gm, poster_player 및 poster_bare를 시트 크기(36x24인치는 7200x4800픽셀)로 PNG 및 JPEG로 씁니다. 보고하기 전에 최종 GM 포스터를 한 번 더 확인하세요.
선택적 추가 기능
- 배틀 맵
python p2p.py render PROJECT plan은 장소의 탑다운 평면도를 제공합니다(renders/plan.png).render PROJECT rooms --rooms 3은 미터당 70픽셀로 하나의 방을 제공합니다(renders/room_3.png). - 방 그림(ComfyUI와 깊이 제어 LoRA 필요. docs/comfyui.md 참조). 방당 한 문장으로
paint.json을 작성한 후:python p2p.py render PROJECT eye및python p2p.py paint PROJECT --comfy URL.out/paintings/의 그림은 모델의 깊이 맵이 안내하기 때문에 각 방의 레이아웃을 유지합니다.--strength(기본값 1.2, 유효 범위 1.0~1.4)는 강도를 설정합니다.
보고
사용자에게 다음을 제공하세요: 포스터 경로; 추정하거나 발명한 내용(크기, 위치, 텍스트가 명시하지 않은 모든 것); 가이드라인에서 수행하지 못한 사항; 변경 방법(파일 편집 후 명령어 재실행).
문제 발생 시
| 메시지 또는 증상 | 원인 및 수정 방법 |
|---|---|
Blender: MISSING | Blender를 설치하거나 BLENDER 환경 변수를 실행 파일로 설정하세요: docs/setup.md |
빌드 시 layout.json has errors | check를 실행하세요. 각 줄이 무엇을 변경해야 하는지 알려줍니다. |
callout ... names feature ..., which props.py never registered | 철자가 feat() 이름과 다릅니다. build는 유효한 이름을 출력합니다. |
| 표면이 평평한 회색 | build 전에 textures를 실행하지 않았습니다. 실행한 후 다시 빌드하세요. |
Failed to retain CUDA context 또는 GPU 메모리 부족 | 다른 프로그램이 GPU를 사용 중입니다. --device cpu를 추가하거나 --gpu NAME으로 카드를 선택하세요. |
no GPU device matching ... | --gpu에 지정한 이름과 일치하는 카드가 없습니다. doctor가 목록을 보여줍니다. |
| 렌더링이 느림 | CPU에서 실행 중입니다. 드래프트는 --dpi 100으로, 최종은 나중에 완료됩니다. |
| 소품이 위쪽이 평평하게 잘림 | 컷어웨이보다 높기 때문에 의도된 것입니다. "cutaway": {"props": {"3": 높이}}를 올리거나 엽니다. |