Files
Hoehenberechnungstool/hoehenberechnungstool.py
T

509 lines
25 KiB
Python

from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.ticker import FormatStrFormatter
from math import floor, ceil
import ctypes
class ProfileHeights:
# Ortsfaktor = Universalwert
_location_factor = 9.81 # m/s^2
# Sicherheitsfaktor gemäß Bauvorschrift
safety_factor = 1.7
# Fahrstreckengewichte nach Produktprogramm "OMNIFLO"
_rail_masses = {"AP110": 1.94, # K: Art-Nr. 821106001 - V: in kg
"APG110": 1.66, # K: Art-Nr. 821106002 - V: in kg
"APS110": 3.89, # K: Art-Nr. 821716001 - V: in kg
"AP60": 1.46 # K: Art-Nr. 821096001 - V: in kg
}
# Spulenzugsegment-Gewichte nach CAD
_train_segment_masses = {"T165": 0.38, # K: Art.-Nr. 826906201 - V: in kg
"T210": 0.248, # K: Art.-Nr. 826906213 - V: in kg
"T225": 0.355 # K: Art.-Nr. 826906200 - V: in kg
}
def __init__(self,
cross_section_type,
cross_section_width,
cross_section_thickness,
has_two_supports,
beam_length,
individual_rail_positions=None,
individual_rail_forces=None,
rail_type="AP110",
min_rail_distance=200,
min_pillar_distance=300,
material_yield_strength=235,
bobbin_train_segment_type="T165",
bobbin_mass=4,
colum_row_distance=3600,
pillar_width=100
):
self.min_rail_distance = min_rail_distance
self.min_dist_first_rail_to_pillar = min_pillar_distance
self.cross_section_type = cross_section_type
self.cross_section_width = cross_section_width
self.cross_section_thickness = cross_section_thickness
self.individual_rail_forces = individual_rail_forces
self.has_two_supports = has_two_supports
self.beam_length = beam_length
self.material_yield_strength = material_yield_strength
self.column_row_distance = colum_row_distance
self.rail_type = rail_type
self.individual_rail_positions = individual_rail_positions
self.pillar_width = pillar_width
self.rail_dist_from_support_a = self._set_rail_dist_from_support_a(individual_rail_positions)
self.train_segment_type = bobbin_train_segment_type
self.single_bobbin_mass = bobbin_mass
self.train_segment_mass = self._set_train_segment_mass()
self.rail_mass = self._set_rail_mass()
self.rail_forces = self._set_rail_forces()
self.bend_moments_from_a = self._summed_bend_moment_from_a()
self.support_bending_moment = self._set_support_a_bending_moment()
self.transverse_force_support_b = self._set_transverse_force_support_b()
self.transverse_force_support_a = self._set_transverse_force_support_a()
self._section_starts = self._set_beam_section_start_positions()
self.all_force_positions = self._set_all_force_positions()
self._beam_section_lengths = self._set_beam_section_lengths()
self._beam_section_forces = self._set_section_forces()
self.allowed_bend_stress = self._set_allowed_bend_stress()
self.bending_moments = self._set_bending_moments()
self.heights = self.all_profile_heights()
def _set_rail_dist_from_support_a(self, individual_rail_positions):
# Gibt Abstände zwischen den einzelnen AP-Profilen am Träger und der Säule zurück.
# Wenn 2 Säulen gegeben → Abstände zur "linken" Säule
# Fall 1: Es wurden keine individuellen Abstände zwischen den AP-Profilen und der Säule definiert.
# In diesem Fall berechnet die Methode die Abstände der AP-Schienen vom Lager "A" ausgehend unter
# Berücksichtigung der Mindestabstände zwischen den Schienen sowie zur Säule am Träger hängen kann.
if individual_rail_positions is None:
if self.has_two_supports:
distance_first_to_last_rail = (self.beam_length - self.pillar_width - 2 * self.min_dist_first_rail_to_pillar)
rail_amount = ceil(distance_first_to_last_rail / self.min_rail_distance)
distance_two_rails = distance_first_to_last_rail / (rail_amount - 1)
else:
distance_first_to_last_rail = self.beam_length - self.pillar_width / 2 - self.min_dist_first_rail_to_pillar
rail_amount = ceil(distance_first_to_last_rail / self.min_rail_distance)
distance_two_rails = distance_first_to_last_rail / (rail_amount - 1)
return [(self.min_dist_first_rail_to_pillar + self.pillar_width / 2) + (i * distance_two_rails) for i in range(rail_amount)]
# Fall 2: Es wurden individuelle Abstände zwischen den Lasten und der Säule definiert.
return self.individual_rail_positions
def _set_rail_forces(self):
# Gibt die Kräfte der einzelnen Fahrschienen am Träger zurück
# Fall 1: Es wurden keine individuellen Kräfte für die einzelnen Fahrschienen definiert.
# Methode berechnet dann für alle Fahrstrecken dieselbe Kraft.
if self.individual_rail_forces is None:
single_rail_force = ((self.train_segment_mass + self.rail_mass) * ProfileHeights._location_factor)
return [single_rail_force] * len(self.rail_dist_from_support_a)
# Fall 2: Es wurden individuelle Kräfte für die einzelnen Fahrstrecken definiert.
# Methode gibt die individuellen Kräfte unverändert zurück
return self.individual_rail_forces
def _set_train_segment_mass(self):
# Methode ermittelt das Spulenzuggewicht zwischen zwei Säulenreihen
bobbin_amount_per_meter = floor(1000 / int(self.train_segment_type.replace("T", ""))) # Anzahl Spulen pro Meter
bobbin_mass_per_meter = self.single_bobbin_mass * bobbin_amount_per_meter # Gesamtes Spulengewicht pro Meter
total_segment_mass_per_meter = 2 * ProfileHeights._train_segment_masses[self.train_segment_type] + bobbin_mass_per_meter
return total_segment_mass_per_meter * (self.column_row_distance / 1000) / 2 # kg pro Säulenreihe
def _set_rail_mass(self):
# Berechnet gesamtes Fahrstreckengewicht für Länge (Abstand) zwischen Säulenreihen zurück.
return ProfileHeights._rail_masses[self.rail_type] * self.column_row_distance / 1000
def _summed_bend_moment_from_a(self):
# Berechnet Gesamtmoment (Summe der Einzelmomente) am Balken vom Lager "A" ausgehend
return sum([force * distance for force, distance in zip(self.rail_forces, self.rail_dist_from_support_a)])
def _set_support_a_bending_moment(self):
# Berechnet Biegemoment im Lager "A", wenn Lager "A" ein dreiwertiges Lager ist (feste Einspannung)
if not self.has_two_supports:
return self.bend_moments_from_a
return 0
def _set_transverse_force_support_a(self):
# Berechnet Querkraft im Lager "A"
return abs(self.transverse_force_support_b - sum(self.rail_forces))
def _set_transverse_force_support_b(self):
# Berechnet Querkraft im Lager "B", wenn sowohl Lager "A", als auch Lager "B" existiert (nur bei Stützträgern)
if self.has_two_supports:
return self.bend_moments_from_a / self.beam_length
return 0
def _set_beam_section_start_positions(self):
# Bestimmt den Abstand der Bereichsanfänge zum Lager "A"
section_start_positions = self.rail_dist_from_support_a.copy()
section_start_positions.insert(0, 0)
return section_start_positions
def _set_section_forces(self):
# Fügt Querkraft im Lager "A" hinzu, da immer linkes Schnittufer betrachtet wird.
# Dies ist notwendig, da Querkraft "A" im Flächenschwerpunkt des Schnittes in allen Trägerbereichen ein
# Moment erzeugt
section_forces = self.rail_forces.copy()
section_forces.insert(0, self.transverse_force_support_a)
return section_forces
def _set_all_force_positions(self):
if self.has_two_supports:
all_force_positions = self._section_starts.copy()
all_force_positions.insert(len(all_force_positions), self.beam_length)
return all_force_positions
return self._section_starts
def all_forces(self):
all_forces = self._beam_section_forces.copy()
all_forces.insert(len(self.all_force_positions), self.transverse_force_support_b)
return all_forces
def _set_beam_section_lengths(self):
# Bereichslänge = Differenz zwischen nächster Kraftposition und vorherigen Kraftposition
return [new_pos - old_pos for new_pos, old_pos in zip(self.all_force_positions[1:], self.all_force_positions)]
def _set_bending_moments(self):
# Methode unterteilt Träger in Bereiche (vom linken Lager "A" ausgehend zum rechten Lager "b") und gibt die
# Schnittreaktionen (konkret Biegemomente) zurück. Aus diesen wird der Momentenverlauf gebildet.
section_bending_moments = []
subsection_lengths = self._beam_section_lengths.copy()
for i, (section_start, section_length) in enumerate(zip(self._section_starts, self._beam_section_lengths)):
section_moment = (self._beam_section_forces[0] * (section_start + section_length) - self.support_bending_moment)
summed_subsection_lengths = 0
for subsection_length, force in zip(subsection_lengths[:i], self._beam_section_forces[1:]):
summed_subsection_lengths += subsection_length
section_moment -= force * (section_start - summed_subsection_lengths + section_length)
section_bending_moments.append(abs(round((section_moment / 1000), 2)))
if self.has_two_supports:
section_bending_moments.insert(0, 0.0)
else:
section_bending_moments.insert(0, self.support_bending_moment / 1000)
return section_bending_moments
def reference_moment_of_resistance(self, bending_moment):
# Funktion berechnet Referenz-Widerstandsmoment für die aktuelle Trägerlänge
return bending_moment / self.allowed_bend_stress
def _set_allowed_bend_stress(self):
return (self.material_yield_strength * 1.2) / ProfileHeights.safety_factor
def recalculated_moment_of_resistance(self, height):
# Aktuelle Profilhöhe wird zur Ermittlung des derzeitigen Widerstandsmoments in die Widerstandsmoment-Formeln
# der unterschiedlichen Querschnittsarten eingesetzt.
inner_width = self.cross_section_width - 2 * self.cross_section_thickness
inner_height = height - 2 * self.cross_section_thickness
bar_width = self.cross_section_width - self.cross_section_thickness
# 1. Idealisiertes rechteckiges Hohlprofil:
if self.cross_section_type == "Hohlprofil":
return (self.cross_section_width * height ** 3 - inner_width * inner_height ** 3) / (6 * height)
# 2. Idealisiertes IPE-Profil / C-Profil:
elif self.cross_section_type in ["C-Profil", "IPE"]:
return (self.cross_section_width * height ** 3 - bar_width * inner_height ** 3) / (6 * height)
def all_profile_heights(self):
heights = []
bending_moments_converted = [i * 1000 for i in self.bending_moments] # Umrechnung von Nm in Nmm
for bending_moment, force_position in zip(bending_moments_converted, self.all_force_positions):
reference_moment_of_resistance = self.reference_moment_of_resistance(abs(bending_moment))
recalculated_moment_of_resistance = 0
height = 2 * self.cross_section_thickness
while recalculated_moment_of_resistance < reference_moment_of_resistance:
recalculated_moment_of_resistance = self.recalculated_moment_of_resistance(height)
height += 0.01
heights.append(height)
return heights
def current_value(self, current_beam_length, elements):
for i, force_position in enumerate(self.all_force_positions):
if current_beam_length <= force_position:
# m = Steigung, x = aktuelle Länge im derzeitigen Bereich, t = Höhe bei Bereichsbeginn
m = (elements[i] - elements[i - 1]) / self._beam_section_lengths[i - 1]
x = current_beam_length - self.all_force_positions[i - 1]
t = elements[i - 1]
return m * x + t
height_1 = ProfileHeights(cross_section_type="Hohlprofil",
cross_section_width=50,
cross_section_thickness=3,
individual_rail_positions=[200, 500, 700, 1100],
individual_rail_forces=[500, 500, 500, 500],
beam_length=1200,
min_rail_distance=200,
has_two_supports=True)
# ===========================================================================================================================
# = PLOTTEN =
# ===========================================================================================================================
# Monitordaten für Figure-Größe
user32 = ctypes.windll.user32
user32.SetProcessDPIAware()
monitor_width = user32.GetSystemMetrics(0)
monitor_height = user32.GetSystemMetrics(1)
dpi = 120
fig, (ax_drawing, ax_bending_moment, ax_heights) = plt.subplots(nrows=3, ncols=1, sharex=True, figsize=(monitor_width / dpi, monitor_height / dpi))
# Globale Einstellungen
ax_drawing.set_title("Tool zur Bestimmung der Mindestquerschnittshöhe eines Profilträgers", fontsize=20)
subplot_adjust_left = 0.1
plt.subplots_adjust(left=subplot_adjust_left, right=1 - subplot_adjust_left, top=1 - subplot_adjust_left, bottom=subplot_adjust_left, hspace=0)
# Abszisseneinstellungen
x_min_val = -(max(height_1.all_force_positions) * 0.1)
x_max_val = max(height_1.all_force_positions) * 1.1
ax_heights.set_xlim(x_min_val, x_max_val)
ax_heights.set_xticks(height_1.all_force_positions)
ax_heights.xaxis.set_major_formatter(FormatStrFormatter("%.1f"))
# Einstellungen Biegemoment-Plot
ax_bending_moment.set_ylabel("Biegemoment [Nm]")
ax_bending_moment_y_max = max(height_1.bending_moments) * 1.25
ax_bending_moment.set_ylim(0, ax_bending_moment_y_max)
# Einstellungen Profilhöhen-Plot
ax_heights.set_xlabel("Kraftpositionen [mm]")
ax_heights.set_ylabel("Benötigte Profilhöhe [mm]")
ax_heights_y_max = max(height_1.heights) * 1.25
ax_heights.set_ylim(0, ax_heights_y_max)
ax_heights.set_xticks(height_1.all_force_positions)
# Berechnet horizontale Pixelanzahl der Plots
x_plot_px_count = monitor_width * (1 - 2 * subplot_adjust_left)
# Rechnet Längen der x-Achse in Pixel um (wie viele Pixel sind 1 mm?).
# Wichtig, damit die Pfeillängen der Kräfte unabhängig von der Länge des Balkens immer gleich lang dargestellt werden.
mm_in_px_converter = (abs(x_min_val) + abs(x_max_val)) / x_plot_px_count
# ===========================================================================================================================
# = Zeichnung =
# ===========================================================================================================================
# Extremwerte der Zeichnungsordinate:
y_max_drawing = 300
ax_drawing.set_ylim(0, y_max_drawing)
ax_drawing.set_yticks([])
# Darstellung eines schematischen Trägers:
beam_y_pos_in_plot = y_max_drawing * 0.35
ax_drawing.hlines(beam_y_pos_in_plot, height_1.all_force_positions[0], height_1.all_force_positions[len(height_1.all_force_positions) - 1],
color="black", linewidth=3)
force_arrow_length = 0.5 * beam_y_pos_in_plot
def draw_transverse_force(x_pos, y_beam_pos, arrow_length, support_name):
# Darstellung des Balkenendes als Punkt mit Lagerbenennung
ax_drawing.plot(x_pos, y_beam_pos, "o", color="black")
# Pfeildarstellung für Querkraft
ax_drawing.annotate('', xytext=(x_pos, y_beam_pos), xycoords='data', xy=(x_pos, y_beam_pos - arrow_length), textcoords='data',
arrowprops=dict(color="red", width=1, headwidth=6))
# Pfeilbeschreibung:
if support_name == "A":
txt_alignment = "left"
txt_x_offset = -120
name_x_offset = -50
name_y_offset = 15
transverse_force = height_1.transverse_force_support_a
else:
txt_alignment = "left"
txt_x_offset = 20
name_x_offset = 20
name_y_offset = 15
transverse_force = height_1.transverse_force_support_b
# Plottet Querkraft-Wert entweder am Festlager "A", oder am Loslager "B"
ax_drawing.annotate(f"F{support_name}y: {transverse_force:.0f}N", xy=(x_pos, y_beam_pos - arrow_length),
xycoords='data', xytext=(txt_x_offset, 0), textcoords='offset pixels', horizontalalignment=txt_alignment)
# Plottet Lagerbezeichnung
ax_drawing.annotate(f"{support_name}", xy=(x_pos, y_beam_pos), xycoords='data', xytext=(name_x_offset, name_y_offset),
textcoords='offset pixels', ha=txt_alignment, fontsize=20)
def draw_fixed_support(y_beam_pos, px_amount_per_mm, arrow_length, support_name):
# Querkraft:
draw_transverse_force(x_pos=height_1.all_force_positions[0], y_beam_pos=y_beam_pos,
arrow_length=arrow_length, support_name=support_name)
# Normalkraft:
x_arrow_length = px_amount_per_mm * arrow_length
ax_drawing.annotate('', xycoords='data', xytext=(height_1.all_force_positions[0], y_beam_pos), xy=(-x_arrow_length, y_beam_pos),
textcoords='data', arrowprops=dict(color="red", width=1, headwidth=6))
def draw_couple(y_beam_pos, px_amount_per_mm, arrow_length, support_name):
# Feste Einspannung
draw_fixed_support(y_beam_pos=y_beam_pos, px_amount_per_mm=px_amount_per_mm, arrow_length=arrow_length, support_name=support_name)
# Plottet Querkraft-Pfeil entweder am Festlager "A", oder am Loslager "B"
ax_drawing.annotate(f"M1: {(height_1.support_bending_moment / 1000):.0f} Nm", xy=(height_1.all_force_positions[0], y_beam_pos - arrow_length),
xycoords='data', xytext=(-120, -20), textcoords='offset pixels', horizontalalignment="left")
ax_drawing.plot([height_1.all_force_positions[0] - 2.5], [y_beam_pos], marker=r'$\circlearrowright$', ms=30, color="red")
def drawing_dimensions(y_pos_beam, px_amount_per_mm, arrow_length):
dim_line_distance_from_beam = 70
dim_line_y_pos = y_pos_beam + dim_line_distance_from_beam
dim_leader_extension = 20
for i, (force_pos, force) in enumerate(zip(height_1.all_force_positions, height_1.all_forces())):
# Darstellung Maßhilfslinien
ax_drawing.vlines(force_pos, y_pos_beam, dim_line_y_pos + dim_leader_extension, colors="black", linewidth=1)
if height_1.has_two_supports:
end_index = len(height_1.all_force_positions) - 1
else:
end_index = len(height_1.all_force_positions)
if 0 < i < end_index:
# Kraftpfeile
# Pfeildarstellung
ax_drawing.annotate('', xycoords='data', xytext=(height_1.all_force_positions[i], beam_y_pos_in_plot + arrow_length),
xy=(height_1.all_force_positions[i], beam_y_pos_in_plot), textcoords='data',
arrowprops=dict(color="red", width=1, headwidth=6))
# Pfeilbeschreibung
ax_drawing.annotate(f'F{i}:\n{force:.1f}N', xy=(force_pos, y_pos_beam - arrow_length), xycoords='data', xytext=(0, 0),
textcoords='offset pixels', horizontalalignment="center", fontsize=10)
if i < (len(height_1.all_force_positions) - 1):
# Darstellung Maßlinien
ax_drawing.annotate('', xy=(height_1.all_force_positions[i], dim_line_y_pos), xycoords='data',
xytext=(height_1.all_force_positions[i + 1], dim_line_y_pos), textcoords='data', arrowprops={'arrowstyle': '<->'})
# Darstellung Maße (Bereichslängen)
section_length = (height_1.all_force_positions[i + 1] - force_pos)
dim_x_pos = (section_length / 2) / px_amount_per_mm
ax_drawing.annotate(f'{section_length:.1f}', xy=(force_pos, dim_line_y_pos), xycoords='data', xytext=(dim_x_pos, 5),
textcoords='offset pixels', horizontalalignment="center")
drawing_dimensions(y_pos_beam=beam_y_pos_in_plot, px_amount_per_mm=mm_in_px_converter, arrow_length=force_arrow_length)
if height_1.has_two_supports:
draw_transverse_force(x_pos=height_1.all_force_positions[len(height_1.all_force_positions) - 1], y_beam_pos=beam_y_pos_in_plot,
arrow_length=force_arrow_length, support_name="B")
draw_fixed_support(y_beam_pos=beam_y_pos_in_plot, px_amount_per_mm=mm_in_px_converter, arrow_length=force_arrow_length, support_name="A")
else:
draw_couple(y_beam_pos=beam_y_pos_in_plot, px_amount_per_mm=mm_in_px_converter, arrow_length=force_arrow_length, support_name="A")
# ===========================================================================================================================
# = Höhe und Biegemoment =
# ===========================================================================================================================
def text_alignment():
if height_1.has_two_supports:
return "center"
return "left"
def plot_bending_moment():
# Momentenlinie
ax_bending_moment.plot(height_1.all_force_positions, height_1.bending_moments, color="black")
ax_bending_moment.fill_between(height_1.all_force_positions, height_1.bending_moments, hatch="/", facecolor="#FFFFFF")
# Maximalmoment
bending_moments_and_beam_lengths = dict(zip(height_1.bending_moments, height_1.all_force_positions))
max_bending_moment = max(bending_moments_and_beam_lengths.keys())
len_at_max_bending_moment = bending_moments_and_beam_lengths[max_bending_moment]
text = f"max: {round(max_bending_moment, 2)} Nm"
ax_bending_moment.plot(len_at_max_bending_moment, max_bending_moment, "o", color="black")
ax_bending_moment.annotate(text, xy=(len_at_max_bending_moment, max_bending_moment), xytext=(0, 10), textcoords='offset points',
horizontalalignment=text_alignment())
def plot_heights():
# Hauptplot
ax_heights.plot(height_1.all_force_positions, height_1.heights, color="black",
label=f"{height_1.cross_section_type} - Breite: {height_1.cross_section_width} mm")
ax_heights.fill_between(height_1.all_force_positions, height_1.heights, hatch="/", facecolor="#FFFFFF")
# Plot Maximalhöhe
heights_and_beam_lengths = dict(zip(height_1.heights, height_1.all_force_positions))
max_height = max(heights_and_beam_lengths.keys())
beam_length_at_max_height = heights_and_beam_lengths[max_height]
text = f"max: {max_height:.2f} mm"
ax_heights.plot(beam_length_at_max_height, max_height, "o", color="black")
ax_heights.annotate(text, xy=(beam_length_at_max_height, max_height), xytext=(0, 10), textcoords='offset points',
horizontalalignment=text_alignment())
ax_heights.legend(loc=1)
def update_slider_length(current_length):
ax_heights.clear()
ax_bending_moment.clear()
plot_heights()
current_height = round(height_1.current_value(current_length, height_1.heights), 2)
current_bending_moment = round(height_1.current_value(current_length, height_1.bending_moments), 2)
# Plot aktuelle Höhe
plot_bending_moment()
ax_heights.plot(current_length, current_height, "o", color="black")
ax_heights.annotate(current_height, xy=(current_length, current_height), xytext=(0, 7), textcoords='offset points',
horizontalalignment="center")
# Plot aktuelles Moment
ax_bending_moment.plot(current_length, current_bending_moment, "o", color="black")
ax_bending_moment.annotate(current_bending_moment, xy=(current_length, current_bending_moment), xytext=(0, 7), textcoords='offset points',
horizontalalignment="center")
ax_heights.vlines(current_length, 0, max(height_1.heights) * 1.25, color="gray", linewidth=1)
ax_bending_moment.vlines(current_length, 0, max(height_1.bending_moments) * 1.25, color="gray", linewidth=1)
plt.draw()
plot_bending_moment()
plot_heights()
ax_slider = plt.axes([0.125, 0.0025, 0.775, 0.05], facecolor="blue")
slider = Slider(ax_slider, "Trägerlänge", valmin=0, valmax=height_1.beam_length, valstep=1, valinit=0)
slider.on_changed(update_slider_length)
plt.show()