Geometrijski i matematički kontekst
Ova eliptična kupola nije klasična geodetska sfera niti segment sfere ili paraboloida. Umesto toga, zasniva se na minimalnoj površini — vrsti površine koja u svakoj tački minimizuje površinu pod uslovom da zadovoljava zadate ivice ili konture. To je ista matematika koja opisuje sapunske opne (soap films).
U ovom slučaju, konkretan oblik kupole je verovatno inspirisan Enneperovom površinom ili jednom od Schwarzovih P, D, H površina (poznatih minimalnih površina).
Minimalne površine se formalno definišu kao površine kod kojih je srednja zakrivljenost (mean curvature) jednaka nuli u svakoj tački:
Eliptična kupola je sofisticirana arhitektonska forma koja kombinuje estetsku privlačnost sa tehničkim i akustičkim prednostima. Koristi se u građevinama koje zahtevaju posebnu akustiku, jedinstven vizuelni doživljaj i često se sreće u sakralnoj i javnoj arhitekturi.
Eliptična kupola zasnovana na minimalnim površinama
Geometrijski i matematički kontekst
Ova eliptična kupola nije klasična geodetska sfera niti segment sfere ili paraboloida. Umesto toga, zasniva se na minimalnoj površini – vrsti površine koja u svakoj tački minimizuje ukupnu površinu pod uslovom da zadovoljava određene konture. Takve površine se prirodno javljaju u fenomenu sapunskih opni.
U ovom slučaju, oblik kupole je inspirisan poznatim minimalnim površinama kao što su Enneperova površina i Schwarzove P, D i H površine. Njih karakteriše nulta srednja zakrivljenost:
H = (κ₁ + κ₂)/2 = 0
gde su κ₁ i κ₂ glavne zakrivljenosti u datoj tački površine.
Matematički model i numerička aproksimacija
Kupola se generiše numeričkim putem, najčešće diskretizacijom eliptične mreže i relaksacijom čvorova do forme koja zadovoljava minimalnu površinu. Primer parametrizacije Enneperove površine glasi:
x(u, v) = u - (u³)/3 + u·v²
y(u, v) = v - (v³)/3 + v·u²
z(u, v) = u² - v²
Za Schwarzove površine koristi se rešetkasta struktura u fundamentalnoj ćeliji, često generisana numerički. Program u Pythonu koristi sferoidne koordinate i optimizuje položaje tačaka tako da mreža šipki pokrije površinu nalik minimalnoj.
Arhitektonska primena
Strukture inspirisane minimalnim površinama nalaze široku primenu u savremenoj arhitekturi zbog:
- optimalnog prenosa sila i stabilnosti konstrukcije,
- efikasne upotrebe materijala,
- estetskog i skulpturalnog izraza,
- lakog uklapanja u prirodna i urbane sredine.
Eliptične kupole ove vrste mogu se realizovati kao tensigrity sistemi, membranske konstrukcije, ili – kao u ovom slučaju – kao mrežne strukture od krutih šipki.
Tehničke metode proračuna
U numeričkom modeliranju ovakvih kupola koriste se metode kao što su:
- relaksacija opružnih mreža (spring relaxation),
- Laplaceovo izravnavanje čvorova,
- metode konačnih elemenata (FEM) za minimizaciju energije,
- diskretna Delaunay triangulacija na površini elipsoida.
Korišćenjem ovih metoda, kupola dobija stabilnu i optimizovanu formu sa izraženom geometrijskom harmonijom.
Ova konstrukcija je rezultat spoja matematičkog modelovanja i arhitektonske intuicije, predstavljajući savremen pristup oblikovanju prostora kroz forme koje priroda sama preferira.
PROGRAMSKI KOD

Instalacija potrebnih paketa
Otvorite terminal (Command Prompt, PowerShell, Terminal ili Anaconda Prompt) i ukucaj sledeću komandu:
pip install numpy meshio ezdxf matplotlib
Objašnjenje:
numpy— za numeričke proračune.meshio— za čitanje i pisanje 3D mesh fajlova.ezdxf— za rad sa DXF fajlovima.matplotlib— za colormap (cm) i druge grafičke funkcije.
Programski kod za eliptkup.py
#eliptkup.py
# Instalacija potrebnih zavisnosti:pip install numpy meshio ezdxf matplotlib
import numpy as np
import meshio
import ezdxf
import tkinter as tk
from tkinter import ttk, messagebox
from math import pi, cos, sin
from matplotlib import cm # ispravan uvoz cmap
def generate_ellipsoid_grid(a, b, c, u_steps=50, v_steps=25):
u = np.linspace(0, 2 * pi, u_steps)
v = np.linspace(0, pi / 2, v_steps)
points = []
for vi in v:
for ui in u:
x = a * cos(ui) * sin(vi)
y = b * sin(ui) * sin(vi)
z = c * cos(vi)
points.append([x, y, z])
points = np.array(points)
return points, u_steps, v_steps
def generate_faces(u_steps, v_steps):
faces = []
for i in range(v_steps - 1):
for j in range(u_steps - 1):
p0 = i * u_steps + j
p1 = p0 + 1
p2 = p0 + u_steps
p3 = p2 + 1
# dva trougla
faces.append([p0, p2, p1])
faces.append([p1, p2, p3])
# Zatvaranje prstena oko u
p0 = i * u_steps + (u_steps - 1)
p1 = i * u_steps
p2 = p0 + u_steps
p3 = p1 + u_steps
faces.append([p0, p2, p1])
faces.append([p1, p2, p3])
return np.array(faces)
def correct_normals(points, faces):
center = np.array([0,0,0])
new_faces = []
for f in faces:
v0, v1, v2 = points[f[0]], points[f[1]], points[f[2]]
normal = np.cross(v1 - v0, v2 - v0)
centroid = (v0 + v1 + v2) / 3
if np.dot(normal, centroid - center) < 0:
new_faces.append([f[0], f[2], f[1]])
else:
new_faces.append([f[0], f[1], f[2]])
return np.array(new_faces)
def filter_degenerate_faces(points, faces, tol=1e-8):
filtered = []
for f in faces:
v0, v1, v2 = points[f[0]], points[f[1]], points[f[2]]
area = np.linalg.norm(np.cross(v1 - v0, v2 - v0)) / 2
if area > tol:
filtered.append(f)
return np.array(filtered)
def export_stl(filename, points, faces):
cells = [("triangle", faces.astype(np.int32))]
meshio.write_points_cells(filename, points.astype(np.float32), cells)
def export_dxf(filename, points, faces):
doc = ezdxf.new(dxfversion='R2010')
msp = doc.modelspace()
for f in faces:
v0, v1, v2 = points[f[0]], points[f[1]], points[f[2]]
msp.add_3dface([v0.tolist(), v1.tolist(), v2.tolist()])
doc.saveas(filename)
def generate_cylinder_mesh(p0, p1, radius, segments=12):
axis = p1 - p0
length = np.linalg.norm(axis)
if length < 1e-12:
return np.array([]), np.array([])
axis = axis / length
if abs(axis[0]) < 1e-6 and abs(axis[1]) < 1e-6:
tangent = np.array([1,0,0])
else:
tangent = np.array([-axis[1], axis[0], 0])
tangent /= np.linalg.norm(tangent)
bitangent = np.cross(axis, tangent)
angle_step = 2 * pi / segments
circle_points_start = []
circle_points_end = []
for i in range(segments):
angle = i * angle_step
offset = radius * (cos(angle) * tangent + sin(angle) * bitangent)
circle_points_start.append(p0 + offset)
circle_points_end.append(p1 + offset)
vertices = np.array(circle_points_start + circle_points_end)
faces = []
for i in range(segments):
i_next = (i + 1) % segments
faces.append([i, i_next, segments + i])
faces.append([segments + i, i_next, segments + i_next])
return vertices, np.array(faces)
def create_pipes_mesh(points, faces, radius, segments=12):
edges_set = set()
for tri in faces:
for i in range(3):
a, b = sorted((tri[i], tri[(i+1)%3]))
edges_set.add((a,b))
edges = list(edges_set)
# Grupisanje ivica po dužini
lengths = np.array([np.linalg.norm(points[e[0]] - points[e[1]]) for e in edges])
unique_lengths = np.unique(np.round(lengths, decimals=5)) # za grupisanje sa malom tolerancijom
# Karta od dužine do boje
cmap = cm.get_cmap('jet', len(unique_lengths))
length_to_color = {}
for i, ul in enumerate(unique_lengths):
length_to_color[ul] = cmap(i)[:3] # rgb float 0-1
all_vertices = []
all_faces = []
all_colors = []
vertex_offset = 0
for edge, length in zip(edges, lengths):
p0, p1 = points[edge[0]], points[edge[1]]
cyl_verts, cyl_faces = generate_cylinder_mesh(p0, p1, radius, segments)
if cyl_verts.size == 0:
continue
all_vertices.append(cyl_verts)
all_faces.append(cyl_faces + vertex_offset)
# Dodeli boju na osnovu dužine (nalazi najbližu jedinstvenu dužinu)
length_key = unique_lengths[np.abs(unique_lengths - round(length,5)).argmin()]
rgb = length_to_color[length_key]
# Pretvori u 0-255 uint8
color = np.array(rgb) * 255
color = color.astype(np.uint8)
# Svaki vertex cilindra ima istu boju
all_colors.append(np.tile(color, (len(cyl_verts),1)))
vertex_offset += len(cyl_verts)
if len(all_vertices) == 0:
return np.array([]), np.array([]), np.array([])
all_vertices = np.vstack(all_vertices)
all_faces = np.vstack(all_faces)
all_colors = np.vstack(all_colors)
return all_vertices, all_faces, all_colors
def export_ply(filename, vertices, faces, colors):
if vertices.size == 0 or faces.size == 0:
raise ValueError("Nema podataka za eksport PLY")
point_data = {
"red": colors[:, 0],
"green": colors[:, 1],
"blue": colors[:, 2]
}
cells = [("triangle", faces.astype(np.int32))]
meshio.write_points_cells(
filename,
vertices.astype(np.float32),
cells,
point_data=point_data
)
def on_generate():
try:
length = float(entry_length.get())
width = float(entry_width.get())
height = float(entry_height.get())
pipe_diameter = float(entry_diameter.get())
stl_file = entry_stl.get()
ply_file = entry_ply.get()
dxf_file = entry_dxf.get()
a = length / 2
b = width / 2
c = height
print("Generišem tačke...")
points, u_steps, v_steps = generate_ellipsoid_grid(a, b, c)
print("Generišem plohe...")
faces = generate_faces(u_steps, v_steps)
print("Ispravljam normale...")
faces = correct_normals(points, faces)
print("Filtriram degenerisane plohe...")
faces = filter_degenerate_faces(points, faces)
print(f"Exportujem STL u '{stl_file}'...")
export_stl(stl_file, points, faces)
print(f"Exportujem DXF u '{dxf_file}'...")
export_dxf(dxf_file, points, faces)
print("Kreiram cevaste šipke kao cilindre...")
pipe_radius = pipe_diameter / 2
pipe_verts, pipe_faces, pipe_colors = create_pipes_mesh(points, faces, pipe_radius, segments=12)
print(f"Exportujem PLY sa cevima u '{ply_file}'...")
export_ply(ply_file, pipe_verts, pipe_faces, pipe_colors)
messagebox.showinfo("Gotovo", "Model je uspešno generisan i sačuvan!")
except Exception as e:
messagebox.showerror("Greška", str(e))
root = tk.Tk()
root.title("Generator elipsaste kupole")
frame = ttk.Frame(root, padding=10)
frame.grid(row=0, column=0)
ttk.Label(frame, text="Dužina kupole (m):").grid(row=0, column=0, sticky="w")
entry_length = ttk.Entry(frame)
entry_length.insert(0, "6.0")
entry_length.grid(row=0, column=1)
ttk.Label(frame, text="Širina kupole (m):").grid(row=1, column=0, sticky="w")
entry_width = ttk.Entry(frame)
entry_width.insert(0, "4.5")
entry_width.grid(row=1, column=1)
ttk.Label(frame, text="Visina kupole (m):").grid(row=2, column=0, sticky="w")
entry_height = ttk.Entry(frame)
entry_height.insert(0, "3.0")
entry_height.grid(row=2, column=1)
ttk.Label(frame, text="Prečnik šipki (m):").grid(row=3, column=0, sticky="w")
entry_diameter = ttk.Entry(frame)
entry_diameter.insert(0, "0.04")
entry_diameter.grid(row=3, column=1)
ttk.Label(frame, text="Ime STL fajla:").grid(row=4, column=0, sticky="w")
entry_stl = ttk.Entry(frame)
entry_stl.insert(0, "kupola.stl")
entry_stl.grid(row=4, column=1)
ttk.Label(frame, text="Ime PLY fajla:").grid(row=5, column=0, sticky="w")
entry_ply = ttk.Entry(frame)
entry_ply.insert(0, "kupola_pipes.ply")
entry_ply.grid(row=5, column=1)
ttk.Label(frame, text="Ime DXF fajla:").grid(row=6, column=0, sticky="w")
entry_dxf = ttk.Entry(frame)
entry_dxf.insert(0, "kupola.dxf")
entry_dxf.grid(row=6, column=1)
btn_generate = ttk.Button(frame, text="Generiši model", command=on_generate)
btn_generate.grid(row=7, column=0, columnspan=2, pady=10)
root.mainloop()
