Kako kompajlirati Python GUI aplikaciju na Ubuntu i Windows-u koristeći PyInstaller

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:

  1. Python 3
  2. pip (Python paket menadžer)
  3. PyInstaller (alat za pravljenje izvršnih fajlova)
  4. tkinter (za GUI)
  5. 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()

By Abel

Leave a Reply

Your email address will not be published. Required fields are marked *