How do I create a 3D animation app in Python?

69 Views Asked by At

I have created a small basic TKINTER app in python, where the user can insert a dxf file ( AutoCAD file ),and visualize it on a 3D canvas. Also there is a zoom in and zoom out capability. Now I wish to create a 3D animation tool, where by pressing a button e.g. start, the user can navigate himself inside the canvas, troughout all the dxf's entities, with "pause" and "stop" buttons to be enhanced as well. Usually the dxf will be a pure polyline or a road so I wish the navigation to be linear from the beginning to the end of the dxf's entities. Please take a look on my piece of code and give me any suggestions, advice and or any piece of code that could help me achieve that.

import tkinter as tk
from tkinter import filedialog
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import ezdxf
import numpy as np

class DXFViewerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("DXF Viewer")

        # Create and configure the main frame
        self.main_frame = tk.Frame(self.root, padx=10, pady=10)
        self.main_frame.pack()

        # Create the canvas for displaying the DXF file with larger dimensions
        self.canvas_frame = tk.Frame(self.main_frame, width=1500, height=1400)
        self.canvas_frame.pack(side=tk.LEFT)

        # Create the canvas for displaying coordinates information
        self.coord_frame = tk.Frame(self.main_frame)
        self.coord_frame.pack(side=tk.RIGHT)

        # Create the menu bar
        self.menu_bar = tk.Menu(self.root)
        self.root.config(menu=self.menu_bar)

        # Create the "File" menu
        self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="File", menu=self.file_menu)
        self.file_menu.add_command(label="Open DXF", command=self.open_dxf_file)

        # Store the entities for redrawing after zoom
        self.entities = None

        # Create zoom in and zoom out buttons
        self.zoom_in_button = tk.Button(self.canvas_frame, text="Zoom In", command=self.zoom_in)
        self.zoom_in_button.pack(side=tk.BOTTOM, padx=5)
        self.zoom_out_button = tk.Button(self.canvas_frame, text="Zoom Out", command=self.zoom_out)
        self.zoom_out_button.pack(side=tk.BOTTOM, padx=5)

        # Variables for mouse movement and zooming
        self.prev_x = None
        self.prev_y = None

        # Initialize 3D plot
        self.fig = plt.Figure(figsize=(12, 10))
        self.ax = self.fig.add_subplot(111, projection='3d')

        # Create Matplotlib canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.canvas_frame)
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    def open_dxf_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("DXF Files", "*.dxf")])

        if file_path:
            self.entities = self.read_dxf(file_path)
            self.draw_entities(self.entities)

    def read_dxf(self, file_path):
        doc = ezdxf.readfile(file_path)
        return doc.modelspace().query("*")

    def draw_entities(self, entities):
        # Clear the 3D plot
        self.ax.cla()

        # Create a dictionary to store unique colors for each layer
        layer_colors = {}

        for entity in entities:
            layer = entity.dxf.layer
            if layer not in layer_colors:
                # Generate a random color for the layer
                layer_colors[layer] = np.random.rand(3,)

            color = layer_colors[layer]

            if entity.dxftype() == 'LINE':
                start_point = entity.dxf.start
                end_point = entity.dxf.end
                self.ax.plot([start_point.x, end_point.x], [start_point.y, end_point.y], [start_point.z, end_point.z], color=color)

            elif entity.dxftype() == 'LWPOLYLINE':
                points = [(vertex[0], vertex[1], vertex[2]) for vertex in entity]
                xs, ys, zs = zip(*points)
                self.ax.plot(xs, ys, zs, color=color)

            elif entity.dxftype() == 'CIRCLE':
                center = entity.dxf.center
                radius = entity.dxf.radius
                phi = np.linspace(0, 2 * np.pi, 100)
                x = center.x + radius * np.cos(phi)
                y = center.y + radius * np.sin(phi)
                z = center.z * np.ones_like(phi)
                self.ax.plot(x, y, z, color=color)

        # Set axis labels
        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Y')
        self.ax.set_zlabel('Z')

        # Set axis limits
        self.ax.set_xlim(self.ax.get_xlim())
        self.ax.set_ylim(self.ax.get_ylim())
        self.ax.set_zlim(self.ax.get_zlim())

        # Draw coordinates information
        min_x, min_y, max_x, max_y, min_z, max_z = self.ax.get_xlim() + self.ax.get_ylim() + self.ax.get_zlim()
        self.draw_coordinates_info(min_x, min_y, max_x, max_y, min_z, max_z)

        # Update canvas
        self.canvas.draw()

    def zoom_in(self):
        # Zoom in
        self.ax.dist *= 0.9
        self.canvas.draw()

    def zoom_out(self):
        # Zoom out
        self.ax.dist /= 0.9
        self.canvas.draw()

    def draw_coordinates_info(self, min_x, min_y, max_x, max_y, min_z, max_z):
        # Clear the coordinates information frame
        for widget in self.coord_frame.winfo_children():
            widget.destroy()

        # Display coordinates information
        text = f"Min X: {min_x:.2f}\nMin Y: {min_y:.2f}\nMin Z: {min_z:.2f}\nMax X: {max_x:.2f}\nMax Y: {max_y:.2f}\nMax Z: {max_z:.2f}"
        tk.Label(self.coord_frame, text=text, font=("Arial", 10)).pack()

if __name__ == "__main__":
    root = tk.Tk()
    app = DXFViewerApp(root)
    root.mainloop()
0

There are 0 best solutions below