geo.py Izračunavanje geodetske kupole

Program je koristan za inženjere ili entuzijaste koji žele da dizajniraju i izgrade geodetske kupole, jer automatski obračunava potrebne materijale i generiše izveštaje koji se mogu koristiti za nabavku potrebnih materijala.

Ovaj Python kod se koristi za generisanje geodetske kupole (bazirane na ikosaedru) sa mogućnošću izračunavanja i eksportovanja raznih informacija o kupoli, uključujući:

  1. Ulazne vrednosti:
    • Frekvencija (npr. 2, 3, 4… za različite subdivizije ikosaedra).
    • Prečnik kupole.
    • Ugao rotacije oko Y ose.
    • Ugao sečenja od vrha kupole.
    • Dimenzije letvica (visina i širina).
  2. Geometrija kupole:
    • Kreiranje kupole sa promenjivom frekvencijom i prečnikom.
    • Rotacija kupole prema unetom uglu rotacije.
    • Sečenje dela kupole na osnovu ugla od vrha.
  3. Eksport fajlova:
    • STL i OBJ fajlovi sa geometrijom kupole.
    • CSV fajlovi sa podacima o čvorovima, ivicama, dužinama ivica i broju letvica po čvoru.
    • CSV sa podacima o dužinama letvica i njihovoj boji (na osnovu dužine).
    • Montaža šipki u PLY i GLB formatima.
  4. Statistički podaci:
    • Ukupna dužina letvica.
    • Kubikaža letvica (zapremina drvene konstrukcije).
    • Površina kupole, zapremina kupole, pokrivena površina na tlu.
    • Težina drvene konstrukcije, bazirana na gustini drveta.

Kod koristi biblioteku Trimesh za 3D geometriju i kreiranje objekata, a Matplotlib i CSV za vizualizaciju i izvoz podataka.

# 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.
"""
---------------------------------------------------------
Program: Dome Generator & Analyzer
Opis: Program za generisanje geodetske kupole bazirane na ikosaedru, sa mogućnostima izvoza u STL, OBJ, PLY, i GLB formate, kao i izračunavanja statistike i analiza konstrukcije.
Verzija: 1.0.0
Autor: Aleksandar Maričić
Datum: April 2025
Kopirajt: © 2025 Aleksandar Maričić. Sva prava zadržana.
Licenca: MIT License
Zahtevi: Python 3.7+, Trimesh, NumPy, Matplotlib
---------------------------------------------------------
"""


import numpy as np
import trimesh
import matplotlib.pyplot as plt
import csv
from collections import Counter
import os

# Unos frekvencije
try:
    frekvencija = float(input("Unesi frekvenciju (npr. 3V je 2 ): ") or 2)
except ValueError:
    print("Nevazeci unos! Koristi se podrazumevani 2")
    frekvencija = 2

# Unos prečnika
try:
    diameter = float(input("Unesi prečnik kupole u cm (npr. 600): ") or 600)
except ValueError:
    print("Nevazeci unos! Koristi se podrazumevani prečnik 600 cm")
    diameter = 600

# Unos ugla rotacije
try:
    angle_deg = float(input("Rotacija oko Y ose (npr. 58.2825): ") or 58.2825)
except ValueError:
    print("Nevazeci unos! Koristi se podrazumevani 58.2825")
    angle_deg = 58.2825

# Unos ugla sečenja
try:
    stepena_od_vrha = float(input("Sečenje od vrha (npr. 85): ") or 85)
except ValueError:
    print("Nevazeci unos! Koristi se podrazumevani 85")
    stepena_od_vrha = 85



# Visina i širina letvica
try:
    stick_height = float(input("Unesi visinu letvice u cm (npr. 5 ): ") or 5)
except ValueError:
    stick_height = 5
try:
    stick_width = float(input("Unesi širinu letvice u cm (npr. 3 ): ") or 3)
except ValueError:
    stick_width = 3

file_base = f"{int(frekvencija)}V_{diameter:.2f}_{angle_deg:.2f}_{stepena_od_vrha:.2f}"

sphere = trimesh.creation.icosphere(subdivisions=int(frekvencija))
angle_rad = np.deg2rad(angle_deg)
rotation_matrix = trimesh.transformations.rotation_matrix(angle_rad, [0, 1, 0])
sphere.apply_transform(rotation_matrix)

scale_factor = diameter / sphere.bounding_box.extents.max()
sphere.apply_scale(scale_factor)

full_radius = diameter / 2
height_cutoff = -full_radius * np.cos(np.deg2rad(stepena_od_vrha))

vertex_mask = sphere.vertices[:, 2] >= height_cutoff
vertex_indices = np.nonzero(vertex_mask)[0]
old_to_new = {old: new for new, old in enumerate(vertex_indices)}

new_faces = []
for face in sphere.faces:
    if all(v in vertex_indices for v in face):
        new_faces.append([old_to_new[v] for v in face])

new_vertices = sphere.vertices[vertex_indices]
dome = trimesh.Trimesh(vertices=new_vertices, faces=np.array(new_faces), process=True)
dome.export(f"{file_base}.stl")
dome.export(f"{file_base}.obj")

with open(f"{file_base}_cvorovi.csv", 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Index', 'X', 'Y', 'Z'])
    for i, v in enumerate(dome.vertices):
        writer.writerow([i] + list(v))

edges = dome.edges_unique
edge_lengths = dome.edges_unique_length

with open(f"{file_base}_ivice.csv", 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Index', 'Cvor1', 'Cvor2', 'Duzina'])
    for i, (e, l) in enumerate(zip(edges, edge_lengths)):
        writer.writerow([i, e[0], e[1], f"{l:.5f}"])

# Grupisanje po zaokruženim dužinama
rounded_lengths = [round(l, 5) for l in edge_lengths]
length_counter = Counter(rounded_lengths)

# Paleta boja
color_palette = [
    [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
    [1.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0],
    [0.5, 0.5, 0.5], [0.8, 0.3, 0.3], [0.3, 0.8, 0.3],
    [0.3, 0.3, 0.8]
]
color_names = {
    (1.0, 0.0, 0.0): 'Crvena',
    (0.0, 1.0, 0.0): 'Zelena',
    (0.0, 0.0, 1.0): 'Plava',
    (1.0, 1.0, 0.0): 'Žuta',
    (1.0, 0.0, 1.0): 'Magenta',
    (0.0, 1.0, 1.0): 'Cijan',
    (0.5, 0.5, 0.5): 'Siva',
    (0.8, 0.3, 0.3): 'Svetlo crvena',
    (0.3, 0.8, 0.3): 'Svetlo zelena',
    (0.3, 0.3, 0.8): 'Svetlo plava'
}

# Mapiranje dužina na boje
length_color_map = {}
for idx, (length, _) in enumerate(sorted(length_counter.items())):
    color = color_palette[idx % len(color_palette)]
    length_color_map[length] = color

# Snimanje letvi u CSV sa bojom
with open(f"{file_base}_letve.csv", 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Duzina', 'Broj ivica', 'Boja'])
    for length, count in sorted(length_counter.items()):
        color_rgb = tuple(length_color_map.get(length, (0.2, 0.2, 0.2)))
        color_name = color_names.get(color_rgb, f"RGB {color_rgb}")
        writer.writerow([f"{length:.5f}", count, color_name])

# Ispis u konzolu
print("\n📏 Grupisane dužine ivica:")
for length, count in sorted(length_counter.items()):
    color_rgb = tuple(length_color_map.get(length, (0.2, 0.2, 0.2)))
    color_name = color_names.get(color_rgb, f"RGB {color_rgb}")
    print(f"{color_name}: {length:.5f} -> Broj ivica: {count}")

# Dodavanje obojenih šipki
boxes = []
for edge, raw_length in zip(edges, edge_lengths):
    p1 = dome.vertices[edge[0]]
    p2 = dome.vertices[edge[1]]
    center = (p1 + p2) / 2
    direction = p2 - p1
    length = np.linalg.norm(direction)
    if length == 0:
        continue
    direction /= length
    up = np.array([0, 0, 1])
    if np.allclose(direction, up):
        up = np.array([0, 1, 0])
    side1 = np.cross(up, direction)
    side1 /= np.linalg.norm(side1)
    side2 = np.cross(direction, side1)
    T = np.eye(4)
    T[:3, :3] = np.column_stack([side1 * stick_width, side2 * stick_height, direction * length])
    T[:3, 3] = center
    box = trimesh.creation.box(extents=(1, 1, 1))
    box.apply_transform(T)

    rounded = round(length, 5)
    color = length_color_map.get(rounded, [0.2, 0.2, 0.2])
    box.visual.vertex_colors = np.tile(color, (box.vertices.shape[0], 1))
    boxes.append(box)

# Eksport obojenih šipki
if boxes:
    all_sticks = trimesh.util.concatenate(boxes)
    all_sticks.export(f"{file_base}_montaza.ply")
    all_sticks.export(f"{file_base}_web.glb")
    print(f"\n💾 Šipke su eksportovane u {file_base}_montaza.ply")
    print(f"\n💾 Šipke su eksportovane u {file_base}_web.glb")

print("\n✅ Geodetska kupola je uspešno kreirana i sačuvana!")

# Brojanje susednih ivica za svaki čvor
from collections import defaultdict

node_connections = defaultdict(int)
for e in edges:
    node_connections[e[0]] += 1
    node_connections[e[1]] += 1

broj_3 = sum(1 for count in node_connections.values() if count == 3)
broj_4 = sum(1 for count in node_connections.values() if count == 4)
broj_5 = sum(1 for count in node_connections.values() if count == 5)
broj_6 = sum(1 for count in node_connections.values() if count == 6)

print(f"\n🔩 Čvorišta sa 3 letvice: {broj_3}")
print(f"🔩 Čvorišta sa 4 letvice: {broj_4}")
print(f"🔩 Čvorišta sa 5 letvica: {broj_5}")
print(f"🔩 Čvorišta sa 6 letvica: {broj_6}")

with open(f"{file_base}_cvorista.csv", 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Index', 'X', 'Y', 'Z', 'BrojLetvica'])
    for i, v in enumerate(dome.vertices):
        count = node_connections.get(i, 0)
        writer.writerow([i] + list(v) + [count])

# -------------------
# Statistika kupole
# -------------------

# Ukupna dužina svih letvica
ukupna_duzina_cm = sum(edge_lengths)
ukupna_duzina_m = ukupna_duzina_cm / 100

# Kubikaža letvica (pretpostavka: pravougaoni presek, dužina * visina * širina)
# Sve u metrima
volumen_m3 = ukupna_duzina_m * (stick_width / 100) * (stick_height / 100)

# Površina kupole (trouglasta mreža)
povrsina_cm2 = dome.area
povrsina_m2 = povrsina_cm2 / 10000  # iz cm² u m²

# Zapremina kupole (sferni isečak - spherical cap)
R = diameter / 2 / 100  # poluprečnik u metrima
h = (max(dome.vertices[:, 2]) - min(dome.vertices[:, 2])) / 100  # visina kupole u metrima
zapremina_m3 = (1/3) * np.pi * h**2 * (3*R - h)

# Površina koju kupola pokriva na tlu (projekcija baze)
# Nađi najveći radijalni domet (u XY ravni)
xy_coords = dome.vertices[:, :2]
udaljenosti_od_centra = np.linalg.norm(xy_coords, axis=1)
efektivni_poluprecnik = np.max(udaljenosti_od_centra) / 100  # u metrima

pokrivena_povrsina_m2 = np.pi * efektivni_poluprecnik**2

# Gustina drveta (u kg/m³)
gustina_kg_m3 = 500  # može se promeniti npr. za hrast ~750 kg/m³

# Ukupna masa konstrukcije
masa_kg = volumen_m3 * gustina_kg_m3

# Ispis
print("\n📊 Statistika konstrukcije:")
print(f"📏 Ukupna dužina letvica: {ukupna_duzina_m:.2f} m")
print(f"📦 Kubikaža letvica (za {stick_width}x{stick_height} cm): {volumen_m3:.3f} m³")
print(f"📐 Površina kupole: {povrsina_m2:.2f} m²")
print(f"🧮 Zapremina kupole: {zapremina_m3:.2f} m³")
print(f"🟢 Površina koju kupola pokriva na tlu: {pokrivena_povrsina_m2:.2f} m²")
print(f"⚖️ Težina drvene konstrukcije: {masa_kg:.2f} kg")

with open(f"{file_base}_statistika.txt", 'w') as f:
    f.write("📊 Statistika konstrukcije\n")
    f.write(f"📏 Ukupna dužina letvica: {ukupna_duzina_m:.2f} m\n")
    f.write(f"📦 Kubikaža letvica (za {stick_width}x{stick_height} cm): {volumen_m3:.3f} m³\n")
    f.write(f"📐 Površina kupole: {povrsina_m2:.2f} m²\n")
    f.write(f"🧮 Zapremina kupole: {zapremina_m3:.2f} m³\n")
    f.write(f"🟢 Površina koju kupola pokriva na tlu: {pokrivena_povrsina_m2:.2f} m²\n")
    f.write(f"⚖️ Težina drvene konstrukcije: {masa_kg:.2f} kg\n")






Primer rada u programa geo.py


By Abel

Leave a Reply

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