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