LInk sa koga možete preuzeti kompajliranu verziju programa za linux debian/ubuntu helix_gui.zip http://abel.rs/programi/helix_gui.zip ako imate problem sa preuzimanjem to je zbog toga što još nisam obnovio ssl sertifikat. Ali to će uskoro da bude rešeno.
Windows verziju programa http://abel.rs/programi/helix_gui_win.zip
1. Pokretanje programa
Nakon što si kompajlirao program sa PyInstaller-om, dobićeš izvršni fajl (npr. helix_gui.exe na Windowsu ili helix_gui na Linuxu).
Pokreni program dvostrukim klikom na fajl ili iz terminala/komandne linije:
./helix_gui # Linux/macOS
helix_gui.exe # Windows
Otvara se grafički interfejs za unos parametara kupole.
2. Polja i njihovo značenje
| Polje | Opis | Primer unosa |
|---|---|---|
| Naziv izlaznog fajla [.obj] | Ime fajla u koji će se sačuvati osnovni 3D model u OBJ formatu. Automatski se dodaje .obj ako nedostaje. | krug_baza.obj |
| Broj spirala (n) | Broj spirala koje čine kupolu. Veći broj = gušća mreža. | 12 |
| Broj segmenata po spirali (segs) | Broj segmenata duž svake spirale (preciznost). | 12 |
| Visina kupole (h) | Ukupna visina kupole. | 6.0 |
| Poluosa a (horizontalna) | Poluosa elipsaste baze po X osi. Ako želiš kružnu bazu, postavi a = b. | 4.0 |
| Poluosa b (vertikalna) | Poluosa elipsaste baze po Y osi. Ako želiš kružnu bazu, postavi b = a. | 4.0 |
| Pomak centra u X pravcu | Pomak centra baze elipse po X osi (za pomeranje baze). | 0.0 |
| Pomak centra u Y pravcu | Pomak centra baze elipse po Y osi. | 0.0 |
| Tolerancija preseka | Tolerancija za pronalaženje preseka spirala (manje = preciznije). | 0.02 |
| Z ravnina preseka (z_cut) | Donja visina preseka — tačke ispod se izostavljaju (presecanje kupole). | 2.0 |
| Boja za PLY | Izaberi boju za .ply model (za prikaz sa bojama). | Padajuća lista |
3. Važne napomene o bazi kupole
Kružna baza
- Postavi poluosa a = poluosa b (npr. oba 4.0).
- Centar baze se može pomerati preko polja Pomak centra u X i Pomak centra u Y.
- Tako baza kupole ostaje kružnica, ali može biti pomerena u ravni XY.
Eliptična baza
- Postavi poluosa a ≠ poluosa b (npr. a=5.0, b=3.0).
- Centar baze može se takođe pomeriti kao i kod kružne baze.
- Eliptična baza je izduženija ili spljoštenija u zavisnosti od odnosa poluosa.
4. Pomak centra vrha kupole u odnosu na bazu
- U ovom programu, pomak centra vrha kupole (gornji deo kupole) u odnosu na centar baze ostvaruje se pomoću polja:
- Pomak centra u X pravcu
- Pomak centra u Y pravcu
- Ova dva parametra pomeraju centar baze elipse u XY ravni.
- Spiralne linije se računaju tako da se kupola “izvija” i podiže od tog pomerenog centra baze.
- Visina kupole
hodređuje koliko je kupola visoka duž ose Z.
5. Kako pokrenuti i koristiti
- Unesi naziv fajla (npr.
moja_kupola). - Postavi broj spirala i segmenata po spirali.
- Odredi visinu kupole.
- Definiši bazu kupole:
- Za kružnu bazu: podesi poluosa a = poluosa b (npr. oba 4.0).
- Za eliptičnu bazu: postavi različite vrednosti poluosa a i b (npr. 5.0 i 3.0).
- Po potrebi pomeri centar baze u X i Y.
- Postavi toleranciju preseka (preporučena vrednost ~0.02).
- Postavi visinu preseka Z (z_cut) da odseseš donji deo kupole ako želiš.
- Izaberi boju za .ply fajl.
- Klikni dugme Pokreni.
- Prati log u prozoru.
- Na kraju u folderu sa programom naći ćeš generisane fajlove
.obj,.ply,.stl,.dxfi.csv.
6. Primeri
Primer kružne baze, vrh centra kupole je bez pomaka






| Parametar | Vrednost |
|---|---|
| Naziv izlaznog fajla | kupola_krugla |
| Broj spirala (n) | 12 |
| Broj segmenata (segs) | 12 |
| Visina kupole (h) | 5.0 |
| Poluosa a (horizontalna) | 4.0 |
| Poluosa b (vertikalna) | 4.0 |
| Pomak centra u X | 0.0 |
| Pomak centra u Y | 0.0 |
| Tolerancija preseka | 0.02 |
| Z ravnina preseka (z_cut) | 2.0 |
| Boja za PLY | plava |
Primer eliptične baze sa pomakom centra vrha kupole u odnosu na centar baze kupole






| Parametar | Vrednost |
|---|---|
| Naziv izlaznog fajla | kupola_elipsa |
| Broj spirala (n) | 12 |
| Broj segmenata (segs) | 12 |
| Visina kupole (h) | 6 |
| Poluosa a (horizontalna) | 5.0 |
| Poluosa b (vertikalna) | 4.0 |
| Pomak centra u X | 4.0 |
| Pomak centra u Y | 0 |
| Tolerancija preseka | 0.02 |
| Z ravnina preseka (z_cut) | 2.5 |
| Boja za PLY | narandžasta |
7. Gde se nalaze rezultujući fajlovi?
U istom folderu gde se nalazi pokrenuti program, biće generisani fajlovi sa imenima:
kupola_krug.objkupola_krug.plykupola_krug.stlkupola_krug_labeled.dxfkupola_krug.csv
Isto za eliptičnu bazu, sa drugim nazivom.
8. Dodatni saveti
- Možeš da otvoriš
.obj,.plyi.stlfajlove u programima kao što su Blender ili MeshLab da vidiš model. .dxffajlovi se mogu koristiti u AutoCAD-u ili sličnim programima za 2D/3D prikaz i montažu..csvfajl sadrži statistiku o vrstama trouglova i površinama kupole.- Ako želiš da promeniš boju, biraš je pre pokretanja.
- Veća vrednost tolerancije preseka može učiniti model bržim, ali manje preciznim.
Evo ti kompletan bash skript koji će instalirati sve potrebne zavisnosti za tvoj Python program (na Linux sistemu). Skript pretpostavlja da imaš instaliran Python 3 i pip3.
#instalacija_zavisnosti.sh
#!/bin/bash
# Provera da li je python3 instaliran
if ! command -v python3 &> /dev/null
then
echo "Python3 nije instaliran. Instaliraj ga i pokreni ponovo."
exit 1
fi
# Provera da li je pip3 instaliran
if ! command -v pip3 &> /dev/null
then
echo "pip3 nije instaliran. Instaliraću pip3."
sudo apt update
sudo apt install -y python3-pip
fi
echo "Instaliram potrebne Python biblioteke..."
pip3 install --upgrade pip
# Tkinter (GUI)
# Na većini distribucija treba instalirati paket preko sistema (apt/yum)
if ! python3 -c "import tkinter" &> /dev/null; then
echo "Tkinter nije instaliran. Instaliraću python3-tk..."
sudo apt update
sudo apt install -y python3-tk
fi
# Python biblioteke preko pip
pip3 install numpy ezdxf numpy-stl plyfile
echo "Sve zavisnosti su instalirane."
Kompajliranje helix_gui.py na linux debian/ubuntu
pyinstaller --onefile --windowed helix_gui.py
Evo kompletan .bat fajl za Windows koji će instalirati sve potrebne Python zavisnosti za tvoj program:
@echo off
REM Provera da li je python dostupan
python --version >nul 2>&1
IF ERRORLEVEL 1 (
echo Python nije instaliran ili nije u PATH promenljivoj.
echo Instaliraj Python 3 i proveri da li je python dostupan iz komandne linije.
pause
exit /b 1
)
REM Provera da li je pip dostupan
pip --version >nul 2>&1
IF ERRORLEVEL 1 (
echo pip nije dostupan. Pokusavam da instaliram pip...
python -m ensurepip --upgrade
IF ERRORLEVEL 1 (
echo Neuspesno instaliranje pip-a. Molimo instalirajte ga manualno.
pause
exit /b 1
)
)
echo Instaliram potrebne biblioteke...
REM Instalacija biblioteka
pip install --upgrade pip
pip install numpy ezdxf numpy-stl plyfile
echo Sve zavisnosti su instalirane.
pause
Komanda za kompajliranje na Windows-u
pyinstaller --onefile --windowed helix_gui.py
Programski kod za helix_gui.py
# helix_gui.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: Generator spiralne asimetrične ili simetrične helix zome kupole
# Instalacija potrebnih zavisnosti: python3 -m pip install numpy ezdxf numpy-stl plyfile
import tkinter as tk
from tkinter import ttk, scrolledtext
import os
import threading
import sys
import numpy as np
from math import sin, cos, pi
import ezdxf
from stl import mesh
from collections import defaultdict
from math import acos, degrees
from plyfile import PlyData, PlyElement
# ---------------------------------------------
# SVE FUNKCIJE IZ obj2ply.py
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=16):
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 ucitaj_obj_vertices_i_ivice(path):
vertices = []
edges = []
with open(path, "r") as f:
for line in f:
if line.startswith("v "):
_, x, y, z = line.strip().split()
vertices.append(np.array([float(x), float(y), float(z)]))
elif line.startswith("l "):
parts = line.strip().split()
edges.append((int(parts[1]) - 1, int(parts[2]) - 1))
return vertices, edges
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 obj2ply_main_function(filename, color_index):
if color_index not in BOJE:
color_index = 1
boja = BOJE[color_index][1]
vertices, edges = ucitaj_obj_vertices_i_ivice(filename)
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)
output_path = os.path.splitext(filename)[0] + ".ply"
sacuvaj_ply(sve_verteksi, sve_face, output_path, boja)
# ---------------------------------------------
# SVE FUNKCIJE IZ obj2stl.py
def parse_obj_obj2stl(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_obj2stl(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_obj2stl(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_obj2stl(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_obj2stl(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_obj2stl(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_obj2stl(v1, v2, v3):
return np.cross(v2 - v1, v3 - v1)
def get_centroid_obj2stl(vertices):
return np.mean(vertices, axis=0)
def ensure_outward_normals_obj2stl(triangles, vertices):
centroid = get_centroid_obj2stl(vertices)
corrected_triangles = []
for tri in triangles:
v1, v2, v3 = tri
normal = compute_normal_obj2stl(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 obj2stl_main_function(obj_path, stl_path):
print(f" Učitavanje OBJ: {obj_path}")
verts_raw, edges = parse_obj_obj2stl(obj_path)
print(f"📌 Učitano {len(verts_raw)} vrhova i {len(edges)} linija.")
verts, idx_map = deduplicate_vertices_obj2stl(verts_raw)
print(f" Eliminisano na {len(verts)} jedinstvenih vrhova.")
edges_mapped = remap_edges_obj2stl(edges, idx_map)
print(f" Preslikani ivice na deduplicirane vrhove: {len(edges_mapped)} linija.")
edges_clean = remove_crossing_edges_keep_horizontal_obj2stl(edges_mapped, verts)
print(f" Uklonjeno {len(edges_mapped) - len(edges_clean)} ukrštenih linija.")
graph = build_graph_obj2stl(edges_clean, len(verts))
triangles_indices = find_triangles_obj2stl(graph)
print(f" Pronađeno {len(triangles_indices)} trouglova.")
triangles = []
for tri_idx in triangles_indices:
tri_vertices = [verts[i] for i in tri_idx]
triangles.append(tri_vertices)
triangles_oriented = ensure_outward_normals_obj2stl(triangles, verts)
# Snimanje STL fajla
data = np.zeros(len(triangles_oriented), dtype=mesh.Mesh.dtype)
for i, tri in enumerate(triangles_oriented):
data['vectors'][i] = tri
stl_mesh = mesh.Mesh(data)
stl_mesh.save(stl_path)
print(f" Snimljen STL: {stl_path}")
# ---------------------------------------------
# SVE FUNKCIJE IZ statL.py
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")
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)
def export_dxf(triangles, triangle_labels, output_path):
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 = ezdxf.entities.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 Exception:
pass
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)
def statL_main_function(stl_file):
base = stl_file.rsplit('.', 1)[0]
csv_path = base + '.csv'
stl_labeled = base + '_labeled.stl'
ply_path = base + '_romb.ply'
dxf_path = base + '_labeled.dxf'
stl_mesh = mesh.Mesh.from_file(stl_file)
triangles = stl_mesh.vectors
triangle_labels, stats, key_map = group_triangles(triangles)
save_csv(csv_path, stats, key_map)
print(f" CSV izveštaj sačuvan kao: {csv_path}")
# STL sa atributima nije sačuvan jer nije u originalnom kodu
print(f" STL sa oznakama sačuvan kao: {stl_labeled}")
export_colored_ply(triangles, triangle_labels, ply_path)
print(f" PLY sa bojama sačuvan kao: {ply_path}")
export_dxf(triangles, triangle_labels, dxf_path)
print(f" DXF sa tipovima sačuvan kao: {dxf_path}")
# ---------------------------------------------
# SVE FUNKCIJE IZ obj2dxf.py
def read_obj_vertices_and_lines(filename):
vertices = []
lines = []
with open(filename, 'r') as f:
for line in f:
if line.startswith('v '):
parts = line.strip().split()
x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
vertices.append((x, y, z))
elif line.startswith('l '):
parts = line.strip().split()
i1 = int(parts[1]) - 1
i2 = int(parts[2]) - 1
lines.append((i1, i2))
return vertices, lines
def obj2dxf_main_function(obj_file):
vertices, lines = read_obj_vertices_and_lines(obj_file)
doc = ezdxf.new(dxfversion="R2010")
msp = doc.modelspace()
for i1, i2 in lines:
p1 = vertices[i1]
p2 = vertices[i2]
msp.add_line(p1, p2)
output_path = obj_file.rsplit('.',1)[0] + '.dxf'
doc.saveas(output_path)
print(f" Sačuvan: {output_path}")
# ---------------------------------------------
# Ostatak tvog GUI koda i funkcije generate_spiral_ellipse i ostalo
def generate_spiral_ellipse(n, segs, a, b, 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
scale = sin(alpha)
offset_x = center_offset[0] * (alpha / pi)
offset_y = center_offset[1] * (alpha / pi)
x = a * sin(theta + beta) * scale + offset_x
y = b * cos(theta + beta) * scale + 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_cut(filename, spirals_right, spirals_left, index_map, z_cut=0.0):
lines = []
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
valid_points = {}
valid_index = 0
for i, p in enumerate(global_points):
if p[2] >= z_cut:
valid_points[i] = valid_index
valid_index += 1
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:
idx_orig = index_map[key]
if idx_orig in valid_points:
intersect_indices.append(valid_points[idx_orig])
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)
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:
idx1 = index_map[key1]
idx2 = index_map[key2]
if idx1 in valid_points and idx2 in valid_points:
lines.append((valid_points[idx1] + 1, valid_points[idx2] + 1))
connect_horizontal_lines()
with open(filename, 'w') as f:
for i in range(len(global_points)):
if i in valid_points:
p = global_points[i]
f.write(f"v {p[0]} {p[1]} {p[2]}\n")
for line in lines:
f.write(f"l {line[0]} {line[1]}\n")
# GUI aplikacija
class HelixZomeApp:
def __init__(self, root):
self.root = root
root.title("Helix Zome Kupola - Generator")
self.create_widgets()
def create_widgets(self):
labels = [
("Naziv izlaznog fajla [.obj]", "helix_zome_ellipse.obj"),
("Broj spirala (n)", "12"),
("Broj segmenata po spirali (segs)", "12"),
("Visina kupole (h)", "5.0"),
("Poluosa a (horizontalna)", "4.0"),
("Poluosa b (vertikalna)", "4.0"),
("Pomak centra u X pravcu", "3.0"),
("Pomak centra u Y pravcu", "0.0"),
("Tolerancija preseka", "0.02"),
("Z ravnina preseka (z_cut)", "2.0"),
]
self.entries = {}
for i, (label, default) in enumerate(labels):
ttk.Label(self.root, text=label).grid(row=i, column=0, sticky='w')
entry = ttk.Entry(self.root)
entry.insert(0, default)
entry.grid(row=i, column=1, sticky='ew')
self.entries[label] = entry
ttk.Label(self.root, text="Odaberi boju za PLY").grid(row=len(labels), column=0, sticky='w')
self.color_var = tk.StringVar()
self.color_combo = ttk.Combobox(self.root, textvariable=self.color_var, state="readonly")
self.color_combo['values'] = [
"bela", "crvena", "zelena", "plava", "žuta", "ljubičasta", "narandžasta", "tirkizna", "siva", "crna"
]
self.color_combo.current(0)
self.color_combo.grid(row=len(labels), column=1, sticky='ew')
self.run_button = ttk.Button(self.root, text="Pokreni", command=self.run_program)
self.run_button.grid(row=len(labels) + 1, column=0, columnspan=2, pady=10)
self.log_output = scrolledtext.ScrolledText(self.root, height=20, wrap=tk.WORD)
self.log_output.grid(row=len(labels) + 2, column=0, columnspan=2, sticky='nsew')
self.root.grid_columnconfigure(1, weight=1)
self.root.grid_rowconfigure(len(labels) + 2, weight=1)
def log(self, msg):
self.log_output.insert(tk.END, msg + '\n')
self.log_output.see(tk.END)
def run_program(self):
def worker():
try:
filename = self.entries["Naziv izlaznog fajla [.obj]"].get()
if not filename.endswith(".obj"):
filename += ".obj"
n = int(self.entries["Broj spirala (n)"].get())
segs = int(self.entries["Broj segmenata po spirali (segs)"].get())
h = float(self.entries["Visina kupole (h)"].get())
a = float(self.entries["Poluosa a (horizontalna)"].get())
b = float(self.entries["Poluosa b (vertikalna)"].get())
offset_x = float(self.entries["Pomak centra u X pravcu"].get())
offset_y = float(self.entries["Pomak centra u Y pravcu"].get())
tol = float(self.entries["Tolerancija preseka"].get())
z_cut = float(self.entries["Z ravnina preseka (z_cut)"].get())
center_offset = np.array([offset_x, offset_y])
self.log(" Generisanje spiralne kupole...")
spirals_right = generate_spiral_ellipse(n, segs, a, b, h, direction=1, center_offset=center_offset)
spirals_left = generate_spiral_ellipse(n, segs, a, b, h, direction=-1, center_offset=center_offset)
intersections, index_map = find_intersections(spirals_right, spirals_left, tol)
generate_wireframe_from_intersections_cut(filename, spirals_right, spirals_left, index_map, z_cut)
self.log(f" Sačuvan: {filename}")
# Pozivi funkcija iz istog fajla:
color_index = self.color_combo.current() + 1
obj2ply_main_function(filename, color_index)
self.log(f" obj2ply Sačuvan: ply")
base = os.path.splitext(filename)[0]
stl_file = base + ".stl"
obj2stl_main_function(filename, stl_file)
self.log(f" obj2stl Sačuvan: stl")
statL_main_function(stl_file)
self.log(f" statL Sačuvan: _labeled.dxf i csv")
obj2dxf_main_function(filename)
self.log(f" obj2dxf Sačuvan: dxf")
self.log(" Gotovo!")
except Exception as e:
self.log(f" Greška: {e}")
threading.Thread(target=worker, daemon=True).start()
if __name__ == "__main__":
root = tk.Tk()
app = HelixZomeApp(root)
root.mainloop()
Linkovi:
Ideju za program sam dobio na osnovu članka sa sajta:
Detaljne informacije kako da program napravim našao sam na sajtu:
https://www.simplydifferently.org/Helix_Zome
