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 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

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()

By Abel

Leave a Reply

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