Código de Python - Visor interactivo de modelos 3D

Imágen de perfil
Val: 712
Bronce
Ha aumentado 1 puesto en Python (en relación al último mes)
Gráfica de Python
Actualizado

Visor interactivo de modelos 3Dgráfica de visualizaciones


Python

Actualizado el 20 de Abril del 2025 por Antonio (77 códigos) (Publicado el 7 de Febrero del 2025)
3.934 visualizaciones desde el 7 de Febrero del 2025
Este programa es un visor de modelos 3D en formato `.obj` que utiliza `OpenGL` y `pygame` para renderizar y manipular objetos 3D. Ofrece varias funciones de visualización como rotación, zoom, traslación, cambio entre vista en perspectiva y vista ortográfica, y otras acciones útiles para examinar el modelo cargado.

### Principales funciones del programa:

1. **Carga de modelo `.obj`:** El archivo `.obj` se especifica a través de un argumento y se carga mostrando los vértices, aristas y caras del modelo.
2. **Visualización en 3D:** Permite cambiar entre vista ortográfica y perspectiva.
3. **Rotación del modelo:** Utiliza cuaterniones para rotar el modelo sobre cualquier eje.
4. **Zoom y traslación:** Posibilidad de hacer zoom y mover el modelo en la pantalla.
5. **Información en pantalla:** Se puede mostrar/ocultar información como el nombre del modelo, escala, número de vértices, aristas y caras.

### Comandos principales:

- **Flechas del teclado:** Rotan el modelo en diferentes direcciones.
- **Tecla 'R':** Reinicia la rotación y escala del modelo.
- **Teclas 'M' y 'N':** Rotación en sentido horario y antihorario sobre el eje Z.
- **Tecla 'P':** Alterna entre vista en perspectiva y ortográfica.
- **Tecla 'X' y 'Z':** Zoom in y Zoom out, respectivamente.
- **Mouse:** Arrastrar con el clic izquierdo para mover la escena y usar la rueda del ratón para hacer zoom.
- **Tecla 'H':** Mostrar/ocultar la información en pantalla.
- **Tecla 'ESC':** Cierra el programa.
ov1
ov2
ship

Para cualquier duda u observación, usen la sección de comentarios.

Requerimientos

**Requerimientos de librerías:**

- **OpenGL**: Para renderizado gráfico en 3D.
- **PyGame**: Para manejo de ventana, eventos y controles de interfaz.
- **NumPy**: Para operaciones matemáticas y matrices (usado en cuaterniones y transformaciones).
- **Colorama**: Para manejo de colores en la consola.
- **GLU**: Utilizado para funciones adicionales de OpenGL como las proyecciones.

0.1

Actualizado el 21 de Marzo del 2025 (Publicado el 7 de Febrero del 2025)gráfica de visualizaciones de la versión: 0.1
3.691 visualizaciones desde el 7 de Febrero del 2025

0.2

Actualizado el 20 de Abril del 2025 (Publicado el 7 de Abril del 2025)gráfica de visualizaciones de la versión: 0.2
244 visualizaciones desde el 7 de Abril del 2025
estrellaestrellaestrellaestrellaestrella
estrellaestrellaestrellaestrella
estrellaestrellaestrella
estrellaestrella
estrella

Esta versión incorpora el color blanco para el fondo. También añade la opción '-fill'/'--fill _object' (antes -sl'/'--solid') para mostrar el modelo con relleno a color.

Clipboard_03-30-2025_01
screeen
visra

Para cualquier duda u observación, utilicen la sección de comentarios.
screennn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from pathlib import Path
import os
import math
import numpy as np
import argparse
from colorama import init, Fore, Style
 
init()
 
rgb_colors = {'blue':[0.0,0.0,1.0,1.0],
              'gray':[0.2,0.2,0.2,1.0],
              'black':[0.0,0.0,0.0,1.0],
              'white':[1.0,1.0,1.0,1.0]}
 
rgb_t = {'blue':[0,0,255],
               'gray':[51,51,51],
               'black':[0,0,0],
               'white':[255,255,255]}
 
def check_width_value(width):
    val = int(width)
    if val < 800 or val > 1600:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+f"Width value must be less than 1601 and greater than 799."+Fore.RESET+Style.RESET_ALL)
    return val
 
def check_height_value(height):
    val = int(height)
    if val < 600 or val > 900:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+f"Height value must be less than 901 and greater than 599."+Fore.RESET+Style.RESET_ALL)
    return val
 
'''def get_objs():
    extension = '.obj'
    directory = os.getcwd()
    return [file.name for file in Path(directory).glob(f'*{extension}')]'''
 
def check_source_ext(file):
    name, ex = os.path.splitext(file)
    if os.path.exists(file):
        if ex != ".obj":
            raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+f"Source file must be '.obj' ('{ex}' is not valid)."+Fore.RESET+Style.RESET_ALL)
    else:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+f"FILE NOT FOUND: file or path '{file}' not found."+Fore.RESET+Style.RESET_ALL)
    return file
 
def load_obj(filename,color):
    vertices = []
    #edges = []
    edges = set()
    num_edges = 0
    num_verts = 0
    num_triangles = 0
    faces = []
    with open(filename, 'r') as file:
        for line in file:
            if line.startswith('v '):  # Vértice
                num_verts += 1
                parts = line.strip().split()
                vertex = [float(parts[1]), float(parts[2]), float(parts[3])]
                vertices.append(vertex)
            elif line.startswith('f '):  # Cara
                num_triangles += 1
                parts = line.strip().split()
                face_indices = [int(part.split('/')[0]) - 1 for part in parts[1:]]
                if color:
                    faces.append(face_indices)
                for i in range(len(face_indices)):
                    edges.add(tuple(sorted((face_indices[i], face_indices[(i + 1) % len(face_indices)]))))
                    #edges.append((face_indices[i], face_indices[(i + 1) % len(face_indices)]))
                    num_edges = len(edges)
    return vertices, edges, num_verts, num_triangles, num_edges, faces
 
def drawText(f, x, y, text, c, bgc):
    textSurface = f.render(text, True, c, bgc)
    textData = pygame.image.tostring(textSurface, "RGBA", True)
    glWindowPos2d(x, y)
    glDrawPixels(textSurface.get_width(), textSurface.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, textData)
 
def show_controls():
    print("\n--------------------- Controls ---------------------")
 
    print("\nKeyboard Controls (Movement):")
    print("  - Up Arrow: Move the scene forward (rotate upwards)")
    print("  - Down Arrow: Move the scene backward (rotate downwards)")
    print("  - Left Arrow: Move the scene left (rotate left)")
    print("  - Right Arrow: Move the scene right (rotate right)")
 
    print("\nRotation Controls:")
    print("  - 'R' Key: Reset the scene rotation and scaling")
    print("  - 'M' Key: Rotate the scene clockwise around the Z-axis")
    print("  - 'N' Key: Rotate the scene counterclockwise around the Z-axis")
 
    print("\nView Mode Toggle:")
    print("  - 'P' Key: Toggle between Orthographic and Perspective views")
 
    print("\nZoom Controls:")
    print("  - 'Z' Key: Zoom in (decrease scale)")
    print("  - 'X' Key: Zoom out (increase scale)")
    print("  - Mouse Wheel: Zoom in/out")
 
    print("\nTranslation Controls (Drag):")
    print("  - Hold Left Mouse Button: Drag to move the scene")
 
    print("\nMiscellaneous:")
    print("  - 'H' Key: Toggle the visibility of on-screen information (model name, scale, view mode)")
    print("  - ESC Key: Exit the program")
 
    print("\n----------------------------------------------------")
 
 
# Clase para manejar cuaterniones
class Quaternion:
    def __init__(self, w, x, y, z):
        self.w = w
        self.x = x
        self.y = y
        self.z = z
 
    def to_matrix(self):
        ww, xx, yy, zz = self.w * self.w, self.x * self.x, self.y * self.y, self.z * self.z
        wx, wy, wz = self.w * self.x, self.w * self.y, self.w * self.z
        xy, xz, yz = self.x * self.y, self.x * self.z, self.y * self.z
 
        return np.array([
            [1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy), 0],
            [2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx), 0],
            [2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy), 0],
            [0, 0, 0, 1]
        ], dtype=np.float32)
 
    def __mul__(self, other):
        w1, x1, y1, z1 = self.w, self.x, self.y, self.z
        w2, x2, y2, z2 = other.w, other.x, other.y, other.z
        return Quaternion(
            w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
            w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
            w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
            w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
        )
 
# Función para crear un cuaternión de rotación
def create_rotation_quaternion(angle, x, y, z):
    half_angle = math.radians(angle) / 2.0
    sin_half_angle = math.sin(half_angle)
    return Quaternion(math.cos(half_angle), x * sin_half_angle, y * sin_half_angle, z * sin_half_angle)
 
# Función para inicializar la proyección ortogonal
def setup_view_ortho(display):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
 
    # Definir el rango de la proyección ortogonal
    aspect_ratio = display[0] / display[1]
    ortho_size = 10  # Tamaño del área visible en la proyección ortogonal 10
    #glOrtho(-ortho_size * aspect_ratio, ortho_size * aspect_ratio, -ortho_size, ortho_size, -50, 50)
    glOrtho(-ortho_size * 0.5 * aspect_ratio, ortho_size * 0.5 * aspect_ratio, -ortho_size * 0.5, ortho_size * 0.5, -50, 50)
 
 
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    #glTranslatef(0.0,0.0,-3.0)
 
def text_pos(h,p):
    inc_total = (h - 600)
    h_pos = p + inc_total
    return h_pos
 
def check_color(color):
    colors = ['blue','gray','black','white']
    if color not in colors:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+"Background color must be 'blue', 'white', 'gray' or 'black'."+Fore.RESET+Style.RESET_ALL)
    return color
 
def check_lw(w):
    width = float(w)
    if width < 1.0:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+"Line width must be equal or greater than 1.0."+Fore.RESET+Style.RESET_ALL)
    return width
 
def check_positive(v):
    ivalue = float(v)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError(Fore.RED+Style.BRIGHT+f"Object scale must be positive ('{v}' is not valid)."+Fore.RESET+Style.RESET_ALL)
    return ivalue
 
# Función para inicializar la proyección en perspectiva
def setup_view_perspective(display):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(50, (display[0] / display[1]), 0.1, 80)#50.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    glTranslatef(0.0, 0.0, -10.0)
 
def window(args):
    show_controls()
    #list_objects = get_objs()
    #print(list_objects)
    pygame.init()
 
    text_bgR = rgb_t[args.bg_color][0]
    text_bgG = rgb_t[args.bg_color][1]
    text_bgB = rgb_t[args.bg_color][2]
 
    text_pos1 = text_pos(args.window_height,570)
    text_pos2 = text_pos(args.window_height,550)
    text_pos3 = text_pos(args.window_height,530)
    text_pos4 = text_pos(args.window_height,510)
    text_pos5 = text_pos(args.window_height,490)
    text_pos6 = text_pos(args.window_height,470)
 
    display = (args.window_width, args.window_height)
    pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
    pygame.display.set_caption("Model Viewer")
    font = pygame.font.SysFont('arial', 15)
 
    glEnable(GL_DEPTH_TEST)#######################################################
 
    #glClearColor(0.0, 0.0, 1.0, 1.0)
 
    glClearColor(rgb_colors[args.bg_color][0],
                 rgb_colors[args.bg_color][1],
                 rgb_colors[args.bg_color][2],
                 rgb_colors[args.bg_color][3])
 
    # Cargar el modelo OBJ
    #path = r'C:\Users\Usuario\Documents\fondo\temple_maze.obj'
    path = args.load_object
    model_name = os.path.basename(path)
    vertices, edges, num_verts, num_triangles, num_edges, faces = load_obj(path,args.fill_object)
    scale = args.scale
    hide_data = False
    green_val = 255
 
    # Crear la lista de display para el modelo
    model_list = glGenLists(1)
    glNewList(model_list, GL_COMPILE)
 
    glLineWidth(args.line_width)
 
    if args.fill_object:
        glBegin(GL_TRIANGLES)##################
        glColor3f(0.0, 0.5, 0.0)
        for face in faces:
            for vertex in face:
                glVertex3fv(vertices[vertex])
        glEnd()################################
 
    if args.bg_color == 'white':
        glColor3f(0.0, 0.0, 0.0)  # Color negro
        green_val = 100
    else:
        glColor3f(1.0, 1.0, 1.0)  # Color blanco
 
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glVertex3fv(vertices[vertex])
    glEnd()
    glEndList()
 
    # Inicializar la vista en perspectiva por defecto
    is_ortho = False
    setup_view_perspective(display)
 
    # Inicializar el cuaternión de rotación (sin rotación inicial)
    quaternion = Quaternion(1, 0, 0, 0)
 
    dragging = False
    last_mouse_pos = (0, 0)
    translation = [0.0, 0.0]
 
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                '''if event.key == pygame.K_z:
                    scale += 0.05
                elif event.key == pygame.K_x:
                    scale -= 0.05'''
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_h:
                    hide_data = not hide_data
                elif event.key == pygame.K_r:
                    quaternion = Quaternion(1, 0, 0, 0)
                    scale = args.scale
                    dragging = False
                    last_mouse_pos = (0, 0)
                    translation = [0.0, 0.0]
                    is_ortho = False
                    setup_view_perspective(display) # Restablece vista en perspectiva
                    args.solid = False
 
                elif event.key == pygame.K_p:  # Cambiar entre ortogonal y perspectiva
                    is_ortho = not is_ortho
                    if is_ortho:
                        setup_view_ortho(display)
                    else:
                        setup_view_perspective(display)
 
                elif event.key == pygame.K_t:  # Vista cenital (tecla 't')
                    # Aplicar rotación para la vista cenital
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    #translation = [0.0, 0.0]  # Restablece la traslación
                    #scale = 1  # Restablece el zoom
                    # Rotar la cámara 90 grados sobre el eje X para vista cenital
                    rotation = create_rotation_quaternion(-90, 1, 0, 0)
                    quaternion = quaternion * rotation
                elif event.key == pygame.K_b:
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    rotation = create_rotation_quaternion(90, 1, 0, 0)
                    quaternion = quaternion * rotation
                elif event.key == pygame.K_j:
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    rotation = create_rotation_quaternion(90, 0, 1, 0)
                    quaternion = quaternion * rotation
                elif event.key == pygame.K_l:
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    rotation = create_rotation_quaternion(-90, 0, 1, 0)
                    quaternion = quaternion * rotation
                elif event.key == pygame.K_f:
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    rotation = create_rotation_quaternion(0, 0, 1, 0)
                    quaternion = quaternion * rotation
                elif event.key == pygame.K_k:
                    quaternion = Quaternion(1, 0, 0, 0)  # Restablece rotación
                    rotation = create_rotation_quaternion(180, 0, 1, 0)
                    quaternion = quaternion * rotation
 
 
            elif event.type == pygame.MOUSEWHEEL:  # Rueda ratón
                if event.y > 0:
                    scale += 0.05
                elif event.y < 0:
                    scale -= 0.05
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    dragging = True
                    last_mouse_pos = pygame.mouse.get_pos()
            elif event.type == pygame.MOUSEBUTTONUP:
                if event.button == 1:
                    dragging = False
            elif event.type == pygame.MOUSEMOTION:
                if dragging:
                    mouse_x, mouse_y = pygame.mouse.get_pos()
                    dx = mouse_x - last_mouse_pos[0]
                    dy = mouse_y - last_mouse_pos[1]
                    translation[0] += dx * 0.01  # Ajusta la velocidad de desplazamiento
                    translation[1] -= dy * 0.01  # Invertir el movimiento vertical
                    last_mouse_pos = (mouse_x, mouse_y)
 
        key = pygame.key.get_pressed()
 
        # Rotación con cuaterniones (si se presionan las teclas de dirección)
        if key[pygame.K_UP]:
            rotation = create_rotation_quaternion(2, 1, 0, 0)
            quaternion = quaternion * rotation
        if key[pygame.K_DOWN]:
            rotation = create_rotation_quaternion(-2, 1, 0, 0)
            quaternion = quaternion * rotation
        if key[pygame.K_RIGHT]:
            rotation = create_rotation_quaternion(2, 0, 1, 0)
            quaternion = quaternion * rotation
        if key[pygame.K_LEFT]:
            rotation = create_rotation_quaternion(-2, 0, 1, 0)
            quaternion = quaternion * rotation
        if key[pygame.K_m]:
            rotation = create_rotation_quaternion(-2, 0, 0, 1)
            quaternion = quaternion * rotation
        if key[pygame.K_n]:
            rotation = create_rotation_quaternion(2, 0, 0, 1)
            quaternion = quaternion * rotation
        if key[pygame.K_z] and scale > 0.05:
            scale -= 0.05
        if key[pygame.K_x]:
            scale += 0.05
 
        # Limpiar la pantalla y cargar la nueva matriz de rotación
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glPushMatrix()
 
        glTranslatef(translation[0], translation[1], 0)
 
        # Convertir el cuaternión a matriz de rotación
        rotation_matrix = quaternion.to_matrix()
        glMultMatrixf(rotation_matrix)
 
        # Dibujar el modelo
        glScalef(scale, scale, scale)
        glCallList(model_list)
 
        glPopMatrix()
 
        if not hide_data:
            drawText(font, 20, text_pos1, f'Model: {model_name}', (0, green_val, 0, 255), (text_bgR, text_bgG, text_bgB))
            drawText(font, 20, text_pos2, f'Scale: {round(scale, 2)}', (0, green_val, 0, 255), (text_bgR, text_bgG, text_bgB))
            view_mode = "Orthographic" if is_ortho else "Perspective"
            drawText(font, 20, text_pos3, f'View: {view_mode}', (0, green_val, 0, 255),(text_bgR, text_bgG, text_bgB))
            drawText(font, 20, text_pos4, f'Nun Verts: {num_verts}',(0, green_val, 0, 255),(text_bgR, text_bgG, text_bgB))
            drawText(font, 20, text_pos5, f'Nun Faces: {num_triangles}',(0, green_val, 0, 255),(text_bgR, text_bgG, text_bgB))
            drawText(font, 20, text_pos6, f'Num Edges: {num_edges}',(0, green_val, 0, 255),(text_bgR, text_bgG, text_bgB))
 
        pygame.display.flip()
        pygame.time.wait(10)
 
    pygame.quit()
 
def main():
    parser = argparse.ArgumentParser(prog="ModelVisor0.1", conflict_handler='resolve',
                                     description="Show obj models")
    parser.add_argument('-load','--load_object',required=True,type=check_source_ext,help="Obj model to load")
    parser.add_argument('-width','--window_width',type=check_width_value,default=800,help="Widow width")
    parser.add_argument('-height','--window_height',type=check_height_value,default=600,help="Window height")
    parser.add_argument('-bg','--bg_color',type=check_color,default='black',help="Background color")
    parser.add_argument('-lw','--line_width',type=check_lw,default=1.0,help="Line width")
    parser.add_argument('-fill','--fill_object',action='store_true',help="Add solid color to model")
    parser.add_argument('-scl','--scale',type=check_positive,default=1.0,help="Object scale")
 
    args = parser.parse_args()
    window(args)
 
if __name__ =="__main__":
    main()



Comentarios sobre la versión: 0.2 (0)


No hay comentarios
 

Comentar la versión: 0.2

Nombre
Correo (no se visualiza en la web)
Valoración
Comentarios...
CerrarCerrar
CerrarCerrar
Cerrar

Tienes que ser un usuario registrado para poder insertar imágenes, archivos y/o videos.

Puedes registrarte o validarte desde aquí.

Codigo
Negrita
Subrayado
Tachado
Cursiva
Insertar enlace
Imagen externa
Emoticon
Tabular
Centrar
Titulo
Linea
Disminuir
Aumentar
Vista preliminar
sonreir
dientes
lengua
guiño
enfadado
confundido
llorar
avergonzado
sorprendido
triste
sol
estrella
jarra
camara
taza de cafe
email
beso
bombilla
amor
mal
bien
Es necesario revisar y aceptar las políticas de privacidad

http://lwp-l.com/s7560