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/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 cb4644930..e6c735451 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.3
]))
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.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)
t4 = ("textBrowser", "", TYPE_TEXT_BROWSER, DETAILING_DESCRIPTION_LAPJOINT, None)
detailing.append(t4)
@@ -388,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
"
@@ -403,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"
)
@@ -615,21 +625,23 @@ 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
)
- # 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.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 calculation as per Eq. 3.3
+ # β_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
@@ -639,7 +651,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
@@ -906,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)
@@ -933,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)
@@ -955,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)
@@ -1001,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:
@@ -1077,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
@@ -1105,8 +1131,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)
@@ -1143,6 +1169,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:
@@ -1152,10 +1180,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)
@@ -1188,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:
@@ -1260,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:
@@ -1271,10 +1300,15 @@ 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)
+ 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)
+ # 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)
@@ -1291,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 = []
#=============================================================
@@ -1313,18 +1350,26 @@ 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}\\'))
+ # 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
@@ -1334,7 +1379,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)
@@ -1359,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)
@@ -1416,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) ==========
@@ -1733,9 +1785,30 @@ 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
+ # 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_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_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}'))
+
+ 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
#================================
@@ -1743,6 +1816,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}\\'))
@@ -1758,25 +1848,21 @@ 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}'))
self.report_check.append(["Gross Section Yield", "", yield_req, ""])
# 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
-
+ # 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 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 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 \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}'))
@@ -1834,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}\\'))
@@ -1858,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,
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..e50d752b6 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, [
@@ -385,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,
@@ -419,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,
@@ -427,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
@@ -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
@@ -665,27 +671,29 @@ 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
)
- # 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.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)
else:
self.packing_plate_thickness = 0.0
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
@@ -875,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
@@ -919,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
@@ -1262,15 +1286,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}'))
@@ -1290,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)
@@ -1509,10 +1549,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}'))
@@ -1520,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
# ==========================================================================
@@ -1594,6 +1653,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)
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..86eed0bc2 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
@@ -742,43 +742,48 @@ 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
+ 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 >= 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")
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)
-
- 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)
@@ -1155,17 +1161,18 @@ 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)
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
@@ -1212,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) ==========
@@ -1308,7 +1320,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)
@@ -1496,7 +1508,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 +1533,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 +1543,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, ""])
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..6becd99e6 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)
@@ -617,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')
@@ -708,7 +697,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)
@@ -731,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
@@ -881,16 +869,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
@@ -976,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}'))
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
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'
diff --git a/src/osdag_gui/ui/windows/template_page.py b/src/osdag_gui/ui/windows/template_page.py
index 64882b35c..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:
@@ -2011,8 +2013,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,