Evo kratkog članka koji objašnjava kako kompajlirati Python GUI aplikaciju pomoću PyInstaller-a na Ubuntu sistemu, zajedno sa potrebnim zavisnostima i skriptom za automatsku instalaciju.
Pošto imam problem sa ssl sertifikatom (danas sam podneo zahtev da ga provajder aktivira) najbolje je da kopirate adresu programa koji hoćete da preuzmete i kad vam google crhome skrene pažnju da je ssl sertifikat istekao ručno potvrdite da želite da sačuvate taj fajl. Čim mi stigne račun da uplatim za ssl sertifikat taj problem će biti rešen.
Gotov komplajliran program pripremljen za upotrebu na Debian/Ubuntu linuxu možete preuzetu ovde:
http://abel.rs/programi/helixGUI.zip
Gotov kompajlirani program pripremljen za upotrebu na Windows-u možete preuzeti ovde:
http://abel.rs/programi/helixGUI_Windows.zip
I sad prelazim da iMac da napravim izvršni program i objasnim kako se kompajlira.
IIIIiiii…. kada sam video šta treba sve da uradim na macOs 10.13.6 prošla me volja.
Kada budem nabavio noviji iMac napraviću i to. Ovećavam…. a nije mi verovati….
Ako ste razvili GUI aplikaciju u Pythonu koristeći tkinter
, kao što je helixGUI.py
, lako je pretvoriti je u izvršni .exe
(na Windowsu) ili samostalni binarni fajl (na Linuxu). Evo kako se to radi na Ubuntu sistemu.
✅ Priprema: Šta treba da instalirate
Pre nego što počnete, uverite se da vaš Ubuntu sistem ima sledeće:
- Python 3
- pip (Python paket menadžer)
- PyInstaller (alat za pravljenje izvršnih fajlova)
- tkinter (za GUI)
- numpy (ako vaš program koristi naučne izračune)
🔧 Instalacija: Jedna skripta za sve
Evo jedne skripte koja instalira sve potrebne alate i biblioteke:
#!/bin/bash echo "✅ Ažuriranje sistema..." sudo apt update && sudo apt upgrade -y echo "🐍 Instalacija Python 3 i pip-a..." sudo apt install -y python3 python3-pip echo "📦 Instalacija tkinter-a..." sudo apt install -y python3-tk echo "📥 Instalacija potrebnih Python biblioteka (numpy, pyinstaller)..." pip3 install --upgrade pip pip3 install numpy pyinstaller echo "🚀 Sve je spremno! Možete sada kompajlirati vaš GUI program koristeći:" echo " pyinstaller --onefile --windowed helixGUI.py"
Nakon što ste instalirali sve zavisnosti, jednostavno pokrenite sledeću komandu u folderu gde se nalazi helixGUI.py
:
pyinstaller --onefile --windowed helixGUI.py
--onefile
: pravi jedan izvršni fajl--windowed
: sprečava otvaranje terminal prozora (važno za GUI aplikacije)
📁 Gde se nalazi izvršni fajl?
Posle kompajliranja, izvršni fajl se nalazi u dist/helixGUI
(za Linux). Možete ga pokrenuti direktno:
./dist/helixGUI
Rezultat:

Ako koristite windows onda startujte setup_helix_compile.bat
@echo off SETLOCAL ENABLEEXTENSIONS echo ========================================================= echo 🚀 Automatizovana instalacija Python okruženja za echo kompajliranje helixGUI.py na Windows 10 echo ========================================================= echo. :: 1. Proveri da li je Python već instaliran python --version >nul 2>&1 IF %ERRORLEVEL% NEQ 0 ( echo 🐍 Python nije pronađen. Preuzimam Python 3... curl -o python-installer.exe https://www.python.org/ftp/python/3.12.1/python-3.12.1-amd64.exe echo 🔧 Pokrećem instalaciju Pythona... start /wait python-installer.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 del python-installer.exe ) ELSE ( echo ✅ Python je već instaliran. ) :: 2. Proveri da li je pip instaliran pip --version >nul 2>&1 IF %ERRORLEVEL% NEQ 0 ( echo ❗ pip nije instaliran. Instaliram pip... python -m ensurepip --upgrade ) :: 3. Ažuriraj pip echo 🔄 Ažuriram pip... python -m pip install --upgrade pip :: 4. Instaliraj potrebne biblioteke echo 📦 Instaliram numpy i pyinstaller... pip install numpy pyinstaller pip install ezdxf pip install plyfile :: tkinter je već uključen u Python na Windowsu echo ✅ tkinter je uključen uz standardnu Python instalaciju na Windowsu. echo. echo 🛠️ Sve je spremno za kompajliranje helixGUI.py! echo 📁 Stavi tvoj helixGUI.py u isti folder i koristi sledeću komandu: echo. echo pyinstaller --onefile --windowed helixGUI.py echo. echo 🔚 Kraj instalacije. pause
📌 Primer zaglavlja helixGUI.py
Vaš program koristi sledeće biblioteke, što potvrđuje da su numpy
i tkinter
ključne za rad:
import numpy as np from math import sin, cos, pi, acos, degrees import sys from collections import defaultdict from pathlib import Path import tkinter as tk from tkinter import ttk, messagebox, filedialog from tkinter import scrolledtext
Programski kod za helixGui.py
# The MIT License (MIT) # Copyright (c) 2025 Aleksandar Maričić # # Ovim se omogućava bilo kome da koristi, kopira, menja, spaja, objavljuje, # distribuira, daje podlicencu i/ili prodaje kopije ovog softverskog programa, # uz uslov da u svim kopijama ili značajnim delovima softverskog programa bude # uključena sledeća obavest: # # Copyright (c) 2025 Aleksandar Maričić # # Ovaj softverski program je pružen "takav kakav jeste", bez bilo kakvih garancija, # izričitih ili impliciranih, uključujući, ali ne ograničavajući se na, garancije o # prikladnosti za prodaju ili pogodnosti za određenu svrhu. U svakom slučaju, autori # ili nosioci prava nisu odgovorni za bilo kakvu štetu ili druge obaveze koje mogu nastati # usled upotrebe ovog softverskog programa. # Naziv programa: GUI generator spiralne asimetrične helix zome kupole sa komplet pipeline-om import numpy as np from math import sin, cos, pi, acos, degrees import sys from collections import defaultdict from pathlib import Path import tkinter as tk from tkinter import ttk, messagebox, filedialog from tkinter import scrolledtext # ----------- Tvoj originalni generator spirala i preseka ----------- def generate_spiral(n, segs, d, h, direction, center_offset=np.array([0.0, 0.0])): spirals = [] for i in range(n): beta = 2 * pi * i / n spiral = [] for j in range(segs + 1): alpha = pi * j / segs theta = alpha if direction == 1 else -alpha offset_x = center_offset[0] * (alpha / pi) offset_y = center_offset[1] * (alpha / pi) x = sin(theta + beta) * d / 4 + sin(beta) * d / 4 + offset_x y = cos(theta + beta) * d / 4 + cos(beta) * d / 4 + offset_y z = (alpha / pi) * h spiral.append([x, y, z]) spirals.append(np.array(spiral)) return spirals def find_intersections(spirals_right, spirals_left, tol=1e-2): intersections = [] index_map = {} tol2 = tol * tol right_points = [] right_map = [] for si, spiral in enumerate(spirals_right): for pi, p in enumerate(spiral): right_points.append(p) right_map.append((si, pi)) right_points = np.array(right_points) global_idx = 0 for si_left, spiral_left in enumerate(spirals_left): for pi_left, p_left in enumerate(spiral_left): diffs = right_points - p_left dists2 = np.sum(diffs**2, axis=1) close_indices = np.where(dists2 < tol2)[0] if len(close_indices) > 0: key_left = ('L', si_left, pi_left) if key_left not in index_map: index_map[key_left] = global_idx intersections.append(p_left) global_idx += 1 for ci in close_indices: si_right, pi_right = right_map[ci] key_right = ('R', si_right, pi_right) if key_right not in index_map: index_map[key_right] = global_idx intersections.append(right_points[ci]) global_idx += 1 intersections = np.array(intersections) return intersections, index_map def generate_wireframe_from_intersections(filename, spirals_right, spirals_left, index_map): lines = [] def connect_intersections(side, spirals): for si, spiral in enumerate(spirals): intersect_indices = [] for pi in range(len(spiral)): key = (side, si, pi) if key in index_map: intersect_indices.append(index_map[key]) for i in range(len(intersect_indices) - 1): lines.append((intersect_indices[i] + 1, intersect_indices[i + 1] + 1)) connect_intersections('R', spirals_right) connect_intersections('L', spirals_left) # Dodaj horizontalne linije (između spirala) def connect_horizontal_lines(): for side, spirals in [('R', spirals_right), ('L', spirals_left)]: n = len(spirals) segs = len(spirals[0]) for pi in range(segs): for si in range(n): si_next = (si + 1) % n key1 = (side, si, pi) key2 = (side, si_next, pi) if key1 in index_map and key2 in index_map: lines.append((index_map[key1] + 1, index_map[key2] + 1)) connect_horizontal_lines() with open(filename, 'w') as f: global_points = [None] * (max(index_map.values()) + 1) for key, idx in index_map.items(): side, si, pi = key p = spirals_right[si][pi] if side == 'R' else spirals_left[si][pi] global_points[idx] = p for v in global_points: f.write(f"v {v[0]} {v[1]} {v[2]}\n") for line in lines: f.write(f"l {line[0]} {line[1]}\n") print(f"✅ Izvezen wireframe model preseka sa horizontalnim linijama u '{filename}'") # ----------- Konverter OBJ u STL sa triangulacijom ----------- from stl import mesh def parse_obj(filename, tol=1e-3): vertices = [] edges = [] with open(filename, 'r') as f: for line in f: if line.startswith('v '): _, x, y, z = line.strip().split() vertices.append([float(x), float(y), float(z)]) elif line.startswith('l '): parts = line.strip().split() indices = list(map(int, parts[1:])) for i in range(len(indices) - 1): edges.append((indices[i] - 1, indices[i + 1] - 1)) return np.array(vertices), edges def deduplicate_vertices(vertices, tol=1e-1): unique = [] index_map = {} for i, v in enumerate(vertices): for j, u in enumerate(unique): if np.linalg.norm(v - u) < tol: index_map[i] = j break else: index_map[i] = len(unique) unique.append(v) return np.array(unique), index_map def remap_edges(edges, index_map): remapped = [] for a, b in edges: a_m = index_map[a] b_m = index_map[b] if a_m != b_m: remapped.append((a_m, b_m)) return remapped def remove_crossing_edges_keep_horizontal(edges, vertices, tol=1e-5): def segments_intersect(p1, p2, q1, q2): u = p2 - p1 v = q2 - q1 w0 = p1 - q1 a = np.dot(u, u) b = np.dot(u, v) c = np.dot(v, v) d = np.dot(u, w0) e = np.dot(v, w0) denom = a * c - b * b if abs(denom) < 1e-15: return False sc = (b * e - c * d) / denom tc = (a * e - b * d) / denom if not (0 <= sc <= 1 and 0 <= tc <= 1): return False pt1 = p1 + sc * u pt2 = q1 + tc * v return np.linalg.norm(pt1 - pt2) < tol def vertical_angle(p1, p2): v = p2 - p1 norm = np.linalg.norm(v) if norm == 0: return np.pi / 2 horizontal_proj = np.linalg.norm(v[:2]) if horizontal_proj == 0: return np.pi / 2 angle = np.arctan(abs(v[2]) / horizontal_proj) return angle edges_to_keep = set(range(len(edges))) for i in range(len(edges)): if i not in edges_to_keep: continue a1, a2 = edges[i] p1, p2 = vertices[a1], vertices[a2] for j in range(i + 1, len(edges)): if j not in edges_to_keep: continue b1, b2 = edges[j] if len({a1, a2, b1, b2}) < 4: continue q1, q2 = vertices[b1], vertices[b2] if segments_intersect(p1, p2, q1, q2): angle_i = vertical_angle(p1, p2) angle_j = vertical_angle(q1, q2) if angle_i <= angle_j: edges_to_keep.discard(j) else: edges_to_keep.discard(i) break return [edges[i] for i in sorted(edges_to_keep)] def build_graph(edges, num_vertices): graph = defaultdict(set) for a, b in edges: if a != b: graph[a].add(b) graph[b].add(a) return graph def find_triangles(graph): triangles = set() for a in graph: neighbors_a = graph[a] for b in neighbors_a: if b <= a: continue neighbors_b = graph[b] common = neighbors_a.intersection(neighbors_b) for c in common: if c > b: triangle = tuple(sorted([a, b, c])) triangles.add(triangle) return list(triangles) def compute_normal(v1, v2, v3): return np.cross(v2 - v1, v3 - v1) def get_centroid(vertices): return np.mean(vertices, axis=0) def ensure_outward_normals(triangles, vertices): centroid = get_centroid(vertices) corrected_triangles = [] for tri in triangles: v1, v2, v3 = tri normal = compute_normal(v1, v2, v3) tri_center = np.mean([v1, v2, v3], axis=0) vec_from_centroid = tri_center - centroid if np.dot(normal, vec_from_centroid) < 0: tri = [v1, v3, v2] corrected_triangles.append(tri) return corrected_triangles def obj_to_stl(obj_path, stl_path): verts_raw, edges = parse_obj(obj_path) verts, idx_map = deduplicate_vertices(verts_raw) edges_mapped = remap_edges(edges, idx_map) edges_clean = remove_crossing_edges_keep_horizontal(edges_mapped, verts) graph = build_graph(edges_clean, len(verts)) triangles_indices = find_triangles(graph) triangles = [] for tri_idx in triangles_indices: tri_vertices = [verts[i] for i in tri_idx] triangles.append(tri_vertices) triangles = ensure_outward_normals(triangles, verts) stl_data = mesh.Mesh(np.zeros(len(triangles), dtype=mesh.Mesh.dtype)) for i, tri in enumerate(triangles): stl_data.vectors[i] = np.array(tri) stl_data.save(stl_path) print(f"✅ Sačuvan STL: {stl_path} ({len(triangles)} trouglova)") return triangles # ----------- Konverter OBJ u PLY sa cilindričnim šipkama ----------- import sys RADIUS = 0.01 CYLINDER_SEGMENTS = 16 BOJE = { 1: ("bela", (255, 255, 255)), 2: ("crvena", (255, 0, 0)), 3: ("zelena", (0, 255, 0)), 4: ("plava", (0, 0, 255)), 5: ("žuta", (255, 255, 0)), 6: ("ljubičasta", (128, 0, 128)), 7: ("narandžasta", (255, 165, 0)), 8: ("tirkizna", (64, 224, 208)), 9: ("siva", (128, 128, 128)), 10: ("crna", (0, 0, 0)), } def napravi_cilindar(p1, p2, radius, segments=CYLINDER_SEGMENTS): v = p2 - p1 length = np.linalg.norm(v) if length < 1e-8: return [], [] axis = v / length if abs(axis[0]) < 0.001 and abs(axis[1]) < 0.001: ortho = np.array([1, 0, 0]) else: ortho = np.array([0, 0, 1]) n1 = np.cross(axis, ortho) n1 /= np.linalg.norm(n1) n2 = np.cross(axis, n1) n2 /= np.linalg.norm(n2) circle_p1 = [] circle_p2 = [] for i in range(segments): theta = 2 * np.pi * i / segments dir_vec = np.cos(theta) * n1 + np.sin(theta) * n2 circle_p1.append(p1 + radius * dir_vec) circle_p2.append(p2 + radius * dir_vec) vertices = circle_p1 + circle_p2 faces = [] for i in range(segments): i_next = (i + 1) % segments faces.append((i, i_next, i_next + segments)) faces.append((i, i_next + segments, i + segments)) return vertices, faces def sacuvaj_ply(verts, faces, filename, boja): r, g, b = boja with open(filename, "w") as f: f.write("ply\n") f.write("format ascii 1.0\n") f.write(f"element vertex {len(verts)}\n") f.write("property float x\nproperty float y\nproperty float z\n") f.write("property uchar red\nproperty uchar green\nproperty uchar blue\n") f.write(f"element face {len(faces)}\n") f.write("property list uchar int vertex_indices\n") f.write("end_header\n") for v in verts: f.write(f"{v[0]} {v[1]} {v[2]} {r} {g} {b}\n") for face in faces: f.write(f"3 {face[0]} {face[1]} {face[2]}\n") def obj_to_ply(obj_path, ply_path, boja): vertices, edges = parse_obj(obj_path) vertices = np.array(vertices) sve_verteksi = [] sve_face = [] offset = 0 for i1, i2 in edges: p1 = vertices[i1] p2 = vertices[i2] verts_cil, faces_cil = napravi_cilindar(p1, p2, RADIUS, CYLINDER_SEGMENTS) sve_verteksi.extend(verts_cil) for f in faces_cil: sve_face.append((f[0] + offset, f[1] + offset, f[2] + offset)) offset += len(verts_cil) sacuvaj_ply(sve_verteksi, sve_face, ply_path, boja) print(f"🎨 Sačuvan PLY: {ply_path}") # ----------- Konverter OBJ u DXF (žičani) ----------- import ezdxf def obj_to_dxf_wireframe(obj_path, dxf_path): vertices, lines = parse_obj(obj_path) doc = ezdxf.new(dxfversion="R2010") msp = doc.modelspace() for i1, i2 in lines: p1 = vertices[i1] p2 = vertices[i2] msp.add_line(p1, p2) doc.saveas(dxf_path) print(f"✅ Sačuvan DXF žičani: {dxf_path}") # ----------- Statistika i DXF sa tipovima i sa numeracijom trouglova ----------- from stl import mesh as stl_mesh from plyfile import PlyData, PlyElement def side_lengths(triangle): a = np.linalg.norm(triangle[1] - triangle[0]) b = np.linalg.norm(triangle[2] - triangle[1]) c = np.linalg.norm(triangle[0] - triangle[2]) return sorted([a, b, c]) def angles(triangle): a, b, c = triangle ab = b - a bc = c - b ca = a - c def angle(u, v): cos_theta = np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v)) return degrees(acos(np.clip(cos_theta, -1.0, 1.0))) return sorted([ angle(ab, -ca), angle(bc, -ab), angle(ca, -bc) ]) def triangle_area(triangle): a = np.linalg.norm(triangle[1] - triangle[0]) b = np.linalg.norm(triangle[2] - triangle[1]) c = np.linalg.norm(triangle[0] - triangle[2]) s = (a + b + c) / 2 area = max(s * (s - a) * (s - b) * (s - c), 0) return np.sqrt(area) def normalize_tuple(lst, tol=1e-4): return tuple(round(x, 4) for x in lst) def group_triangles(triangles): groups = {} labels = [] group_stats = defaultdict(lambda: {"count": 0, "area": 0.0}) index = 1 for tri in triangles: sides = side_lengths(tri) angs = angles(tri) key = (normalize_tuple(sides), normalize_tuple(angs)) if key not in groups: groups[key] = index index += 1 label = groups[key] labels.append(label) group_stats[label]["count"] += 1 group_stats[label]["area"] += triangle_area(tri) return labels, group_stats, groups def save_csv(report_path, group_stats, groups): with open(report_path, 'w') as f: f.write(",Dužine stranica trouglova,,,Uglovi trouglova,,,," + "\n") f.write("Tip (ID),l1,l2,l3,a1,a2,a3,Broj trouglova,Ukupna površina (m²)\n") sorted_items = sorted(groups.items(), key=lambda x: groups[x[0]]) for key, label in sorted_items: sides, angles_ = key count = group_stats[label]["count"] area = group_stats[label]["area"] row = f"{label}," + ",".join(map(str, sides)) + "," + ",".join(map(str, angles_)) + f",{count},{round(area, 6)}" f.write(row + "\n") print(f"📄 Sačuvan CSV: {report_path}") def generate_colors(n): np.random.seed(0) return [tuple(np.random.randint(0, 255, 3)) for _ in range(n + 1)] def export_colored_ply(triangles, triangle_labels, output_path): vertex_list = [] face_list = [] vertex_map = {} idx = 0 for i, tri in enumerate(triangles): face_indices = [] for v in tri: key = tuple(np.round(v, 8)) if key not in vertex_map: vertex_map[key] = idx vertex_list.append((*key,)) idx += 1 face_indices.append(vertex_map[key]) face_list.append((face_indices, triangle_labels[i])) types = set(triangle_labels) colors = generate_colors(len(types)) type_to_color = {typ: colors[i] for i, typ in enumerate(sorted(types))} vertex_dtype = [('x', 'f4'), ('y', 'f4'), ('z', 'f4')] vertices_np = np.array(vertex_list, dtype=vertex_dtype) face_dtype = [('vertex_indices', 'i4', (3,)), ('red', 'u1'), ('green', 'u1'), ('blue', 'u1')] faces_np = np.empty(len(face_list), dtype=face_dtype) for i, (indices, label) in enumerate(face_list): faces_np[i]['vertex_indices'] = indices r, g, b = type_to_color[label] faces_np[i]['red'] = r faces_np[i]['green'] = g faces_np[i]['blue'] = b el_verts = PlyElement.describe(vertices_np, 'vertex') el_faces = PlyElement.describe(faces_np, 'face') PlyData([el_verts, el_faces], text=True).write(output_path) print(f"🎨 Sačuvan PLY sa bojama: {output_path}") def export_dxf(triangles, triangle_labels, output_path): import ezdxf from ezdxf.entities import Face3d from ezdxf.lldxf.const import DXFValueError doc = ezdxf.new(dxfversion="R2010") msp = doc.modelspace() colors = generate_colors(max(triangle_labels)) type_to_color = {typ: colors[typ] for typ in set(triangle_labels)} for typ in set(triangle_labels): layer_name = f"TIP_{typ}" if layer_name not in doc.layers: doc.layers.add(name=layer_name) for tri, label in zip(triangles, triangle_labels): layer = f"TIP_{label}" r, g, b = type_to_color[label] points = [tuple(tri[0]), tuple(tri[1]), tuple(tri[2]), tuple(tri[2])] try: face = Face3d.new(dxfattribs={ 'layer': layer, 'true_color': (r << 16) + (g << 8) + b }) face.dxf.vtx0 = points[0] face.dxf.vtx1 = points[1] face.dxf.vtx2 = points[2] face.dxf.vtx3 = points[3] msp.add_entity(face) except DXFValueError: continue center = tuple(np.mean(tri, axis=0)) text = msp.add_text( str(label), dxfattribs={ 'height': 0.1, 'layer': layer } ) text.dxf.insert = center doc.saveas(output_path) print(f"📐 Sačuvan DXF sa tipovima i brojevima: {output_path}") def export_dxf_numbered_triangles(triangles, output_path): import ezdxf doc = ezdxf.new(dxfversion="R2010") msp = doc.modelspace() layer_name = "NUM_TRIANGLES" if layer_name not in doc.layers: doc.layers.add(name=layer_name) for i, tri in enumerate(triangles, start=1): # nacrta trougao kao 3 linije for j in range(3): p1 = tri[j] p2 = tri[(j+1)%3] msp.add_line(p1, p2, dxfattribs={'layer': layer_name}) # dodaj tekst sa brojem trougla u centru trougla center = tuple(np.mean(tri, axis=0)) text = msp.add_text( str(i), dxfattribs={ 'height': 0.1, 'layer': layer_name } ) text.dxf.insert = center doc.saveas(output_path) print(f"📐 Sačuvan DXF sa numerisanim trouglovima: {output_path}") # ----------- GUI ----------- class DomeApp(tk.Tk): def __init__(self): super().__init__() self.title("Generator Spiralne Asimetrične Helix Zome Kupole") self.geometry("700x650") # Povećano zbog log prozora self.create_widgets() def create_widgets(self): frm = ttk.Frame(self, padding=10) frm.pack(fill=tk.BOTH, expand=True) row = 0 ttk.Label(frm, text="Broj spirala desno (n)").grid(column=0, row=row, sticky=tk.W) self.n_entry = ttk.Entry(frm) self.n_entry.insert(0, "24") self.n_entry.grid(column=1, row=row) row += 1 ttk.Label(frm, text="Broj segmenata po spirali (segs)").grid(column=0, row=row, sticky=tk.W) self.segs_entry = ttk.Entry(frm) self.segs_entry.insert(0, "24") self.segs_entry.grid(column=1, row=row) row += 1 ttk.Label(frm, text="Prečnik baze kupole (d)").grid(column=0, row=row, sticky=tk.W) self.d_entry = ttk.Entry(frm) self.d_entry.insert(0, "10") self.d_entry.grid(column=1, row=row) row += 1 ttk.Label(frm, text="Visina kupole (h)").grid(column=0, row=row, sticky=tk.W) self.h_entry = ttk.Entry(frm) self.h_entry.insert(0, "6") self.h_entry.grid(column=1, row=row) row += 1 ttk.Label(frm, text="Horizontalno pomeranje centra (x,y)").grid(column=0, row=row, sticky=tk.W) self.center_x_entry = ttk.Entry(frm, width=7) self.center_x_entry.insert(0, "0.0") self.center_x_entry.grid(column=1, row=row, sticky=tk.W) self.center_y_entry = ttk.Entry(frm, width=7) self.center_y_entry.insert(0, "0.0") self.center_y_entry.grid(column=1, row=row, sticky=tk.E) row += 1 ttk.Label(frm, text="Izaberite boju šipki za PLY").grid(column=0, row=row, sticky=tk.W) self.color_var = tk.IntVar(value=2) color_names = [f"{k}. {v[0]}" for k, v in sorted(BOJE.items())] self.color_combo = ttk.Combobox(frm, values=color_names, state="readonly") self.color_combo.current(1) self.color_combo.grid(column=1, row=row) row += 1 ttk.Label(frm, text="Ime izlaznog fajla (bez ekstenzije)").grid(column=0, row=row, sticky=tk.W) self.filename_entry = ttk.Entry(frm) self.filename_entry.insert(0, "kupola") self.filename_entry.grid(column=1, row=row) row += 1 self.generate_btn = ttk.Button(frm, text="Generiši i snimi fajlove", command=self.generate_all) self.generate_btn.grid(column=0, row=row, columnspan=2, pady=15) row += 1 ttk.Label(frm, text="🧾 Status izvođenja", font=("Arial", 10, "bold")).grid(column=0, row=row, columnspan=2, sticky="w") row += 1 self.log_box = scrolledtext.ScrolledText(frm, width=80, height=15, background="white", foreground="black") self.log_box.grid(column=0, row=row, columnspan=2, sticky="nsew") frm.rowconfigure(row, weight=1) frm.columnconfigure(1, weight=1) def log(self, poruka): self.log_box.insert(tk.END, poruka + "\n") self.log_box.see(tk.END) self.update() def generate_all(self): try: n = int(self.n_entry.get()) segs = int(self.segs_entry.get()) d = float(self.d_entry.get()) h = float(self.h_entry.get()) cx = float(self.center_x_entry.get()) cy = float(self.center_y_entry.get()) center_offset = np.array([cx, cy]) filename_base = self.filename_entry.get().strip() if not filename_base: messagebox.showerror("Greška", "Unesite ime fajla") return boja = BOJE[self.color_combo.current() + 1][1] self.log("➡️ Generišem spirale...") spirals_right = generate_spiral(n, segs, d, h, direction=1, center_offset=center_offset) spirals_left = generate_spiral(n, segs, d, h, direction=-1, center_offset=center_offset) self.log("➡️ Pronalazim preseke spirala...") intersections, index_map = find_intersections(spirals_right, spirals_left) self.log("➡️ Pravi se žičani OBJ...") obj_wireframe = filename_base + "_wireframe.obj" generate_wireframe_from_intersections(obj_wireframe, spirals_right, spirals_left, index_map) self.log("➡️ Konvertujem u STL...") stl_path = filename_base + ".stl" triangles = obj_to_stl(obj_wireframe, stl_path) self.log("➡️ Konvertujem u PLY...") ply_path = filename_base + ".ply" obj_to_ply(obj_wireframe, ply_path, boja) self.log("➡️ Konvertujem u DXF (žičani)...") dxf_wire_path = filename_base + "_wireframe.dxf" obj_to_dxf_wireframe(obj_wireframe, dxf_wire_path) self.log("➡️ Pravim CSV izveštaj i označene modele...") csv_path = filename_base + ".csv" dxf_tips_path = filename_base + "_tips.dxf" triangle_labels, group_stats, groups = group_triangles(triangles) save_csv(csv_path, group_stats, groups) export_colored_ply(triangles, triangle_labels, filename_base + "_labeled.ply") export_dxf(triangles, triangle_labels, dxf_tips_path) self.log("✅ Svi fajlovi su uspešno generisani.") messagebox.showinfo("Uspeh", "Svi fajlovi su uspešno generisani!") except Exception as e: messagebox.showerror("Greška", f"Došlo je do greške:\n{str(e)}") self.log(f"❌ Greška: {str(e)}") if __name__ == "__main__": app = DomeApp() app.mainloop()