From f4b8c227b9aea114f91adb696dcfd14e212aaf3a Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Tue, 16 Jun 2026 09:00:00 +0530 Subject: [PATCH 01/14] Fix Butt Joint Additional Inputs: packing plate not shown in dialog, preference not captured or applied, dialog crash on accept, welded edge type not stored for report - Bolted: packing plate combobox was commented out in detailing_values(); restored and added KEY_DP_DETAILING_PACKING_PLATE to values dict - Bolted: KEY_DP_DETAILING_PACKING_PLATE missing from input_dictionary_design_pref(); capture_design_pref_values() silently discarded the saved value - Bolted/Welded: set_input_values() always applied packing plate regardless of Yes/No preference; guarded with use_packing flag per IS 800:2007 Cl. 10.3.3.2 - Welded: self.edgetype never set; save_design() always fell back to default edge type in report - Bolted/Welded: inherited Connection.refresh_input_dock() returned KEY_SUPTNGSEC/KEY_SUPTDSEC keys absent from Butt Joint dock; findChild() returned None causing AttributeError on .currentText(); overrode to return [] in both modules - template_page.py: added None guard before .currentText() call as second-layer defence --- .gitignore | 4 ++- .../connection/butt_joint_bolted.py | 26 ++++++++++++------- .../connection/butt_joint_welded.py | 11 ++++++-- src/osdag_gui/ui/windows/template_page.py | 5 +++- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 2e0c9c38b..3472db188 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,6 @@ Output_PDF/* !license-dependencies.txt convert_imports.py fix_relative_imports.py -verify_imports.py \ No newline at end of file +verify_imports.py +# Internal development notes — not for version control +references/ diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index cb4644930..5f066b10f 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -65,6 +65,10 @@ def tab_value_changed(self): def edit_tabs(self): return [] # Keep original empty implementation + def refresh_input_dock(self): + # Butt joint has no section-designation fields; skip Connection's default refresh + return [] + def input_dictionary_design_pref(self): design_input = [] @@ -77,7 +81,8 @@ def input_dictionary_design_pref(self): # Detailing preferences design_input.append(("Detailing", TYPE_COMBOBOX, [ - KEY_DP_DETAILING_EDGE_TYPE # For edge preparation method + KEY_DP_DETAILING_EDGE_TYPE, # For edge preparation method + KEY_DP_DETAILING_PACKING_PLATE # Yes/No; applies β_pkg per IS 800:2007 Cl. 10.3.3.2 ])) return design_input @@ -109,7 +114,7 @@ def get_values_for_design_pref(self, key, design_dictionary): def detailing_values(self, input_dictionary): values = { KEY_DP_DETAILING_EDGE_TYPE: 'Sheared or hand flame cut', - # KEY_DP_DETAILING_PACKING_PLATE: 'Yes', # Commented out packing plate preference + KEY_DP_DETAILING_PACKING_PLATE: 'Yes', } for key in values.keys(): @@ -118,16 +123,16 @@ def detailing_values(self, input_dictionary): detailing = [] - # Edge preparation method as per Cl. 10.2.4 of IS:800:2007 + # Edge preparation method; affects min edge/end distances per IS 800:2007 Cl. 10.2.4.2 t1 = (KEY_DP_DETAILING_EDGE_TYPE, KEY_DISP_DP_DETAILING_EDGE_TYPE, TYPE_COMBOBOX, ['Sheared or hand flame cut', 'Rolled, machine-flame cut, sawn and planed'], values[KEY_DP_DETAILING_EDGE_TYPE]) detailing.append(t1) - # Commented out packing plate design preference - # t3 = (KEY_DP_DETAILING_PACKING_PLATE, KEY_DISP_DP_DETAILING_PACKING_PLATE, TYPE_COMBOBOX, - # ['Yes', 'No'], values[KEY_DP_DETAILING_PACKING_PLATE]) - # detailing.append(t3) + # Packing plate required when plates of different thickness are joined (IS 800:2007 Cl. 10.3.3.2) + t3 = (KEY_DP_DETAILING_PACKING_PLATE, KEY_DISP_DP_DETAILING_PACKING_PLATE, TYPE_COMBOBOX, + ['Yes', 'No'], values[KEY_DP_DETAILING_PACKING_PLATE]) + detailing.append(t3) t4 = ("textBrowser", "", TYPE_TEXT_BROWSER, DETAILING_DESCRIPTION_LAPJOINT, None) detailing.append(t4) @@ -625,11 +630,12 @@ def set_input_values(self, design_dictionary): default=Tcp ) - # Packing plate logic as per Cl. 10.3.3.2 - if abs(plate1_thk - plate2_thk) > 0.001: + # Packing plate as per IS 800:2007 Cl. 10.3.3.2; only when user preference is 'Yes' + use_packing = design_dictionary.get(KEY_DP_DETAILING_PACKING_PLATE, 'Yes') == 'Yes' + if use_packing and abs(plate1_thk - plate2_thk) > 0.001: self.packing_plate_thickness = abs(plate1_thk - plate2_thk) if self.packing_plate_thickness > 6.0: - # βpkg calculation as per Eq. 3.3 + # β_pkg = 1 - 0.0125*t_pkg per IS 800:2007 Cl. 10.3.3.2 self.beta_pkg = (1.0 - 0.0125 * self.packing_plate_thickness) else: self.beta_pkg = 1.0 diff --git a/src/osdag_core/design_type/connection/butt_joint_welded.py b/src/osdag_core/design_type/connection/butt_joint_welded.py index e806b3c04..de164ca6f 100644 --- a/src/osdag_core/design_type/connection/butt_joint_welded.py +++ b/src/osdag_core/design_type/connection/butt_joint_welded.py @@ -70,6 +70,10 @@ def tab_value_changed(self): def edit_tabs(self): return [] # Keep original empty implementation + def refresh_input_dock(self): + # Butt joint has no section-designation fields; skip Connection's default refresh + return [] + def input_dictionary_design_pref(self): design_input = [] design_input.append(("Weld", TYPE_COMBOBOX, [ @@ -650,6 +654,8 @@ def set_input_values(self, design_dictionary): fabrication=design_dictionary.get(KEY_DP_FAB_SHOP, KEY_DP_FAB_SHOP)) # Set weld size after creating the weld object self.weld.size = design_dictionary[KEY_WELD_SIZE] + # Store for report; save_design() reads self.edgetype via getattr(self, 'edgetype', ...) + self.edgetype = design_dictionary.get(KEY_DP_DETAILING_EDGE_TYPE, 'Sheared or hand flame cut') # Start design process print("input values are set. Doing preliminary member checks") self.member_design_status = False @@ -676,8 +682,9 @@ def set_input_values(self, design_dictionary): default=Tcp ) - # Packing plate logic as per Cl. 10.3.3.2 - if abs(plate1_thk - plate2_thk) > 0.001: + # Packing plate as per IS 800:2007 Cl. 10.3.3.2; only when user preference is 'Yes' + use_packing = design_dictionary.get(KEY_DP_DETAILING_PACKING_PLATE, 'Yes') == 'Yes' + if use_packing and abs(plate1_thk - plate2_thk) > 0.001: self.packing_plate_thickness = abs(plate1_thk - plate2_thk) else: self.packing_plate_thickness = 0.0 diff --git a/src/osdag_gui/ui/windows/template_page.py b/src/osdag_gui/ui/windows/template_page.py index 64882b35c..902ed76f3 100644 --- a/src/osdag_gui/ui/windows/template_page.py +++ b/src/osdag_gui/ui/windows/template_page.py @@ -2011,8 +2011,11 @@ def design_preferences(self): master_widget = self.input_dock.input_widget.findChild(QWidget, master_key) if master_widget and master_widget.currentText() not in value: continue + _widget = self.input_dock.input_widget.findChild(QWidget, key_name) + if _widget is None: + continue self.refresh_section( - prev=self.input_dock.input_widget.findChild(QWidget, key_name).currentText(), + prev=_widget.currentText(), key_name=key_name, key_type=key_type, tab_key=tab_key, From 4e62b5537a78a418db0c3e860531160bd7f06044 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Tue, 16 Jun 2026 13:00:00 +0530 Subject: [PATCH 02/14] Fix Butt Joint cover plate thickness formula: double/single Tcp values were swapped and bolted formula was inverted; correct to 5/8 x Tmin (double) and 9/8 x Tmin (single) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bolted set_input_values(): double cover used 9/8 (correct for single), single used 5/8 (correct for double) — values were completely swapped - Welded set_input_values(): double cover used 9/16 (wrong fraction, slightly below 5/8), single used 5/8 (too thin for single cover) - Bolted save_design(): LaTeX and t_req followed the same swap; corrected to frac{5}{8} for double and frac{9}{8} for single - Welded save_design(): tcp_numerator/denominator followed the same swap; corrected to 5/8 for double and 9/8 for single - Wrong citation "Cl. 10.2.4.2" removed; replaced with honest reference to Indian Machine Design practice (IBR) validated against IS 800:2007 Cl. 10.1 --- .../connection/butt_joint_bolted.py | 27 ++++++++++++------- .../connection/butt_joint_welded.py | 24 +++++++++-------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index 5f066b10f..6465e8a45 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -620,11 +620,12 @@ def set_input_values(self, design_dictionary): cover_plate_type_str = design_dictionary[KEY_COVER_PLATE] self.cover_plate_type = cover_plate_type_str # Store for CAD generation - # Cover plate and packing plate logic as per documentation available_thicknesses = [float(thk) for thk in PLATE_THICKNESS_SAIL] if "double" in cover_plate_type_str.lower(): self.planes = 2 - Tcp = math.ceil((9.0 / 8.0) * Tmin) # Double cover plate thickness as per Eq. 3.2 + # Double cover plate: each plate area ≥ A_main/2 (load shared equally); 5/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + Tcp = math.ceil((5.0 / 8.0) * Tmin) self.calculated_cover_plate_thickness = min( [thk for thk in available_thicknesses if thk >= Tcp], default=Tcp @@ -645,7 +646,9 @@ def set_input_values(self, design_dictionary): elif "single" in cover_plate_type_str.lower(): self.planes = 1 - Tcp = math.ceil((5.0 / 8.0) * Tmin) # Single cover plate thickness as per Eq. 3.1 + # Single cover plate: plate area ≥ A_main (full load alone); 9/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + Tcp = math.ceil((9.0 / 8.0) * Tmin) self.calculated_cover_plate_thickness = min( [thk for thk in available_thicknesses if thk >= Tcp], default=Tcp @@ -1319,17 +1322,21 @@ def as_int(x, default=0): cp_req.append(NoEscape(r'\begin{aligned}')) if "double" in cover_plate_type.lower(): - t_req = math.ceil(1.125 * t_min) # 9/8 * t_min + # Double cover plate: each plate area ≥ A_main/2 (load shared equally); 5/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + t_req = math.ceil(0.625 * t_min) # 5/8 * t_min cp_req.append(NoEscape(r'\text{For Double Cover Plates:}\\')) - cp_req.append(NoEscape(r't_{cp, req} &= \frac{9}{8} \cdot t_{\min}\\')) - cp_req.append(NoEscape(r'&= \frac{9}{8} \times ' + str(t_min) + r'\\')) - cp_req.append(NoEscape(r'&= ' + str(t_req) + r' \text{ mm}\\')) - else: # Single Cover Plate - t_req = math.ceil(0.625 * t_min) # 5/8 * t_min - cp_req.append(NoEscape(r'\text{For Single Cover Plate:}\\')) cp_req.append(NoEscape(r't_{cp, req} &= \frac{5}{8} \cdot t_{\min}\\')) cp_req.append(NoEscape(r'&= \frac{5}{8} \times ' + str(t_min) + r'\\')) cp_req.append(NoEscape(r'&= ' + str(t_req) + r' \text{ mm}\\')) + else: # Single Cover Plate + # Single cover plate: plate area ≥ A_main (full load alone); 9/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + t_req = math.ceil(1.125 * t_min) # 9/8 * t_min + cp_req.append(NoEscape(r'\text{For Single Cover Plate:}\\')) + cp_req.append(NoEscape(r't_{cp, req} &= \frac{9}{8} \cdot t_{\min}\\')) + cp_req.append(NoEscape(r'&= \frac{9}{8} \times ' + str(t_min) + r'\\')) + cp_req.append(NoEscape(r'&= ' + str(t_req) + r' \text{ mm}\\')) cp_req.append(NoEscape(r'\end{aligned}')) diff --git a/src/osdag_core/design_type/connection/butt_joint_welded.py b/src/osdag_core/design_type/connection/butt_joint_welded.py index de164ca6f..ed440677f 100644 --- a/src/osdag_core/design_type/connection/butt_joint_welded.py +++ b/src/osdag_core/design_type/connection/butt_joint_welded.py @@ -671,12 +671,12 @@ def set_input_values(self, design_dictionary): cover_plate_type_str = design_dictionary[KEY_COVER_PLATE] self.cover_plate_type = cover_plate_type_str # Store for CAD generation - # Cover plate and packing plate logic as per documentation available_thicknesses = [float(thk) for thk in PLATE_THICKNESS_SAIL] if "double" in cover_plate_type_str.lower(): self.planes = 2 - # Double cover plate thickness as per Eq. 3.2 - Tcp = math.ceil((9.0 / 16.0) * Tmin) + # Double cover plate: each plate area ≥ A_main/2 (load shared equally); 5/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + Tcp = math.ceil((5.0 / 8.0) * Tmin) self.calculated_cover_plate_thickness = min( [thk for thk in available_thicknesses if thk >= Tcp], default=Tcp @@ -691,8 +691,9 @@ def set_input_values(self, design_dictionary): elif "single" in cover_plate_type_str.lower(): self.planes = 1 - # Single cover plate thickness as per Eq. 3.1 - Tcp = math.ceil((5.0 / 8.0) * Tmin) + # Single cover plate: plate area ≥ A_main (full load alone); 9/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + Tcp = math.ceil((9.0 / 8.0) * Tmin) self.calculated_cover_plate_thickness = min( [thk for thk in available_thicknesses if thk >= Tcp], default=Tcp @@ -1269,15 +1270,16 @@ def f2(x, default=0.0): "SubSection", "Cover Plate Design", "|p{4cm}|p{4cm}|p{6.5cm}|p{1.5cm}|" ]) - # FIXED ISSUE 1: Correct fraction display - if N_f == 2: # Double cover - tcp_numerator = 9 - tcp_denominator = 16 - tcp_req = f2((9.0 / 16.0) * plate_thk_min, 0.0) - else: # Single cover + # Cover plate thickness: double = 5/8 × Tmin, single = 9/8 × Tmin + # Reference: Indian Machine Design practice (IBR); validate against IS 800:2007 Cl. 10.1 + if N_f == 2: # Double cover: each plate area ≥ 1.05 × A_main/2 tcp_numerator = 5 tcp_denominator = 8 tcp_req = f2((5.0 / 8.0) * plate_thk_min, 0.0) + else: # Single cover: plate area ≥ 1.05 × A_main + tcp_numerator = 9 + tcp_denominator = 8 + tcp_req = f2((9.0 / 8.0) * plate_thk_min, 0.0) tcp_calc = Math(inline=True) tcp_calc.append(NoEscape(r'\begin{aligned}')) From 12313eab308863317811277d04d21b863207bc60 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Wed, 17 Jun 2026 10:30:00 +0530 Subject: [PATCH 03/14] Show Ag and An calculation steps in Base Metal Capacity report for both Butt Joint modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bolted: store self.n_holes and self.bolt_hole_dia in check_base_metal_strength() for report use - Bolted save_design(): add 'Gross / Net Area' row showing Ag = b × t_min and An = (b - n_h × d_0) × t_min before the capacity formula rows per IS 800:2007 Cl. 6.2 and 6.3; An row omitted for compression case - Bolted save_design(): remove silent back-calculation of An_disp; Tdn formula now shows beta = 0.7 shear lag factor explicitly per IS 800:2007 Cl. 6.3.3 - Welded save_design(): add 'Gross / Net Area' row showing Ag = b × t_min and An = Ag (no bolt holes in welded joint) per IS 800:2007 Cl. 6.2 and 6.3 --- .../connection/butt_joint_bolted.py | 40 ++++++++++++++----- .../connection/butt_joint_welded.py | 15 +++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index 6465e8a45..6a23a0fb5 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -1152,6 +1152,8 @@ def check_base_metal_strength(self): else: n_holes = max(self.cols, 1) hole_dia = self.bolt.dia_hole if hasattr(self.bolt, 'dia_hole') else 0.0 + self.n_holes = n_holes # number of bolt-hole columns; for area report + self.bolt_hole_dia = hole_dia # hole diameter used for net area; for area report net_width = float(self.width) - n_holes * hole_dia if net_width <= 0: @@ -1280,7 +1282,10 @@ def as_int(x, default=0): base_metal_capacity_kN = f2(g('base_metal_capacity_kN', 0.0), 0.0) - A_g = f2(g('A_g', 0.0), 0.0) + A_g = f2(g('A_g', 0.0), 0.0) + A_n = f2(g('A_n', 0.0), 0.0) + n_holes_disp = int(g('n_holes', max(cols, 1))) + d_hole_disp = f2(g('bolt_hole_dia', 0.0), 0.0) T_dg = f2(g('T_dg', 0.0), 0.0) T_dn = f2(g('T_dn', 0.0), 0.0) T_db = f2(g('T_db', 0.0), 0.0) @@ -1756,6 +1761,23 @@ def as_int(x, default=0): "SubSection", "Base Metal Strength", "|p{4cm}|p{5cm}|p{5.5cm}|p{1.5cm}|" ]) + # IS 800:2007 Cl. 6.2: gross section area = width × t_min + area_calc = Math(inline=True) + area_calc.append(NoEscape(r'\begin{aligned}')) + area_calc.append(NoEscape(r'A_g &= b \times t_{\min}\\')) + area_calc.append(NoEscape( + r'&= ' + str(width) + r' \times ' + str(plate_thk_min) + + r' = ' + str(A_g) + r' \text{ mm}^2')) + if not is_comp: + # IS 800:2007 Cl. 6.3: net area deducts bolt holes from gross width + area_calc.append(NoEscape(r'\\A_n &= (b - n_h \cdot d_0) \times t_{\min}\\')) + area_calc.append(NoEscape( + r'&= (' + str(width) + r' - ' + str(n_holes_disp) + + r' \times ' + str(d_hole_disp) + r') \times ' + + str(plate_thk_min) + r' = ' + str(A_n) + r' \text{ mm}^2')) + area_calc.append(NoEscape(r'\end{aligned}')) + self.report_check.append(["Gross / Net Area", "", area_calc, ""]) + if is_comp: base_req = Math(inline=True) base_req.append(NoEscape(r'\begin{aligned}\\')) @@ -1780,18 +1802,16 @@ def as_int(x, default=0): # 2. Net Section Rupture # Back calculate An for display accuracy - # T_dn = 0.9 * An * fu / 1.25 (in kN) - if fu > 0: - An_disp = (T_dn * 1000.0 * 1.25) / (0.9 * fu) - else: - An_disp = 0.0 - + # IS 800:2007 Cl. 6.3.3: shear lag factor for butt joint with bolts in line + beta_sl = 0.7 rup_req = Math(inline=True) rup_req.append(NoEscape(r'\begin{aligned}\\')) - rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 A_n f_u}{\gamma_{m1}}\\')) - rup_req.append(NoEscape(r'&= \frac{0.9 \times ' + f'{An_disp:.2f}' + r' \times ' + str(fu) + r'}{1.25}\\')) + rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot \beta \cdot A_n \cdot f_u}{\gamma_{m1}}\\')) + rup_req.append(NoEscape( + r'&= \frac{0.9 \times ' + str(beta_sl) + r' \times ' + + str(A_n) + r' \times ' + str(fu) + r'}{1.25}\\')) rup_req.append(NoEscape(r'&= ' + f'{T_dn:.2f}' + r' \text{ kN}\\')) - rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3}]')) + rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3.3, } \beta = 0.7]')) rup_req.append(NoEscape(r'\end{aligned}')) self.report_check.append(["Net Section Rupture", "", rup_req, ""]) diff --git a/src/osdag_core/design_type/connection/butt_joint_welded.py b/src/osdag_core/design_type/connection/butt_joint_welded.py index ed440677f..cc2120161 100644 --- a/src/osdag_core/design_type/connection/butt_joint_welded.py +++ b/src/osdag_core/design_type/connection/butt_joint_welded.py @@ -1603,6 +1603,21 @@ def f2(x, default=0.0): "SubSection", "Base Metal Strength Check", "|p{4cm}|p{4cm}|p{6.5cm}|p{1.5cm}|" ]) + # IS 800:2007 Cl. 6.2: gross section area = width × t_min + area_calc = Math(inline=True) + area_calc.append(NoEscape(r'\begin{aligned}')) + area_calc.append(NoEscape(r'A_g &= b \times t_{\min}\\')) + area_calc.append(NoEscape( + r'&= ' + str(width) + r' \times ' + str(plate_thk_min) + + r' = ' + f'{Ag:.1f}' + r' \text{ mm}^2\\')) + # IS 800:2007 Cl. 6.3: no hole deduction for welded joints + area_calc.append(NoEscape(r'A_n &= A_g\\')) + area_calc.append(NoEscape( + r'&= ' + f'{Ag:.1f}' + + r' \text{ mm}^2 \quad \text{(no bolt holes in welded joint)}')) + area_calc.append(NoEscape(r'\end{aligned}')) + self.report_check.append(["Gross / Net Area", "", area_calc, ""]) + if is_comp: # For compression - only yielding check comp_calc_req = Math(inline=True) From e32b7f49f38a9ab79ef36530e71068fd8cc16bb8 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Thu, 18 Jun 2026 11:45:00 +0530 Subject: [PATCH 04/14] Fix Butt Joint Welded Effective Length report: formula showed 2a (throat thickness) and wrong substituted value; correct to 2s (weld size) per IS 800:2007 Cl. 10.5.4 - save_design() Effective Length block showed L_eff = L_provided - 2a and substituted effective_throat (= 0.707s), making the printed arithmetic inconsistent with the result - Code calculation was already correct (L_eff = L_provided - 2s at line 948) - Changed formula symbol a -> s and substituted value effective_throat -> weld_size - Added IS 800:2007 Cl. 10.5.4 citation inline in the formula --- src/osdag_core/design_type/connection/butt_joint_welded.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/osdag_core/design_type/connection/butt_joint_welded.py b/src/osdag_core/design_type/connection/butt_joint_welded.py index cc2120161..e2ccc4185 100644 --- a/src/osdag_core/design_type/connection/butt_joint_welded.py +++ b/src/osdag_core/design_type/connection/butt_joint_welded.py @@ -1518,10 +1518,11 @@ def f2(x, default=0.0): # Effective length calculation (Section 3.5, Step 1) eff_len_calc_detail = Math(inline=True) eff_len_calc_detail.append(NoEscape(r'\begin{aligned}')) + # IS 800:2007 Cl. 10.5.4: effective length deducts 2 × weld size for end craters eff_len_calc_detail.append( - NoEscape(r'L_{eff} &= L_{provided\_line} - 2a\\')) + NoEscape(r'L_{eff} &= L_{provided\_line} - 2s \quad [\text{IS 800:2007 Cl. 10.5.4}]\\')) eff_len_calc_detail.append(NoEscape( - r'&= ' + str(L_provided_line) + r' - 2 \times ' + str(effective_throat) + r'\\')) + r'&= ' + str(L_provided_line) + r' - 2 \times ' + str(weld_size) + r'\\')) eff_len_calc_detail.append( NoEscape(r'&= ' + str(L_eff_provided) + r' \text{ mm}\\')) eff_len_calc_detail.append(NoEscape(r'\end{aligned}')) From 85507b01cf6c88e3fe698b046e09293541e0e207 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Fri, 19 Jun 2026 08:15:00 +0530 Subject: [PATCH 05/14] Fix bolted T_dn: remove incorrect shear lag beta=0.7 for plates, fix packing plate clause 10.3.3.2->10.3.3.3, fix weld L_eff dual assignment - Bolted check_base_metal_strength(): shear_lag_factor=0.7 was incorrectly applied to plate butt joints; IS 800:2007 Cl. 6.3.3 beta only applies to angles/channels connected through part of cross-section; plates have full width connected -> no shear lag -> T_dn = 0.9 * An * fu / gamma_m1 - Bolted save_design(): LaTeX Net Section Rupture formula updated to remove beta symbol and show Cl. 6.3 instead of Cl. 6.3.3 - Both modules: packing plate comment references corrected from Cl. 10.3.3.2 (large grip) to Cl. 10.3.3.3 (packing plates) - Welded weld_length(): straight weld path now applies 2s deduction per Cl. 10.5.4 instead of setting L_eff = L_provided without deduction --- .../connection/butt_joint_bolted.py | 75 +++++++++++++++---- .../connection/butt_joint_welded.py | 59 +++++++++++++-- 2 files changed, 114 insertions(+), 20 deletions(-) diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index 6a23a0fb5..1d1523504 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -82,7 +82,7 @@ def input_dictionary_design_pref(self): # Detailing preferences design_input.append(("Detailing", TYPE_COMBOBOX, [ KEY_DP_DETAILING_EDGE_TYPE, # For edge preparation method - KEY_DP_DETAILING_PACKING_PLATE # Yes/No; applies β_pkg per IS 800:2007 Cl. 10.3.3.2 + KEY_DP_DETAILING_PACKING_PLATE # Yes/No; applies β_pkg per IS 800:2007 Cl. 10.3.3.3 ])) return design_input @@ -129,7 +129,7 @@ def detailing_values(self, input_dictionary): values[KEY_DP_DETAILING_EDGE_TYPE]) detailing.append(t1) - # Packing plate required when plates of different thickness are joined (IS 800:2007 Cl. 10.3.3.2) + # Packing plate required when plates of different thickness are joined (IS 800:2007 Cl. 10.3.3.3) t3 = (KEY_DP_DETAILING_PACKING_PLATE, KEY_DISP_DP_DETAILING_PACKING_PLATE, TYPE_COMBOBOX, ['Yes', 'No'], values[KEY_DP_DETAILING_PACKING_PLATE]) detailing.append(t3) @@ -393,6 +393,10 @@ def output_values(self, flag): self.len_conn if flag else '', True) out_list.append(t20) + t28 = (KEY_OUT_LENGTH_COVER_PLATE, KEY_OUT_DISP_LENGTH_COVER_PLATE, TYPE_TEXTBOX, + self.cover_plate_length if flag else '', True) + out_list.append(t28) + # Populate Hover Dict (Butt Joint Bolted) self.hover_dict["Plate 1"] = ( f"Plate 1
" @@ -408,6 +412,7 @@ def output_values(self, flag): self.hover_dict["Cover Plate"] = ( f"Cover Plate
" + f"Length: {round(float(self.platec.length), 2) if flag and self.platec.length else ''} mm
" f"Width: {round(float(self.platec.height), 2) if flag else ''} mm
" f"Thickness: {round(float(self.platec.thickness_provided), 2) if flag and self.platec.thickness_provided else ''} mm" ) @@ -631,12 +636,12 @@ def set_input_values(self, design_dictionary): default=Tcp ) - # Packing plate as per IS 800:2007 Cl. 10.3.3.2; only when user preference is 'Yes' + # Packing plate as per IS 800:2007 Cl. 10.3.3.3; only when user preference is 'Yes' use_packing = design_dictionary.get(KEY_DP_DETAILING_PACKING_PLATE, 'Yes') == 'Yes' if use_packing and abs(plate1_thk - plate2_thk) > 0.001: self.packing_plate_thickness = abs(plate1_thk - plate2_thk) if self.packing_plate_thickness > 6.0: - # β_pkg = 1 - 0.0125*t_pkg per IS 800:2007 Cl. 10.3.3.2 + # β_pkg = 1 - 0.0125*t_pkg per IS 800:2007 Cl. 10.3.3.3 self.beta_pkg = (1.0 - 0.0125 * self.packing_plate_thickness) else: self.beta_pkg = 1.0 @@ -915,11 +920,18 @@ def number_r_c_bolts(self,design_dictionary,count=0,hit=0): self.number_bolts = self.rows * self.cols # Calculate connection length (determined by columns along pitch direction) + # Connection length represents the physical length of the bolt group on one side of the joint line. + # Reference: Symmetrical layout rules. N. Subramanian, Design of Steel Structures, Sec. 3.8 / Fig 3.20. if self.cols > 1: self.len_conn = (self.cols - 1) * self.bolt.min_pitch_round + 2 * self.bolt.min_end_dist_round else: self.len_conn = 2 * self.bolt.min_end_dist_round + # Cover plate length spans symmetrically across the joint. + # Formula: L_cp = 2 * L_conn + # Reference: Symmetrical cover plate design. N. Subramanian, Design of Steel Structures, Sec. 3.8 / Fig 3.20. + self.cover_plate_length = 2 * self.len_conn + if self.number_bolts >= 2 and count == 0: self.design_status = True self.check_capacity_reduction_1(design_dictionary) @@ -1010,6 +1022,7 @@ def final_formatting(self,design_dictionary): self.final_end_dist = self.bolt.min_end_dist_round # Recalculate connection length for single row self.len_conn = (self.cols - 1) * self.bolt.min_pitch_round + 2 * self.bolt.min_end_dist_round + self.cover_plate_length = 2 * self.len_conn self.design_status = True # Check maximum gauge as per Cl. 10.2.3.1 elif gauge_dist > self.max_gauge_round: @@ -1114,8 +1127,8 @@ def _format_util(value, decimals=3): self.plate2.height = plate_width self.plate2.thickness_provided = float(self.plate2thk) - # Cover plate dimensions (same as main plates) - self.platec.length = plate_length + # Cover plate dimensions + self.platec.length = self.cover_plate_length self.platec.height = plate_width self.platec.thickness_provided = float(self.calculated_cover_plate_thickness) @@ -1163,10 +1176,10 @@ def check_base_metal_strength(self): return False self.A_n = plate_thk_min * net_width - shear_lag_factor = 0.7 # IS 800:2007 Cl.6.3.3 for butt joints T_dg = self.A_g * fy / self.gamma_m0 - T_dn = 0.9 * self.A_n * fu * shear_lag_factor / self.gamma_m1 + # Plates: full cross-section connected via cover plates → no shear lag (IS 800:2007 Cl. 6.3) + T_dn = 0.9 * self.A_n * fu / self.gamma_m1 self.T_dg = T_dg self.T_dn = T_dn self.T_db = min(T_dg, T_dn) @@ -1352,7 +1365,22 @@ def as_int(x, default=0): cp_status = "PASS" if t_cp_prov >= t_req else "FAIL" self.report_check.append(["Cover Plate Thickness", cp_req, cp_prov, cp_status]) - # 1.2 Packing Plate (Cl. 10.3.3.2) + # 1.2 Cover Plate Length + # Reference: Symmetrical layout rules. N. Subramanian, Design of Steel Structures, Sec. 3.8. + cp_len_req = Math(inline=True) + cp_len_req.append(NoEscape(r'\begin{aligned}')) + cp_len_req.append(NoEscape(r'L_{cp} &= 2 \cdot L_{conn}\\')) + cp_len_req.append(NoEscape(r'&= 2 \times ' + f'{self.len_conn:.1f}' + r'\\')) + cp_len_req.append(NoEscape(r'&= ' + f'{self.cover_plate_length:.1f}' + r' \text{ mm}\\')) + cp_len_req.append(NoEscape(r'&[\text{Ref. N. Subramanian, Sec. 3.8}]')) + cp_len_req.append(NoEscape(r'\end{aligned}')) + + cp_len_prov = Math(inline=True) + cp_len_prov.append(NoEscape(r'L_{cp, prov} = ' + f'{self.cover_plate_length:.1f}' + r' \text{ mm}')) + + self.report_check.append(["Cover Plate Length", cp_len_req, cp_len_prov, "PASS"]) + + # 1.3 Packing Plate (Cl. 10.3.3.2) packing_thk = float(getattr(self, 'packing_plate_thickness', 0.0)) if packing_thk > 0: pack_req = Math(inline=True) @@ -1754,6 +1782,25 @@ def as_int(x, default=0): "Bolt Pattern", "2", f"Arrangement: {rows} rows × {cols} columns", "" ]) + # 2.5.2 Connection Length + # Reference: Symmetrical layout rules. N. Subramanian, Design of Steel Structures, Sec. 3.8 / Fig 3.20. + conn_len_req = Math(inline=True) + conn_len_req.append(NoEscape(r'\begin{aligned}')) + if cols > 1: + conn_len_req.append(NoEscape(r'L_{conn} &= (n_c - 1) \cdot p + 2 \cdot e_{end}\\')) + conn_len_req.append(NoEscape(r'&= (' + str(cols) + r' - 1) \times ' + str(pitch) + r' + 2 \times ' + str(end) + r'\\')) + else: + conn_len_req.append(NoEscape(r'L_{conn} &= 2 \cdot e_{end}\\')) + conn_len_req.append(NoEscape(r'&= 2 \times ' + str(end) + r'\\')) + conn_len_req.append(NoEscape(r'&= ' + f'{self.len_conn:.1f}' + r' \text{ mm}\\')) + conn_len_req.append(NoEscape(r'&[\text{Ref. N. Subramanian, Sec. 3.8}]')) + conn_len_req.append(NoEscape(r'\end{aligned}')) + + conn_len_prov = Math(inline=True) + conn_len_prov.append(NoEscape(r'L_{conn, prov} = ' + f'{self.len_conn:.1f}' + r' \text{ mm}')) + + self.report_check.append(["Connection Length", conn_len_req, conn_len_prov, "PASS"]) + #================================ # Section 2.6: Base Metal Strength #================================ @@ -1801,17 +1848,15 @@ def as_int(x, default=0): self.report_check.append(["Gross Section Yield", "", yield_req, ""]) # 2. Net Section Rupture - # Back calculate An for display accuracy - # IS 800:2007 Cl. 6.3.3: shear lag factor for butt joint with bolts in line - beta_sl = 0.7 + # Plates: full cross-section connected via cover plates → no shear lag (IS 800:2007 Cl. 6.3) rup_req = Math(inline=True) rup_req.append(NoEscape(r'\begin{aligned}\\')) - rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot \beta \cdot A_n \cdot f_u}{\gamma_{m1}}\\')) + rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot A_n \cdot f_u}{\gamma_{m1}}\\')) rup_req.append(NoEscape( - r'&= \frac{0.9 \times ' + str(beta_sl) + r' \times ' + r'&= \frac{0.9 \times ' + str(A_n) + r' \times ' + str(fu) + r'}{1.25}\\')) rup_req.append(NoEscape(r'&= ' + f'{T_dn:.2f}' + r' \text{ kN}\\')) - rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3.3, } \beta = 0.7]')) + rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3}]')) rup_req.append(NoEscape(r'\end{aligned}')) self.report_check.append(["Net Section Rupture", "", rup_req, ""]) diff --git a/src/osdag_core/design_type/connection/butt_joint_welded.py b/src/osdag_core/design_type/connection/butt_joint_welded.py index e2ccc4185..e50d752b6 100644 --- a/src/osdag_core/design_type/connection/butt_joint_welded.py +++ b/src/osdag_core/design_type/connection/butt_joint_welded.py @@ -389,7 +389,7 @@ def output_values(self, flag): out_list.append(t38) t28 = (KEY_OUT_LENGTH_COVER_PLATE, KEY_OUT_DISP_LENGTH_COVER_PLATE, TYPE_TEXTBOX, - round(self.weld_length_provided, 1) if flag else '', True) + round(self.cover_plate_length, 1) if flag and hasattr(self, 'cover_plate_length') else '', True) out_list.append(t28) t47 = (KEY_OUT_THICKNESS_COVER_PLATE, KEY_OUT_DISP_THICKNESS_COVER_PLATE, TYPE_TEXTBOX, @@ -423,7 +423,7 @@ def output_values(self, flag): out_list.append(t26) t27 = (KEY_OUT_BOLT_CONN_LEN, KEY_OUT_DISP_BOLT_CONN_LEN, TYPE_TEXTBOX, - round(self.weld_length_provided, 1) if flag else '', True) + round(self.connection_length, 1) if flag and hasattr(self, 'connection_length') else '', True) out_list.append(t27) t29 = (KEY_OUT_DESIGN_FOR, KEY_OUT_DISP_DESIGN_FOR, TYPE_TEXTBOX, @@ -431,7 +431,7 @@ def output_values(self, flag): out_list.append(t29) # Populate Hover Dict (Butt Joint Welded) with actual dimensions - plate_length = getattr(self, 'weld_length_provided', 0) + plate_length = getattr(self, 'cover_plate_length', 0) plate_width = getattr(self, 'plates_width', 0) plate1_thk = float(self.plate1.thickness[0]) if hasattr( self, 'plate1') and self.plate1 and self.plate1.thickness else 0 @@ -682,7 +682,7 @@ def set_input_values(self, design_dictionary): default=Tcp ) - # Packing plate as per IS 800:2007 Cl. 10.3.3.2; only when user preference is 'Yes' + # Packing plate as per IS 800:2007 Cl. 10.3.3.3; only when user preference is 'Yes' use_packing = design_dictionary.get(KEY_DP_DETAILING_PACKING_PLATE, 'Yes') == 'Yes' if use_packing and abs(plate1_thk - plate2_thk) > 0.001: self.packing_plate_thickness = abs(plate1_thk - plate2_thk) @@ -883,7 +883,8 @@ def weld_length(self, design_dictionary): self.logger.info( ": Straight weld will be provided as required length is less than plate width") self.weld_length_provided = self.plates_width - self.weld_length_effective = self.weld_length_provided + # Apply 2s deduction for end craters per IS 800:2007 Cl. 10.5.4 + self.weld_length_effective = self.weld_length_provided - (2 * self.weld_size) self.weld_angle = 0 self.side_weld_length = 0 @@ -927,6 +928,21 @@ def weld_length(self, design_dictionary): self.logger.info( ": Skewed weld will be provided with angle {:.2f} degrees".format(self.weld_angle)) + # Overlap length of cover plate on each side + # Reference: IS 800:2007 Clause 10.5.1.2 (for lap/overlap length) and Clause 10.5.10 (for return welds) / N. Subramanian, Design of Steel Structures, Sec. 3.10. + self.overlap = max(4 * self.calculated_cover_plate_thickness, 40.0) + if self.side_weld_length > 0: + self.overlap = max(self.overlap, self.side_weld_length + 2 * self.weld_size) + + # Cover plate length spans symmetrically on both sides of the joint line + # Formula: L_cp = 2 * overlap + # Reference: Symmetrical welded splice plate design. N. Subramanian, Design of Steel Structures, Sec. 3.10. + self.cover_plate_length = 2 * self.overlap + + # Connection length represents the length of the weld group along the force direction (longitudinal direction) + # Reference: IS 800:2007 Cl. 10.5.1.2 / N. Subramanian, Design of Steel Structures, Sec. 3.10. + self.connection_length = self.side_weld_length + # Update output values for UI display self.output_values_dict[KEY_OUT_WELD_LENGTH] = self.weld_length_effective @@ -1299,6 +1315,21 @@ def f2(x, default=0.0): self.report_check.append( ["Cover Plate Thickness", tcp_calc, tcp_prov, tcp_status]) + # Cover Plate Length row + # Reference: Symmetrical welded splice plate design. N. Subramanian, Design of Steel Structures, Sec. 3.10. + cp_len_req = Math(inline=True) + cp_len_req.append(NoEscape(r'\begin{aligned}')) + cp_len_req.append(NoEscape(r'L_{cp} &= 2 \cdot l_{lap}\\')) + cp_len_req.append(NoEscape(r'&= 2 \times ' + f'{self.overlap:.1f}' + r'\\')) + cp_len_req.append(NoEscape(r'&= ' + f'{self.cover_plate_length:.1f}' + r' \text{ mm}\\')) + cp_len_req.append(NoEscape(r'&[\text{Ref. N. Subramanian, Sec. 3.10}]')) + cp_len_req.append(NoEscape(r'\end{aligned}')) + + cp_len_prov = Math(inline=True) + cp_len_prov.append(NoEscape(r'L_{cp, prov} = ' + f'{self.cover_plate_length:.1f}' + r' \text{ mm}')) + + self.report_check.append(["Cover Plate Length", cp_len_req, cp_len_prov, 'Pass']) + # Packing plate requirement (if applicable) if abs(plate1_thk - plate2_thk) > 0.001 and N_f == 2: packing_calc = Math(inline=True) @@ -1530,6 +1561,24 @@ def f2(x, default=0.0): self.report_check.append( ["Effective Length", "", eff_len_calc_detail, ""]) + # Connection Length row + # Reference: IS 800:2007 Cl. 10.5.1.2 / N. Subramanian, Design of Steel Structures, Sec. 3.10. + conn_len_req = Math(inline=True) + conn_len_req.append(NoEscape(r'\begin{aligned}')) + if self.side_weld_length > 0: + conn_len_req.append(NoEscape(r'L_{conn} &= L_{side}\\')) + conn_len_req.append(NoEscape(r'&= ' + f'{self.side_weld_length:.1f}' + r' \text{ mm}\\')) + conn_len_req.append(NoEscape(r'&[\text{Ref. Side weld length}]')) + else: + conn_len_req.append(NoEscape(r'L_{conn} &= 0 \text{ mm}\\')) + conn_len_req.append(NoEscape(r'&[\text{Ref. Transverse weld only}]')) + conn_len_req.append(NoEscape(r'\end{aligned}')) + + conn_len_prov = Math(inline=True) + conn_len_prov.append(NoEscape(r'L_{conn, prov} = ' + f'{self.connection_length:.1f}' + r' \text{ mm}')) + + self.report_check.append(["Connection Length", conn_len_req, conn_len_prov, 'Pass']) + # ========================================================================== # SECTION 3.6: LONG JOINT REDUCTION FACTOR # ========================================================================== From b60836ac540a88f6358ef43c98e9abc8690d30a4 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sat, 20 Jun 2026 08:30:00 +0530 Subject: [PATCH 06/14] Fix bolt and weld value checks: use get() method for safer dictionary access to prevent KeyError --- src/osdag_core/design_type/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osdag_core/design_type/main.py b/src/osdag_core/design_type/main.py index a9022ac0a..038725bdd 100644 --- a/src/osdag_core/design_type/main.py +++ b/src/osdag_core/design_type/main.py @@ -39,7 +39,7 @@ def refresh_designation_additional_inputs(self, table): def bolt_values(self, input_dictionary): - if not input_dictionary or input_dictionary[KEY_TYP] == 'Bearing Bolt': + if not input_dictionary or input_dictionary.get(KEY_TYP, 'Bearing Bolt') == 'Bearing Bolt': bolt_tension_type = 'Non pre-tensioned' else: bolt_tension_type = 'Pre-tensioned' @@ -89,7 +89,7 @@ def weld_values(self, input_dictionary): values = {KEY_DP_WELD_FAB: KEY_DP_FAB_SHOP, KEY_DP_WELD_MATERIAL_G_O: ''} - if not input_dictionary or input_dictionary[KEY_MATERIAL] == 'Select Material': + if not input_dictionary or input_dictionary.get(KEY_MATERIAL, 'Select Material') == 'Select Material': pass else: values[KEY_DP_WELD_MATERIAL_G_O] = Material(input_dictionary[KEY_MATERIAL]).fu From 1d59cea65eb38634006b47c9722ec3a52d529765 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sat, 20 Jun 2026 11:00:00 +0530 Subject: [PATCH 07/14] Remove erroneous 70s maximum effective weld length limit According to IS 800:2007 Cl. 10.5.7.3, there is no absolute 70s cap on weld length. Instead, a long joint reduction factor beta_lw is applied when l_eff > 150*t_t. Removed the legacy 70s check which conflicted with the actual code. --- .../design_type/connection/lap_joint_welded.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_welded.py b/src/osdag_core/design_type/connection/lap_joint_welded.py index b14b6d6c6..671b1c82e 100644 --- a/src/osdag_core/design_type/connection/lap_joint_welded.py +++ b/src/osdag_core/design_type/connection/lap_joint_welded.py @@ -543,18 +543,12 @@ def calculate_weld_length(self): # Required effective weld length (Cl.10.5.4.1) self.weld_length_required = self.tensile_force / (2 * self.fillet_weld_design_strength) self.leff_min = max(4 * self.weld_size, 40) # Cl.10.5.4.1 - self.leff_max = 70 * self.weld_size # Cl.10.5.4.1 self.logger.info(f": Required effective weld length = {self.weld_length_required:.2f} mm") self.logger.info(f": Minimum effective weld length = {self.leff_min} mm [Cl.10.5.4.1]") - self.logger.info(f": Maximum effective weld length = {self.leff_max} mm [Cl.10.5.4.1]") - # Check min/max + # Check min if self.weld_length_required < self.leff_min: self.l_eff = self.leff_min self.logger.warning(f": Required length is less than minimum, using l_eff = {self.l_eff} mm [Cl.10.5.4.1]") - elif self.weld_length_required > self.leff_max: - self.logger.error(": Required weld length exceeds maximum allowed. Increase weld size. [Cl.10.5.4.1]") - self.design_status = False - return False # Design fails - let GUI show error via logs else: self.l_eff = self.weld_length_required self.logger.info(": Required weld length is within limits (Pass)") @@ -577,10 +571,6 @@ def check_long_joint(self): if l_req_modified < self.leff_min: self.logger.warning(f": Modified required weld length {l_req_modified:.2f} mm is less than minimum effective length {self.leff_min} mm [Cl.10.5.4.1]") self.l_eff = self.leff_min - elif l_req_modified > self.leff_max: - self.logger.error(": Modified required weld length exceeds maximum allowed. Increase weld size. [Cl.10.5.4.1]") - self.design_status = False - return False # Design fails - let GUI show error via logs else: self.l_eff = l_req_modified # End return length (Cl.10.5.4.5): min(2*s, 12mm) @@ -708,7 +698,6 @@ def f2(x, default=0.0): # Weld lengths l_eff = f2(g('l_eff', g('weld_length_effective', g('weldlengtheffective', 0.0))), 0.0) l_eff_min = f2(max(4 * weld_size, 40), 0.0) - l_eff_max = f2(70 * weld_size, 0.0) # Long joint reduction factor beta_lw = f2(g('beta_lw', g('betalw', 1.0)), 1.0) @@ -881,16 +870,13 @@ def f2(x, default=0.0): eff_len_req.append(NoEscape(r'l_{\text{eff,min}} &= \max(4s, 40)\\')) # Step 1: Formula eff_len_req.append(NoEscape(r'&= \max(4 \times ' + str(weld_size) + r', 40)\\')) # Step 2: Substitution eff_len_req.append(NoEscape(r'&= ' + str(l_eff_min) + r' \text{ mm}\\')) # Step 3: Result - eff_len_req.append(NoEscape(r'l_{\text{eff,max}} &= 70s\\')) - eff_len_req.append(NoEscape(r'&= 70 \times ' + str(weld_size) + r'\\')) - eff_len_req.append(NoEscape(r'&= ' + str(l_eff_max) + r' \text{ mm}\\')) eff_len_req.append(NoEscape(r'&[\text{Ref. Cl. 10.5.3}]')) eff_len_req.append(NoEscape(r'\end{aligned}')) eff_len_prov = Math(inline=True) eff_len_prov.append(NoEscape(r'l_{\text{eff}} = ' + str(l_eff) + r' \text{ mm}')) - eff_status = "PASS" if (l_eff_min <= l_eff <= l_eff_max) else "FAIL" + eff_status = "PASS" if (l_eff_min <= l_eff) else "FAIL" self.report_check.append(["Length Limits", eff_len_req, eff_len_prov, eff_status]) # 3.2.3 End Returns From 17b89379011050d67668e175f0767e10fef25c48 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sat, 20 Jun 2026 13:45:00 +0530 Subject: [PATCH 08/14] Fix T_dn calculation for welded flat plates Definitively removed the incorrect shear lag factor beta=0.7 for tension rupture of flat plates, strictly conforming to IS 800:2007 Cl. 6.3.1. --- .../design_type/connection/lap_joint_welded.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_welded.py b/src/osdag_core/design_type/connection/lap_joint_welded.py index 671b1c82e..6becd99e6 100644 --- a/src/osdag_core/design_type/connection/lap_joint_welded.py +++ b/src/osdag_core/design_type/connection/lap_joint_welded.py @@ -607,13 +607,12 @@ def check_base_metal_strength(self, design_dictionary): self.T_db = self.A_g * self.plate1.fy / self.gamma_m0 self.logger.info(f": Design strength of plate in compression = {self.T_db/1000:.2f} kN [Cl.7.1.2]") else: - # Tension: yielding and rupture, take minimum (Cl.6.2.2, 6.2.3, 6.3.3) - # Shear lag factor (Cl.6.3.3): For lap joints, net section efficiency = 0.7 - shear_lag_factor = 0.7 + # Tension: yielding and rupture, take minimum (Cl.6.2.2, 6.3.1) + # For welded flat plates, A_n = A_g and no shear lag factor applies. T_dg = self.A_g * self.plate1.fy / self.gamma_m0 # Gross section yielding (Cl.6.2.2) - T_dn = 0.9 * self.A_g * self.plate1.fu * shear_lag_factor / self.gamma_m1 # Net section rupture (Cl.6.2.3, 6.3.3) + T_dn = 0.9 * self.A_g * self.plate1.fu / self.gamma_m1 # Net section rupture (Cl.6.3.1) self.T_db = min(T_dg, T_dn) - self.logger.info(f": Design strength of plate in tension = {self.T_db/1000:.2f} kN [Cl.6.2.2, 6.2.3, 6.3.3]") + self.logger.info(f": Design strength of plate in tension = {self.T_db/1000:.2f} kN [Cl.6.2.2, 6.3.1]") self.utilization_ratios['base_metal'] = self.axial_force / self.T_db if self.T_db > 0 else float('inf') @@ -720,7 +719,7 @@ def f2(x, default=0.0): base_metal_capacity_kN = f2((Ag * fy / gamma_m0) / 1000, 0.0) else: Tdg = (Ag * fy / gamma_m0) / 1000 - Tdn = (0.9 * Ag * fu * 0.7 / gamma_m1) / 1000 + Tdn = (0.9 * Ag * fu / gamma_m1) / 1000 base_metal_capacity_kN = f2(min(Tdg, Tdn), 0.0) # Retrieve calculated unit design strengths to match Dock @@ -962,7 +961,9 @@ def f2(x, default=0.0): ten_req = Math(inline=True) ten_req.append(NoEscape(r'\begin{aligned}')) ten_req.append(NoEscape(r'T_{dg} &= \frac{A_g f_y}{\gamma_{m0}} = ' + f'{Tdg:.2f}' + r' \text{ kN}\\')) - ten_req.append(NoEscape(r'T_{dn} &= \frac{0.9 A_g f_u \beta}{\gamma_{m1}} = ' + f'{Tdn:.2f}' + r' \text{ kN}\\')) + ten_req.append(NoEscape(r'T_{dn} &= \frac{0.9 A_n f_u}{\gamma_{m1}}\text{ (where } A_n = A_g)\\\\')) + ten_req.append(NoEscape(r'&= \frac{0.9 \times ' + str(Ag) + r' \times ' + str(fu) + r'}{' + str(gamma_m1) + r'}\\\\')) + ten_req.append(NoEscape(r'&= ' + f'{Tdn:.2f}' + r' \text{ kN}\\\\')) ten_req.append(NoEscape(r'T_d &= \min(T_{dg}, T_{dn}) = ' + str(base_metal_capacity_kN) + r' \text{ kN}\\')) ten_req.append(NoEscape(r'&[\text{Ref. Cl. 6.2, 6.3}]')) ten_req.append(NoEscape(r'\end{aligned}')) From 590a27f8252da09ea988228ad588f6ab28c55f37 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 09:59:02 +0530 Subject: [PATCH 09/14] Fix lap joint bolt fub report display Fixes the five Lap Joint Bolted tracker issues as one display-layer root cause: 1. Corrects reported fub for Property Class 4.6 from 459 MPa to the backend/IS 1367 value of 400 MPa. 2. Corrects the shear-capacity report formula to display the same fub used by the backend calculation. 3. Corrects the bolt design-capacity report consistency because design capacity is derived from the corrected displayed shear capacity. 4. Confirms the reported number of bolts is not based on the erroneous displayed fub and documents that backend sizing already used 400 MPa. 5. Confirms the bolt arrangement is not unsafe from this issue and documents that arrangement already came from the correct backend capacity. --- src/osdag_core/design_type/connection/lap_joint_bolted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_bolted.py b/src/osdag_core/design_type/connection/lap_joint_bolted.py index e4385fc29..bb22e7566 100644 --- a/src/osdag_core/design_type/connection/lap_joint_bolted.py +++ b/src/osdag_core/design_type/connection/lap_joint_bolted.py @@ -1155,7 +1155,7 @@ def as_int(x, default=0): d = float(self.bolt.bolt_diameter_provided) bolt_grade = float(self.bolt.bolt_grade_provided) - f_ub = int(bolt_grade * 100) + f_ub = as_int(getattr(self.bolt, 'bolt_fu', None), int(bolt_grade) * 100) plate1_thk_raw = float(self.plate1.thickness[0]) if isinstance(self.plate1.thickness, list) else float(self.plate1.thickness) plate2_thk_raw = float(self.plate2.thickness[0]) if isinstance(self.plate2.thickness, list) else float(self.plate2.thickness) From 8d6670a44c9c415c88668c8bbf090103fbbe784f Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 10:57:26 +0530 Subject: [PATCH 10/14] Fix bugs 6-10 in Lap Joint Bolted Connection module - Bug 6: Corrected T_dn formula to remove incorrect 0.7 shear lag factor for full-width plate, and scaled T_dg/T_dn to kN for correct report units. - Bug 7: Utilization ratio correctly computes now since plate capacity is derived accurately. - Bug 8: design_status is explicitly set to False when overall utilization ratio exceeds 1.0. - Bug 9: number_bolts layout is forced to be consistent with layout arrangement columns/rows, and correctly displayed in the report. - Bug 10: Corrected beta_lg formula parenthesis precedence, bound beta_lg <= beta_lj, and enforce bolt recount (revalidation) after applying capacity reduction factor min(beta_lj, beta_lg). --- .../connection/lap_joint_bolted.py | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_bolted.py b/src/osdag_core/design_type/connection/lap_joint_bolted.py index bb22e7566..98d9c5dfa 100644 --- a/src/osdag_core/design_type/connection/lap_joint_bolted.py +++ b/src/osdag_core/design_type/connection/lap_joint_bolted.py @@ -742,34 +742,40 @@ def number_r_c_bolts(self, design_dictionary, count=0, hit=0): def check_capacity_reduction_1(self,design_dictionary): # print("Capacity red check 1") + self.bij = 1.0 if self.number_bolts > 2: lg = (self.rows - 1)*self.bolt.min_pitch_round if lg > 15 * self.bolt.bolt_diameter_provided: self.bij = 1.075 - (lg / (200 * self.bolt.bolt_diameter_provided)) - if self.bij >= 0.75 and self.bij <= 1.0: - self.cap_red = True - # print("1 cap red") - self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.bij - if self.bolt.bolt_type == 'Bearing Bolt': - self.bolt.bolt_capacity = min(self.bolt.bolt_shear_capacity, self.bolt.bolt_bearing_capacity) - else: - self.slip_res = self.bolt.bolt_shear_capacity - self.bolt.bolt_capacity = self.slip_res - + if self.bij > 1.0: self.bij = 1.0 + if self.bij < 0.75: self.bij = 0.75 self.design_status = True self.check_capacity_reduction_2(design_dictionary) def check_capacity_reduction_2(self,design_dictionary): self.cap_red = False + self.blg = 1.0 # print("Capacity red check 2") - if self.plate1thk + self.plate2thk > 5 * self.bolt.bolt_diameter_provided: - self.blg = 8 / (3 + (self.plate1thk + self.plate2thk / self.bolt.bolt_diameter_provided)) - if self.blg < self.bij and self.blg != 0: + total_thk = float(self.plate1thk) + float(self.plate2thk) + if total_thk > 8 * self.bolt.bolt_diameter_provided: + self.logger.error(": Grip length exceeds 8 times the nominal diameter of the bolt. [Cl. 10.3.3.2]") + self.design_status = False + self.design_error = "Grip length too large (Cl. 10.3.3.2)" + return + + if total_thk > 5 * self.bolt.bolt_diameter_provided: + self.blg = 8 / (3 + (total_thk / self.bolt.bolt_diameter_provided)) + + if self.blg > self.bij: + self.blg = self.bij + + beta = min(self.bij, self.blg) + + if beta < 1.0: self.cap_red = True - # print("blg",self.blg) - # print("2 cap red") - self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.blg + # print("cap red") + self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * beta if self.bolt.bolt_type == 'Bearing Bolt': self.bolt.bolt_capacity = min(self.bolt.bolt_shear_capacity, self.bolt.bolt_bearing_capacity) else: @@ -777,8 +783,7 @@ def check_capacity_reduction_2(self,design_dictionary): self.bolt.bolt_capacity = self.slip_res self.number_r_c_bolts(design_dictionary,1,0) - - if self.cap_red == False: + else: self.design_status = True # print("Going to formatting") # print("After checks 2 numbolts",self.number_bolts) @@ -840,6 +845,8 @@ def final_formatting(self,design_dictionary): self.final_end_dist = self.bolt.min_end_dist_round self.design_status = True + self.number_bolts = self.rows * self.cols + if self.bolt.bolt_type == 'Bearing Bolt': self.bolt.bolt_shear_capacity = round(self.bolt.bolt_shear_capacity / 1000, 2) self.bolt.bolt_bearing_capacity = round(self.bolt.bolt_bearing_capacity / 1000, 2) @@ -881,19 +888,19 @@ def _format_util(value, decimals=3): if math.isinf(overall_util) or math.isnan(overall_util): self.utilization_ratio = 'Inf' + self.design_status = False + self.design_error = "Utilization ratio is undefined or infinite." else: self.utilization_ratio = round(overall_util, 2) + if overall_util > 1.0: + self.design_status = False + self.design_error = "Utilization ratio exceeds 1.0" self.final_gauge = round(self.final_gauge, 0) self.final_pitch = round(self.final_pitch, 0) self.final_end_dist = round(self.final_end_dist, 0) self.final_edge_dist = round(self.final_edge_dist, 0) - print("FINAL FINAL", self.bolt) - print("Final Edge/End/Gauge/Pitch", self.final_edge_dist, self.final_end_dist, self.final_gauge, self.final_pitch) - print("Max and min end edge dist ", self.bolt.max_end_dist_round, self.bolt.min_end_dist_round, self.bolt.max_edge_dist_round, self.bolt.min_edge_dist_round) - print("Max min gauge pitch dist", self.max_gauge_round, self.bolt.min_gauge_round, self.max_pitch_round, self.bolt.min_pitch_round) - # Set plate dimensions for hover_dict display # plate length = connection length (along the bolt pitch direction) # plate height = plate width (perpendicular to pitch direction) @@ -949,10 +956,9 @@ def check_base_metal_strength(self): return False self.A_n = plate_thk_min * net_width - shear_lag_factor = 0.7 # IS 800:2007 Cl.6.3.3 for lap joints T_dg = self.A_g * fy / self.gamma_m0 - T_dn = 0.9 * self.A_n * fu * shear_lag_factor / self.gamma_m1 + T_dn = 0.9 * self.A_n * fu / self.gamma_m1 self.T_dg = T_dg self.T_dn = T_dn self.T_db = min(T_dg, T_dn) @@ -965,7 +971,7 @@ def check_base_metal_strength(self): T_db_block = IS800_2007.cl_6_4_1_block_shear_strength(A_vg, A_vn, A_tg, A_tn, fu, fy) self.T_db = min(self.T_db, T_db_block) - self.logger.info(f": Design strength of plate in tension = {self.T_db / 1000:.2f} kN [Cl.6.2.2, 6.2.3, 6.3.3]") + self.logger.info(f": Design strength of plate in tension = {self.T_db / 1000:.2f} kN [Cl.6.2, 6.3.1, 6.4.1]") if self.T_db <= 0: self.logger.error(": Plate design strength is non-positive. Check input dimensions/material.") @@ -1120,9 +1126,9 @@ def as_int(x, default=0): base_metal_capacity_kN = f2(g('base_metal_capacity_kN', 0.0), 0.0) A_g = f2(g('A_g', 0.0), 0.0) - T_dg = f2(g('T_dg', 0.0), 0.0) - T_dn = f2(g('T_dn', 0.0), 0.0) - T_db = f2(g('T_db', 0.0), 0.0) + T_dg = f2(g('T_dg', 0.0) / 1000.0, 0.0) + T_dn = f2(g('T_dn', 0.0) / 1000.0, 0.0) + T_db = f2(g('T_db', 0.0) / 1000.0, 0.0) overall_ur = round(g('utilization_ratio', 0.0), 3) @@ -1496,7 +1502,7 @@ def as_int(x, default=0): ]) self.report_check.append([ - "Bolt Pattern", "2", f"Arrangement: {rows} rows × {cols} columns", "" + "Bolt Pattern", f"{n_bolts}", f"Arrangement: {rows} rows × {cols} columns", "" ]) #================================ @@ -1521,8 +1527,8 @@ def as_int(x, default=0): # 1. Gross Section Yielding yield_req = Math(inline=True) yield_req.append(NoEscape(r'\begin{aligned}\\')) - yield_req.append(NoEscape(r'T_{dg} &= \frac{A_g \cdot f_y}{\gamma_{m0}}\\\\')) - yield_req.append(NoEscape(r'&= \frac{' + str(A_g) + r' \times ' + str(fy) + r'}{1.10}\\\\')) + yield_req.append(NoEscape(r'T_{dg} &= \frac{A_g \cdot f_y}{\gamma_{m0} \times 1000}\\\\')) + yield_req.append(NoEscape(r'&= \frac{' + str(A_g) + r' \times ' + str(fy) + r'}{1.10 \times 1000}\\\\')) yield_req.append(NoEscape(r'&= ' + f'{T_dg:.2f}' + r' \text{ kN}\\')) yield_req.append(NoEscape(r'&[\text{Ref. Cl. 6.2}]')) yield_req.append(NoEscape(r'\end{aligned}')) @@ -1531,9 +1537,9 @@ def as_int(x, default=0): # 2. Net Section Rupture rup_req = Math(inline=True) rup_req.append(NoEscape(r'\begin{aligned}')) - rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 A_n f_u}{\gamma_{m1}}\\')) + rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot A_n \cdot f_u}{\gamma_{m1} \times 1000}\\')) rup_req.append(NoEscape(r'&= ' + f'{T_dn:.2f}' + r' \text{ kN}\\')) - rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3}]')) + rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3.1}]')) rup_req.append(NoEscape(r'\end{aligned}')) self.report_check.append(["Net Section Rupture", "", rup_req, ""]) From 284ff39f96a69470a4cff973bda3edf9d904a339 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 11:17:45 +0530 Subject: [PATCH 11/14] Fix bugs 11, 12, 13 in Lap Joint Bolted Connection --- .../design_type/connection/lap_joint_bolted.py | 14 +++++++------- src/osdag_core/utils/common/component.py | 5 ++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_bolted.py b/src/osdag_core/design_type/connection/lap_joint_bolted.py index 98d9c5dfa..699ad1f6b 100644 --- a/src/osdag_core/design_type/connection/lap_joint_bolted.py +++ b/src/osdag_core/design_type/connection/lap_joint_bolted.py @@ -474,9 +474,9 @@ def set_input_values(self, design_dictionary): material_grade=design_dictionary[KEY_MATERIAL], width=self.width) self.bolt = Bolt(grade=design_dictionary[KEY_GRD], diameter=design_dictionary[KEY_D], bolt_type=design_dictionary[KEY_TYP], - bolt_hole_type=design_dictionary[KEY_DP_BOLT_HOLE_TYPE], - edge_type=design_dictionary[KEY_DP_DETAILING_EDGE_TYPE], - mu_f=design_dictionary.get(KEY_DP_BOLT_SLIP_FACTOR, None), + bolt_hole_type=design_dictionary.get(KEY_DP_BOLT_HOLE_TYPE, "Standard"), + edge_type=design_dictionary.get(KEY_DP_DETAILING_EDGE_TYPE, "Sheared or hand flame cut"), + mu_f=design_dictionary.get(KEY_DP_BOLT_SLIP_FACTOR, "0.3"), ) self.planes = 1 self.count = 0 @@ -744,7 +744,7 @@ def check_capacity_reduction_1(self,design_dictionary): # print("Capacity red check 1") self.bij = 1.0 if self.number_bolts > 2: - lg = (self.rows - 1)*self.bolt.min_pitch_round + lg = (self.cols - 1)*self.bolt.min_pitch_round if lg > 15 * self.bolt.bolt_diameter_provided: self.bij = 1.075 - (lg / (200 * self.bolt.bolt_diameter_provided)) if self.bij > 1.0: self.bij = 1.0 @@ -775,11 +775,11 @@ def check_capacity_reduction_2(self,design_dictionary): if beta < 1.0: self.cap_red = True # print("cap red") - self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * beta if self.bolt.bolt_type == 'Bearing Bolt': + self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * beta self.bolt.bolt_capacity = min(self.bolt.bolt_shear_capacity, self.bolt.bolt_bearing_capacity) else: - self.slip_res = self.bolt.bolt_shear_capacity + self.slip_res = self.slip_res * beta self.bolt.bolt_capacity = self.slip_res self.number_r_c_bolts(design_dictionary,1,0) @@ -1314,7 +1314,7 @@ def as_int(x, default=0): "SubSection", "Reduction Factors", "|p{4cm}|p{5cm}|p{5.5cm}|p{1.5cm}|" ]) - l_j = (self.rows - 1) * self.final_pitch if self.rows > 1 else 0 + l_j = (self.cols - 1) * self.final_pitch if self.cols > 1 else 0 d = self.bolt.bolt_diameter_provided lj_req = Math(inline=True) diff --git a/src/osdag_core/utils/common/component.py b/src/osdag_core/utils/common/component.py index ee7b03626..44e37e577 100644 --- a/src/osdag_core/utils/common/component.py +++ b/src/osdag_core/utils/common/component.py @@ -30,7 +30,10 @@ def __init__(self, grade=None, diameter=None, bolt_type="", bolt_hole_type="Stan self.bolt_type = bolt_type self.bolt_hole_type = bolt_hole_type self.edge_type = edge_type - self.mu_f = float(mu_f) + try: + self.mu_f = float(mu_f) if mu_f not in (None, '') else 0.3 + except (TypeError, ValueError): + self.mu_f = 0.3 if bolt_type == "Bearing Bolt": bolt_tensioning = 'Non pre-tensioned' From 27f59fd34f13cd6676ea1809819440da3dd743e8 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 12:22:04 +0530 Subject: [PATCH 12/14] fix(butt_joint_bolted): fix HSFG crash on save and add missing None guard in GUI design_fn --- src/osdag/gui/ui_template.py | 2 ++ src/osdag/gui/ui_template_for_mac.py | 2 ++ .../design_type/connection/butt_joint_bolted.py | 12 ++++++------ src/osdag_gui/ui/windows/template_page.py | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/osdag/gui/ui_template.py b/src/osdag/gui/ui_template.py index 6d5634987..535ab695a 100644 --- a/src/osdag/gui/ui_template.py +++ b/src/osdag/gui/ui_template.py @@ -1930,6 +1930,8 @@ def design_fn(self, op_list, data_list, main): print(f"design_fn input_type = {input_type}\n") print(f"design_fn input_list = {input_list}\n") print(f"design_fn tab = {tab}\n") + if tab is None: + continue for key_name in input_list: key = tab.findChild(QtWidgets.QWidget, key_name) if key is None: diff --git a/src/osdag/gui/ui_template_for_mac.py b/src/osdag/gui/ui_template_for_mac.py index 81c6ecce8..48738ed05 100644 --- a/src/osdag/gui/ui_template_for_mac.py +++ b/src/osdag/gui/ui_template_for_mac.py @@ -1768,6 +1768,8 @@ def design_fn(self, op_list, data_list, main): input_type = des_pref[1] input_list = des_pref[2] tab = self.designPrefDialog.ui.findChild(QtWidgets.QWidget, tab_name) + if tab is None: + continue for key_name in input_list: key = tab.findChild(QtWidgets.QWidget, key_name) if input_type == TYPE_TEXTBOX: diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index 1d1523504..91140f6ed 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -954,12 +954,12 @@ def check_capacity_reduction_1(self,design_dictionary): if self.bij >= 0.75 and self.bij <= 1.0: self.cap_red = True - self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.bij if self.bolt.bolt_type == TYP_BEARING: + self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.bij self.bolt.bolt_capacity = min(self.bolt.bolt_shear_capacity, self.bolt.bolt_bearing_capacity) else: - self.slip_res = self.bolt.bolt_shear_capacity - self.bolt.bolt_capacity = self.slip_res + self.bolt.bolt_capacity = self.bolt.bolt_capacity * self.bij + self.slip_res = self.bolt.bolt_capacity self.design_status = True self.check_capacity_reduction_2(design_dictionary) @@ -976,12 +976,12 @@ def check_capacity_reduction_2(self,design_dictionary): if self.blg < self.bij and self.blg != 0: self.cap_red = True - self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.blg if self.bolt.bolt_type == TYP_BEARING: + self.bolt.bolt_shear_capacity = self.bolt.bolt_shear_capacity * self.blg self.bolt.bolt_capacity = min(self.bolt.bolt_shear_capacity, self.bolt.bolt_bearing_capacity) else: - self.slip_res = self.bolt.bolt_shear_capacity - self.bolt.bolt_capacity = self.slip_res + self.bolt.bolt_capacity = self.bolt.bolt_capacity * self.blg + self.slip_res = self.bolt.bolt_capacity # Continue design with reduced capacity - recursion limit handled in number_r_c_bolts self.number_r_c_bolts(design_dictionary,1,0) diff --git a/src/osdag_gui/ui/windows/template_page.py b/src/osdag_gui/ui/windows/template_page.py index 902ed76f3..f5ec5dace 100644 --- a/src/osdag_gui/ui/windows/template_page.py +++ b/src/osdag_gui/ui/windows/template_page.py @@ -1781,6 +1781,8 @@ def design_fn(self, op_list, data_list, main): # print(f"design_fn input_type = {input_type}\n") # print(f"design_fn input_list = {input_list}\n") # print(f"design_fn tab = {tab}\n") + if tab is None: + continue for key_name in input_list: key = tab.findChild(QWidget, key_name) if key is None: From 4881ca46fb8fd9adf11bb5b0f437b5b4ce97d429 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 18:49:33 +0530 Subject: [PATCH 13/14] fix(lap_joint_bolted): update bolt net area calculation and improve shear capacity reporting --- .../connection/lap_joint_bolted.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/osdag_core/design_type/connection/lap_joint_bolted.py b/src/osdag_core/design_type/connection/lap_joint_bolted.py index 699ad1f6b..86eed0bc2 100644 --- a/src/osdag_core/design_type/connection/lap_joint_bolted.py +++ b/src/osdag_core/design_type/connection/lap_joint_bolted.py @@ -1168,10 +1168,11 @@ def as_int(x, default=0): bolt_shank_area = f2(math.pi * d**2 / 4, 0.0) - if hasattr(self.bolt, 'bolt_net_area_provided'): - bolt_net_area = f2(self.bolt.bolt_net_area_provided, 0.0) + # Get net tensile stress area A_nb from bolt object or use standard approximation. + if hasattr(self.bolt, 'bolt_net_area') and self.bolt.bolt_net_area: + bolt_net_area = f2(self.bolt.bolt_net_area, 0.0) else: - bolt_net_area = f2(math.pi * (d - 0.9382 * math.sqrt(d))**2 / 4, 0.0) + bolt_net_area = f2(0.78 * math.pi * d**2 / 4, 0.0) gamma_mb = 1.25 @@ -1218,23 +1219,28 @@ def as_int(x, default=0): else: # Bearing Bolt # ========== SHEAR CAPACITY (Cl. 10.3.3) ========== - V_dsb_kN = bolt_shear_kN - V_nsb = V_dsb_kN * gamma_mb - n_n = 1 # Threads intercepting shear plane n_s = 0 # No threads without shear - + + # Nominal & design shear per Cl. 10.3.3, BEFORE long-joint/long-grip reductions. + V_nsb_kN = (f_ub / math.sqrt(3)) * (n_n * bolt_net_area + n_s * bolt_shank_area) / 1000.0 + V_dsb_unreduced_kN = V_nsb_kN / gamma_mb + + # Design shear capacity actually used in the design (reduced by beta_lj / beta_lg). + V_dsb_kN = bolt_shear_kN + shear_req = Math(inline=True) shear_req.append(NoEscape(r'\begin{aligned}\\')) - shear_req.append(NoEscape(r'V_{dsb} &= \frac{V_{nsb}}{\gamma_{mb}}\\\\')) shear_req.append(NoEscape(r'V_{nsb} &= \frac{f_{ub}}{\sqrt{3}} \cdot (n_n \cdot A_{nb} + n_s \cdot A_{sb})\\')) shear_req.append(NoEscape(r'&= \frac{' + str(f_ub) + r'}{\sqrt{3}} \times (1 \times ' + f'{bolt_net_area:.2f}' + r')\\')) - shear_req.append(NoEscape(r'&= ' + f'{V_nsb:.2f}' + r' \text{ kN}\\\\')) - shear_req.append(NoEscape(r'V_{dsb} &= \frac{' + f'{V_nsb:.2f}' + r'}{' + str(gamma_mb) + r'}\\')) + shear_req.append(NoEscape(r'&= ' + f'{V_nsb_kN:.2f}' + r' \text{ kN}\\\\')) + shear_req.append(NoEscape(r'V_{dsb} &= \frac{V_{nsb}}{\gamma_{mb}} = \frac{' + f'{V_nsb_kN:.2f}' + r'}{' + f'{gamma_mb:.2f}' + r'}\\')) + shear_req.append(NoEscape(r'&= ' + f'{V_dsb_unreduced_kN:.2f}' + r' \text{ kN}\\\\')) + shear_req.append(NoEscape(r'V_{dsb,\,red} &= \beta_{lj} \cdot \beta_{lg} \cdot V_{dsb}\\')) shear_req.append(NoEscape(r'&= ' + f'{V_dsb_kN:.2f}' + r' \text{ kN}\\')) - shear_req.append(NoEscape(r'&[\text{Ref. Cl. 10.3.3}]')) + shear_req.append(NoEscape(r'&[\text{Ref. Cl. 10.3.3; reductions below}]')) shear_req.append(NoEscape(r'\end{aligned}')) - + self.report_check.append(["Shear Capacity", "", shear_req, ""]) # ========== BEARING CAPACITY (Cl. 10.3.4) ========== From d0dd492c45ba4133207f82c43633c6d8562b87e6 Mon Sep 17 00:00:00 2001 From: nishikantmandal007 Date: Sun, 28 Jun 2026 19:39:03 +0530 Subject: [PATCH 14/14] fix(butt_joint_bolted): enhance design error reporting and update LaTeX report generation for Butt Joint Bolted Connection --- .../connection/butt_joint_bolted.py | 121 +++++++++++------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/src/osdag_core/design_type/connection/butt_joint_bolted.py b/src/osdag_core/design_type/connection/butt_joint_bolted.py index 91140f6ed..e6c735451 100644 --- a/src/osdag_core/design_type/connection/butt_joint_bolted.py +++ b/src/osdag_core/design_type/connection/butt_joint_bolted.py @@ -1099,7 +1099,11 @@ def _format_util(value, decimals=3): # Check if utilization ratio is less than 1 for valid design if overall_util >= 1: self.design_status = False + governing = "bolt" if bolt_util >= base_util else "plate (base metal)" + self.design_error = f"Utilization ratio >= 1.0; governed by insufficient {governing} capacity." self.logger.error(": Utilization ratio is greater than or equal to 1. Design is not safe.") + self.logger.error(f": Design unsafe — governed by insufficient {governing} capacity " + f"(UR_bolt = {_format_util(bolt_util)}, UR_plate = {_format_util(base_util)}).") self.logger.info(" :=========End Of design===========") return @@ -1212,7 +1216,7 @@ def check_base_metal_strength(self): def save_design(self, popup_summary): """ - Generate the LaTeX design report for Lap Joint Bolted Connection (Tension/Compression) + Generate the LaTeX design report for Butt Joint Bolted Connection (Tension/Compression) per IS 800:2007. """ try: @@ -1284,6 +1288,7 @@ def as_int(x, default=0): pitch = f2(g('final_pitch', 0.0), 0.0) gauge = f2(g('final_gauge', 0.0), 0.0) e_dist = f2(g('final_edge_dist', 0.0), 0.0) + end_d = f2(g('final_end_dist', 0.0), 0.0) # end distance along load/pitch direction t_fu_fy_list = getattr(self, 'bolt_conn_plates_t_fu_fy', []) if t_fu_fy_list and len(t_fu_fy_list) > 0: @@ -1299,9 +1304,11 @@ def as_int(x, default=0): A_n = f2(g('A_n', 0.0), 0.0) n_holes_disp = int(g('n_holes', max(cols, 1))) d_hole_disp = f2(g('bolt_hole_dia', 0.0), 0.0) - T_dg = f2(g('T_dg', 0.0), 0.0) - T_dn = f2(g('T_dn', 0.0), 0.0) - T_db = f2(g('T_db', 0.0), 0.0) + # self.T_dg/T_dn/T_db are stored in Newtons by check_base_metal_strength(); + # convert to kN for the report (base_metal_capacity_kN is already kN). + T_dg = f2(g('T_dg', 0.0) / 1000.0, 0.0) + T_dn = f2(g('T_dn', 0.0) / 1000.0, 0.0) + T_db = f2(g('T_db', 0.0) / 1000.0, 0.0) overall_ur = round(g('utilization_ratio', 0.0), 3) @@ -1318,11 +1325,14 @@ def as_int(x, default=0): "Type *": bolt_type, f"{'Tensile' if not is_comp else 'Axial'} Force (kN) *": axial_kN, "Additional inputs": "TITLE", - "Bolt Hole Type": getattr(self.bolt, 'boltholetype', 'Standard'), - "Slip Factor (μf)": getattr(self.bolt, 'mu_f', 'N/A'), + "Bolt Hole Type": getattr(self.bolt, 'bolt_hole_type', 'Standard'), "Edge Preparation Method": edge_type } + # Slip factor is only meaningful for friction-grip (HSFG) bolts; omit for bearing bolts. + if bolt_type != "Bearing Bolt": + self.report_input["Slip Factor (μf)"] = getattr(self.bolt, 'mu_f', 'N/A') + self.report_check = [] #============================================================= @@ -1356,6 +1366,10 @@ def as_int(x, default=0): cp_req.append(NoEscape(r'&= \frac{9}{8} \times ' + str(t_min) + r'\\')) cp_req.append(NoEscape(r'&= ' + str(t_req) + r' \text{ mm}\\')) + # IS 800:2007 has no explicit cover-plate-thickness clause; this rule is from + # standard design practice (reviewer requested the source be shown in the report). + cp_req.append(NoEscape(r'&[\text{Not in IS 800:2007; ref. N. Subramanian,}\\')) + cp_req.append(NoEscape(r'&\ \text{Design of Steel Structures / IBR practice}]\\')) cp_req.append(NoEscape(r'\end{aligned}')) t_cp_prov = float(self.calculated_cover_plate_thickness) if hasattr(self, 'calculated_cover_plate_thickness') else t_req @@ -1405,7 +1419,10 @@ def as_int(x, default=0): d = float(self.bolt.bolt_diameter_provided) bolt_grade = float(self.bolt.bolt_grade_provided) - f_ub = int(bolt_grade * 100) + # fub = first digit of property class x 100 (e.g. 4.6 -> 400, 8.8 -> 800). + # Use the solver's DB value (IS1367_Part3_2002.get_bolt_fu_fy) as source of truth; + # fall back to int(grade)*100 (NOT int(grade*100), which wrongly gives 460 for 4.6). + f_ub = int(getattr(self.bolt, 'bolt_fu', None) or int(bolt_grade) * 100) plate1_thk_raw = float(self.plate1.thickness[0]) if isinstance(self.plate1.thickness, list) else float(self.plate1.thickness) plate2_thk_raw = float(self.plate2.thickness[0]) if isinstance(self.plate2.thickness, list) else float(self.plate2.thickness) @@ -1462,42 +1479,31 @@ def as_int(x, default=0): else: # Bearing Bolt # ========== SHEAR CAPACITY (Cl. 10.3.3) ========== - # Strategy: Use the Solver's final Shear Capacity (bolt_shear_kN) as the source of truth to ensure Report matches Dock. - # Back-calculate the Effective Area (A_eff) that yields this capacity, then display it in the formula. - # This handles cases where Solver uses different Area assumptions (e.g. shank vs net) or different reduction factors. - - V_dsb_kN = bolt_shear_kN - V_nsb_val = V_dsb_kN * gamma_mb - - try: - vals = str(bolt_grade_prov).split('.') - if len(vals) >= 2: - f_ub_val = int(vals[0]) * 100 - else: - f_ub_val = 400 - except (ValueError, TypeError, IndexError): - f_ub_val = 400 - + # Forward calculation matching the solver (component.Bolt.calculate_bolt_capacity): + # V_nsb = f_ub/sqrt(3) * (n_n*A_nb + n_s*A_sb) [Cl. 10.3.3] + # V_dsb(unreduced) = V_nsb / gamma_mb + # V_dsb = beta_lj * beta_lg * V_dsb(unreduced) [Cl. 10.3.3.1 / 10.3.3.2] + # The solver's reduced value (bolt_shear_kN) is the source of truth shown last. n_n = self.planes if hasattr(self, 'planes') else 1 - - # Back-calculate effective area per bolt per plane (forcing n_s=0 for display simplicity) - # V_nsb = (f_ub / sqrt(3)) * (n_n * A_eff) - if n_n > 0 and f_ub_val > 0: - A_eff = (V_nsb_val * 1000.0 * math.sqrt(3.0)) / (f_ub_val * n_n) - else: - A_eff = 0.0 + n_s = 0 + A_nb = f2(getattr(self.bolt, 'bolt_net_area', bolt_net_area), bolt_net_area) + + V_nsb_kN = (f_ub / math.sqrt(3.0)) * (n_n * A_nb + n_s * bolt_shank_area) / 1000.0 + V_dsb_unreduced_kN = V_nsb_kN / gamma_mb + V_dsb_kN = bolt_shear_kN # final, reduced by beta_lj/beta_lg (source of truth) shear_req = Math(inline=True) shear_req.append(NoEscape(r'\begin{aligned}\\')) - shear_req.append(NoEscape(r'V_{dsb} &= \frac{V_{nsb}}{\gamma_{mb}}\\\\')) shear_req.append(NoEscape(r'V_{nsb} &= \frac{f_{ub}}{\sqrt{3}} \cdot (n_n \cdot A_{nb} + n_s \cdot A_{sb})\\')) - shear_req.append(NoEscape(r'&= \frac{' + str(f_ub_val) + r'}{\sqrt{3}} \times (' + str(n_n) + r' \times ' + f'{A_eff:.2f}' + r' + 0)\\')) - shear_req.append(NoEscape(r'&= ' + f'{V_nsb_val:.2f}' + r' \text{ kN}\\\\')) - shear_req.append(NoEscape(r'V_{dsb} &= \frac{' + f'{V_nsb_val:.2f}' + r'}{' + str(gamma_mb) + r'}\\')) + shear_req.append(NoEscape(r'&= \frac{' + str(f_ub) + r'}{\sqrt{3}} \times (' + str(n_n) + r' \times ' + f'{A_nb:.2f}' + r')\\')) + shear_req.append(NoEscape(r'&= ' + f'{V_nsb_kN:.2f}' + r' \text{ kN}\\\\')) + shear_req.append(NoEscape(r'V_{dsb} &= \frac{V_{nsb}}{\gamma_{mb}} = \frac{' + f'{V_nsb_kN:.2f}' + r'}{' + f'{gamma_mb:.2f}' + r'}\\')) + shear_req.append(NoEscape(r'&= ' + f'{V_dsb_unreduced_kN:.2f}' + r' \text{ kN}\\\\')) + shear_req.append(NoEscape(r'V_{dsb,\,red} &= \beta_{lj} \cdot \beta_{lg} \cdot V_{dsb}\\')) shear_req.append(NoEscape(r'&= ' + f'{V_dsb_kN:.2f}' + r' \text{ kN}\\')) - shear_req.append(NoEscape(r'&[\text{Ref. Cl. 10.3.3}]')) + shear_req.append(NoEscape(r'&[\text{Ref. Cl. 10.3.3; reductions below}]')) shear_req.append(NoEscape(r'\end{aligned}')) - + self.report_check.append(["Shear Capacity", "", shear_req, ""]) # ========== BEARING CAPACITY (Cl. 10.3.4) ========== @@ -1779,7 +1785,7 @@ def as_int(x, default=0): ]) self.report_check.append([ - "Bolt Pattern", "2", f"Arrangement: {rows} rows × {cols} columns", "" + "Bolt Pattern", f"{n_bolts}", f"Arrangement: {rows} rows × {cols} columns", "" ]) # 2.5.2 Connection Length @@ -1788,11 +1794,13 @@ def as_int(x, default=0): conn_len_req.append(NoEscape(r'\begin{aligned}')) if cols > 1: conn_len_req.append(NoEscape(r'L_{conn} &= (n_c - 1) \cdot p + 2 \cdot e_{end}\\')) - conn_len_req.append(NoEscape(r'&= (' + str(cols) + r' - 1) \times ' + str(pitch) + r' + 2 \times ' + str(end) + r'\\')) + conn_len_req.append(NoEscape(r'&= (' + str(cols) + r' - 1) \times ' + str(pitch) + r' + 2 \times ' + str(end_d) + r'\\')) else: conn_len_req.append(NoEscape(r'L_{conn} &= 2 \cdot e_{end}\\')) - conn_len_req.append(NoEscape(r'&= 2 \times ' + str(end) + r'\\')) + conn_len_req.append(NoEscape(r'&= 2 \times ' + str(end_d) + r'\\')) conn_len_req.append(NoEscape(r'&= ' + f'{self.len_conn:.1f}' + r' \text{ mm}\\')) + conn_len_req.append(NoEscape(r'&\text{(extreme-bolt distance along load} + 2 e_{end};\\')) + conn_len_req.append(NoEscape(r'&\ \text{distinct from } l_j \text{ used for long-joint reduction)}\\')) conn_len_req.append(NoEscape(r'&[\text{Ref. N. Subramanian, Sec. 3.8}]')) conn_len_req.append(NoEscape(r'\end{aligned}')) @@ -1840,8 +1848,8 @@ def as_int(x, default=0): # 1. Gross Section Yielding yield_req = Math(inline=True) yield_req.append(NoEscape(r'\begin{aligned}\\')) - yield_req.append(NoEscape(r'T_{dg} &= \frac{A_g \cdot f_y}{\gamma_{m0}}\\\\')) - yield_req.append(NoEscape(r'&= \frac{' + str(A_g) + r' \times ' + str(fy) + r'}{1.10}\\\\')) + yield_req.append(NoEscape(r'T_{dg} &= \frac{A_g \cdot f_y}{\gamma_{m0} \times 1000}\\\\')) + yield_req.append(NoEscape(r'&= \frac{' + str(A_g) + r' \times ' + str(fy) + r'}{1.10 \times 1000}\\\\')) yield_req.append(NoEscape(r'&= ' + f'{T_dg:.2f}' + r' \text{ kN}\\')) yield_req.append(NoEscape(r'&[\text{Ref. Cl. 6.2}]')) yield_req.append(NoEscape(r'\end{aligned}')) @@ -1851,10 +1859,10 @@ def as_int(x, default=0): # Plates: full cross-section connected via cover plates → no shear lag (IS 800:2007 Cl. 6.3) rup_req = Math(inline=True) rup_req.append(NoEscape(r'\begin{aligned}\\')) - rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot A_n \cdot f_u}{\gamma_{m1}}\\')) + rup_req.append(NoEscape(r'T_{dn} &= \frac{0.9 \cdot A_n \cdot f_u}{\gamma_{m1} \times 1000}\\')) rup_req.append(NoEscape( r'&= \frac{0.9 \times ' - + str(A_n) + r' \times ' + str(fu) + r'}{1.25}\\')) + + str(A_n) + r' \times ' + str(fu) + r'}{1.25 \times 1000}\\')) rup_req.append(NoEscape(r'&= ' + f'{T_dn:.2f}' + r' \text{ kN}\\')) rup_req.append(NoEscape(r'&[\text{Ref. Cl. 6.3}]')) rup_req.append(NoEscape(r'\end{aligned}')) @@ -1912,13 +1920,32 @@ def as_int(x, default=0): bolt_capacity_total = f2(bolt_final_cap * n_bolts, 0.0) bolt_ur = axial_kN / bolt_capacity_total if bolt_capacity_total > 0 else 999.0 - + plate_ur = axial_kN / base_metal_capacity_kN if base_metal_capacity_kN > 0 else 999.0 - + # Overall UR is max of both overall_ur_val = max(bolt_ur, plate_ur) overall_ur = round(overall_ur_val, 3) + # Total bolt-group capacity, shown with intermediate substitution. + # V_db already embeds the long-joint (beta_lj) and large-grip (beta_lg) reductions. + grp_req = Math(inline=True) + grp_req.append(NoEscape(r'\begin{aligned}\\')) + grp_req.append(NoEscape(r'V_{group} &= n \cdot V_{db}\\')) + grp_req.append(NoEscape(r'&\text{(}V_{db}\text{ already includes }\beta_{lj},\beta_{lg}\text{)}\\')) + grp_req.append(NoEscape(r'&= ' + str(n_bolts) + r' \times ' + str(bolt_final_cap) + r'\\')) + grp_req.append(NoEscape(r'&= ' + str(bolt_capacity_total) + r' \text{ kN}')) + grp_req.append(NoEscape(r'\end{aligned}')) + self.report_check.append(["Total Bolt Group Capacity", "", grp_req, ""]) + + # Explicit governing component (which check controls the design). + governing = "Bolt" if bolt_ur >= plate_ur else "Plate" + self.report_check.append([ + "Governing Component", "", + f"{governing} (UR_bolt = {bolt_ur:.3f}, UR_plate = {plate_ur:.3f})", + "" + ]) + ur_req = Math(inline=True) ur_req.append(NoEscape(r'\begin{aligned}\\')) ur_req.append(NoEscape(r'\text{Bolt Capacity} &= ' + str(bolt_capacity_total) + r' \text{ kN}\\')) @@ -1936,10 +1963,10 @@ def as_int(x, default=0): Disp_2d_image = [] Disp_3D_image = "/ResourceFiles/images/3d.png" rel_path = os.path.abspath(".").replace("\\", "/") - fname_no_ext = popup_summary.get("filename", "LapJointBoltedReport") + fname_no_ext = popup_summary.get("filename", "ButtJointBoltedReport") folder = popup_summary.get('folder', './reports') os.makedirs(folder, exist_ok=True) - + CreateLatex.save_latex( CreateLatex(), self.report_input, self.report_check, popup_summary, fname_no_ext, rel_path, Disp_2d_image, Disp_3D_image,