diff --git a/src/osdag/data/ResourceFiles/images/c_beam.jpeg b/src/ResourceFiles/images/c_beam.jpeg similarity index 100% rename from src/osdag/data/ResourceFiles/images/c_beam.jpeg rename to src/ResourceFiles/images/c_beam.jpeg diff --git a/src/osdag/data/ResourceFiles/images/c_beam.png b/src/osdag/data/ResourceFiles/images/c_beam.png new file mode 100644 index 000000000..609ee3e79 Binary files /dev/null and b/src/osdag/data/ResourceFiles/images/c_beam.png differ diff --git a/src/osdag/data/ResourceFiles/images/ss_beam.png b/src/osdag/data/ResourceFiles/images/ss_beam.png index cd58b1419..cb480ab21 100644 Binary files a/src/osdag/data/ResourceFiles/images/ss_beam.png and b/src/osdag/data/ResourceFiles/images/ss_beam.png differ diff --git a/src/osdag/design_type/flexural_member/flexure.py b/src/osdag/design_type/flexural_member/flexure.py index 7f6093743..4fadf8b6b 100644 --- a/src/osdag/design_type/flexural_member/flexure.py +++ b/src/osdag/design_type/flexural_member/flexure.py @@ -35,6 +35,8 @@ from ...utils.common import is800_2007 from ...utils.common.component import * from osdag.cad.items.plate import Plate +from PyQt5.QtWidgets import QPushButton +from .plot_bmd_sfd import PlotInputWidget class Flexure(Member): @@ -256,11 +258,11 @@ def input_values(self): # options_list.append(t3) # - #t4 = (KEY_SUPPORT, KEY_DISP_SUPPORT, TYPE_NOTE,KEY_DISP_SUPPORT1, True, 'No Validator') - #options_list.append(t4) + t4 = (KEY_SUPPORT, KEY_DISP_SUPPORT, TYPE_NOTE,KEY_DISP_SUPPORT1, True, 'No Validator') + options_list.append(t4) - #t12 = (KEY_IMAGE, None, TYPE_IMAGE, Simply_Supported_img, True, 'No Validator') - #options_list.append(t12) + t12 = (KEY_IMAGE, None, TYPE_IMAGE, Simply_Supported_img, True, 'No Validator') + options_list.append(t12) # t3 = (KEY_BUCKLING_METHOD, KEY_WEB_BUCKLING, TYPE_COMBOBOX, KEY_WEB_BUCKLING_option, False, 'No Validator') @@ -286,10 +288,20 @@ def input_values(self): t8 = (KEY_MOMENT, KEY_DISP_MOMENT, TYPE_TEXTBOX, None, True, 'No Validator') options_list.append(t8) + bm_button = QPushButton("Plot BMD") + self.bmd_widget = PlotInputWidget("BM") + bm_button.clicked.connect(self.bmd_widget.show) + t8b = ("bm_plot_button", "", 'BUTTON', bm_button, True, "") + options_list.append(t8b) t8 = (KEY_SHEAR, KEY_DISP_SHEAR, TYPE_TEXTBOX, None, True, 'No Validator') options_list.append(t8) + sf_button = QPushButton("Plot SFD") + self.sfd_widget = PlotInputWidget("SFD") + sf_button.clicked.connect(self.sfd_widget.show) + t9b = ("sf_plot_button", "", 'BUTTON', sf_button, True, "") + options_list.append(t9b) return options_list @@ -3089,4 +3101,3 @@ def save_design(self, popup_summary): fname_no_ext = popup_summary['filename'] CreateLatex.save_latex(CreateLatex(), self.report_input, self.report_check, popup_summary, fname_no_ext, rel_path, Disp_2d_image, Disp_3D_image, module=self.module) # - diff --git a/src/osdag/design_type/flexural_member/flexure_cantilever.py b/src/osdag/design_type/flexural_member/flexure_cantilever.py index 312c5eff0..7eb2f0a31 100644 --- a/src/osdag/design_type/flexural_member/flexure_cantilever.py +++ b/src/osdag/design_type/flexural_member/flexure_cantilever.py @@ -34,6 +34,8 @@ from ...utils.common.Section_Properties_Calculator import BBAngle_Properties from ...utils.common import is800_2007 from ...utils.common.component import * +from PyQt5.QtWidgets import QPushButton +from .plot_bmd_sfd import PlotInputWidget # TODO DEBUG class Flexure_Cantilever(Member): @@ -256,11 +258,11 @@ def input_values(self): # options_list.append(t3) # # - #t4 = (KEY_SUPPORT, KEY_DISP_SUPPORT, TYPE_NOTE,KEY_DISP_SUPPORT2, True, 'No Validator') - #options_list.append(t4) + t4 = (KEY_SUPPORT, KEY_DISP_SUPPORT, TYPE_NOTE,KEY_DISP_SUPPORT2, True, 'No Validator') + options_list.append(t4) - #t12 = (KEY_IMAGE, None, TYPE_IMAGE, Cantilever_img, True, 'No Validator') - #options_list.append(t12) + t12 = (KEY_IMAGE, None, TYPE_IMAGE, Cantilever_img, True, 'No Validator') + options_list.append(t12) # # t10 = (KEY_TORSIONAL_RES, DISP_TORSIONAL_RES, TYPE_COMBOBOX, Torsion_Restraint_list, True, 'No Validator') # options_list.append(t10) @@ -282,11 +284,23 @@ def input_values(self): t8 = (KEY_MOMENT, KEY_DISP_MOMENT, TYPE_TEXTBOX, None, True, 'No Validator') options_list.append(t8) + bm_button = QPushButton("Plot BMD") + self.bmd_widget = PlotInputWidget("BM") + bm_button.clicked.connect(self.bmd_widget.show) + t8b = ("bm_plot_button", "", 'BUTTON', bm_button, True, "") + options_list.append(t8b) t8 = (KEY_SHEAR, KEY_DISP_SHEAR, TYPE_TEXTBOX, None, True, 'No Validator') options_list.append(t8) + sf_button = QPushButton("Plot SFD") + self.sfd_widget = PlotInputWidget("SFD") + sf_button.clicked.connect(self.sfd_widget.show) + t9b = ("sf_plot_button", "", 'BUTTON', sf_button, True, "") + options_list.append(t9b) + + return options_list diff --git a/src/osdag/design_type/flexural_member/flexure_othersupp.py b/src/osdag/design_type/flexural_member/flexure_othersupp.py index 09d662338..c33018c2f 100644 --- a/src/osdag/design_type/flexural_member/flexure_othersupp.py +++ b/src/osdag/design_type/flexural_member/flexure_othersupp.py @@ -293,6 +293,10 @@ def input_values(self): t8 = (KEY_SHEAR, KEY_DISP_SHEAR, TYPE_TEXTBOX, None, True, 'No Validator') options_list.append(t8) + t_support = (KEY_SUPPORT, "Support Type", TYPE_COMBOBOX, ["Simply Supported", "Cantilever"], True, "") + options_list.append(t_support) + + return options_list diff --git a/src/osdag/design_type/flexural_member/plot_bmd_sfd.py b/src/osdag/design_type/flexural_member/plot_bmd_sfd.py new file mode 100644 index 000000000..6fa5df7d3 --- /dev/null +++ b/src/osdag/design_type/flexural_member/plot_bmd_sfd.py @@ -0,0 +1,188 @@ + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication +from PyQt5.QtGui import QKeySequence +from PyQt5.QtWidgets import QDialog + +from PyQt5.QtWidgets import ( + QWidget, QLabel, QPushButton, QVBoxLayout, QTableWidget, QTableWidgetItem, + QHBoxLayout, QFileDialog, QHeaderView, QMessageBox +) +import matplotlib.pyplot as plt + + +class PlotInputWidget(QWidget): + def __init__(self, plot_type="BM"): # "BM" or "SF" + super().__init__() + + self.plot_type = plot_type + self.setWindowTitle(f"{'BMD' if plot_type == 'BM' else 'SFD'} Plotter") + + self.layout = QVBoxLayout(self) + + # Setup Table + self.table = QTableWidget(5, 2) + self.table.setHorizontalHeaderLabels(["Distance (m)", "BM (kNm)" if plot_type == "BM" else "SF (kN)"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + self.layout.addWidget(QLabel(f"Enter Distance and {'Bending Moment' if plot_type == 'BM' else 'Shear Force'} values:")) + self.layout.addWidget(self.table) + + # Buttons + btn_layout = QHBoxLayout() + self.plot_btn = QPushButton(f"Plot {'BMD' if plot_type == 'BM' else 'SFD'}") + # self.save_btn = QPushButton("Save as PNG") + self.add_row_btn = QPushButton("Add Row") + btn_layout.addWidget(self.add_row_btn) + btn_layout.addWidget(self.plot_btn) + # btn_layout.addWidget(self.save_btn) + self.layout.addLayout(btn_layout) + + # Connections + self.add_row_btn.clicked.connect(self.add_row) + self.plot_btn.clicked.connect(self.plot_graph) + # self.save_btn.clicked.connect(self.save_plot) + + def add_row(self): + self.table.insertRow(self.table.rowCount()) + + def get_data(self): + x_vals = [] + y_vals = [] + + for row in range(self.table.rowCount()): + try: + xi = float(self.table.item(row, 0).text()) + yi = float(self.table.item(row, 1).text()) + x_vals.append(xi) + y_vals.append(yi) + except (ValueError, AttributeError): + continue + + if len(x_vals) < 2: + QMessageBox.warning(self, "Invalid Input", "Please enter at least two valid data points.") + return None, None + + # Sort by distance + return zip(*sorted(zip(x_vals, y_vals))) + + def plot_graph(self): + x, y = self.get_data() + if x is None: + return + + color = 'r' if self.plot_type == "BM" else 'b' + title = "Bending Moment Diagram" if self.plot_type == "BM" else "Shear Force Diagram" + ylabel = "BM (kNm)" if self.plot_type == "BM" else "SF (kN)" + # plt.rcParams['home'] = 'none' + fig, ax = plt.subplots(figsize=(8, 4)) + line, = ax.plot(x, y, color=color, linewidth=2, marker='o', label=title) + + # Draw vertical dashed lines from each point to X-axis + for xi, yi in zip(x, y): + ax.vlines(xi, 0, yi, colors=color, linestyles='dashed', alpha=0.6) + + ax.set_title(title) + ax.set_xlabel("Distance (m)") + ax.set_ylabel(ylabel) + ax.grid(True) + ax.axhline(0, color='black', linewidth=1) + + # Hover annotation + annot = ax.annotate("", xy=(0,0), xytext=(15,15), textcoords="offset points", + bbox=dict(boxstyle="round", fc="w"), + arrowprops=dict(arrowstyle="->")) + annot.set_visible(False) + + def update_annot(ind): + index = ind["ind"][0] + x_val = x[index] + y_val = y[index] + annot.xy = (x_val, y_val) + annot.set_text(f"x={x_val:.2f}\ny={y_val:.2f}") + annot.get_bbox_patch().set_alpha(0.9) + + def hover(event): + if event.inaxes == ax and event.xdata is not None: + x_mouse = event.xdata + + # Find two x-values around the mouse (for interpolation) + for i in range(len(x) - 1): + if x[i] <= x_mouse <= x[i + 1]: + # Linear interpolation + x1, x2 = x[i], x[i + 1] + y1, y2 = y[i], y[i + 1] + + # Interpolated y value + slope = (y2 - y1) / (x2 - x1) + y_interp = y1 + slope * (x_mouse - x1) + + # Update annotation + annot.xy = (x_mouse, y_interp) + annot.set_text(f"x={x_mouse:.2f}\ny={y_interp:.2f}") + annot.set_visible(True) + fig.canvas.draw_idle() + return + + annot.set_visible(False) + fig.canvas.draw_idle() + + + fig.canvas.mpl_connect("motion_notify_event", hover) + plt.tight_layout() + plt.legend() + plt.show() + + + def save_plot(self): + x, y = self.get_data() + if x is None: + return + + fname, _ = QFileDialog.getSaveFileName(self, "Save Plot", "", "PNG Files (*.png)") + if fname: + color = 'r' if self.plot_type == "BM" else 'b' + title = "Bending Moment Diagram" if self.plot_type == "BM" else "Shear Force Diagram" + ylabel = "BM (kNm)" if self.plot_type == "BM" else "SF (kN)" + + plt.figure(figsize=(8, 4)) + plt.plot(x, y, color=color, linewidth=2, marker='o') + for xi, yi in zip(x, y): + plt.vlines(xi, 0, yi, colors=color, linestyles='dashed', alpha=0.7) + plt.axhline(0, color='black', linewidth=1) + plt.title(title) + plt.xlabel("Distance (m)") + plt.ylabel(ylabel) + plt.grid(True) + plt.tight_layout() + plt.savefig(fname) + plt.close() + + def keyPressEvent(self, event): + if event.matches(QKeySequence.Paste): + clipboard = QApplication.clipboard() + text = clipboard.text() + rows = text.strip().split("\n") + current_row = self.table.currentRow() + current_col = self.table.currentColumn() + + for r_idx, row_data in enumerate(rows): + columns = row_data.split("\t") + for c_idx, cell in enumerate(columns): + target_row = current_row + r_idx + target_col = current_col + c_idx + # Expand if necessary + if target_row >= self.table.rowCount(): + self.table.insertRow(self.table.rowCount()) + item = QTableWidgetItem(cell.strip()) + self.table.setItem(target_row, target_col, item) + else: + super().keyPressEvent(event) + def showEvent(self, event): + # Clear table contents every time the widget is shown + self.table.clearContents() + self.table.setRowCount(5) # Reset to default number of rows + super().showEvent(event) + + + diff --git a/src/osdag/gui/ui_OsdagMainPage.py b/src/osdag/gui/ui_OsdagMainPage.py index 2811fdc46..8f4cae337 100644 --- a/src/osdag/gui/ui_OsdagMainPage.py +++ b/src/osdag/gui/ui_OsdagMainPage.py @@ -151,7 +151,7 @@ def setupUi(self, MainWindow): self.comboBox_help.setCurrentIndex(0) self.myStackedWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) - + def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Osdag")) diff --git a/src/osdag/gui/ui_template.py b/src/osdag/gui/ui_template.py index 305371666..bfbda8834 100644 --- a/src/osdag/gui/ui_template.py +++ b/src/osdag/gui/ui_template.py @@ -5,7 +5,7 @@ import pandas as pd from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtGui import * -from PyQt5.QtWidgets import * +from PyQt5.QtWidgets import * from PyQt5.QtCore import * from .ui_tutorial import Ui_Tutorial from .ui_aboutosdag import Ui_AboutOsdag @@ -60,7 +60,10 @@ from ..get_DPI_scale import scale,height,width from ..cad.cad3dconnection import cadconnection from pynput.mouse import Button, Controller - +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import QThread +from PyQt5.QtCore import pyqtSignal, QObject, QEvent +from PyQt5.QtCore import * class MyTutorials(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) @@ -582,6 +585,11 @@ def setupUi(self, MainWindow, main,folder): for option in option_list: lable = option[1] type = option[2] + if type == 'BUTTON': + b = option[3] + b.setObjectName(option[0]) + in_layout2.addWidget(b, j, 2, 1, 1) + if type not in [TYPE_TITLE, TYPE_IMAGE, TYPE_MODULE, TYPE_IMAGE_COMPRESSION]: l = QtWidgets.QLabel(self.dockWidgetContents) l.setObjectName(option[0] + "_label") @@ -865,6 +873,7 @@ def setupUi(self, MainWindow, main,folder): else: for t in updated_list: for key_name in t[0]: + key_changed = self.dockWidgetContents.findChild(QtWidgets.QWidget, key_name) self.on_change_connect(key_changed, updated_list, data, main) print(f"key_name{key_name} \n key_changed{key_changed} \n self.on_change_connect ") @@ -2133,7 +2142,24 @@ def common_function_for_save_and_design(self, main, data, trigger_type): action.setEnabled(True) fName = str('./ResourceFiles/images/3d.png') file_extension = fName.split(".")[-1] + + # if file_extension == 'png': + # self.display.ExportToImage(fName) + # im = Image.open('./ResourceFiles/images/3d.png') + # w,h=im.size + # if(w< 640 or h < 360): + # print('Re-taking Screenshot') + # self.resize(700,500) + # self.outputDock.hide() + # self.inputDock.hide() + # self.textEdit.hide() + # QTimer.singleShot(0, lambda:self.retakeScreenshot(fName)) + else: + for fName in ['3d.png', 'top.png', + 'front.png', 'side.png']: + with open("./ResourceFiles/images/"+fName, 'w'): + pass self.display.EraseAll() for chkbox in main.get_3d_components(main): self.frame.findChild(QtWidgets.QCheckBox, chkbox[0]).setEnabled(False)