CommunityImagemgithub.com

michaelweber/page-to-poster

Transforme uma descrição escrita de um local de aventura (PDF ou texto) em um modelo 3D no Blender e um pôster de mapa isométrico recortado pronto para impressão, em estilo serigráfico, com versões para Mestre e jogadores.

O que é page-to-poster?

Esta skill permite que um agente de IA leia a descrição textual de um local de aventura (como uma masmorra, bunker, nave, estação ou edifício de um RPG) e, a partir dela, gere automaticamente um pôster de mapa isométrico recortado, no estilo serigráfico. O processo começa com a extração de informações do texto fonte (PDF ou Markdown), que são organizadas em cinco arquivos de configuração: brief.md (resumo do local), layout.json (geometria das salas em metros), props.py (mobília e objetos de cada sala), poster.json (título, legendas, chamadas) e guidelines.md (diretrizes do usuário). A skill utiliza o Blender em modo headless para construir o modelo 3D, aplicar texturas, renderizar a cena isométrica com cortes nas paredes, e compor o pôster final com chamadas, caixas de texto e rótulos. O resultado são dois pôsteres: um para o Mestre (com informações ocultas) e um para os jogadores (sem segredos). A skill é ideal para mestres de RPG que desejam mapas prontos para impressão a partir de descrições de aventuras, suportando PDFs e textos, e é compatível com Claude Code, Cursor e Codex.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/michaelweber/page-to-poster/tree/HEAD

Installed? Explore more Imagem skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Página para pôster

Você lê a aventura e escreve cinco pequenos arquivos que descrevem o local. O código nesta pasta os verifica, constrói um modelo 3D no Blender headless, renderiza e compõe o pôster.

adventure (PDF/texto) + diretrizes
   -> brief.md        o que o texto diz sobre o local          (você escreve)
   -> layout.json     salas, passagens, entradas, em metros       (você escreve; `check` valida e desenha)
   -> props.py        o que está em cada sala                    (você escreve; `build` modela)
   -> poster.json     título, rótulos, chamadas, caixa de texto  (você escreve; `poster` renderiza e compõe)
   -> out/poster_gm.png, poster_player.png, poster_bare.png  (+ .jpg)

p2p.py está nesta pasta. Chame-o pelo caminho completo a partir do diretório de trabalho do usuário, para que as pastas do projeto sejam criadas lá e não aqui; os comandos abaixo escrevem python p2p.py por simplicidade. Use python3 onde python não for Python 3.

Regras que valem durante todo o processo

  1. Olhe para cada imagem que você fizer. Cada etapa escreve uma imagem; abra-a com sua ferramenta de leitura de imagens antes de prosseguir. As etapas nomeiam o arquivo. A maioria dos erros é óbvia na imagem e invisível nos números.
  2. Metros, +x leste, +y norte, piso em z = 0. Ângulos em props.py são em radianos.
  3. Diga o que você estimou. Aventuras raramente dão tamanhos ou posições exatas. Estime, escreva "est." em brief.md e liste as estimativas ao relatar. Nunca apresente um fato inventado como sendo do livro.
  4. Blender roda em headless através de p2p.py. Não abra sua janela e não use um servidor MCP do Blender.
  5. Rascunhos são baratos. Faça pôsteres de rascunho com --dpi 100; renderize o final com --dpi 200 quando o rascunho estiver correto.
  6. A pasta do projeto contém o texto e os mapas da aventura. Mantenha-a fora de repositórios públicos, a menos que a aventura seja sua para publicar.

Etapa 0: verificar a máquina

python p2p.py doctor

Deve terminar com Core pipeline: ready. Se faltarem pacotes Python: pip install -r requirements.txt. Se o Blender estiver faltando, siga docs/setup.md. Não é necessária GPU (a renderização cai para a CPU) e ComfyUI é opcional: só passe --comfy URL se o usuário tiver lhe fornecido um (docs/comfyui.md).

Etapa 1: iniciar o projeto e ler a fonte

python p2p.py new PROJETO --source aventura.pdf --pages 53-56

PROJETO é uma nova pasta para este mapa, por exemplo maps/o-nome-do-local no diretório de trabalho do usuário. --pages são os números de página do próprio PDF (a primeira página é 1) para o local a ser mapeado; omita para um PDF curto. Uma fonte de texto ou Markdown é copiada como está. O comando escreve modelos para os cinco arquivos e, para um PDF, source/page_NNN.txt e source/page_NNN.png.

Leia todo o texto. Em seguida, olhe cada imagem de página: mapas, diagramas e barras laterais estão apenas nas imagens. Se o livro tiver um mapa, esse mapa decide onde as salas ficam; o texto decide o que há nelas.

Preencha guidelines.md com o que o usuário pediu; qualquer coisa que ele não mencionou mantém seu padrão. Pergunte ao usuário apenas se você não conseguir identificar qual local mapear.

Preencha brief.md: uma linha por sala, uma por conexão, uma por entrada. Cite números de página. Esta é a etapa em que você decide o que é dado e o que é estimado.

Tamanhos quando o texto não fornece nenhum: uma porta tem 1 m, um corredor 2,5 a 3,5 m, uma cela ou escritório 4 x 5 m, uma sala comum 6 x 8 m, um salão 15 x 25 m, uma caverna ou hangar 30 x 50 m. Tetos: 3 m comum, 5 a 8 m salões. Um quadrado de mapa geralmente tem 5 pés (1,5 m) em jogos de fantasia; um "passo" tem cerca de um metro.

Etapa 2: layout.json

Substitua o modelo pelas salas, passagens e entradas do local. docs/layout.md tem todas as chaves. Dois projetos completos para aprender: examples/brine-chapel/ (uma masmorra de pedra, a partir de texto sem mapa) e examples/kestrel-9/ (uma estação pré-fabricada, a partir de um PDF com um mapa esboçado). Os essenciais:

  • Uma sala é um retângulo alinhado aos eixos de piso claro: "min" é seu canto sudoeste, "max" seu nordeste. Deixe pelo menos 0,6 m (uma parede) entre quaisquer duas salas.
  • Um link une duas salas ao longo de um "path" de pontos. O primeiro ponto fica na borda da sala a, o último na borda da sala b; o caminho sai de cada sala em ângulo reto com a parede. "kind" é open, blocked (um desabamento obstrui) ou vent (um duto ou passagem estreita, de sala a sala; "z" define sua altura). "doors": ["a", "b"] coloca portas nessas extremidades. "secret": true marca um link que os jogadores não devem ver: suas portas se tornam portas secretas e o pôster do jogador omite sua marca de rota.
  • Onde três ou mais passagens se encontram, nomeie o ponto sob "junctions" e termine os caminhos lá.
  • Uma entrada é um caminho de fora para dentro do mapa: stairs, ramp, tunnel ou door em uma parede ("side": "N|S|E|W", "at": posição ao longo dessa parede), ou um shaft em um ponto dentro de uma sala. Uma passagem que sai do mapa e está desabada é um tunnel com "blocked": true.
  • Uma sala que não é um retângulo: dê a ela seu retângulo delimitador, depois modele-a em props.py (cave_fill para cavernas e desabamentos, partition para paredes internas), ou divida-a em duas salas unidas por um link curto e largo.
  • Um pôster mostra um nível. Para um local com vários níveis, faça um projeto por nível e una-os com entradas rotuladas como "Escadas para o nível 2".

Disponha o local de modo que seja compacto e aproximadamente tão profundo quanto largo; isso preenche melhor uma folha 3:2 na vista isométrica. Coloque a primeira sala perto de (0, 0) e trabalhe para fora em uma grade de 1 m.

python p2p.py check PROJETO

Corrija cada erro (cada mensagem diz o que mudar). Em seguida, abra PROJETO/build/layout_check.png e compare-o com o mapa do livro ou com seu brief: todas as salas presentes, vizinhos nos lados corretos, todas as conexões desenhadas, portas e entradas nas paredes corretas. Repita até que esteja correto. Não prossiga com um layout que você não tenha olhado.

Etapa 3: texturas

python p2p.py textures PROJETO

Isso desenha as texturas de superfície e decalques por código, em alguns segundos. Com um ComfyUI que passou em doctor --comfy URL, adicione --comfy URL para tê-las pintadas em vez disso (mais lento, mais rico). Execute isso antes da primeira construção; se você refizer texturas depois, construa novamente.

Abra dois ou três de PROJETO/textures/_preview/<texture>_tiled.png (concrete_floor, stone_wall, rust_metal, planks, tile, ...) e escolha materiais de piso e parede que combinem com o local; defina-os como "floor" e "wall" em layout.json, para o local ou por sala. A tabela de materiais em docs/kit-reference.md lista cada material e a textura que ele usa.

Etapa 4: props.py, depois build

props.py contém uma função por sala, chamada room_<id>, que coloca o que o brief lista para aquela sala. docs/props.md explica o kit e mostra cada adereço pronto; docs/kit-reference.md lista cada chamada; examples/brine-chapel/props.py é um módulo completo. Uma sala mínima:

def room_3(r, rng, P):
    mb = MB("r3_props")                                  # um construtor de malha: adicione sólidos, depois faça um objeto
    pillar(mb, r.minx + 2, r.cy, h=r.h)                  # adereços prontos usam x, y em metros ...
    altar(mb, T(r.cx, r.maxy - 2, 0, rz=0))              # ... ou uma transformação T(x, y, z, rz=ângulo)
    box(mb, "wood", (r.cx, r.cy, 0.4), (2.0, 1.0, 0.8))  # qualquer outra coisa: caixas, cilindros, varreduras
    mb.obj(P)                                            # P é a coleção desta sala
    feat("3.altar", r.cx, r.maxy - 2, 1.1)               # um ponto nomeado ao qual uma chamada do pôster pode se prender

O que faz uma sala ser legível no pôster:

  • Modele o que o texto nomeia primeiro e dê a cada um um feat(). Depois, adicione de 3 a 10 coisas comuns que digam para que serve a sala. Uma sala vazia parece um erro.
  • As formas são lidas pela silhueta e pelo tom. Materiais brilhantes (tile, bone, ice, mushroom, glass) imprimem em branco, vermelhos (hazard, liquid_acid, emissive_red) imprimem na tinta de destaque, escuros imprimem em preto. Coisas menores que cerca de 0,3 m desaparecem.
  • Mantenha 1,5 m livre na frente de cada porta e mantenha os adereços dentro do retângulo da sala.
  • Use rng para cada escolha aleatória para que cada construção seja idêntica.
  • Fossos e piscinas precisam do quarto parâmetro: def room_4(r, rng, P, cut).
python p2p.py build PROJETO
python p2p.py render PROJETO closeup --rooms 3,4

build leva segundos e lista os nomes das features que você registrou. Abra PROJETO/build/iso_preview.png (o modelo inteiro, sem textura) após cada construção, e PROJETO/renders/closeup_<id>.png para cada sala em que você trabalhou. Verifique: nada flutuando, nada atravessando uma parede, portas desobstruídas, a sala reconhecível a partir do texto. Um erro Python em props.py é impresso com seu número de linha; o log completo é build/build.log.

Closeups usam a vista e o recorte de poster.json, o mesmo que o pôster usará. Paredes são cortadas abertas em "cutaway": {"wall": 3.2} metros, e cada uma das duas paredes mais próximas do visualizador esconde uma faixa de piso com cerca da mesma largura dessa altura. Isso é adequado para salões. Se as salas são pequenas (menos de cerca de 8 m), reduza agora, para 1,5 ou 2, ou tudo ao longo das paredes próximas desaparecerá.

Etapa 5: poster.json, depois o pôster

Edite o modelo: título, subtítulo, rodapé, uma chamada por feature notável e as caixas de texto. docs/poster.md tem todas as chaves.

  • Uma chamada é {"feature": "3.altar", "text": "Altar de sal"}; o nome da feature é um que você registrou com feat(). Adicione "gm_only": true para mantê-la fora da versão do jogador. Mantenha o texto com cerca de oito palavras ou menos.
  • box contém as notas do Mestre ("gm": uma tabela de encontros, cronômetros, a verdade em uma linha) e o que os jogadores sabem ("player"). Seis a oito linhas curtas cada.
  • A versão do jogador remove chamadas gm_only e links marcados como "secret". A arte é a mesma para ambos, então uma sala secreta ainda é desenhada; esconda seu nome com "room_labels": {"5": {"gm": "Ossuário", "player": ""}} e informe ao usuário que a sala em si aparece.
  • Escolha uma paleta que combine com o local (papel, tinta, branco, destaque) ou mantenha o padrão amarelo-esverdeado ácido.
  • size_in é a folha em polegadas, [36, 24] a menos que o usuário tenha pedido outro tamanho. Os rótulos encolhem com a folha; em uma pequena, aumente "label_scale" até que as chamadas fiquem confortáveis de ler.
python p2p.py poster PROJETO --dpi 100

Abra PROJETO/out/poster_gm.png e poster_player.png. Verifique e corrija em poster.json:

ProblemaCorreção
Um rótulo de sala cobre o que importa na sala"room_label_offsets": {"3": [dx, dy]}, em metros leste e norte. Na vista padrão, leste é direita-para-cima na folha e norte é esquerda-para-cima: [d, -d] move um rótulo para a direita, [d, d] para cima
Uma etiqueta de chamada fica em cima de algo"side": "nw" nessa chamada coloca sua etiqueta nesse lado do ponto (n é para cima na folha); ou encurte, mescle ou remova chamadas em uma sala lotada
Coisas ao longo das paredes próximas estão ocultasabaixe "cutaway": {"wall": 1.6}, depois renderize novamente
Rótulos muito pequenos ou muito grandes para o local"label_scale": 1.3
Título, caixa ou legenda fica em cima da arte"corners": {"title": "tl", "box": "br", "legend": "bl"}, ou encolha a arte com "art_box"
O lado interessante do local está virado para longe"view": {"azimuth": 135} (tente -45, 45, 135, -135), depois renderize novamente
Adereços altos escondem o que está atrás deles"cutaway": {"props": {"3": 4.0}} corta os adereços da sala 3 a 4 m
Impressão inteira muito escura ou muito clara"exposure": 1.15 (mais claro) ou 0.9 (mais escuro)
Uma chamada aponta para nadaas coordenadas de feat() em props.py estão erradas; corrija, construa, renderize

Após alterar apenas texto ou rótulos, adicione --reuse-render para pular a renderização. Após alterar layout.json, props.py, a vista ou o recorte, construa e renderize novamente. Quando o rascunho estiver correto:

python p2p.py poster PROJETO --dpi 200

Isso escreve out/poster_gm, poster_player e poster_bare como PNG e JPEG no tamanho da folha (36 x 24 polegadas são 7200 x 4800 pixels). Abra o pôster final do Mestre mais uma vez antes de relatar.

Extras opcionais

  • Mapas de batalha. python p2p.py render PROJETO plan dá uma planta baixa do local (renders/plan.png); render PROJETO rooms --rooms 3 dá uma sala a 70 pixels por metro (renders/room_3.png).
  • Pinturas de sala (precisa de ComfyUI com a LoRA de controle de profundidade; veja docs/comfyui.md). Escreva paint.json com uma frase por sala, então: python p2p.py render PROJETO eye e python p2p.py paint PROJETO --comfy URL. As pinturas em out/paintings/ mantêm o layout de cada sala porque o mapa de profundidade do modelo as orienta. --strength (padrão 1,2, faixa útil 1,0 a 1,4) define o quão firmemente.

Relatar

Forneça ao usuário: os caminhos dos pôsteres; o que você estimou ou inventou (tamanhos, posições, qualquer coisa que o texto deixou em aberto); qualquer coisa nas diretrizes que você não conseguiu fazer; e como mudar as coisas (editar os arquivos, executar o comando novamente).

Quando algo dá errado

Mensagem ou sintomaCausa e correção
Blender: MISSINGInstale-o ou defina a variável de ambiente BLENDER para o executável: docs/setup.md
layout.json has errors na construçãoExecute check; cada linha diz o que mudar
callout ... names feature ..., which props.py never registeredA grafia difere do nome de feat(); build imprime os nomes válidos
Superfícies são cinza planotextures não foi executado antes de build; execute-o, depois construa novamente
Failed to retain CUDA context, ou a GPU fica sem memóriaOutro programa está usando a GPU: adicione --device cpu, ou --gpu NOME para escolher uma placa
no GPU device matching ...O nome dado a --gpu não corresponde a nenhuma placa; doctor as lista
A renderização está lentaEstá na CPU. Rascunhe com --dpi 100; o final ainda termina, apenas mais tarde
Um adereço é cortado plano no topoÉ mais alto que o recorte; isso é intencional. R

Habilidades Relacionadas