ماشین حساب مهندسی پیشرفته با رسم نمودار دوبعدی و سه بعدی
ماشین حساب مهندسی پیشرفته با رسم نمودار دوبعدی و سه بعدی#خب اولین کارمون رایگان در اختیار شما
# scientific_calculator_3d.py
"""
ماشینحساب مهندسی با رسم 2D و 3D (z = f(x,y))
- امنسازی eval با توابع math و numpy محدود
- رسم دوبعدی f(x) و رسم سهبعدی z=f(x,y) با matplotlib (Axes3D)
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import math
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from mpl_toolkits.mplot3d import Axes3D # noqa: F401
import sympy as sp
import traceback
# --- امنسازی محیط eval ---
_safe_dict = {}
for name in dir(math):
if not name.startswith("_"):
_safe_dict[name] = getattr(math, name)
# allowed numpy functions (limited)
_np_allowed = ["sin", "cos", "tan", "arcsin", "arccos", "arctan",
"sinh", "cosh", "tanh", "exp", "log", "log10",
"sqrt", "abs", "pi", "e", "sin", "cos"]
for name in _np_allowed:
if hasattr(np, name):
_safe_dict["np_"+name] = getattr(np, name)
def safe_eval(expr, local_vars=None):
if local_vars is None:
local_vars = {}
expr = expr.replace("^", "**")
env = dict(_safe_dict)
# expose math names directly
for name in dir(math):
if not name.startswith("_"):
env[name] = getattr(math, name)
# limited numpy as dict under 'np'
limited_np = {}
for name in set(_np_allowed):
if hasattr(np, name):
limited_np[name] = getattr(np, name)
limited_np["pi"] = np.pi
limited_np["e"] = np.e
env["np"] = limited_np
env.update(local_vars)
env["__builtins__"] = {}
return eval(expr, env)
# --- GUI ---
class ScientificCalculator3D(tk.Tk):
def __init__(self):
super().__init__()
self.title("ماشینحساب مهندسی 2D/3D")
self.geometry("1200x780")
self.resizable(True, True)
self.expr_var = tk.StringVar()
self.result_var = tk.StringVar()
self.plot_expr = tk.StringVar(value="sin(x)")
self.plot3_expr = tk.StringVar(value="sin(x*y)")
self.history = []
self._build_ui()
def _build_ui(self):
top = ttk.Frame(self)
top.pack(side="top", fill="x", padx=8, pady=6)
entry = ttk.Entry(top, textvariable=self.expr_var, font=("Consolas", 18))
entry.pack(side="left", fill="x", expand=True, padx=(0,6))
entry.bind("<Return>", lambda e: self.evaluate())
ttk.Label(top, textvariable=self.result_var, font=("Consolas", 14)).pack(side="right")
main = ttk.Frame(self)
main.pack(fill="both", expand=True)
left = ttk.Frame(main)
left.pack(side="left", fill="y", padx=6, pady=6)
btns = [
["7","8","9","/","sqrt("],
["4","5","6","*","^"],
["1","2","3","-","("],
["0",".","%","+",")"],
["pi","e","ans","C","DEL"]
]
for r,row in enumerate(btns):
rf = ttk.Frame(left); rf.pack(pady=2)
for txt in row:
ttk.Button(rf, text=txt, width=6, command=lambda t=txt: self.on_button(t)).pack(side="left", padx=2)
func_frame = ttk.LabelFrame(left, text="Functions")
func_frame.pack(pady=6)
funcs = ["sin(","cos(","tan(","asin(","acos(","atan(","sinh(","cosh(","tanh(","log(","log10(","exp(","abs(","factorial("]
for i,fn in enumerate(funcs):
ttk.Button(func_frame, text=fn.rstrip("("), width=8, command=lambda f=fn: self.on_button(f)).grid(row=i//3, column=i%3, padx=2, pady=2)
mid = ttk.Frame(main)
mid.pack(side="left", fill="y", padx=6, pady=6)
ttk.Button(mid, text="=", width=20, command=self.evaluate).pack(pady=6)
ttk.Button(mid, text="Clear All", width=20, command=self.clear_all).pack(pady=6)
hist_frame = ttk.LabelFrame(mid, text="History")
hist_frame.pack(fill="both", expand=True)
self.hist_list = tk.Listbox(hist_frame, height=12, width=30)
self.hist_list.pack(side="left", fill="both", expand=True)
ttk.Scrollbar(hist_frame, orient="vertical", command=self.hist_list.yview).pack(side="right", fill="y")
self.hist_list.config(yscrollcommand=lambda *args: None)
self.hist_list.bind("<Double-Button-1>", self.on_history_select)
right = ttk.Frame(main)
right.pack(side="right", fill="both", expand=True, padx=6, pady=6)
# Plot 2D
plot2 = ttk.LabelFrame(right, text="Plot 2D: f(x)")
plot2.pack(fill="x", pady=4)
ttk.Label(plot2, text="f(x)=").grid(row=0,column=0, sticky="w")
ttk.Entry(plot2, textvariable=self.plot_expr, width=40).grid(row=0,column=1,columnspan=3, sticky="w")
ttk.Label(plot2, text="xmin").grid(row=1,column=0, sticky="w")
self.xmin_var = tk.StringVar(value="-10"); ttk.Entry(plot2, textvariable=self.xmin_var, width=8).grid(row=1,column=1)
ttk.Label(plot2, text="xmax").grid(row=1,column=2, sticky="w")
self.xmax_var = tk.StringVar(value="10"); ttk.Entry(plot2, textvariable=self.xmax_var, width=8).grid(row=1,column=3)
ttk.Label(plot2, text="samples").grid(row=2,column=0, sticky="w")
self.samples_var = tk.IntVar(value=1000); ttk.Entry(plot2, textvariable=self.samples_var, width=8).grid(row=2,column=1)
ttk.Button(plot2, text="Plot 2D", command=self.plot_2d).grid(row=3,column=0, pady=6)
ttk.Button(plot2, text="Save PNG", command=self.save_plot).grid(row=3,column=1, pady=6)
ttk.Button(plot2, text="Clear", command=self.clear_plot).grid(row=3,column=2, pady=6)
# Plot 3D
plot3 = ttk.LabelFrame(right, text="Plot 3D: z=f(x,y)")
plot3.pack(fill="x", pady=4)
ttk.Label(plot3, text="z=").grid(row=0,column=0, sticky="w")
ttk.Entry(plot3, textvariable=self.plot3_expr, width=40).grid(row=0,column=1,columnspan=3, sticky="w")
ttk.Label(plot3, text="xmin").grid(row=1,column=0, sticky="w")
self.x3_min = tk.StringVar(value="-5"); ttk.Entry(plot3, textvariable=self.x3_min, width=8).grid(row=1,column=1)
ttk.Label(plot3, text="xmax").grid(row=1,column=2, sticky="w")
self.x3_max = tk.StringVar(value="5"); ttk.Entry(plot3, textvariable=self.x3_max, width=8).grid(row=1,column=3)
ttk.Label(plot3, text="ymin").grid(row=2,column=0, sticky="w")
self.y3_min = tk.StringVar(value="-5"); ttk.Entry(plot3, textvariable=self.y3_min, width=8).grid(row=2,column=1)
ttk.Label(plot3, text="ymax").grid(row=2,column=2, sticky="w")
self.y3_max = tk.StringVar(value="5"); ttk.Entry(plot3, textvariable=self.y3_max, width=8).grid(row=2,column=3)
ttk.Label(plot3, text="samples (each axis)").grid(row=3,column=0, sticky="w")
self.samples3_var = tk.IntVar(value=200); ttk.Entry(plot3, textvariable=self.samples3_var, width=8).grid(row=3,column=1)
ttk.Button(plot3, text="Plot 3D", command=self.plot_3d).grid(row=4,column=0, pady=6)
ttk.Button(plot3, text="Save PNG", command=self.save_plot).grid(row=4,column=1, pady=6)
# Figure
fig_frame = ttk.Frame(right); fig_frame.pack(fill="both", expand=True)
self.fig = Figure(figsize=(6,5), dpi=110)
self.ax = self.fig.add_subplot(111) # will be replaced with 3D when needed
self.canvas = FigureCanvasTkAgg(self.fig, master=fig_frame)
self.canvas.get_tk_widget().pack(fill="both", expand=True)
bottom = ttk.Frame(self); bottom.pack(side="bottom", fill="x", padx=8, pady=6)
ttk.Label(bottom, text="برای توابع برداری از np.sin(x), np.cos(...) یا sin(...) استفاده کنید. برای 3D از متغیرهای x و y استفاده کنید.").pack(side="left")
# buttons
def on_button(self, text):
if text == "C":
self.expr_var.set("")
return
if text == "DEL":
s = self.expr_var.get(); self.expr_var.set(s[:-1]); return
if text == "ans":
if self.history:
last_ans = self.history[-1][1]
self.expr_var.set(self.expr_var.get() + str(last_ans))
return
if text == "pi":
self.expr_var.set(self.expr_var.get() + "pi"); return
if text == "e":
self.expr_var.set(self.expr_var.get() + "e"); return
if text == "%":
self.expr_var.set(self.expr_var.get() + "/100"); return
self.expr_var.set(self.expr_var.get() + text)
def clear_all(self):
self.expr_var.set(""); self.result_var.set(""); self.history.clear(); self.hist_list.delete(0,tk.END); self.clear_plot()
def on_history_select(self, event):
sel = self.hist_list.curselection()
if not sel: return
idx = sel[0]; expr, val = self.history[idx]
self.expr_var.set(expr); self.result_var.set(str(val))
def evaluate(self):
expr = self.expr_var.get().strip()
if not expr: return
try:
val = safe_eval(expr)
if isinstance(val, float) and abs(val - round(val)) < 1e-12:
val = int(round(val))
self.result_var.set(str(val))
self.history.append((expr, val)); self.hist_list.insert(tk.END, f"{expr} = {val}")
except Exception as e:
messagebox.showerror("Evaluation error", f"{e}\n\n{traceback.format_exc()}")
# plotting 2D
def plot_2d(self):
expr = self.plot_expr.get().strip()
if not expr:
messagebox.showwarning("Warning", "عبارت تابع خالی است."); return
try:
xmin = float(self.xmin_var.get()); xmax = float(self.xmax_var.get())
if xmax <= xmin: raise ValueError("xmax باید بزرگتر از xmin باشد.")
samples = int(self.samples_var.get()); samples = max(20, samples)
x = np.linspace(xmin, xmax, samples)
ef = expr.replace("^", "**")
repl_map = ["sin","cos","tan","arcsin","arccos","arctan","sinh","cosh","tanh","exp","log","log10","sqrt","abs"]
for fn in repl_map:
ef = ef.replace(fn+"(", "np."+fn+"(")
local_vars = {"x": x}
try:
y = safe_eval(ef, local_vars=local_vars)
if np.isscalar(y): y = np.full_like(x, float(y), dtype=float)
else: y = np.array(y, dtype=float)
except Exception:
y = []
for xv in x:
try:
yv = safe_eval(expr, local_vars={"x": float(xv)}); y.append(float(yv))
except Exception:
y.append(np.nan)
y = np.array(y, dtype=float)
self.fig.clf()
self.ax = self.fig.add_subplot(111)
self.ax.plot(x, y, label=f"f(x)={expr}")
self.ax.set_xlim(xmin, xmax); self.ax.grid(True); self.ax.legend()
self.canvas.draw()
except Exception as e:
messagebox.showerror("Plot error", str(e))
# plotting 3D
def plot_3d(self):
expr = self.plot3_expr.get().strip()
if not expr:
messagebox.showwarning("Warning", "عبارت تابع خالی است."); return
try:
xmin = float(self.x3_min.get()); xmax = float(self.x3_max.get())
ymin = float(self.y3_min.get()); ymax = float(self.y3_max.get())
if xmax <= xmin or ymax <= ymin:
raise ValueError("حدود x و y نامعتبرند.")
samples = int(self.samples3_var.get()); samples = max(10, min(500, samples))
xs = np.linspace(xmin, xmax, samples)
ys = np.linspace(ymin, ymax, samples)
X, Y = np.meshgrid(xs, ys)
# prepare expression for numpy evaluation: replace functions to np.*
ef = expr.replace("^", "**")
repl_map = ["sin","cos","tan","arcsin","arccos","arctan","sinh","cosh","tanh","exp","log","log10","sqrt","abs"]
for fn in repl_map:
ef = ef.replace(fn+"(", "np."+fn+"(")
local_vars = {"x": X, "y": Y}
try:
Z = safe_eval(ef, local_vars=local_vars)
Z = np.array(Z, dtype=float)
except Exception:
# fallback pointwise (slower)
Z = np.empty_like(X, dtype=float)
rows, cols = X.shape
for i in range(rows):
for j in range(cols):
xv = float(X[i,j]); yv = float(Y[i,j])
try:
zv = safe_eval(expr, local_vars={"x": xv, "y": yv})
Z[i,j] = float(zv)
except Exception:
Z[i,j] = np.nan
# plot surface
self.fig.clf()
self.ax = self.fig.add_subplot(111, projection='3d')
# choose a colormap and plot_surface
surf = self.ax.plot_surface(X, Y, Z, cmap='viridis', linewidth=0, antialiased=True)
self.fig.colorbar(surf, ax=self.ax, shrink=0.6)
self.ax.set_xlabel('x'); self.ax.set_ylabel('y'); self.ax.set_zlabel('z')
self.ax.set_title(f"z = {expr}")
self.canvas.draw()
except Exception as e:
messagebox.showerror("3D Plot error", f"{e}\n\n{traceback.format_exc()}")
def save_plot(self):
path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG","*.png")])
if not path: return
try:
self.fig.savefig(path)
messagebox.showinfo("Saved", f"Saved plot to {path}")
except Exception as e:
messagebox.showerror("Save error", str(e))
def clear_plot(self):
self.fig.clf()
self.ax = self.fig.add_subplot(111)
self.ax.set_title("Plot"); self.ax.grid(True)
self.canvas.draw()
if __name__ == "__main__":
app = ScientificCalculator3D()
app.mainloop()