Saltar la navegación

Modelo

Sobre el proyecto

Ahora empezaremos con la codificación del programa. Se recomienda instalar algún editor de textos o ambiente de desarrollo integrado, como Visual Studio Code o Spyder. Es posible descargarlos e instalarlos desde sus páginas oficiales respectivas. Claro que también es posible usar el bloc de notas para la programación. Para ejecutar los programas, solo se debará ejecutar el comando python nombre_del_archivo.py.

Para la elaboración del programa, se seguirá el patrón de diseño Modelo-Vista-Controlador. Éste nos permite separar las funcionalidades de la lógica del programa e interfaz de usuario. El Modelo se encarga de la primera, y lo encapsularemos dentro de un archivo de código python. En la próxima sección se realizará la Vista y el Controlador en su respectivo archivo.

El archivo para el Modelo será matriz.py, que contendrá una clase que representa una matriz en dos dimensiones, y proveerá operaciones que serán útiles para posteriormente realizar las transformaciones.

La clase Matriz

Necesitamos importar la librería math, que nos brinda las funciones trigonométricas.

import math

Posteriormente declaramos la clase Matriz

class Matriz:
    def __init__(self, filas, columnas, valor=0):
        self.filas = filas
        self.columnas = columnas
        self.matriz = [[]]
        for fila in range(filas):
            for _ in range(columnas):
                self.matriz[fila].append(valor)
            self.matriz.append([])

El método __init__ es el método constructor de la clase. Tiene como parámetros el número de filas y columnas de la matriz, junto con un valor númerico que se puede declarar para todos los valores de la matriz. El método se encarga de incializar los atributos del nuevo objeto con estos valores.

Un método que nos resultará útil es el método __str__.

def __str__(self):
        cadena = ''
        for fila in range(self.filas):
            cadena += str(self.matriz[fila]) + '\n'
        return cadena

Este método devuelve una cadena de caracteres multilínea que representa a la matriz. Este método se llama automáticamente en contextos donde se requiere una cadena, como en la función print().

El método más importante de esta clase es __mul__,

def __mul__(self, multiplicando):
        if (type(multiplicando) is int or type(multiplicando) is float):
            return self.multiplicacion_escalar(multiplicando)
        elif (self.columnas == multiplicando.filas):
            return self.multiplicacion_matricial(multiplicando)
        print("Las matrices no se pueden multiplicar")
        return None

__mul__ se llama cuando usamos el operador de multiplicación (*) en un operando de tipo Matriz, y devuelve otro objeto con el resultado correspondiente u algún otro tipo de dato. Existen dos formas de realizar multiplicaciones con matrices, escalar y matricial. La mutliplicación escalar mutliplica a una matriz por un solo número, mientras que la matricial, como su nombre lo dice, es entre dos matrices. Este método revisa el tipo del operando con el que se llama, si es un solo número entero o flotante, se envía a otro método que realiza la multiplicación escalar. De otra forma se asume que el mutliplicando es otro objeto matriz, y se envía al método correspondiente de mutliplicación matricial. Sin embargo, para poder llevar a cabo la multiplicación matricial, se debe verificar que el número de columnas del primer operando coincida con el número de filas del segundo. De no satisfacerse alguna de estas condiciones, se avisa al usuario con un mensaje y se devuelve None.

Los métodos de multiplicación respectivos son los siguientes.

def multiplicacion_escalar(self, escalar):
        producto = Matriz(self.filas, self.columnas)
        for i in range(producto.filas):
            for j in range(producto.columnas):
                producto.matriz[i][j] = self.matriz[i][j] * escalar
        return producto
   

def multiplicacion_matricial(self, otra):
        producto = Matriz(self.filas, otra.columnas)
        for i in range(producto.filas):
            for j in range(producto.columnas):
                for k in range(self.columnas):
                    producto.matriz[i][j] += self.matriz[i][k] * otra.matriz[k][j]
        return producto

En la multiplicación escalar, simplemente se mutliplica cada elemento de la matriz por el valor escalar. La multiplicación matricial se lleva a cabo como fue visto en la sección de Desarrollo. El algoritmo está simplificado para aprovechar las variables de los ciclos for.

Para propósitos de prueva, declararemos una función que nos permita asignar cada elemento de la matriz por entradas de usuario.

def llenar_individual(self):
        for fila in range(self.filas):
            for columna in range(self.columnas):
                entrada = input(f"Valor [{fila}][{columna}]: ")
                try:
                    self.matriz[fila][columna] = int(entrada)
                except:
                    self.matriz[fila][columna] = float(entrada)
        print()

Se recomienda que, hasta este punto, el lector realice pruebas con los métodos creados para asegurarse de que el código escrito sea correcto. Cabe recordar que es posible imprimir los elementos de cualquier objeto matriz con simplemente llamar print(objeto_matriz).

La clase Transformador

matriz.py también contendrá una clase llamada Transformador, que hará uso de objetos Matriz para llevar a cabo las transformaciones de traslación, rotación y escalamiento.

class Transformador:

El método trasladar tomará cuatro parámetros: px y py, la ubicación de un punto P en un plano bidimensional; y tx y ty, los valores de traslación en ambos ejes.

def trasladar(self, px, py, tx, ty):
        p = Matriz(1, 3)
        p.matriz = [[px, py, 1]]
        t = Matriz(3, 3)
        t.matriz = [
            [1, 0, 0],
            [0, 1, 0],
            [tx, ty, 1]
        ]
        p_prima = p * t
        return [p_prima.matriz[0][0], p_prima.matriz[0][1]]

Se declara a P como un objeto matriz de 1x3, y la matriz de traslación T como una matriz de 3x3 predefinida con los valores de tx y ty. Después, simplemente se multiplican ambas matrices, y se devuelve una lista de dos elementos que representan los valores x y y del vector resultante.

Los métodos rotar y traslación siguen el mismo patrón.

def rotar(self, px, py, angulo, cx=0, cy=0):
        p = Matriz(1, 3)
        p.matriz = [[px, py, 1]]
        c_negativa = Matriz(3, 3)
        c_negativa.matriz = [
            [1, 0, 0],
            [0, 1, 0],
            [-cx, -cy, 1]
        ]
        grados = math.radians(angulo)
        r = Matriz(3, 3)
        r.matriz = [
            [math.cos(grados), math.sin(grados), 0],
            [-math.sin(grados), math.cos(grados), 0],
            [0, 0, 1]
        ]
        c_positiva = Matriz(3, 3)
        c_positiva.matriz = [
            [1, 0, 0],
            [0, 1, 0],
            [cx, cy, 1]
        ]
        p_prima = p * c_negativa * r * c_positiva
        return [p_prima.matriz[0][0], p_prima.matriz[0][1]]

def escalar(self, px, py, sx, sy, cx=0, cy=0):
        p = Matriz(1, 3)
        p.matriz = [[px, py, 1]]
        c_negativa = Matriz(3, 3)
        c_negativa.matriz = [
            [1, 0, 0],
            [0, 1, 0],
            [-cx, -cy, 1]
        ]
        s = Matriz(3, 3)
        s.matriz = [
            [sx, 0, 0],
            [0, sy, 0],
            [0, 0, 1]
        ]
        c_positiva = Matriz(3, 3)
        c_positiva.matriz = [
            [1, 0, 0],
            [0, 1, 0],
            [cx, cy, 1]
        ]
        p_prima = p * c_negativa * s * c_positiva
        return [p_prima.matriz[0][0], p_prima.matriz[0][1]]

rotar necesita, además de las coordenadas del punto, un ángulo y las coordenadas de un punto C para tomar como centro de rotación. El ángulo necesita ser convertido a radianes para poder usarlo con las funciones de seno y coseno. Similarmente, escalar toma las coordenadas para P y C, junto con los valores sx y sy como los valores escalares en ambos ejes. Se declaran dos matrices adicionales para la transformación con respecto a C, que se multiplican con la matriz de transformación y P.

Con estas clases, ya se pueden llevar a cabo transformaciones y obtener sus resultados de manera automática. Se invita a que el lector realice diferentes pruebas.