'rectangular_cs.py' in 'hoehenberechnungstool.py' umbenannt. 'rectangular_cs' war ursprünglich nur für die Berechnung der Höhen von rechteckigen Querschnitten ('rectangular cross sections') gedacht. Im Laufe der Entwicklung des Rietergerüstes haben sich rechteckige Profilquerschnitte jedoch nicht als zielführend erwiesen.

This commit is contained in:
2022-10-26 00:48:19 +02:00
parent 8f2fc3842c
commit 9a0a4d7320
+508
View File
@@ -0,0 +1,508 @@
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()