struct_utils 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A-Thomas-eng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: struct_utils
3
+ Version: 0.0.1
4
+ Summary: Development package consisting of many small utility functions, mainly oriented towards structural engineering. Currently structured for personal use, hit me up if there is interest in further development of additional or extended functionality. Published functions should be feature complete. USERS ASSUME FULL RISK AND RESPONSIBILITY FOR VERIFYING THAT THE RESULTS ARE ACCURATE AND APPROPRIATE FOR THEIR APPLICATION.
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 A-Thomas-eng
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ License-File: LICENSE.txt
27
+ Keywords: engineering,mechanical,utilities,structural,fastener,fasteners,bolt,bolts,stress,materials,NASA
28
+ Author: T. Andrade
29
+ Author-email: andrade.thomas.eng@gmail.com
30
+ Requires-Python: >=3,<4
31
+ Classifier: Development Status :: 1 - Planning
32
+ Classifier: Intended Audience :: Science/Research
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Topic :: Scientific/Engineering :: Physics
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: License :: OSI Approved :: MIT License
40
+ Classifier: Operating System :: OS Independent
41
+ Requires-Dist: matplotlib (>=3.7)
42
+ Requires-Dist: numpy (>=1.24)
43
+ Requires-Dist: pandas (>=3.0.0)
44
+ Requires-Dist: pint (>=0.25.2)
45
+ Requires-Dist: plotly (>=6.6.0)
46
+ Requires-Dist: uncertainties (>=3.2.3)
47
+ Project-URL: Homepage, https://github.com/A-Thomas-eng/
48
+ Project-URL: Owner_contact, https://www.linkedin.com/in/andrade-t/
49
+ Project-URL: PIP, https://pypi.org/user/eng_calcs/
50
+ Description-Content-Type: text/markdown
51
+
52
+ Development package consisting of many small utility functions, mostly for structural engineering. Currently oriented for personal use, hit me up if there is interest in further development of these tools. USERS ASSUME FULL RISK AND RESPONSIBILITY FOR VERIFYING THAT THE RESULTS ARE ACCURATE AND APPROPRIATE FOR THEIR APPLICATION.
53
+
54
+
55
+ Engineering is a series of approximations. There is always a risk of errors, Validate the assumptions, models, and implementation of the methods provided are appropriate for your usecase prior to using any of these utilities. Always version lock your use of these packages, as the packages are subject to change.
56
+
57
+
58
+ Main modules:
59
+ │ └── eng_utils
60
+ │ ├── general_utils
61
+ │ │ └── logprint.py (from .logprint import logprint)
62
+ │ ├── software_utils
63
+ │ │ └── folder_structure_tree.py (from .folder_structure_tree import folder_structure_tree)
64
+ │ ├── structural_utils
65
+ │ │ ├── bolt_pattern_elastic_method.py (from .bolt_pattern_elastic_method import (Bolt, AppliedLoad, BoltResult, BoltPatternAnalysis, bolt_pattern_force_distribution, plot_bolt_pattern_3d, print_results))
66
+ │ │ ├── NASA_TM_108378_fitting_factor.py (from .NASA_TM_108378_fitting_factor import NASA_TM_108378_fitting_factor)
67
+ │ │ ├── margin_table.py (from .margin_table import add_row, print_table)
68
+ │ │ └── shared_helpers.py (from .shared_helpers import von_mises)
69
+
70
+
@@ -0,0 +1,18 @@
1
+ Development package consisting of many small utility functions, mostly for structural engineering. Currently oriented for personal use, hit me up if there is interest in further development of these tools. USERS ASSUME FULL RISK AND RESPONSIBILITY FOR VERIFYING THAT THE RESULTS ARE ACCURATE AND APPROPRIATE FOR THEIR APPLICATION.
2
+
3
+
4
+ Engineering is a series of approximations. There is always a risk of errors, Validate the assumptions, models, and implementation of the methods provided are appropriate for your usecase prior to using any of these utilities. Always version lock your use of these packages, as the packages are subject to change.
5
+
6
+
7
+ Main modules:
8
+ │ └── eng_utils
9
+ │ ├── general_utils
10
+ │ │ └── logprint.py (from .logprint import logprint)
11
+ │ ├── software_utils
12
+ │ │ └── folder_structure_tree.py (from .folder_structure_tree import folder_structure_tree)
13
+ │ ├── structural_utils
14
+ │ │ ├── bolt_pattern_elastic_method.py (from .bolt_pattern_elastic_method import (Bolt, AppliedLoad, BoltResult, BoltPatternAnalysis, bolt_pattern_force_distribution, plot_bolt_pattern_3d, print_results))
15
+ │ │ ├── NASA_TM_108378_fitting_factor.py (from .NASA_TM_108378_fitting_factor import NASA_TM_108378_fitting_factor)
16
+ │ │ ├── margin_table.py (from .margin_table import add_row, print_table)
17
+ │ │ └── shared_helpers.py (from .shared_helpers import von_mises)
18
+
@@ -0,0 +1,80 @@
1
+ [project]
2
+ name = "struct_utils"
3
+ version = "0.0.1"
4
+ description = "Development package consisting of many small utility functions, mainly oriented towards structural engineering. Currently structured for personal use, hit me up if there is interest in further development of additional or extended functionality. Published functions should be feature complete. USERS ASSUME FULL RISK AND RESPONSIBILITY FOR VERIFYING THAT THE RESULTS ARE ACCURATE AND APPROPRIATE FOR THEIR APPLICATION."
5
+ readme = "README.md"
6
+ license = { file = "LICENSE.txt" }
7
+
8
+ authors = [
9
+ { name = "T. Andrade", email = "andrade.thomas.eng@gmail.com" }
10
+ ]
11
+
12
+ keywords = ["engineering", "mechanical", "utilities", "structural", "fastener", "fasteners", "bolt", "bolts", "stress", "materials", "NASA"]
13
+ #exclude = ["src/eng_utils/*/DEV"]
14
+
15
+ classifiers = [
16
+ "Development Status :: 1 - Planning",
17
+ "Intended Audience :: Science/Research",
18
+ "Intended Audience :: Developers",
19
+ "Topic :: Scientific/Engineering :: Physics",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+
28
+ # ── Runtime dependencies ───────────────────────────────────────────────────────
29
+ [tool.poetry.dependencies]
30
+ python = "^3"
31
+ numpy = ">=1.24"
32
+ matplotlib = ">=3.7"
33
+ pandas = ">=3.0.0"
34
+ uncertainties = ">=3.2.3"
35
+ pint = ">=0.25.2"
36
+ plotly = ">=6.6.0"
37
+
38
+ [tool.poetry.group.dev.dependencies]
39
+ pytest = "*"
40
+ pytest-cov = "*"
41
+
42
+ [build-system]
43
+ requires = ["poetry-core"]
44
+ build-backend = "poetry.core.masonry.api"
45
+
46
+ # ── Project URLs ───────────────────────────────────────────────────────────────
47
+ [project.urls]
48
+ Homepage = "https://github.com/A-Thomas-eng/"
49
+ PIP = "https://pypi.org/user/eng_calcs/"
50
+ Owner_contact = "https://www.linkedin.com/in/andrade-t/"
51
+
52
+ # ── Ruff (linting) ─────────────────────────────────────────────────────────────
53
+ [tool.ruff]
54
+ line-length = 100
55
+ target-version = "py310"
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "W", "I"] # pycodestyle, pyflakes, isort
59
+ # select — which rule sets to enforce:
60
+ # E — formatting errors (spacing, indentation)
61
+ # F — logic errors (unused imports, undefined variables)
62
+ # W — warnings (less severe style issues)
63
+ # I — import ordering (keeps imports sorted and grouped)
64
+
65
+ # ── Mypy (type checking) ───────────────────────────────────────────────────────
66
+ [tool.mypy]
67
+ python_version = "3.10"
68
+ warn_return_any = true
69
+ warn_unused_configs = true
70
+ ignore_missing_imports = true
71
+
72
+ # ── Pytest ─────────────────────────────────────────────────────────────────────
73
+ [tool.pytest.ini_options]
74
+ testpaths = ["tests"]
75
+ addopts = "--cov=structural_tools --cov-report=term-missing"
76
+
77
+ # ── Coverage ─────────────────────────────────────────────────────────────────
78
+ [tool.coverage.run]
79
+ source = ["src"]
80
+ omit = ["*/DEV/*"] # exclude dev code from coverage reports
@@ -0,0 +1,2 @@
1
+ #just to keep things importable.
2
+
@@ -0,0 +1,2 @@
1
+ from .logprint import (logprint)
2
+
@@ -0,0 +1,132 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+
5
+ __all__ = ['setup_logging']
6
+
7
+ def logprint(
8
+ name: str = 'app',
9
+ *,
10
+ verbose: bool = False,
11
+ log_dir: str = '/tmp',
12
+ log_path: str | None = None,
13
+ file_level: int = logging.DEBUG,
14
+ console_level: int | None = None,
15
+ fmt: str = '%(asctime)s %(name)s %(levelname)-8s %(message)s',
16
+ datefmt: str = '%G%V_%u_%H_%M_%S',
17
+ ) -> logging.Logger:
18
+ """Configure and return a named logger.
19
+
20
+ Parameters
21
+ ----------
22
+ name : str
23
+ Logger name (also used in the default filename).
24
+ verbose : bool
25
+ If True, add a StreamHandler so output also appears on the console.
26
+ log_dir : str
27
+ Directory for the auto-generated log file (default /tmp).
28
+ log_path : str | None
29
+ Explicit log file path. Overrides log_dir + auto-naming.
30
+ file_level : int
31
+ Minimum level written to the file (default DEBUG — capture everything).
32
+ console_level : int | None
33
+ Minimum level printed to console. Defaults to DEBUG if verbose,
34
+ but you can set e.g. logging.WARNING for quieter console output.
35
+ fmt : str
36
+ Log format string.
37
+ datefmt : str
38
+ Timestamp format for log lines.
39
+
40
+ Returns
41
+ -------
42
+ logging.Logger with an extra attribute `log_path` (pathlib.Path or None).
43
+ `log_path` is None when file logging failed and the logger fell back to
44
+ console-only output.
45
+ """
46
+ logger = logging.getLogger(name)
47
+
48
+ # Avoid stacking duplicate handlers on repeated calls
49
+ if logger.handlers:
50
+ logger.log_path = getattr(logger, 'log_path', None)
51
+ return logger
52
+
53
+ logger.setLevel(logging.DEBUG) # let handlers decide what to pass
54
+
55
+ formatter = logging.Formatter(fmt, datefmt=datefmt)
56
+
57
+ # ── File handler (always attempted) ──────────────────────────
58
+ p = None
59
+ try:
60
+ if log_path is None:
61
+ ts = datetime.now().strftime('%Y%m%d_%H%M%S')
62
+ p = Path(log_dir) / f'{name}_{ts}.log'
63
+ else:
64
+ p = Path(log_path)
65
+ p.parent.mkdir(parents=True, exist_ok=True)
66
+
67
+ fh = logging.FileHandler(str(p), mode='w', encoding='utf-8')
68
+ fh.setLevel(file_level)
69
+ fh.setFormatter(formatter)
70
+ logger.addHandler(fh)
71
+
72
+ except Exception as exc:
73
+ # File logging unavailable (bad path, permissions, read-only fs, …)
74
+ # Add a fallback StreamHandler only if verbose hasn't already arranged one.
75
+ p = None
76
+ if not verbose:
77
+ verbose = True # let the normal console block below handle it
78
+ ch_fallback = logging.StreamHandler()
79
+ ch_fallback.setLevel(file_level)
80
+ ch_fallback.setFormatter(formatter)
81
+ logger.addHandler(ch_fallback)
82
+ logger.warning(
83
+ f'File logging unavailable ({exc}); falling back to console.'
84
+ )
85
+
86
+ # ── Console handler (when verbose or after fallback) ─────────
87
+ if verbose:
88
+ ch = logging.StreamHandler()
89
+ ch.setLevel(console_level if console_level is not None else logging.DEBUG)
90
+ ch.setFormatter(formatter)
91
+ logger.addHandler(ch)
92
+
93
+ # Stash path on the logger for callers to reference (None if fallback)
94
+ logger.log_path = p
95
+ if p is not None:
96
+ logger.info(f'Log file: {p}')
97
+
98
+ return logger
99
+ """
100
+ ## EXAMPLE USAGE:
101
+ from shared_helpers import logprint
102
+
103
+ # Basic usage — file only
104
+ log = logprint('my_app', verbose=1)
105
+ log.debug('Starting up')
106
+ log.info('Processing data')
107
+ log.warning('Disk usage high')
108
+ log.error('Failed to connect')
109
+
110
+ # Verbose — also prints to console
111
+ log = logprint('my_app', verbose=True)
112
+ log.info('You will see this in terminal too')
113
+
114
+ # Custom log directory
115
+ log = logprint('my_app', log_dir='/var/log/myproject', verbose=True)
116
+
117
+ # Explicit path
118
+ log = logprint('my_app', log_path='/tmp/my_app_debug.log')
119
+
120
+ # Quieter console (only warnings+), but full debug to file
121
+ log = logprint(
122
+ 'my_app',
123
+ verbose=True,
124
+ console_level=logging.WARNING,
125
+ file_level=logging.DEBUG,
126
+ )
127
+ log.debug('Goes to file only')
128
+ log.warning('Goes to both file and console')
129
+
130
+ # Access the log file path
131
+ log = logprint('pipeline', log_dir='/tmp/logs')
132
+ print(f'Logging to: {log.log_path}') """
@@ -0,0 +1,3 @@
1
+ from .folder_structure_tree import (
2
+ folder_structure_tree
3
+ )
@@ -0,0 +1,24 @@
1
+ from pathlib import Path
2
+
3
+ def folder_structure_tree(path=Path("."), prefix=""):
4
+ r"""
5
+ #usage example:
6
+ location_to_map=Path(r"C:\Users\USER\Desktop")
7
+ print(location_to_map.resolve())
8
+ folder_structure_tree(location_to_map)
9
+ """
10
+ entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
11
+ for i, entry in enumerate(entries):
12
+ connector = "└── " if i == len(entries) - 1 else "├── "
13
+ print(prefix + connector + entry.name)
14
+ if entry.is_dir():
15
+ extension = " " if i == len(entries) - 1 else "│ "
16
+ folder_structure_tree(entry, prefix + extension)
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
@@ -0,0 +1,228 @@
1
+ ## =============================================================================
2
+ ##
3
+ #
4
+ ## Reference:
5
+ ## H. M. Lee, "Shear-Joint Capability Versus Bolt Clearance", NASA TM-108378 (1992).
6
+ ## PDF: https://ntrs.nasa.gov/api/citations/19930003231/downloads/19930003231.pdf
7
+ ## =============================================================================
8
+ """
9
+ #assumes a single fastener needs to penetrate a single abutment by CL/2 to bring other fasteners in contact with the hole edge. For two plates, penetration is CL. Distribution between plates is a function of contact stiffness.
10
+ ****************************************************************************Terms*********************************************************************************************
11
+ | Symbol | Description | Units | Notes / Physical Meaning |
12
+ | ---------------------- | ----------------------------------------------- | ----- | --------------------------------------------------------------------------------------- |
13
+ | D_b | Bolt shank diameter | in | Diameter of the fastener |
14
+ | D_h | Hole diameter in plate | in | D_h = D_b + CL |
15
+ | CL | Clearance | in | Gap between bolt and hole; free play that must be taken up |
16
+ | b | Hertzian contact width | in | Width of contact at the bolt/plate interface |
17
+ | b_i | Initial Hertzian width | in | Computed iteratively from initial trial shear V_i. |
18
+ | b_e | Hertzian width at full embedment | in | Assumed b_e=D_b in paper. Technically should be computed recursively for accuracy. |
19
+ | dy / ΔY | Deformation distance | in | Distance fastener must yield to take up clearance (eq. 4) |
20
+ | dy_e | ΔY at full embed (closing CL so others engage) | in | Distance needed for full embedment to start load sharing with other bolts |
21
+ | E_b | Modulus of elasticity of bolt | psi | Stiffness of the bolt material |
22
+ | v_b | Poisson's ratio of bolt | - | Typically ~0.3 for steel |
23
+ | E_h | Modulus of elasticity of abutment plate | psi | Governs Hertzian compliance (e.g., aluminum) |
24
+ | v_h | Poisson's ratio of abutment plate | - | Typically ~0.33 for aluminum |
25
+ | V_i | Initial trial shear | lbf | Arbitrary starting value to compute initial Hertzian width |
26
+ | V_e | Embedment shear | lbf | Shear to completely embed bolt into abutment (eq. 8) |
27
+ | V_CL_star | Clearance closure shear helper value. | lbf | Shear needed to take up bolt-hole clearance, not accounting for flange thicknesses |
28
+ | V_CL | Clearance closure shear adjusted for two plates | lbf | Shear needed to take up bolt-hole clearance, accounting for flange thickness |
29
+ | V_ult | Ultimate shear of bolt | lbf | Material shear limit of the bolt |
30
+ | bolt_limit_shear | Limit shear in fastener | lbf | Shear load of single fastener prior to accounting for clearance. |
31
+ | joint_limit_shear | Limit shear in fastener | lbf | Shear load of all fasteners (accounting for n) prior to accounting for clearance. |
32
+ | T1 | Thickness of abutment plate 1 | in | Used to scale V_CL for series plates |
33
+ | T2 | Thickness of abutment plate 2 | in | ^ |
34
+ | T_i | Arbitrary dy_e guess for V_CL_star calc | in | Used only in initial Hertz calculation (eq. 6); should not be used in final V_CL calc |
35
+ | n | Number of fasteners | - | Number of bolts in the joint |
36
+ | FOS | Factor of safety | - | Usually 1 in TM examples |
37
+ """
38
+
39
+
40
+ ##---
41
+ ##-Calculations
42
+ ##-----
43
+ import math
44
+
45
+ #Material properties #E (lb/in^2), Poissons, F_su (psi).
46
+ steel_from_paper = [30.0e6, 0.30, 95e3]
47
+ aluminum_from_paper = [10.0e6, 0.33, 51e3] #F_SU for AL was never given.
48
+
49
+ MP35N_from_paper = [34.0e6, 0.34, 145e3]
50
+ metal_4130_from_paper = [30.0e6, 0.30, 0] #Guess? poorly documented in paper.
51
+
52
+ Inco_steel_AMS5662 = [29.4e6, 0.29, 125e3]
53
+ A286_steel_AMS5737 = [29.1e6, 0.31, 95e3]
54
+ titanium_AMS4928 = [16.9e6, 0.31, 95e3]
55
+
56
+ class_8_fastener = [30e6, 0.31, 85e3]
57
+ steel_A572_G50 = [30e6, 0.31, 50e3]
58
+
59
+
60
+ def NASA_TM_108378_fitting_factor (input_matrix): #input={D_b, CL, n, FOS, bolt_material, f1_mat, f2_mat, f1_thick, f2_thick, bolt_limit_shear}
61
+ """
62
+ Programmatic implementation of NASA TM-108378 (https://ntrs.nasa.gov/api/citations/19930003231/downloads/19930003231.pdf). Validated output against NASA_TM_108378 table 5. Returns a knockdown for fitting factor due to hole clearance.
63
+ Warning: This calculator should only be used for assessing the strength of unshimmed bolted joints, with threads not in the shear plane. This calculator only assesses the increased shear load in the bolt, and does not assess flange health, tensile loading, or any other factors. If stacking more than two flanges, it is conservative to define a material with the maximum Poissons and maximum Youngs modulus in the stackup to define bounding properties. The thickness of that composite flange should be input as the sum of the constituent thicknesses.
64
+ Material input matrices are formatted as: [E, Poissons, F_su]. Inputs require consistent base units.
65
+
66
+ Example case:
67
+
68
+ input_matrix_validation2= { # Appendix D validation.
69
+ "D_b" : 0.375, # Units: Inches | LMC fastener diameter.
70
+ "CL" : 0.016, # Units: Inches | CL=D_h-D_b (diameter of hole ho minus diameter of bolt). Larger values of CL are conservative.
71
+ "n" : 4, # Units: Qty | Number of fasteners.
72
+ "FOS" : 1, # Usually = 1
73
+ "bolt_material" : [34.0e6, 0.34, 145e3], # See material names at top of sheet. Verify material spec aligns with your use.
74
+ "f1_mat" : [30.0e6, 0.30, 0], # ^
75
+ "f2_mat" : [30.0e6, 0.30, 0], # ^
76
+ "f1_thick" : 0.4, # Units: Inches | MMC thickness.
77
+ "f2_thick" : 0.375, # ^
78
+ "bolt_limit_shear" : 16014 # Units: lbf | Max nominal shear loading for a single fastener, accounting for peaking, do not enter fastener strength.
79
+ }
80
+ Output_clearanced_fitting_factor=NASA_TM_108378_fitting_factor(input_matrix_validation2)
81
+
82
+ """
83
+ #--
84
+ ## Inputs and trivial identities.
85
+ D_b = input_matrix["D_b"]
86
+ CL = input_matrix["CL"] #maximum distance the bolt can be from an edge of the hole.
87
+ D_h = D_b + CL #hole diameter
88
+ n = input_matrix["n"] #number of fasteners, more fasteners is a greater knockdown but log-ish impact.
89
+ FOS = input_matrix["FOS"]
90
+ bolt_material = input_matrix["bolt_material"]
91
+ F_su = bolt_material[2] #units psi, usually 60% of minimum tensile strength.
92
+ E_b = bolt_material[0]; poissons_b=bolt_material[1]
93
+ V_ult = 0.25 * (math.pi) * F_su * D_b**2
94
+ #--
95
+ ## Flange properties.
96
+ f1_mat = input_matrix["f1_mat"]; f2_mat = input_matrix["f2_mat"]
97
+ T1 = input_matrix["f1_thick"]; T2 = input_matrix["f2_thick"]
98
+ E_h = max(f1_mat[0], f2_mat[0])
99
+ poissons_h = max(f1_mat[1], f2_mat[1])
100
+ #--
101
+ ## Fastener properties.
102
+ try:
103
+ bolt_limit_shear=input_matrix["bolt_limit_shear"]
104
+ except: #unconservative assumption, useful when comparing results to paper.
105
+ bolt_limit_shear=V_ult
106
+ print('Assuming limit_shear=V_ult')
107
+ joint_limit_shear = n*bolt_limit_shear
108
+
109
+ #--
110
+ """
111
+ note to self: looks like the paper used V_i/T in places that asked for V_i, and did not have T_i in the b_i equation.
112
+ Y_i is calculated the same as the paper, and sigma_e_max=0.798*sqrt(AG35/(Dh*Db/CL)/CE) where CE=(1-poissons_b**2)/E_b+(1-poissons_h**2)/E_h). This approach allows the V_CL* they calculate to represent shear force per unit length, which they can then adjust for T1 and T2. For the same inputs as Testcase2, the calc gives 1.163 for clearanced fitting factor and 18624 on adjusted load on fastener, and ultimate joint capability=53623, and original limit shear=64059 (through joint, n*single bolt limit). The limit shear is only used in the logic checks and factors calcs. Factors are calculated against joint limit shear, not fastener limit shear.
113
+ """
114
+ ## Arbitrary initial conditions for equations
115
+ V_i = 100 #arbitrary initial shear force to find initial deformation, will be used to compute V_e later. Changing this doesn't impact the final result.
116
+ T_i = max(T1,T2) #arbitrary initial thickness.
117
+ #--
118
+ ## Main relevant equations from NASA TM-108378
119
+ def eq_4(b): #distance fastener has to yield to take up CL so other fasteners start to share loading, eq4.
120
+ dy = 0.5*((D_h**2-b**2)**(0.5)-(D_b**2-b**2)**(0.5)-CL)
121
+ return dy
122
+ def eq_6(V, T_i): #width of hertzian contact defined by hertzian equation, eq6.
123
+ b = 1.6*(((V*D_h*D_b)/(T_i*CL))*((1-poissons_b**2)/E_b+(1-poissons_h**2)/E_h))**(0.5)
124
+ return b
125
+ def eq_8(V_i, b_i): #shear to completely embed the bolt shank diameter in the AL abutment. Eq8.
126
+ V_e = V_i*(D_b/b_i)**2
127
+ return V_e
128
+ #--
129
+ ## Actually solving the problem
130
+ b_i = eq_6(V_i, T_i) #width of initial hertzian contact.
131
+ dy_i = eq_4(b_i) #distance fastener has to yield to take up CL, eq4.
132
+ V_e = eq_8(V_i, b_i) #shear to completely embed the bolt shank diameter in the AL abutment. Eq8.
133
+ b_e = D_b #eq_6(V_e) #width of hertzian contact defined by hertzian equation, eq6. B_e as using V_embedded as input.
134
+ dy_e = eq_4(b_e) #distance fastener has to yield to take up CL, eq4.
135
+ V_CL_star = V_e * CL / (2 * dy_e) #last equation from figure 4. shear such that the initial Hertzian width leads to exactly the deformation needed to close the gap. Should be solved iteratively to avoid impacting the value of V_CL
136
+ V_CL = (V_CL_star / T_i) * (T1 * T2) / (T1 + T2) #equation from below figure 6. Paper poorly presents this equation, ignores T_i. It represents the force needed to close the gap. Applies if there are two abutment plates.
137
+
138
+ #--
139
+ ## Debugging print statements:
140
+ for name, val in [("V_ult", V_ult),
141
+ #("b_i", b_i), ("dy_i", dy_i),("V_e", V_e), ("V_CL_star", V_CL_star), #arbitrary values given T_i and V_i
142
+ ("dy_e", dy_e), ("V_CL", V_CL)]:
143
+ print(f"{name}: {val:.7f}", end="\n")
144
+ print() # final newline
145
+ #--
146
+ ## Calculating Clearanced_fitting_factor
147
+ if joint_limit_shear<=V_CL:
148
+ Adjusted_load_on_fastener=joint_limit_shear #one fastener takes all the load as the shear doesn't close the gap
149
+ else:
150
+ Adjusted_load_on_fastener=V_CL+(joint_limit_shear-V_CL)/n #shear closes the gap, afterwards all load is shared evenly between the fasteners.
151
+
152
+ #Capability_perc1 = 1 - ((n-1)*FOS*V_CL)/(n * V_ult) #joint capability relative to no clearance design, found on P16. Lines up with appendix B.
153
+ #pei=-1.2695*10**9*CL**3+9.5361*10**7*CL**2+4.9919*10**5*CL-146.7 #appendix A
154
+ #Capability_perc2=1/(1-pei*((n-1)*FOS*T1*T2*D_b**2/((T1+T2)*n*V_ult))) #not giving me the correct answer for appendix B, eben when I get the right answer for V_CL
155
+
156
+ #--
157
+ ## Logic checks and final print statements.
158
+ if bolt_limit_shear>V_ult: #errors if there is an input error and does not print any calculated values.
159
+ print("ERROR in inputs: bolt_limit_shear is greater than V_ult. \n bolt_limit_shear = ", bolt_limit_shear, "\n V_ult = ", V_ult)
160
+ else:
161
+ if Adjusted_load_on_fastener>V_ult/FOS:
162
+ print("Fastener potentially overloads (accounting for FOS provided). Adjusted_load_on_fastener > V_ult \n Adjusted_load_on_fastener = ", Adjusted_load_on_fastener, "\n V_ult = ", V_ult)
163
+ #--
164
+ if joint_limit_shear<=V_CL:
165
+ print("Clearanced fastener takes the whole load because joint_limit_shear < V_CL")
166
+ else:
167
+ print("Joint will engage all fasteners because joint_limit_shear > V_CL")
168
+ if V_ult/FOS<=V_CL: #we do not close the gap prior to reaching ult, so other fasteners never share the load.
169
+ Adjusted_joint_capability=V_ult/FOS
170
+ print("All load potentially goes through a single fastener, gap is not closed")
171
+ else:
172
+ Adjusted_joint_capability=(n*V_ult/FOS)-((n-1)*V_CL) #eq 12
173
+ print("All load potentially goes through a single fastener, gap is not closed")
174
+ rel_adjusted_joint_capability=Adjusted_joint_capability/(n*V_ult/FOS)
175
+
176
+ Clearanced_fitting_factor=Adjusted_load_on_fastener/(bolt_limit_shear) #does not account for all fitting factor, multiply this by your other fitting factor contributions.
177
+
178
+ #--
179
+ #if inputs are good, prints adjusted shear values:
180
+ print(f"Adjusted shear load on fastener: {Adjusted_load_on_fastener:.0f}")
181
+ print(f"Adjusted_joint_capability : {Adjusted_joint_capability:.0f}")
182
+ print(f"Joint capability relative to no clearance design: {rel_adjusted_joint_capability:.3f}")
183
+ print(f"Clearanced fitting factor (No FOS applied): {Clearanced_fitting_factor:.3f}")
184
+
185
+ return Clearanced_fitting_factor
186
+ #----------------------------------------------------------------------------
187
+ #### USER INPUTS->
188
+ #----------------------------------------------------------------------------
189
+
190
+ """
191
+ Program function: Programmatic implementation of NASA TM - 108378 (https://ntrs.nasa.gov/api/citations/19930003231/downloads/19930003231.pdf). Validated output against NASA_TM_108378 table 5. Returns a knockdown for fitting factor due to hole clearance.
192
+
193
+
194
+ Warning: This calculator should only be used for assessing the strength of unshimmed bolted joints, with threads not in the shear plane. This calculator only assesses the increased shear load in the bolt, and does not assess flange health, tensile loading, among other factors. If stacking more than two flanges, define a new material near the top of this script with the maximum poissons and maximum Youngs modulus in the stackup. The thickness of that composite flange should be input as the sum of the constituent thicknesses.
195
+ """
196
+
197
+ input_matrix_validation1= { # Table 5 validation.
198
+ "D_b" : 0.196, # Units: Inches | LMC fastener diameter.
199
+ "CL" : 0.02, # Units: Inches | CL=D_h-D_b (diameter of hole ho minus diameter of bolt). Larger values of CL are conservative.
200
+ "n" : 2, # Units: Qty | Number of fasteners.
201
+ "FOS" : 1, # Usually = 1
202
+ "bolt_material" : steel_from_paper, # See material names at top of sheet. Verify material spec aligns with your use.
203
+ "f1_mat" : aluminum_from_paper, # ^
204
+ "f2_mat" : aluminum_from_paper, # ^
205
+ "f1_thick" : 0.1, # Units: Inches | MMC thickness.
206
+ "f2_thick" : 0.1, # ^
207
+ "bolt_limit_shear" : 2100 # Units: lbf | Max nominal shear loading for a single fastener, accounting for peaking, do not enter fastener strength.
208
+ }
209
+
210
+ Output_clearanced_fitting_factor=NASA_TM_108378_fitting_factor(input_matrix_validation1)
211
+
212
+ input_matrix_validation2= { # Appendix D validation.
213
+ "D_b" : 0.375, # Units: Inches | LMC fastener diameter.
214
+ "CL" : 0.016, # Units: Inches | CL=D_h-D_b (diameter of hole ho minus diameter of bolt). Larger values of CL are conservative.
215
+ "n" : 4, # Units: Qty | Number of fasteners.
216
+ "FOS" : 1, # Usually = 1
217
+ "bolt_material" : MP35N_from_paper, # See material names at top of sheet. Verify material spec aligns with your use.
218
+ "f1_mat" : metal_4130_from_paper, # ^
219
+ "f2_mat" : metal_4130_from_paper, # ^
220
+ "f1_thick" : 0.4, # Units: Inches | MMC thickness.
221
+ "f2_thick" : 0.375, # ^
222
+ "bolt_limit_shear" : 16014 # Units: lbf | Max nominal shear loading for a single fastener, accounting for peaking, do not enter fastener strength.
223
+ }
224
+
225
+ Output_clearanced_fitting_factor=NASA_TM_108378_fitting_factor(input_matrix_validation2)
226
+ # To increase Conservatism, choose:
227
+ # min(D_b, bolt_limit_shear, bolt_material[2])
228
+ # max(n, CL, FOS, f1_thick, f2_thick, f2_mat[0:1], f2_mat[0:1], bolt_material[0:1])
@@ -0,0 +1,17 @@
1
+ #this file controls the "API level imports"
2
+ #The double dot represents one level up in the folder hierarchy.
3
+ #The single dot represents the current package or directory.
4
+ from .bolt_pattern_elastic_method import (
5
+ Bolt,
6
+ AppliedLoad,
7
+ BoltResult,
8
+ BoltPatternAnalysis,
9
+ bolt_pattern_force_distribution,
10
+ plot_bolt_pattern_3d,
11
+ print_results,
12
+ )
13
+ from .margin_table import add_row, print_table
14
+ from .shared_helpers import von_mises
15
+ from .NASA_TM_108378_fitting_factor import NASA_TM_108378_fitting_factor
16
+
17
+