menu16.py

Download
import os, datetime, math, random
import numpy as np
import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
from skyfield.api import Loader

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

class Menu16App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Sistem Tata Surya 3D Geosentris (Modul 16)")
        self.geometry("1100x700")
        ctk.set_appearance_mode("Dark")
        
        self.load_obj = Loader(BASE_DIR)
        self.eph = self.load_obj('de421.bsp')
        self.ts = self.load_obj.timescale()
        
        self.setup_ui()
        self.is_viewing_3d = True
        self.update_3d_animation()

    def setup_ui(self):
        self.sidebar = ctk.CTkFrame(self, width=250)
        self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
        
        now = datetime.datetime.now()
        ctk.CTkLabel(self.sidebar, text="SIMULASI 3D\n(Geosentris)", font=("Segoe UI", 16, "bold"), text_color="#00E5FF").pack(pady=20)
        
        self.entry_y = ctk.CTkEntry(self.sidebar, placeholder_text="Tahun"); self.entry_y.insert(0, str(now.year)); self.entry_y.pack(pady=5)
        self.entry_m = ctk.CTkEntry(self.sidebar, placeholder_text="Bulan"); self.entry_m.insert(0, str(now.month)); self.entry_m.pack(pady=5)
        self.entry_d = ctk.CTkEntry(self.sidebar, placeholder_text="Tanggal"); self.entry_d.insert(0, str(now.day)); self.entry_d.pack(pady=5)
        
        ctk.CTkButton(self.sidebar, text="▶ TERAPKAN WAKTU", command=lambda: messagebox.showinfo("Update", "Waktu 3D telah diubah.")).pack(pady=20)
        
        ctk.CTkLabel(self.sidebar, text="Navigasi Mouse:\n- Klik Kiri & Geser: Rotasi\n- Scroll Wheel: Zoom", text_color="yellow").pack(pady=20)

        # Main 3D Canvas
        self.main_frame = ctk.CTkFrame(self, fg_color="#020205")
        self.main_frame.pack(side="right", fill="both", expand=True, padx=10, pady=10)

        self.anim_3d_canvas = tk.Canvas(self.main_frame, bg='#010105', highlightthickness=0)
        self.anim_3d_canvas.pack(fill="both", expand=True, padx=15, pady=15)
        
        # Inisialisasi Kamera
        self.cam_angle_x = 0.8
        self.cam_angle_y = 0.3
        self.cam_zoom = 1.0  

        self.anim_3d_canvas.bind("<B1-Motion>", self.rotate_3d_view)
        self.anim_3d_canvas.bind("<MouseWheel>", self.on_mouse_wheel_3d) 
        self.anim_3d_canvas.bind("<Button-4>", self.on_mouse_wheel_3d)   
        self.anim_3d_canvas.bind("<Button-5>", self.on_mouse_wheel_3d)   

    def rotate_3d_view(self, event):
        w, h = self.anim_3d_canvas.winfo_width(), self.anim_3d_canvas.winfo_height()
        if w > 0 and h > 0:
            self.cam_angle_x = (event.x / w) * 2 * math.pi
            self.cam_angle_y = (event.y / h) * math.pi

    def on_mouse_wheel_3d(self, event):
        if str(event.type) == 'MouseWheel':
            if event.delta > 0: self.cam_zoom *= 1.2
            else: self.cam_zoom *= 0.8
        else:
            if getattr(event, 'num', 0) == 4: self.cam_zoom *= 1.2
            elif getattr(event, 'num', 0) == 5: self.cam_zoom *= 0.8
        
        self.cam_zoom = max(0.1, min(12.0, self.cam_zoom)) # Batasi Zoom

    def project_3d(self, x, y, z, width, height):
        # Rotasi Matriks X dan Y
        x1 = x * math.cos(self.cam_angle_x) - z * math.sin(self.cam_angle_x)
        z1 = x * math.sin(self.cam_angle_x) + z * math.cos(self.cam_angle_x)
        y2 = y * math.cos(self.cam_angle_y) - z1 * math.sin(self.cam_angle_y)
        z2 = y * math.sin(self.cam_angle_y) + z1 * math.cos(self.cam_angle_y)
        
        factor = 500 / (500 + z2) if (500 + z2) != 0 else 1
        px = x1 * factor + (width / 2)
        py = -y2 * factor + (height / 2)
        return px, py, z2

    def update_3d_animation(self):
        if not self.is_viewing_3d: return
        canvas = self.anim_3d_canvas
        w, h = canvas.winfo_width(), canvas.winfo_height()
        if w <= 1:
            self.after(200, self.update_3d_animation)
            return

        canvas.delete("all")
        
        # Bintang statis background
        random.seed(42)
        for _ in range(100):
            rx, ry = random.randint(0, w), random.randint(0, h)
            canvas.create_oval(rx, ry, rx+1, ry+1, fill="#555555", outline="")

        try:
            y, m, d = int(self.entry_y.get()), int(self.entry_m.get()), int(self.entry_d.get())
            now = datetime.datetime.now()
            t_sim = self.ts.utc(y, m, d, now.hour, now.minute, now.second)
            
            earth_obj = self.eph['earth']
            
            celestial_bodies = [
                ('moon', "#ECEFF1", 15, 180, "BULAN"),
                ('mercury', "#B0BEC5", 10, 230, "MERKURIUS"),
                ('venus', "#FFCC80", 18, 290, "VENUS"),
                ('sun', "#FFD600", 50, 350, "MATAHARI"),
                ('mars', "#EF5350", 16, 450, "MARS"),
                ('jupiter', "#FFB74D", 35, 600, "JUPITER")
            ]

            draw_list = []
            
            # Bumi di tengah
            p_earth = self.project_3d(0, 0, 0, w, h)
            draw_list.append(('earth', p_earth, "#2196F3", 30 * self.cam_zoom, "BUMI"))

            # Benda Langit Lainnya
            for name, color, size, vis_dist, label in celestial_bodies:
                try:
                    target_key = f'{name} barycenter' if name not in ['sun', 'moon'] else name
                    target_obj = self.eph[target_key]

                    pos_km = earth_obj.at(t_sim).observe(target_obj).position.km
                    dist_km = np.linalg.norm(pos_km)
                    
                    px, py, pz = (pos_km / dist_km) * (vis_dist * self.cam_zoom)
                    proj_p = self.project_3d(px, py, pz, w, h)
                    draw_list.append((name, proj_p, color, size * self.cam_zoom, label))
                except Exception: pass

            # Algoritma Z-Buffer: Urutkan objek dari yang paling belakang (pz terbesar)
            draw_list.sort(key=lambda x: x[1][2], reverse=True)

            for tag, p, color, size, label in draw_list:
                canvas.create_oval(p[0]-size, p[1]-size, p[0]+size, p[1]+size, fill=color, outline="white" if tag=='earth' else "")
                if label and size > 4: 
                    font_size = max(7, int(9 * min(self.cam_zoom, 1.5)))
                    canvas.create_text(p[0], p[1]+size+12, text=label, fill="white", font=("Consolas", font_size, "bold"))

            # Garis Orbit
            r_moon, r_sun = 180 * self.cam_zoom, 350 * self.cam_zoom
            canvas.create_oval(w/2-r_moon, h/2-r_moon, w/2+r_moon, h/2+r_moon, outline="#222")
            canvas.create_oval(w/2-r_sun, h/2-r_sun, w/2+r_sun, h/2+r_sun, outline="#222")
            
            info = f"3D GEOCENTRIC (LIVE)\nZoom Level: {self.cam_zoom:.1f}x\nTime: {now.strftime('%H:%M:%S')}"
            canvas.create_text(20, 20, text=info, fill="#00E5FF", font=("Consolas", 12, "bold"), anchor="nw")

        except Exception as e:
            canvas.create_text(w/2, h/2, text=f"Sedang mengkalkulasi vektor ruang... {e}", fill="gray")

        self.after(50, self.update_3d_animation)

if __name__ == "__main__":
    app = Menu16App()
    app.mainloop()