"""Atividade 2 — Botão de wireframe. PONTO DE PARTIDA. Aula 02 — Visualização 2D: primitivas gráficas. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python wireframe2d_base.py O programa abre uma janela com um triângulo preenchido e uma barra de botões vazia acima do canvas. Sua tarefa é acrescentar um botão que alterne o triângulo entre **preenchido** e **apenas as arestas** (wireframe), sem alterar os vértices desenhados. Antes de começar, responda para si: * Quem guarda o estado da cena: o canvas ou a janela? * O que exatamente acontece se o slot do botão chamar uma função gl*? """ from __future__ import annotations import sys from PySide6.QtCore import Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtWidgets import ( QApplication, QHBoxLayout, QMainWindow, QPushButton, QVBoxLayout, QWidget, ) from PySide6.QtOpenGLWidgets import QOpenGLWidget from OpenGL.GL import * from OpenGL.GLU import gluOrtho2D class Canvas(QOpenGLWidget): """Canvas OpenGL que desenha um triângulo.""" def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) # TODO 2 — estado da cena. # Acrescente aqui o atributo que registra em qual dos dois modos # a cena deve ser desenhada. Que tipo de dado basta para isso? # TODO 3 — método que altera o estado. # Escreva um método que inverta o modo atual e peça o redesenho. # Cuidado: qual é a ÚNICA chamada permitida aqui para provocar o # redesenho? Chamar paintGL() diretamente não é. def initializeGL(self) -> None: glClearColor(0.10, 0.11, 0.13, 1.0) def resizeGL(self, w: int, h: int) -> None: w, h = max(1, w), max(1, h) glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() if w <= h: gluOrtho2D(-1.0, 1.0, -1.0 * h / w, 1.0 * h / w) else: gluOrtho2D(-1.0 * w / h, 1.0 * w / h, -1.0, 1.0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT) # TODO 4 — escolha do modo de rasterização. # Antes de desenhar, informe ao OpenGL se os polígonos devem sair # preenchidos ou apenas com suas arestas. Procure por # glPolygonMode(face, modo) na referência do OpenGL 2.1: # - que valores o parâmetro `face` aceita, e qual serve aqui? # - que constante corresponde a cada um dos dois modos? # Repare que esta decisão é tomada a CADA quadro. Por quê? glColor3f(0.95, 0.20, 0.20) glBegin(GL_TRIANGLES) glVertex2f(-0.6, -0.5) glVertex2f( 0.6, -0.5) glVertex2f( 0.0, 0.6) glEnd() def keyPressEvent(self, event) -> None: if event.key() == Qt.Key.Key_Escape: self.window().close() class JanelaPrincipal(QMainWindow): """Janela com a barra de botões e o canvas OpenGL.""" def __init__(self) -> None: super().__init__() self.setWindowTitle("Atividade 2 — Preenchido x Wireframe") self.canvas = Canvas(self) # TODO 1 — o botão. # Crie o botão, dê a ele um rótulo que descreva o que vai # acontecer ao ser clicado, e conecte seu sinal ao método do # canvas que troca o modo. # Compare com gui2d.py: qual sinal um QPushButton emite? linha_botoes = QHBoxLayout() linha_botoes.addStretch() coluna = QVBoxLayout() coluna.addLayout(linha_botoes) coluna.addWidget(self.canvas, stretch=1) central = QWidget() central.setLayout(coluna) self.setCentralWidget(central) # TODO 5 — retorno visual (opcional, mas recomendado). # Use a barra de status para informar o modo atual. Onde essa # mensagem deve ser atualizada para nunca ficar defasada? self.statusBar().showMessage("Pronto") def main() -> int: fmt = QSurfaceFormat() fmt.setVersion(2, 1) fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(0) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) janela = JanelaPrincipal() janela.resize(640, 520) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())