diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..6a50dfd8
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,48 @@
+name: Tests
+
+on:
+ push:
+ branches:
+ - modern_dev
+ pull_request:
+ branches:
+ - modern_dev
+
+jobs:
+ pytest:
+ name: Python tests on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-latest, ubuntu-latest]
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+ cache: pip
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install -r requirements-dev.txt
+ python -m pip install -e .
+
+ - name: Import package
+ run: python -c "import anystruct; print('anystruct import OK')"
+
+ - name: Run pytest
+ run: python -m pytest
+
+ - name: Run GUI smoke on Windows
+ if: runner.os == 'Windows'
+ run: python -m tests.gui_automatic_run
+
+ - name: Run GUI smoke on Linux
+ if: runner.os == 'Linux'
+ run: xvfb-run -a python -m tests.gui_automatic_run
diff --git a/.gitignore b/.gitignore
index 0e82e245..983c3b32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
+.pytest_tmp/
cover/
# Translations
@@ -161,4 +162,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
\ No newline at end of file
+#.idea/
diff --git a/.idea/ANYstructure.iml b/.idea/ANYstructure.iml
index aee9ef2d..79ac038b 100644
--- a/.idea/ANYstructure.iml
+++ b/.idea/ANYstructure.iml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index 481181c0..a0d21cbd 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -27,43 +27,43 @@ include anystruct\ship_section_example.txt
include anystruct\ANYstructure_documentation.pdf
include anystruct\sections.csv
include anystruct\bulb_anglebar_tbar_flatbar.csv
-include anystruct\ml_files\CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_CSR-Tank_req_cl_predictor.pickle
-include anystruct\ml_files\CL_CSR-Tank_req_cl_scaler.pickle
-include anystruct\ml_files\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle
-include anystruct\ml_files\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle
-include anystruct\ml_files\CL_CSR-Tank_req_cl_predictor.pickle
-include anystruct\ml_files\CL_CSR-Tank_req_cl_scaler.pickle
-include anystruct\ml_files\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle
-include anystruct\ml_files\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle
\ No newline at end of file
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR-Tank_req_cl_predictor.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR-Tank_req_cl_scaler.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR-Tank_req_cl_predictor.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR-Tank_req_cl_scaler.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle
+include anystruct\ml_files\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle
\ No newline at end of file
diff --git a/README.md b/README.md
index add7b042..3ac29d65 100644
--- a/README.md
+++ b/README.md
@@ -1,70 +1,62 @@
+
+
# ANYstructure #
ANYstructure is the ultimate steel structure design tool for plate fields and cylinders!
Weight optimization for all structures with machine learning capabilities.
Calculations are based on DNV standards and rules
-### What's new in 5.3.1 ###
-* Force to stress to force conversion fix for cylinders.
-### What's new in 5.3.0 ###
-* Fixed critical caluculation bug in column buckling
-* Some minor bug fixing
-* Changed some text
-### What's new in 5.2.0 ###
-* Support for python 3.13
-* Some bug fixing
-### What's new in 5.1.4 ###
-* Excel input option
-* Add load factor 1.1 for Machine Learning buckling (PULS based)
-* Continuous update of Utilization Factor when updating pressure loads
-### What's new in 5.0.2 ###
-* API introduced
-* API documenatation at readthedocs (anystructure.readthedocs.io)
-* GUI modifications
-* Import of python package set to "anystruct"
-### What's new in 4.10 ###
-* Corrected minor bug on membrane stresses for unstiffened cylinder.
-### What's new in 4.9.1 ###
-* Corrected bug in loading old save files
-* Corrected error on buckling flat plate calculation
-### What's new in 4.8 ###
-* Reporting table on cylinders.
-* Color coding on come cylinder properties.
-* Corrected error on additional hoop stress input for cylinders.
-### What's new in 4.7 ###
-* Corrected error on girder caluculation for cylinder buckling.
-* Added 1.10 load factor option for cylinder buckling.
-* Better compability with linux.
-* Python 3.11 based.
-### What's new in 4.4 ###
-* Backup and restore feature added.
-### What's new in 4.3 ###
-* General stability.
-* User friendliness.
-### What's new in 4.2 ###
-* Bug fixing.
-* Ukraininan theme.
-### What's new in 4.0 ###
-* Cylinder design and optimization!
-* Flat plate prescriptive buckling improved. Girder calculation added.
-* Updated GUI with color themes.
-### What's new in 3.3 ###
-* Extremely efficient Machine Learning version of PULS called ML-CL. Implemented for all optimizer options.
-* Calculation of Center of Gravity and Center of Buoyancy.
-* Reporting of weights and COG.
-* Lots of bug fixes.
+### What's new in 6.0.0 ###
+* New buckling method: Semi-analytical buckling for flat plates.
+* Updated ML buckling to numerical instead of classification. Improved accurancy of the neural network.
+* Updated color coding option.
+* Implemented extended testing regime.
+* Started work on separating calculation code from the GUI.
------------------------------------------------------------------------
-For documnetation:
+For documentation:
https://anystructure.readthedocs.io/en/latest/
+## Development setup ##
+
+ANYstructure is currently maintained as a Python package named `anystruct`. The GUI can still be launched through the `ANYstructure` console command after an editable install.
+
+Recommended local setup:
+
+```powershell
+python -m venv .venv
+.\.venv\Scripts\Activate.ps1
+python -m pip install --upgrade pip
+python -m pip install -r requirements-dev.txt
+python -m pip install -e .
+python -m pytest
+```
+
+Launch the desktop app after installation:
+
+```powershell
+ANYstructure
+```
+
+Dependency groups are also available for focused installs:
+
+```powershell
+python -m pip install -r requirements-core.txt
+python -m pip install -r requirements-ml.txt
+python -m pip install -r requirements-excel.txt
+```
+
+Equivalent package extras are exposed as `core`, `ml`, `excel`, `dev`, and `all`. The default package install still includes Excel and ML dependencies for backwards compatibility.
+
+Excel project import requires a local Excel installation and is not expected to run in basic automated tests.
+The external Excel-sheet DNV PULS calculation workflow has been removed from this release; ML-CL remains available.
+
## The following is calculated: ##
* Minimum plate thickness (DNV-OS-C101)
* Minimum section modulus of stiffener/plate (DNVGL-OS-C101)
* Minimum shear area (DNVGL-OS-C101)
-* Buckling (DNVGL-RP-C201)or PULS (licenced DNV software)
+* Buckling (DNVGL-RP-C201)
* Buckling strength of shells DNV-RP-C202
-* PULS buckling (DNV license needed)
* Machine learning buckling, PULS based
* Fatigue for plate/stiffener connection (DNVGL-RP-C203)
@@ -83,4 +75,4 @@ Please like, share or comment on LinkedIn: https://www.linkedin.com/in/audun-arn
Screenshot (this example can be loaded from file "ship_section_example.txt"):
-
+
diff --git a/__init__.py b/__init__.py
index 6d63338c..5bcde4ec 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,6 +1,6 @@
# __init__.py
# Project information.
__title__ = 'ANYstructure'
-__version__ = '5.3.3'
+__version__ = '6.0.0'
__author__ = 'Audun Arnesen Nyhus'
__license__ = 'MIT'
\ No newline at end of file
diff --git a/__main__.py b/__main__.py
index cdea46e4..6680ea43 100644
--- a/__main__.py
+++ b/__main__.py
@@ -1,23 +1,5 @@
-import sys, multiprocessing, ctypes
-import tkinter as tk
-from anystruct.main_application import Application
+from anystruct.__main__ import main
-def main(args=None):
- """The main routine."""
- if args is None:
- args = sys.argv[1:]
-
- multiprocessing.freeze_support()
- errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
- root = tk.Tk()
- width = root.winfo_screenwidth()
- height = root.winfo_screenheight()
- root.geometry(f'{width}x{height}')
- my_app = Application(root)
- root.mainloop()
-
- # Do argument parsing here (eg. with argparse) and anything else
- # you want your project to do.
if __name__ == "__main__":
main()
diff --git a/anystruct/ANYstructure_documentation.pdf b/anystruct/ANYstructure_documentation.pdf
index f82970db..49d30dde 100644
Binary files a/anystruct/ANYstructure_documentation.pdf and b/anystruct/ANYstructure_documentation.pdf differ
diff --git a/anystruct/__main__.py b/anystruct/__main__.py
index 7369713d..7c98dd0c 100644
--- a/anystruct/__main__.py
+++ b/anystruct/__main__.py
@@ -1,24 +1,36 @@
-import sys, multiprocessing, ctypes, os
+import ctypes
+import multiprocessing
+import sys
import tkinter as tk
+
from anystruct.main_application import Application
-def main(args=None):
- """The main routine."""
+def _set_windows_dpi_awareness():
+ if sys.platform != 'win32':
+ return
+
+ try:
+ ctypes.windll.shcore.SetProcessDpiAwareness(2)
+ except (AttributeError, OSError):
+ pass
+
+
+def main(args=None):
+ """Launch the ANYstructure Tkinter application."""
if args is None:
args = sys.argv[1:]
multiprocessing.freeze_support()
- errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
+ _set_windows_dpi_awareness()
+
root = tk.Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
root.geometry(f'{width}x{height}')
- my_app = Application(root)
+ Application(root)
root.mainloop()
- # Do argument parsing here (eg. with argparse) and anything else
- # you want your project to do.
if __name__ == "__main__":
main()
diff --git a/anystruct/api.py b/anystruct/api.py
index 4b06de0a..c864d97a 100644
--- a/anystruct/api.py
+++ b/anystruct/api.py
@@ -14,6 +14,16 @@
import anystruct.stresses_window as stress
import anystruct.fatigue_window as fatigue
import anystruct.load_factor_window as load_factors
+ import anystruct.api_helpers as api_helpers
+ from anystruct.project_application import (
+ ProjectFileCodec,
+ ProjectHydrationDefaults,
+ ProjectOpenService,
+ ProjectPersistenceService,
+ ProjectSaveInput,
+ ProjectSaveService,
+ )
+ from anystruct.project_state import ProjectState
from anystruct.report_generator import LetterMaker
import anystruct.sesam_interface as sesam
except ModuleNotFoundError:
@@ -33,10 +43,42 @@
import ANYstructure.anystruct.stresses_window as stress
import ANYstructure.anystruct.fatigue_window as fatigue
import ANYstructure.anystruct.load_factor_window as load_factors
+ import ANYstructure.anystruct.api_helpers as api_helpers
+ from ANYstructure.anystruct.project_application import (
+ ProjectFileCodec,
+ ProjectHydrationDefaults,
+ ProjectOpenService,
+ ProjectPersistenceService,
+ ProjectSaveInput,
+ ProjectSaveService,
+ )
+ from ANYstructure.anystruct.project_state import ProjectState
from ANYstructure.anystruct.report_generator import LetterMaker
import ANYstructure.anystruct.sesam_interface as sesam
+def load_project_state(path):
+ """Load an ANYstructure project file into canonical project state."""
+ return ProjectPersistenceService.load_state_from_path(path)
+
+
+def save_project_state(project_state, path):
+ """Save canonical project state using the supported project-file codec."""
+ return ProjectPersistenceService.save_state_to_path(project_state, path)
+
+
+def open_project(path, hydration_defaults=None):
+ """Load and hydrate an ANYstructure project through the application service facade."""
+ if hydration_defaults is None:
+ hydration_defaults = ProjectHydrationDefaults()
+ return ProjectOpenService.open_path(path, hydration_defaults)
+
+
+def save_project(path, save_input):
+ """Create and save project state from a public project save input."""
+ return ProjectSaveService.save_path(path, save_input)
+
+
class FlatStru():
'''
API class for all flat plates.\n
@@ -52,12 +94,7 @@ def __init__(self, calculation_domain: str = None):
:type calculation_domain: str
'''
super().__init__()
- assert calculation_domain in ["Flat plate, unstiffened", "Flat plate, stiffened",
- "Flat plate, stiffened with girder"], ('Calculation domain missing!\n '
- 'Alternatives:'
- '\n "Flat plate, unstiffened"'
- '\n "Flat plate, stiffened"'
- '\n "Flat plate, stiffened with girder"')
+ api_helpers.assert_choice(calculation_domain, api_helpers.FLAT_STRUCTURE_DOMAINS, 'calculation_domain')
self._Plate = CalcScantlings()
self._Stiffeners = CalcScantlings()
@@ -108,15 +145,15 @@ def set_material(self, mat_yield = 355, emodule = 210000, material_factor = 1.15
:return:
:rtype:
'''
- self._FlatStructure.mat_yield = mat_yield*1e6
- self._FlatStructure.E = emodule*1e6
+ self._FlatStructure.mat_yield = api_helpers.mpa_to_pa(mat_yield)
+ self._FlatStructure.E = api_helpers.mpa_to_pa(emodule)
self._FlatStructure.v = poisson
self._FlatStructure.mat_factor = material_factor
for sub_cls in [self.Plate, self.Stiffeners, self.Girder]:
if sub_cls is not None:
- sub_cls.mat_yield = mat_yield * 1e6
- sub_cls.E = emodule * 1e6
+ sub_cls.mat_yield = api_helpers.mpa_to_pa(mat_yield)
+ sub_cls.E = api_helpers.mpa_to_pa(emodule)
sub_cls.v = poisson
sub_cls.mat_factor = material_factor
@@ -181,7 +218,7 @@ def set_plate_geometry(self, spacing: float = 700, thickness: float = 20, span:
self._FlatStructure.Plate.t = thickness
self._FlatStructure.Plate.spacing = spacing
- self._FlatStructure.Plate.span = span/1000
+ self._FlatStructure.Plate.span = api_helpers.mm_to_m(span)
self._FlatStructure.Plate.girder_lg = 10 # placeholder value
@@ -246,7 +283,7 @@ def set_stiffener(self, hw: float = 260, tw: float = 12, bf: float = 49,
self._FlatStructure.Stiffener.tw = tw
self._FlatStructure.Stiffener.b = bf
self._FlatStructure.Stiffener.tf = tf
- self._FlatStructure.Stiffener.stiffener_type = 'L-bulb' if stf_type in ['hp HP HP-bulb bulb'] else stf_type
+ self._FlatStructure.Stiffener.stiffener_type = api_helpers.normalize_bulb_stiffener_type(stf_type)
self._FlatStructure.Stiffener.spacing = spacing
self._FlatStructure.Stiffener.girder_lg = 10
self._FlatStructure.Stiffener.t = self._FlatStructure.Plate.t
@@ -293,9 +330,10 @@ def set_buckling_parameters(self, calculation_method: str= None, buckling_accept
Various buckling realted parameters are set here. For details, see\n
DNV-RP-C201 Buckling strength of plated structures.\n
- :param calculation_method: 'DNV-RP-C201 - prescriptive', 'ML-CL (PULS based)'
+ :param calculation_method: 'DNV-RP-C201 - prescriptive', 'SemiAnalytical S3/U3',
+ or 'ML-Numeric (PULS based)'
:type calculation_method: str
- :param buckling_acceptance: for ML-CL calculations, either 'buckling' or 'ultimate'
+ :param buckling_acceptance: selected UF family, either 'buckling' or 'ultimate'
:type buckling_acceptance: str
:param stiffened_plate_effective_aginst_sigy:
:type stiffened_plate_effective_aginst_sigy:
@@ -318,12 +356,10 @@ def set_buckling_parameters(self, calculation_method: str= None, buckling_accept
:return:
:rtype:
'''
- assert calculation_method in ['DNV-RP-C201 - prescriptive', 'ML-CL (PULS based)']
- assert buckling_acceptance in ['buckling', 'ultimate']
- assert stiffener_support in ['Continuous', 'Sniped']
- assert girder_support in ['Continuous', 'Sniped']
- if calculation_method == 'ML-CL (PULS based)':
- raise NotImplementedError('ML-CL (PULS based) not yet implemented')
+ api_helpers.assert_choice(calculation_method, api_helpers.BUCKLING_CALCULATION_METHODS, 'calculation_method')
+ api_helpers.assert_choice(buckling_acceptance, api_helpers.BUCKLING_ACCEPTANCE_TYPES, 'buckling_acceptance')
+ api_helpers.assert_choice(stiffener_support, api_helpers.SUPPORT_TYPES, 'stiffener_support')
+ api_helpers.assert_choice(girder_support, api_helpers.SUPPORT_TYPES, 'girder_support')
sigy_mapper = {True: 'Stf. pl. effective against sigma y', False:'All sigma y to girder'}
self._FlatStructure._stiffened_plate_effective_aginst_sigy = sigy_mapper[stiffened_plate_effective_aginst_sigy]
self._FlatStructure.method = buckling_acceptance
@@ -375,10 +411,7 @@ class CylStru():
8. 'Orthogonally Stiffened panel'\n
'''
- geotypes = ['Unstiffened shell', 'Unstiffened panel',
- 'Longitudinal Stiffened shell', 'Longitudinal Stiffened panel',
- 'Ring Stiffened shell', 'Ring Stiffened panel',
- 'Orthogonally Stiffened shell', 'Orthogonally Stiffened panel']
+ geotypes = api_helpers.CYLINDER_STRUCTURE_DOMAINS
def __init__(self, calculation_domain: str = 'Unstiffened shell'):
'''
:param calculation_domain: calculation domain, 'Unstiffened shell', 'Unstiffened panel',
@@ -387,12 +420,12 @@ def __init__(self, calculation_domain: str = 'Unstiffened shell'):
:type calculation_domain: str
'''
super().__init__()
- assert calculation_domain in self.geotypes, 'Geometry type must be either of: ' + str(self.geotypes)
- self._load_type = 'Stress' if 'panel' in calculation_domain else 'Force'
+ api_helpers.assert_choice(calculation_domain, self.geotypes, 'calculation_domain')
+ self._load_type = api_helpers.cylinder_input_mode(calculation_domain)
- self._calculation_domain = calculation_domain + ' (' + self._load_type + ' input)'
+ self._calculation_domain = api_helpers.cylinder_domain_with_input_mode(calculation_domain)
self._CylinderMain = CylinderAndCurvedPlate()
- self._CylinderMain.geometry = CylinderAndCurvedPlate.geomeries_map_no_input_spec[calculation_domain]
+ self._CylinderMain.geometry = api_helpers.geometry_id_for_domain(self._calculation_domain)
self._CylinderMain.ShellObj = Shell()
if calculation_domain in ['Unstiffened shell', 'Unstiffened panel']:
self._CylinderMain.LongStfObj = None
@@ -432,12 +465,12 @@ def set_stresses(self, sasd = 0, smsd = 0, tTsd = 0, tQsd = 0, psd = 0, shsd = 0
:rtype:
'''
- self._CylinderMain.sasd = sasd*1e6
- self._CylinderMain.smsd = smsd*1e6
- self._CylinderMain.tTsd = abs(tTsd*1e6)
- self._CylinderMain.tQsd = abs(tQsd*1e6)
- self._CylinderMain.psd = psd*1e6
- self._CylinderMain.shsd = shsd*1e6
+ self._CylinderMain.sasd = api_helpers.mpa_to_pa(sasd)
+ self._CylinderMain.smsd = api_helpers.mpa_to_pa(smsd)
+ self._CylinderMain.tTsd = abs(api_helpers.mpa_to_pa(tTsd))
+ self._CylinderMain.tQsd = abs(api_helpers.mpa_to_pa(tQsd))
+ self._CylinderMain.psd = api_helpers.mpa_to_pa(psd)
+ self._CylinderMain.shsd = api_helpers.mpa_to_pa(shsd)
def set_forces(self, Nsd: float = 0, Msd: float = 0, Tsd: float = 0, Qsd: float = 0, psd: float = 0):
'''
@@ -452,16 +485,7 @@ def set_forces(self, Nsd: float = 0, Msd: float = 0, Tsd: float = 0, Qsd: float
:return:
'''
- geomeries = {11: 'Flat plate, stiffened', 10: 'Flat plate, unstiffened',
- 12: 'Flat plate, stiffened with girder',
- 1: 'Unstiffened shell (Force input)', 2: 'Unstiffened panel (Stress input)',
- 3: 'Longitudinal Stiffened shell (Force input)', 4: 'Longitudinal Stiffened panel (Stress input)',
- 5: 'Ring Stiffened shell (Force input)', 6: 'Ring Stiffened panel (Stress input)',
- 7: 'Orthogonally Stiffened shell (Force input)', 8: 'Orthogonally Stiffened panel (Stress input)'}
- geomeries_map = dict()
- for key, value in geomeries.items():
- geomeries_map[value] = key
- geometry = geomeries_map[self._calculation_domain]
+ geometry = api_helpers.geometry_id_for_domain(self._calculation_domain)
forces = [Nsd, Msd, Tsd, Qsd]
sasd, smsd, tTsd, tQsd, shsd = hlp.helper_cylinder_stress_to_force_to_stress(
stresses=None, forces=forces, geometry=geometry, shell_t=self._CylinderMain.ShellObj.thk,
@@ -496,8 +520,8 @@ def set_material(self, mat_yield = 355, emodule = 210000, material_factor = 1.15
:return:
:rtype:
'''
- self._CylinderMain.mat_yield = mat_yield*1e6
- self._CylinderMain.E = emodule*1e6
+ self._CylinderMain.mat_yield = api_helpers.mpa_to_pa(mat_yield)
+ self._CylinderMain.E = api_helpers.mpa_to_pa(emodule)
self._CylinderMain.v = poisson
self._CylinderMain.mat_factor = material_factor
def set_imperfection(self, delta_0 = 0.005):
@@ -523,8 +547,8 @@ def set_fabrication_method(self, stiffener: str = 'Fabricated', girder: str = '
:return:
:rtype:
'''
- options = ['Fabricated', 'Cold formed']
- assert stiffener in options, 'Method must be either of: ' + str(options)
+ api_helpers.assert_choice(stiffener, api_helpers.FABRICATION_METHODS, 'stiffener fabrication method')
+ api_helpers.assert_choice(girder, api_helpers.FABRICATION_METHODS, 'girder fabrication method')
self._CylinderMain.fab_method_ring_stf = stiffener
self._CylinderMain.fab_method_ring_girder = girder
def set_end_cap_pressure_included_in_stress(self, is_included: bool = True):
@@ -549,7 +573,7 @@ def set_uls_or_als(self, kind = 'ULS'):
:return:
:rtype:
'''
- assert kind in ['ULS', 'ALS'], 'Must be either of: ' + str(kind)
+ api_helpers.assert_choice(kind, api_helpers.LIMIT_STATE_TYPES, 'limit state')
self._CylinderMain.uls_or_als = kind
def set_exclude_ring_stiffener(self, is_excluded: bool = True):
'''
@@ -593,7 +617,7 @@ def set_panel_spacing(self, val: float = 0):
:return:
:rtype:
'''
- self._CylinderMain.panel_spacing = val/1000
+ self._CylinderMain.panel_spacing = api_helpers.mm_to_m(val)
def set_shell_geometry(self, radius: float = 0, thickness: float = 0,distance_between_rings: float = 0,
tot_length_of_shell: float = 0):
@@ -612,15 +636,15 @@ def set_shell_geometry(self, radius: float = 0, thickness: float = 0,distance_be
:rtype:
'''
- self._CylinderMain.ShellObj.radius = radius/1000
- self._CylinderMain.ShellObj.thk = thickness/1000
- self._CylinderMain.ShellObj.dist_between_rings = distance_between_rings/1000
+ self._CylinderMain.ShellObj.radius = api_helpers.mm_to_m(radius)
+ self._CylinderMain.ShellObj.thk = api_helpers.mm_to_m(thickness)
+ self._CylinderMain.ShellObj.dist_between_rings = api_helpers.mm_to_m(distance_between_rings)
if tot_length_of_shell == 0:
# Setting a default.
- self._CylinderMain.ShellObj.length_of_shell = distance_between_rings * 10/1000
- self._CylinderMain.ShellObj.tot_cyl_length = distance_between_rings * 10/1000
+ self._CylinderMain.ShellObj.length_of_shell = api_helpers.mm_to_m(distance_between_rings * 10)
+ self._CylinderMain.ShellObj.tot_cyl_length = api_helpers.mm_to_m(distance_between_rings * 10)
else:
- self._CylinderMain.ShellObj.tot_cyl_length = tot_length_of_shell/1000
+ self._CylinderMain.ShellObj.tot_cyl_length = api_helpers.mm_to_m(tot_length_of_shell)
def set_shell_buckling_parmeters(self, eff_buckling_length_factor: float = 1.0):
'''
@@ -656,7 +680,7 @@ def set_longitudinal_stiffener(self, hw: float = 260, tw: float = 12, bf: float
self._CylinderMain.LongStfObj.tw = tw
self._CylinderMain.LongStfObj.b = bf
self._CylinderMain.LongStfObj.tf = tf
- self._CylinderMain.LongStfObj.stiffener_type = 'L-bulb' if stf_type in ['hp HP HP-bulb bulb'] else stf_type
+ self._CylinderMain.LongStfObj.stiffener_type = api_helpers.normalize_bulb_stiffener_type(stf_type)
self._CylinderMain.LongStfObj.spacing = spacing
self._CylinderMain.LongStfObj.t = self._CylinderMain.ShellObj.thk
@@ -686,7 +710,7 @@ def set_ring_stiffener(self, hw: float = 260, tw: float = 12, bf: float = 49,
self._CylinderMain.RingStfObj.tw = tw
self._CylinderMain.RingStfObj.b = bf
self._CylinderMain.RingStfObj.tf = tf
- self._CylinderMain.RingStfObj.stiffener_type = 'L-bulb' if stf_type in ['hp HP HP-bulb bulb'] else stf_type
+ self._CylinderMain.RingStfObj.stiffener_type = api_helpers.normalize_bulb_stiffener_type(stf_type)
self._CylinderMain.RingStfObj.s = spacing
self._CylinderMain.RingStfObj.t = self._CylinderMain.ShellObj.thk
@@ -715,7 +739,7 @@ def set_ring_girder(self, hw: float = 500, tw: float = 15, bf: float = 200,
self._CylinderMain.RingFrameObj.tw = tw
self._CylinderMain.RingFrameObj.b = bf
self._CylinderMain.RingFrameObj.tf = tf
- self._CylinderMain.RingFrameObj.stiffener_type = 'L-bulb' if stf_type in ['hp HP HP-bulb bulb'] else stf_type
+ self._CylinderMain.RingFrameObj.stiffener_type = api_helpers.normalize_bulb_stiffener_type(stf_type)
self._CylinderMain.RingFrameObj.s = spacing
self._CylinderMain.RingFrameObj.t = self._CylinderMain.ShellObj.thk
@@ -767,6 +791,3 @@ def get_buckling_results(self):
-
-
-
diff --git a/anystruct/api_helpers.py b/anystruct/api_helpers.py
new file mode 100644
index 00000000..0d17e837
--- /dev/null
+++ b/anystruct/api_helpers.py
@@ -0,0 +1,120 @@
+FLAT_STRUCTURE_DOMAINS = (
+ "Flat plate, unstiffened",
+ "Flat plate, stiffened",
+ "Flat plate, stiffened with girder",
+)
+
+CYLINDER_STRUCTURE_DOMAINS = (
+ "Unstiffened shell",
+ "Unstiffened panel",
+ "Longitudinal Stiffened shell",
+ "Longitudinal Stiffened panel",
+ "Ring Stiffened shell",
+ "Ring Stiffened panel",
+ "Orthogonally Stiffened shell",
+ "Orthogonally Stiffened panel",
+)
+
+BUCKLING_CALCULATION_METHODS = (
+ "DNV-RP-C201 - prescriptive",
+ "SemiAnalytical S3/U3",
+ "ML-Numeric (PULS based)",
+)
+
+BUCKLING_ACCEPTANCE_TYPES = (
+ "buckling",
+ "ultimate",
+)
+
+SUPPORT_TYPES = (
+ "Continuous",
+ "Sniped",
+)
+
+FABRICATION_METHODS = (
+ "Fabricated",
+ "Cold formed",
+)
+
+LIMIT_STATE_TYPES = (
+ "ULS",
+ "ALS",
+)
+
+FLAT_GEOMETRY_IDS = {
+ "Flat plate, unstiffened": 10,
+ "Flat plate, stiffened": 11,
+ "Flat plate, stiffened with girder": 12,
+}
+
+CYLINDER_GEOMETRY_IDS = {
+ "Unstiffened shell (Force input)": 1,
+ "Unstiffened panel (Stress input)": 2,
+ "Longitudinal Stiffened shell (Force input)": 3,
+ "Longitudinal Stiffened panel (Stress input)": 4,
+ "Ring Stiffened shell (Force input)": 5,
+ "Ring Stiffened panel (Stress input)": 6,
+ "Orthogonally Stiffened shell (Force input)": 7,
+ "Orthogonally Stiffened panel (Stress input)": 8,
+}
+
+CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT = tuple(CYLINDER_GEOMETRY_IDS.keys())
+
+GEOMETRY_IDS = {
+ **FLAT_GEOMETRY_IDS,
+ **CYLINDER_GEOMETRY_IDS,
+}
+
+GEOMETRY_DOMAINS = {value: key for key, value in GEOMETRY_IDS.items()}
+
+
+def normalize_domain_string(calculation_domain):
+ return " ".join(str(calculation_domain).split())
+
+
+def assert_choice(value, choices, label):
+ assert value in choices, f"{label} must be one of: {list(choices)}"
+
+
+def cylinder_input_mode(calculation_domain):
+ calculation_domain = normalize_domain_string(calculation_domain)
+ assert_choice(calculation_domain, CYLINDER_STRUCTURE_DOMAINS, "calculation_domain")
+ return "Stress" if "panel" in calculation_domain.lower() else "Force"
+
+
+def cylinder_domain_with_input_mode(calculation_domain):
+ calculation_domain = normalize_domain_string(calculation_domain)
+ input_mode = cylinder_input_mode(calculation_domain)
+ return f"{calculation_domain} ({input_mode} input)"
+
+
+def geometry_id_for_domain(calculation_domain):
+ calculation_domain = normalize_domain_string(calculation_domain)
+
+ if calculation_domain in GEOMETRY_IDS:
+ return GEOMETRY_IDS[calculation_domain]
+
+ if calculation_domain in CYLINDER_STRUCTURE_DOMAINS:
+ calculation_domain = cylinder_domain_with_input_mode(calculation_domain)
+ return GEOMETRY_IDS[calculation_domain]
+
+ raise KeyError(
+ f"Unknown calculation domain: {calculation_domain!r}. "
+ f"Expected one of: {list(GEOMETRY_IDS)} or {list(CYLINDER_STRUCTURE_DOMAINS)}"
+ )
+
+
+def domain_for_geometry_id(geometry_id):
+ return GEOMETRY_DOMAINS[geometry_id]
+
+
+def mpa_to_pa(value):
+ return value * 1e6
+
+
+def mm_to_m(value):
+ return value / 1000
+
+
+def normalize_bulb_stiffener_type(stiffener_type):
+ return "L-bulb" if stiffener_type in ["hp", "HP", "HP-bulb", "bulb"] else stiffener_type
diff --git a/anystruct/calc_structure.py b/anystruct/calc_structure.py
index 6d5ed02f..1b0b3fc9 100644
--- a/anystruct/calc_structure.py
+++ b/anystruct/calc_structure.py
@@ -2619,16 +2619,16 @@ def local_buckling(self, optimizing = False):
def get_one_line_string_mixed(self):
''' Returning a one line string. '''
- return 'pl_'+str(round(self.Plate.s, 1))+'x'+str(round(self.Plate.t,1))+' stf_'+\
+ return 'pl_'+str(round(self.Plate.get_s(), 1))+'x'+str(round(self.Plate.get_pl_thk(),1))+' stf_'+\
self.Stiffener.get_stiffener_type()+\
- str(round(self.Stiffener.hw,1))+'x'+str(round(self.Stiffener.tw,1))+'+'\
- +str(round(self.Stiffener.b,1))+'x'+\
- str(round(self.Stiffener.tf,1))
+ str(round(self.Stiffener.get_web_h(),1))+'x'+str(round(self.Stiffener.get_web_thk(),1))+'+'\
+ +str(round(self.Stiffener.get_fl_w(),1))+'x'+\
+ str(round(self.Stiffener.get_fl_thk(),1))
def get_extended_string_mixed(self):
''' Some more information returned. '''
- return 'span: '+str(round(self.Plate.span,4))+' structure type: '+ self.Stiffener.get_structure_type() + ' stf. type: ' + \
- self.Stiffener.get_stiffener_type() + ' pressure side: ' + self.Plate.overpressure_side
+ return 'span: '+str(round(self.Plate.get_span(),4))+' structure type: '+ self.Stiffener.get_structure_type() + ' stf. type: ' + \
+ self.Stiffener.get_stiffener_type() + ' pressure side: ' + self.overpressure_side
class Shell():
'''
diff --git a/anystruct/calculate_semianalytical.py b/anystruct/calculate_semianalytical.py
new file mode 100644
index 00000000..34486f01
--- /dev/null
+++ b/anystruct/calculate_semianalytical.py
@@ -0,0 +1,4200 @@
+"""Reduced semi-analytical PULS S3/U3 panel calculations.
+
+This module is a first physics milestone for regular stiffened S3 panels and
+unstiffened U3 panels. It keeps the PULS CSV files as benchmark data and does
+not fit corrections from them. The implementation follows the direct
+semi-analytical shape described in DNV-CG-0128 Sec.4:
+
+* panel deflections are represented with Rayleigh-Ritz sine modes,
+* a nonlinear equilibrium residual is traced along a proportional in-plane
+ load path while lateral pressure remains fixed,
+* elastic mode factors and a major-yield collapse check are reported as
+ usage factors.
+
+The production PULS code uses a richer element library and element validity
+manual than the public guideline. The result diagnostics therefore name the
+covered assumptions instead of presenting this reduced model as PULS parity.
+"""
+
+from __future__ import annotations
+
+import argparse
+import csv
+import json
+import math
+import random
+import statistics
+from dataclasses import asdict, dataclass, field, replace
+from pathlib import Path
+from typing import Any, Iterable, Iterator, Mapping, Sequence
+
+import numpy as np
+
+
+EPS = 1.0e-12
+SUPPORTED_IN_PLANE_SUPPORTS = {
+ "Integrated": "method-a",
+ "Girder - long": "method-b-long",
+ "Girder - trans": "method-b-trans",
+}
+SUPPORTED_STIFFENER_TYPES = {"T-bar", "L-bulb", "Angle", "Flatbar"}
+SUPPORTED_STIFFENER_BOUNDARIES = {"Cont", "Sniped"}
+SUPPORTED_ROTATIONAL_SUPPORTS = {"SS", "CL", "FS", ""}
+CSV_SAMPLE_METHOD_FIRST_N = "first-n"
+CSV_SAMPLE_METHOD_STRATIFIED = "stratified-shuffle"
+CSV_SAMPLE_METHOD_FULL = "full"
+DEFAULT_CSV_SAMPLE_SIZE = 1200
+DEFAULT_CSV_FIXTURE_SAMPLE_SIZE = 64
+DEFAULT_CSV_SAMPLE_SEED = 128
+DEFAULT_CSV_MODE_CONVERGENCE_SAMPLE_SIZE = 200
+CSV_STRATIFICATION_KEYS = (
+ "target_validity",
+ "stiffener_type",
+ "support",
+ "stiffener_boundary",
+ "pressure_state",
+ "pressure_band",
+ "usage_region",
+)
+DEFAULT_SHIP_SECTION_INPUTS = (
+ r"C:\Github\ANYstructure\anystruct\ship_section_example.txt",
+ r"C:\Users\AudunArnesenNyhus\OneDrive - Cefront\Documents\OKEA side section.txt",
+ r"C:\Users\AudunArnesenNyhus\OneDrive - Cefront\Documents\OKEA mid section.txt",
+ r"C:\Users\AudunArnesenNyhus\OneDrive - Cefront\Documents\OKEA transversal section.txt",
+)
+DEFAULT_RELIABILITY_BASELINE = Path("reports/puls_reliability_baseline.json")
+PULS_MANUAL_S3_LIMITS = {
+ "source": r"C:\Program Files\DNV\NauticusHull 20.36.2508\Manuals\UserManual PULS.pdf",
+ "manual_file_date": "2025-08-05",
+ "plate_slenderness_max": 200.0,
+ "aspect_ratio_min": 0.17,
+ "aspect_ratio_max": 20.0,
+ "flatbar_web_slenderness_max": 35.0,
+ "open_profile_web_slenderness_max": 90.0,
+ "free_flange_slenderness_max": 15.0,
+ "min_flange_width_to_web_height": 0.22,
+}
+PULS_MANUAL_U3_LIMITS = {
+ "source": PULS_MANUAL_S3_LIMITS["source"],
+ "manual_file_date": PULS_MANUAL_S3_LIMITS["manual_file_date"],
+ "plate_slenderness_max": 200.0,
+ "long_to_short_aspect_ratio_max": 20.0,
+}
+
+
+@dataclass(frozen=True)
+class S3PanelInput:
+ """Input surface for the regular stiffened S3 panel milestone.
+
+ Geometric dimensions are millimetres and stresses/pressure are MPa. CSV
+ PULS exports use positive in-plane normal stress for compression; the
+ helper functions in this module preserve that sign convention.
+ """
+
+ length: float
+ stiffener_spacing: float
+ plate_thickness: float
+ stiffener_type: str
+ stiffener_boundary: str
+ stiffener_height: float
+ web_thickness: float
+ flange_width: float
+ flange_thickness: float
+ yield_stress_plate: float
+ yield_stress_stiffener: float
+ axial_stress: float
+ transverse_stress_1: float
+ transverse_stress_2: float
+ shear_stress: float
+ pressure: float
+ in_plane_support: str
+ elastic_modulus: float = 210000.0
+ poisson_ratio: float = 0.3
+
+ @property
+ def width(self) -> float:
+ return self.stiffener_spacing
+
+ @property
+ def mean_transverse_stress(self) -> float:
+ return 0.5 * (self.transverse_stress_1 + self.transverse_stress_2)
+
+
+@dataclass(frozen=True)
+class U3PanelInput:
+ """Input surface for the regular unstiffened U3 plate milestone.
+
+ Geometric dimensions are millimetres and stresses/pressure are MPa. The
+ ANYstructure/PULS exports use two end values for longitudinal and
+ transverse stress; the current U3 Ritz path uses their linear plate-field
+ interpolation in yield checks and their mean values in elastic buckling.
+ """
+
+ length: float
+ width: float
+ plate_thickness: float
+ yield_stress_plate: float
+ axial_stress_1: float
+ axial_stress_2: float
+ transverse_stress_1: float
+ transverse_stress_2: float
+ shear_stress: float
+ pressure: float
+ in_plane_support: str
+ rotational_support_1: str = "SS"
+ rotational_support_2: str = "SS"
+ elastic_modulus: float = 210000.0
+ poisson_ratio: float = 0.3
+
+ @property
+ def axial_stress(self) -> float:
+ return 0.5 * (self.axial_stress_1 + self.axial_stress_2)
+
+ @property
+ def mean_transverse_stress(self) -> float:
+ return 0.5 * (self.transverse_stress_1 + self.transverse_stress_2)
+
+
+@dataclass(frozen=True)
+class S3SolverConfig:
+ """Numerical and covered-domain controls for the reduced S3 solver."""
+
+ longitudinal_modes: tuple[int, ...] = (1, 2, 3)
+ transverse_modes: tuple[int, ...] = (1, 2)
+ web_longitudinal_modes: tuple[int, ...] = (1, 2, 3)
+ web_depth_modes: tuple[int, ...] = (1, 2)
+ initial_imperfection_ratio: float = 0.001
+ initial_imperfection_floor_mm: float = 0.02
+ nonlinear_membrane_factor: float = 0.75
+ local_global_coupling_floor: float = 0.25
+ local_global_coupling_gain: float = 1.0
+ global_stiffened_strip_capacity_factor: float | None = None
+ web_shear_interaction_exponent: float = 1.0
+ local_plate_web_interaction_exponent: float = 2.0
+ flanged_local_plate_restraint_factor: float = 1.10
+ s3_shear_buckling_capacity_factor: float = 0.75
+ use_effective_stiffener_width: bool = False
+ sniped_eccentricity_factor: float = 1.2
+ web_local_sniped_eccentricity_factor: float = 1.0
+ torsional_imperfection_scale: float = 1.0
+ torsional_restraint_factor: float = 0.65
+ pressure_local_share: float = 0.0
+ pressure_global_share: float = 1.0
+ use_separate_s3_pressure_modes: bool = True
+ s3_pressure_mode_stiffness_factor: float = 5.0
+ include_pressure_dominated_yield_in_buckling_strength: bool = False
+ include_lateral_deformation_in_ultimate_yield: bool = False
+ include_global_curvature_in_plate_yield: bool = False
+ pressure_dominated_yield_preload_ratio: float = 0.05
+ s3_major_yield_reserve_factor: float = 1.10
+ yield_utilization_limit: float = 1.0
+ pressure_yield_limit: float = 1.0
+ max_load_factor: float = 100.0
+ initial_load_step: float = 0.05
+ load_step_growth: float = 1.08
+ max_load_step: float = 1.5
+ min_load_step: float = 1.0e-4
+ load_step_cutback: float = 0.5
+ max_load_step_cutbacks: int = 12
+ newton_max_iterations: int = 40
+ newton_tolerance: float = 1.0e-7
+ min_aspect_ratio: float = 0.15
+ max_aspect_ratio: float = 12.0
+ max_plate_slenderness: float = 250.0
+ max_web_slenderness: float = 180.0
+ max_flange_slenderness: float = 45.0
+ max_web_to_flange_ratio: float = 5.0
+ hot_spot_grid: tuple[float, ...] = (0.125, 0.25, 0.5, 0.75, 0.875)
+ check_mode_convergence: bool = False
+ medium_longitudinal_modes: tuple[int, ...] = (1, 2, 3, 4)
+ medium_transverse_modes: tuple[int, ...] = (1, 2, 3)
+ high_longitudinal_modes: tuple[int, ...] = (1, 2, 3, 4, 5)
+ high_transverse_modes: tuple[int, ...] = (1, 2, 3, 4)
+ high_confidence_drift_limit: float = 0.05
+ medium_confidence_drift_limit: float = 0.12
+
+
+@dataclass(frozen=True)
+class S3SectionProperties:
+ area: float
+ centroid_from_plate_midplane: float
+ inertia_x: float
+ top_distance: float
+ bottom_distance: float
+ plate_area: float
+ stiffener_area: float
+
+ @property
+ def section_modulus(self) -> float:
+ return self.inertia_x / max(self.top_distance, self.bottom_distance, EPS)
+
+ @property
+ def top_section_modulus(self) -> float:
+ return self.inertia_x / max(self.top_distance, EPS)
+
+ @property
+ def attached_plate_section_modulus(self) -> float:
+ return self.inertia_x / max(self.bottom_distance, EPS)
+
+
+@dataclass(frozen=True)
+class OrthotropicStiffness:
+ d11: float
+ d12: float
+ d22: float
+ d66: float
+ membrane_thickness: float
+
+
+@dataclass(frozen=True)
+class RitzMode:
+ family: str
+ m: int
+ n: int
+ kx: float
+ ky: float
+ linear_stiffness: float
+ geometric_stiffness: float
+ pressure_force: float
+ imperfection: float
+ nonlinear_stiffness: float
+
+ @property
+ def label(self) -> str:
+ return f"{self.family}:{self.m},{self.n}"
+
+
+@dataclass
+class S3Result:
+ buckling_usage_factor: float | None
+ ultimate_usage_factor: float | None
+ valid: bool
+ elastic_buckling_usage_factor: float | None = None
+ invalid_reason: str | None = None
+ diagnostics: dict[str, Any] = field(default_factory=dict)
+ covered_domain_notes: list[str] = field(default_factory=list)
+ confidence: str = "low"
+ confidence_reasons: list[str] = field(default_factory=list)
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "buckling_usage_factor": self.buckling_usage_factor,
+ "ultimate_usage_factor": self.ultimate_usage_factor,
+ "elastic_buckling_usage_factor": self.elastic_buckling_usage_factor,
+ "valid": self.valid,
+ "invalid_reason": self.invalid_reason,
+ "diagnostics": self.diagnostics,
+ "covered_domain_notes": list(self.covered_domain_notes),
+ "confidence": self.confidence,
+ "confidence_reasons": list(self.confidence_reasons),
+ }
+
+
+@dataclass(frozen=True)
+class _Rectangle:
+ area: float
+ centroid: float
+ height: float
+
+ @property
+ def local_inertia_x(self) -> float:
+ return self.area * self.height * self.height / 12.0
+
+
+def _float_from_row(row: Mapping[str, Any], key: str) -> float:
+ value = row.get(key, "")
+ if value is None:
+ raise ValueError(f"Missing numeric value for {key}")
+ text = str(value).strip()
+ if text == "":
+ raise ValueError(f"Missing numeric value for {key}")
+ return float(text)
+
+
+def _optional_float(value: Any) -> float | None:
+ if value is None:
+ return None
+ text = str(value).strip()
+ if not text:
+ return None
+ try:
+ parsed = float(text)
+ except ValueError:
+ return None
+ if math.isnan(parsed):
+ return None
+ return parsed
+
+
+def row_to_s3_input(row: Mapping[str, Any]) -> S3PanelInput:
+ """Map a PULS stiffened-panel CSV row into the solver input type."""
+
+ elastic_modulus = _optional_float(row.get("Modulus of elasticity"))
+ poisson_ratio = _optional_float(row.get("Poisson's ratio"))
+ if poisson_ratio is None:
+ poisson_ratio = _optional_float(row.get("Poisson ratio"))
+ return S3PanelInput(
+ length=_float_from_row(row, "Length of panel"),
+ stiffener_spacing=_float_from_row(row, "Stiffener spacing"),
+ plate_thickness=_float_from_row(row, "Plate thick."),
+ stiffener_type=str(row.get("Stiffener type", "")).strip(),
+ stiffener_boundary=str(row.get("Stiffener boundary", "")).strip(),
+ stiffener_height=_float_from_row(row, "Stiff. Height"),
+ web_thickness=_float_from_row(row, "Web thick."),
+ flange_width=_float_from_row(row, "Flange width"),
+ flange_thickness=_float_from_row(row, "Flange thick."),
+ yield_stress_plate=_float_from_row(row, "Yield stress plate"),
+ yield_stress_stiffener=_float_from_row(row, "Yield stress stiffener"),
+ axial_stress=_float_from_row(row, "Axial stress"),
+ transverse_stress_1=_float_from_row(row, "Trans. stress 1"),
+ transverse_stress_2=_float_from_row(row, "Trans. stress 2"),
+ shear_stress=_float_from_row(row, "Shear stress"),
+ pressure=_float_from_row(row, "Pressure (fixed)"),
+ in_plane_support=str(row.get("In-plane support", "")).strip(),
+ elastic_modulus=(
+ elastic_modulus
+ if elastic_modulus is not None
+ else S3PanelInput.__dataclass_fields__["elastic_modulus"].default
+ ),
+ poisson_ratio=(
+ poisson_ratio
+ if poisson_ratio is not None
+ else S3PanelInput.__dataclass_fields__["poisson_ratio"].default
+ ),
+ )
+
+
+def row_to_u3_input(row: Mapping[str, Any]) -> U3PanelInput:
+ """Map a PULS unstiffened-panel row/export into the solver input type."""
+
+ elastic_modulus = _optional_float(row.get("Modulus of elasticity"))
+ poisson_ratio = _optional_float(row.get("Poisson's ratio"))
+ if poisson_ratio is None:
+ poisson_ratio = _optional_float(row.get("Poisson ratio"))
+ axial_1 = _float_from_row(row, "Axial stress")
+ axial_2 = _optional_float(row.get("Axial stress 2"))
+ transverse_1 = _optional_float(row.get("Trans. stress 1"))
+ if transverse_1 is None:
+ transverse_1 = _optional_float(row.get("Trans. Stress"))
+ if transverse_1 is None:
+ transverse_1 = _optional_float(row.get("Trans. stress"))
+ if transverse_1 is None:
+ raise ValueError("Missing numeric value for Trans. stress 1")
+ transverse_2 = _optional_float(row.get("Trans. stress 2"))
+ if transverse_2 is None:
+ transverse_2 = _optional_float(row.get("Trans. Stress 2"))
+ if transverse_2 is None:
+ transverse_2 = transverse_1
+ return U3PanelInput(
+ length=_float_from_row(row, "Plate length"),
+ width=_float_from_row(row, "Plate width"),
+ plate_thickness=_float_from_row(row, "Plate thick."),
+ yield_stress_plate=_float_from_row(row, "Yield stress plate"),
+ axial_stress_1=axial_1,
+ axial_stress_2=axial_1 if axial_2 is None else axial_2,
+ transverse_stress_1=transverse_1,
+ transverse_stress_2=transverse_2,
+ shear_stress=_float_from_row(row, "Shear stress"),
+ pressure=_float_from_row(row, "Pressure (fixed)"),
+ in_plane_support=str(row.get("In-plane support", "")).strip(),
+ rotational_support_1=str(row.get("Rotational support", "SS") or "SS").strip(),
+ rotational_support_2=str(row.get("Rotational support 2", "SS") or "SS").strip(),
+ elastic_modulus=(
+ elastic_modulus
+ if elastic_modulus is not None
+ else U3PanelInput.__dataclass_fields__["elastic_modulus"].default
+ ),
+ poisson_ratio=(
+ poisson_ratio
+ if poisson_ratio is not None
+ else U3PanelInput.__dataclass_fields__["poisson_ratio"].default
+ ),
+ )
+
+
+_MISSING = object()
+
+
+def _ship_section_value(section: Mapping[str, Any], key: str, default: Any = _MISSING) -> Any:
+ if key in section:
+ value = section[key]
+ elif default is not _MISSING:
+ value = default
+ else:
+ value = section[key]
+ if isinstance(value, (list, tuple)):
+ return value[0]
+ return value
+
+
+def _ship_section_optional_value(
+ section: Mapping[str, Any],
+ *keys: str,
+ default: Any = "",
+) -> Any:
+ for key in keys:
+ if key in section:
+ return _ship_section_value(section, key)
+ return default
+
+
+def ship_section_record_to_csv_row(record: Mapping[str, Any]) -> dict[str, Any]:
+ """Flatten one ANYstructure ship-section PULS result record to CSV-like S3 fields."""
+
+ plate = record["Plate geometry"]
+ stiffener = record["Primary stiffeners"]
+ material = record["Material"]
+ loads = record["Applied loads"]
+ support = record.get("Bound cond.", {})
+ buckling = record.get("Buckling strength", {})
+ ultimate = record.get("Ultimate capacity", {})
+ global_elastic = record.get("Global elastic buckling", {})
+ local_elastic = record.get("Local elastic buckling", {})
+ failure_modes = record.get("Failure modes", {})
+ transverse_stress = _ship_section_value(loads, "Trans. stress")
+
+ return {
+ "_ship_line": record.get("Identification", ""),
+ "Length of panel": _ship_section_value(plate, "Length of panel"),
+ "Stiffener spacing": _ship_section_value(plate, "Stiffener spacing"),
+ "Plate thick.": _ship_section_value(plate, "Plate thick."),
+ "Stiffener type": _ship_section_value(stiffener, "Stiffener type"),
+ "Stiffener boundary": _ship_section_value(stiffener, "Stiffener boundary"),
+ "Stiff. Height": _ship_section_value(stiffener, "Stiff. Height"),
+ "Web thick.": _ship_section_value(stiffener, "Web thick."),
+ "Flange width": _ship_section_value(stiffener, "Flange width"),
+ "Flange thick.": _ship_section_value(stiffener, "Flange thick."),
+ "Yield stress plate": _ship_section_value(material, "Yield stress plate"),
+ "Yield stress stiffener": _ship_section_value(material, "Yield stress stiffener"),
+ "Modulus of elasticity": _ship_section_value(material, "Modulus of elasticity"),
+ "Poisson's ratio": _ship_section_value(material, "Poisson's ratio"),
+ "Axial stress": _ship_section_value(loads, "Axial stress"),
+ "Trans. stress 1": transverse_stress,
+ "Trans. stress 2": _ship_section_value(loads, "Trans. stress 2", transverse_stress),
+ "Shear stress": _ship_section_value(loads, "Shear stress"),
+ "Pressure (fixed)": _ship_section_value(loads, "Pressure (fixed)"),
+ "In-plane support": _ship_section_value(support, "In-plane support") if support else "",
+ "Buckling Actual usage Factor inc NaN": (
+ _ship_section_value(buckling, "Actual usage Factor") if buckling else ""
+ ),
+ "Ultimate Actual usage Factor inc NaN": (
+ _ship_section_value(ultimate, "Actual usage Factor") if ultimate else ""
+ ),
+ "output cl str buc": _ship_section_value(buckling, "Status") if buckling else "",
+ "PULS global axial stress": _ship_section_optional_value(
+ global_elastic,
+ "Axial stress",
+ ),
+ "PULS global trans stress": _ship_section_optional_value(
+ global_elastic,
+ "Trans. Stress",
+ "Trans. stress",
+ ),
+ "PULS global shear stress": _ship_section_optional_value(
+ global_elastic,
+ "Shear stress",
+ ),
+ "PULS local axial stress": _ship_section_optional_value(
+ local_elastic,
+ "Axial stress",
+ ),
+ "PULS local trans stress": _ship_section_optional_value(
+ local_elastic,
+ "Trans. Stress",
+ "Trans. stress",
+ ),
+ "PULS local shear stress": _ship_section_optional_value(
+ local_elastic,
+ "Shear stress",
+ ),
+ "PULS failure plate buckling percent": _ship_section_optional_value(
+ failure_modes,
+ "Plate buckling",
+ ),
+ "PULS failure global stiffener buckling percent": _ship_section_optional_value(
+ failure_modes,
+ "Global stiffener buckling",
+ ),
+ "PULS failure torsional stiffener buckling percent": _ship_section_optional_value(
+ failure_modes,
+ "Torsional stiffener buckling",
+ ),
+ "PULS failure web stiffener buckling percent": _ship_section_optional_value(
+ failure_modes,
+ "Web stiffener buckling",
+ ),
+ }
+
+
+def ship_section_record_to_u3_row(record: Mapping[str, Any]) -> dict[str, Any]:
+ """Flatten one ANYstructure ship-section PULS result record to CSV-like U3 fields."""
+
+ plate = record["Geometry"]
+ material = record["Material"]
+ loads = record["Applied loads"]
+ support = record.get("Boundary conditions", record.get("Bound cond.", {}))
+ buckling = record.get("Buckling strength", {})
+ ultimate = record.get("Ultimate capacity", {})
+ transverse_stress = _ship_section_value(loads, "Trans. Stress", _ship_section_value(loads, "Trans. stress", 0.0))
+
+ return {
+ "_ship_line": record.get("Identification", ""),
+ "Panel family": "U3",
+ "Plate length": _ship_section_value(plate, "Plate length"),
+ "Plate width": _ship_section_value(plate, "Plate width"),
+ "Plate thick.": _ship_section_value(plate, "Plate thick."),
+ "Yield stress plate": _ship_section_value(material, "Yield st. plate", _ship_section_value(material, "Yield stress plate", "")),
+ "Modulus of elasticity": _ship_section_value(material, "Modulus of elasticity"),
+ "Poisson's ratio": _ship_section_value(material, "Poisson's ratio"),
+ "Axial stress": _ship_section_value(loads, "Axial stress"),
+ "Axial stress 2": _ship_section_value(loads, "Axial stress 2", _ship_section_value(loads, "Axial stress")),
+ "Trans. stress 1": transverse_stress,
+ "Trans. stress 2": _ship_section_value(loads, "Trans. Stress 2", transverse_stress),
+ "Shear stress": _ship_section_value(loads, "Shear stress"),
+ "Pressure (fixed)": _ship_section_value(loads, "Pressure (fixed)"),
+ "In-plane support": _ship_section_value(support, "In-plane support") if support else "",
+ "Rotational support": _ship_section_value(support, "Rotational support", "SS") if support else "SS",
+ "Rotational support 2": _ship_section_value(support, "Rotational support 2", "SS") if support else "SS",
+ "Buckling Actual usage Factor inc NaN": (
+ _ship_section_value(buckling, "Actual usage Factor") if buckling else ""
+ ),
+ "Ultimate Actual usage Factor inc NaN": (
+ _ship_section_value(ultimate, "Actual usage Factor") if ultimate else ""
+ ),
+ "output cl str buc": _ship_section_value(buckling, "Status") if buckling else "",
+ }
+
+
+def _anystructure_float(value: Any, default: float = 0.0) -> float:
+ try:
+ if value is None:
+ return default
+ return float(value)
+ except Exception:
+ return default
+
+
+def _anystructure_stiffener_type(value: Any) -> str:
+ return {
+ "T": "T-bar",
+ "T-bar": "T-bar",
+ "L": "Angle",
+ "Angle": "Angle",
+ "L-bulb": "L-bulb",
+ "FB": "Flatbar",
+ "F": "Flatbar",
+ "Flatbar": "Flatbar",
+ }.get(str(value), str(value))
+
+
+def _anystructure_stiffener_boundary(value: Any) -> str:
+ return {
+ "C": "Cont",
+ "Cont": "Cont",
+ "Continuous": "Cont",
+ "S": "Sniped",
+ "Sniped": "Sniped",
+ }.get(str(value), str(value))
+
+
+def _anystructure_in_plane_support(value: Any) -> str:
+ return {
+ "Int": "Integrated",
+ "Integrated": "Integrated",
+ "GL": "Girder - long",
+ "Girder - long": "Girder - long",
+ "GT": "Girder - trans",
+ "Girder - trans": "Girder - trans",
+ }.get(str(value), str(value))
+
+
+def _anystructure_rotational_supports(value: Any) -> tuple[str, str]:
+ text = str(value or "SSSS").strip().upper().replace("-", "")
+ if text in {"CCCC", "CLCL", "CC", "CL"}:
+ return "CL", "CL"
+ if text in {"FSFS", "FFFF", "FS"}:
+ return "FS", "FS"
+ return "SS", "SS"
+
+
+def _anystructure_selected_method(value: Any) -> str:
+ text = str(value).strip().lower()
+ if text in {"1", "buckling"}:
+ return "buckling"
+ if text in {"2", "ultimate"}:
+ return "ultimate"
+ return text
+
+
+def _anystructure_material_factor(all_structure: Any) -> float | None:
+ for candidate in (
+ getattr(all_structure, "Plate", None),
+ getattr(all_structure, "Stiffener", None),
+ all_structure,
+ ):
+ if candidate is None:
+ continue
+ for name in ("mat_factor", "_mat_factor"):
+ try:
+ value = getattr(candidate, name)
+ except Exception:
+ continue
+ parsed = _optional_float(value)
+ if parsed is not None and parsed > EPS:
+ return parsed
+ return None
+
+
+def _anystructure_axial_design_stress(sigma_x1: float, sigma_x2: float) -> float:
+ if sigma_x1 * sigma_x2 >= 0:
+ return sigma_x1 if abs(sigma_x1) > abs(sigma_x2) else sigma_x2
+ return max(sigma_x1, sigma_x2)
+
+
+def anystructure_panel_input(calc_object: Any, lat_press: float = 0.0) -> S3PanelInput | U3PanelInput | None:
+ """Build an S3/U3 input from an ANYstructure AllStructure-like object.
+
+ This is intentionally duck-typed so ANYstructure can call it without making
+ this repository import ANYstructure. The `lat_press` convention follows
+ ANYstructure optimization: kPa in, MPa on the solver input surface.
+ """
+
+ all_structure = calc_object[0] if isinstance(calc_object, (list, tuple)) else calc_object
+ plate = getattr(all_structure, "Plate", None)
+ stiffener = getattr(all_structure, "Stiffener", None)
+ if plate is None:
+ return None
+
+ sp_or_up = str(plate.get_puls_sp_or_up()).strip().upper()
+ pressure_mpa = _anystructure_float(lat_press) / 1000.0
+ elastic_modulus = _anystructure_float(getattr(all_structure, "E", None), 210000.0e6) / 1.0e6
+ poisson_ratio = _anystructure_float(getattr(all_structure, "v", None), 0.3)
+
+ if sp_or_up == "SP" and stiffener is not None:
+ puls_boundary = stiffener.get_puls_boundary()
+ sigxd = _anystructure_axial_design_stress(stiffener.sigma_x1, stiffener.sigma_x2)
+ return S3PanelInput(
+ length=stiffener.span * 1000.0,
+ stiffener_spacing=stiffener.spacing,
+ plate_thickness=stiffener.t,
+ stiffener_type=_anystructure_stiffener_type(stiffener.get_stiffener_type()),
+ stiffener_boundary=_anystructure_stiffener_boundary(stiffener.get_puls_stf_end()),
+ stiffener_height=stiffener.hw,
+ web_thickness=stiffener.tw,
+ flange_width=stiffener.b,
+ flange_thickness=stiffener.tf,
+ yield_stress_plate=stiffener.mat_yield / 1.0e6,
+ yield_stress_stiffener=stiffener.mat_yield / 1.0e6,
+ axial_stress=0.0 if puls_boundary == "GT" else sigxd,
+ transverse_stress_1=0.0 if puls_boundary == "GL" else stiffener.sigma_y1,
+ transverse_stress_2=0.0 if puls_boundary == "GL" else stiffener.sigma_y2,
+ shear_stress=stiffener.tau_xy,
+ pressure=pressure_mpa,
+ in_plane_support=_anystructure_in_plane_support(puls_boundary),
+ elastic_modulus=elastic_modulus,
+ poisson_ratio=poisson_ratio,
+ )
+
+ up_boundary = plate.get_puls_up_boundary() if hasattr(plate, "get_puls_up_boundary") else "SSSS"
+ rotational_1, rotational_2 = _anystructure_rotational_supports(up_boundary)
+ puls_boundary = plate.get_puls_boundary() if hasattr(plate, "get_puls_boundary") else "GL"
+ return U3PanelInput(
+ length=plate.span * 1000.0,
+ width=plate.spacing,
+ plate_thickness=plate.t,
+ yield_stress_plate=plate.mat_yield / 1.0e6,
+ axial_stress_1=plate.sigma_x1,
+ axial_stress_2=plate.sigma_x2,
+ transverse_stress_1=plate.sigma_y1,
+ transverse_stress_2=plate.sigma_y2,
+ shear_stress=plate.tau_xy,
+ pressure=pressure_mpa,
+ in_plane_support=_anystructure_in_plane_support(puls_boundary),
+ rotational_support_1=rotational_1,
+ rotational_support_2=rotational_2,
+ elastic_modulus=elastic_modulus,
+ poisson_ratio=poisson_ratio,
+ )
+
+
+def solve_anystructure_panel(
+ calc_object: Any,
+ lat_press: float = 0.0,
+ config: S3SolverConfig | None = None,
+) -> dict[str, Any]:
+ """Return an ANYstructure-friendly SemiAnalytical result dictionary."""
+
+ config = config or S3SolverConfig()
+ all_structure = calc_object[0] if isinstance(calc_object, (list, tuple)) else calc_object
+ material_factor = _anystructure_material_factor(all_structure)
+ acceptance_limit = None if material_factor is None else 1.0 / material_factor
+ panel = anystructure_panel_input(calc_object, lat_press)
+ if panel is None:
+ return {
+ "panel_family": None,
+ "buckling_usage_factor": None,
+ "ultimate_usage_factor": None,
+ "material_factor": material_factor,
+ "acceptance_limit": acceptance_limit,
+ "valid": False,
+ "available": False,
+ "valid_prediction": 0,
+ "valid_label": "SemiAnalytical S3/U3 unsupported or invalid",
+ "invalid_reason": "unsupported-anystructure-input",
+ "confidence": "low",
+ "confidence_reasons": ["unsupported-anystructure-input"],
+ "diagnostics": {},
+ }
+
+ panel_family = "S3" if isinstance(panel, S3PanelInput) else "U3"
+ solved = solve_s3_panel(panel, config) if panel_family == "S3" else solve_u3_panel(panel, config)
+ valid_prediction = (
+ solved.valid
+ and solved.buckling_usage_factor is not None
+ and solved.ultimate_usage_factor is not None
+ )
+ valid_label = (
+ f"valid SemiAnalytical {panel_family} UF predicted ({solved.confidence} confidence)"
+ if valid_prediction
+ else f"SemiAnalytical {panel_family} unsupported or invalid"
+ )
+ return {
+ "panel_family": panel_family,
+ "buckling_usage_factor": solved.buckling_usage_factor,
+ "ultimate_usage_factor": solved.ultimate_usage_factor,
+ "elastic_buckling_usage_factor": solved.elastic_buckling_usage_factor,
+ "material_factor": material_factor,
+ "acceptance_limit": acceptance_limit,
+ "valid": solved.valid,
+ "available": bool(valid_prediction),
+ "valid_prediction": 1 if valid_prediction else 0,
+ "valid_label": valid_label,
+ "invalid_reason": solved.invalid_reason,
+ "confidence": solved.confidence,
+ "confidence_reasons": list(solved.confidence_reasons),
+ "diagnostics": solved.diagnostics,
+ "result": solved.to_dict(),
+ }
+
+
+def predict_anystructure_uf(
+ calc_object: Any,
+ lat_press: float = 0.0,
+ config: S3SolverConfig | None = None,
+) -> np.ndarray:
+ """Return legacy ANYstructure vector [buckling UF, ultimate UF, valid].
+
+ The factors are intentionally returned un-factored. ANYstructure compares
+ them against a separate PULS acceptance limit, typically 1 / material factor.
+ """
+
+ result = solve_anystructure_panel(calc_object, lat_press, config)
+ if result["available"]:
+ return np.array(
+ [
+ float(result["buckling_usage_factor"]),
+ float(result["ultimate_usage_factor"]),
+ 1.0,
+ ],
+ dtype=float,
+ )
+ return np.array([float("inf"), float("inf"), 0.0], dtype=float)
+
+
+def predict_anystructure_uf_with_acceptance(
+ calc_object: Any,
+ lat_press: float = 0.0,
+ config: S3SolverConfig | None = None,
+ default_acceptance: float = 0.87,
+) -> np.ndarray:
+ """Return [buckling UF, ultimate UF, valid, acceptance limit] for ANYstructure."""
+
+ result = solve_anystructure_panel(calc_object, lat_press, config)
+ acceptance = _optional_float(result.get("acceptance_limit"))
+ if acceptance is None:
+ acceptance = float(default_acceptance)
+ vector = np.array([float("inf"), float("inf"), 0.0, acceptance], dtype=float)
+ if result["available"]:
+ vector[0] = float(result["buckling_usage_factor"])
+ vector[1] = float(result["ultimate_usage_factor"])
+ vector[2] = 1.0
+ return vector
+
+
+def normalized_load_components(panel: S3PanelInput) -> dict[str, float]:
+ """Return stress resultants and destabilizing load components.
+
+ Positive values in the `compression_*` fields are compression resultants
+ per unit length. Negative normal stresses still enter the yield checks
+ with their signed values, but are not treated as elastic buckling drivers.
+ """
+
+ plate_t = panel.plate_thickness
+ return {
+ "signed_axial_stress": panel.axial_stress,
+ "signed_transverse_stress": panel.mean_transverse_stress,
+ "signed_shear_stress": panel.shear_stress,
+ "compression_nx": max(panel.axial_stress, 0.0) * plate_t,
+ "compression_ny": max(panel.mean_transverse_stress, 0.0) * plate_t,
+ "shear_nxy": abs(panel.shear_stress) * plate_t,
+ "pressure": max(panel.pressure, 0.0),
+ }
+
+
+def build_section_properties(
+ panel: S3PanelInput,
+ attached_plate_width: float | None = None,
+ stiffener_web_thickness: float | None = None,
+) -> S3SectionProperties:
+ """Return strip section properties about the plate/stiffener axis."""
+
+ plate_t = panel.plate_thickness
+ web_h = panel.stiffener_height
+ web_thickness = panel.web_thickness if stiffener_web_thickness is None else stiffener_web_thickness
+ plate_width = panel.width if attached_plate_width is None else min(attached_plate_width, panel.width)
+ rectangles = [
+ _Rectangle(
+ area=max(plate_width, EPS) * plate_t,
+ centroid=0.0,
+ height=plate_t,
+ ),
+ _Rectangle(
+ area=web_h * max(web_thickness, EPS),
+ centroid=0.5 * plate_t + 0.5 * web_h,
+ height=web_h,
+ ),
+ ]
+
+ if panel.stiffener_type != "Flatbar" and panel.flange_width > 0.0 and panel.flange_thickness > 0.0:
+ rectangles.append(
+ _Rectangle(
+ area=panel.flange_width * panel.flange_thickness,
+ centroid=0.5 * plate_t + web_h + 0.5 * panel.flange_thickness,
+ height=panel.flange_thickness,
+ )
+ )
+
+ total_area = sum(rectangle.area for rectangle in rectangles)
+ centroid = sum(rectangle.area * rectangle.centroid for rectangle in rectangles) / max(total_area, EPS)
+ inertia = sum(
+ rectangle.local_inertia_x + rectangle.area * (rectangle.centroid - centroid) ** 2
+ for rectangle in rectangles
+ )
+
+ top_coordinate = max(
+ 0.5 * plate_t,
+ 0.5 * plate_t + web_h,
+ 0.5 * plate_t + web_h + max(panel.flange_thickness, 0.0),
+ )
+ bottom_coordinate = -0.5 * plate_t
+ plate_area = rectangles[0].area
+ return S3SectionProperties(
+ area=total_area,
+ centroid_from_plate_midplane=centroid,
+ inertia_x=inertia,
+ top_distance=max(top_coordinate - centroid, EPS),
+ bottom_distance=max(centroid - bottom_coordinate, EPS),
+ plate_area=plate_area,
+ stiffener_area=max(total_area - plate_area, 0.0),
+ )
+
+
+def effective_stiffener_plate_width(panel: S3PanelInput) -> dict[str, float]:
+ """Return the shear-lag effective width used by stiffener checks.
+
+ IACS S35 and the matching DNV stiffener text reduce attached plating by an
+ effective-width coefficient based on stiffener effective length. The S3 CSV
+ surface has a regular strip width but not separate plate reduction factors
+ for the two sides of a stiffener, so this helper applies the length-based
+ coefficient only and records that width in diagnostics.
+ """
+
+ effective_length = (
+ panel.length / math.sqrt(3.0)
+ if panel.stiffener_boundary == "Cont"
+ else panel.length
+ )
+ length_ratio = effective_length / max(panel.width, EPS)
+ if length_ratio >= 1.0:
+ coefficient = 1.12 / (1.0 + 1.75 * length_ratio**1.6)
+ coefficient = min(coefficient, 1.0)
+ else:
+ coefficient = 0.407 * length_ratio
+ coefficient = min(1.0, max(coefficient, EPS))
+ return {
+ "width": coefficient * panel.width,
+ "coefficient": coefficient,
+ "effective_length": effective_length,
+ "length_ratio": length_ratio,
+ }
+
+
+def effective_flatbar_web_thickness(
+ panel: S3PanelInput,
+ attached_plate_width: float,
+) -> dict[str, float]:
+ """Return the effective flat-bar web thickness for stiffener checks."""
+
+ if panel.stiffener_type != "Flatbar":
+ return {
+ "gross_thickness": panel.web_thickness,
+ "effective_thickness": panel.web_thickness,
+ "reduction_factor": 1.0,
+ "attached_plate_width": attached_plate_width,
+ }
+
+ width_ratio = min(max(attached_plate_width / max(panel.width, EPS), 0.0), 1.0)
+ reduction_factor = (
+ 1.0
+ - 2.0
+ * math.pi**2
+ / 3.0
+ * (panel.stiffener_height / max(panel.width, EPS)) ** 2
+ * (1.0 - width_ratio)
+ )
+ reduction_factor = min(1.0, max(reduction_factor, EPS))
+ return {
+ "gross_thickness": panel.web_thickness,
+ "effective_thickness": reduction_factor * panel.web_thickness,
+ "reduction_factor": reduction_factor,
+ "attached_plate_width": attached_plate_width,
+ }
+
+
+def build_effective_stiffener_section(
+ panel: S3PanelInput,
+ config: S3SolverConfig,
+) -> tuple[S3SectionProperties, dict[str, Any]]:
+ effective_width = effective_stiffener_plate_width(panel)
+ attached_width = effective_width["width"] if config.use_effective_stiffener_width else panel.width
+ flatbar_web = effective_flatbar_web_thickness(panel, attached_width)
+ if not config.use_effective_stiffener_width:
+ effective_width = {**effective_width, "applied_width": panel.width}
+ else:
+ effective_width = {**effective_width, "applied_width": attached_width}
+ effective_width = {**effective_width, "flatbar_web_thickness": flatbar_web}
+ return (
+ build_section_properties(
+ panel,
+ attached_plate_width=attached_width,
+ stiffener_web_thickness=flatbar_web["effective_thickness"],
+ ),
+ effective_width,
+ )
+
+
+def _plate_bending_rigidity(panel: S3PanelInput) -> float:
+ return panel.elastic_modulus * panel.plate_thickness**3 / (
+ 12.0 * (1.0 - panel.poisson_ratio**2)
+ )
+
+
+def build_orthotropic_stiffness(
+ panel: S3PanelInput,
+ section: S3SectionProperties,
+ family: str,
+ config: S3SolverConfig | None = None,
+) -> OrthotropicStiffness:
+ """Build local-plate or distributed stiffened-strip stiffness terms."""
+
+ config = config or S3SolverConfig()
+ plate_d = _plate_bending_rigidity(panel)
+ if family == "local":
+ restraint_factor = (
+ max(config.flanged_local_plate_restraint_factor, EPS)
+ if panel.stiffener_type != "Flatbar"
+ else 1.0
+ )
+ d11 = plate_d * restraint_factor
+ d22 = plate_d * restraint_factor
+ membrane_thickness = panel.plate_thickness
+ elif family == "global":
+ d11 = max(panel.elastic_modulus * section.inertia_x / panel.width, plate_d)
+ d22 = plate_d
+ membrane_thickness = max(section.area / panel.width, panel.plate_thickness)
+ else:
+ raise ValueError(f"Unknown stiffness family: {family}")
+
+ d12 = panel.poisson_ratio * math.sqrt(d11 * d22)
+ d66 = 0.5 * (1.0 - panel.poisson_ratio) * math.sqrt(d11 * d22)
+ return OrthotropicStiffness(
+ d11=d11,
+ d12=d12,
+ d22=d22,
+ d66=d66,
+ membrane_thickness=membrane_thickness,
+ )
+
+
+def _with_longitudinal_stiffness_scale(
+ stiffness: OrthotropicStiffness,
+ scale: float,
+) -> OrthotropicStiffness:
+ scale = max(scale, EPS)
+ d11 = stiffness.d11 * scale
+ d22 = stiffness.d22
+ return OrthotropicStiffness(
+ d11=d11,
+ d12=math.sqrt(scale) * stiffness.d12,
+ d22=d22,
+ d66=math.sqrt(scale) * stiffness.d66,
+ membrane_thickness=stiffness.membrane_thickness,
+ )
+
+
+def _support_membrane_factor(panel: S3PanelInput) -> float:
+ if panel.in_plane_support == "Integrated":
+ return 1.0
+ if panel.in_plane_support == "Girder - long":
+ return 0.90
+ if panel.in_plane_support == "Girder - trans":
+ return 0.90
+ return 1.0
+
+
+def _load_family(panel: S3PanelInput | U3PanelInput) -> str:
+ components = []
+ if panel.axial_stress > EPS:
+ components.append("axial-compression")
+ elif panel.axial_stress < -EPS:
+ components.append("axial-tension")
+ if panel.mean_transverse_stress > EPS:
+ components.append("transverse-compression")
+ elif panel.mean_transverse_stress < -EPS:
+ components.append("transverse-tension")
+ if abs(panel.shear_stress) > EPS:
+ components.append("shear")
+ if panel.pressure > EPS:
+ components.append("pressure")
+ return "+".join(components) if components else "zero-variable-load"
+
+
+def _limit_check(value: float | None, *, maximum: float | None = None, minimum: float | None = None) -> bool | None:
+ if value is None:
+ return None
+ if maximum is not None and value > maximum:
+ return False
+ if minimum is not None and value < minimum:
+ return False
+ return True
+
+
+def _puls_manual_reference_domain(panel: S3PanelInput | U3PanelInput) -> dict[str, Any]:
+ """Return source-backed PULS manual limit diagnostics without gating solves."""
+
+ if isinstance(panel, S3PanelInput):
+ limits = PULS_MANUAL_S3_LIMITS
+ aspect_ratio = panel.length / panel.width if panel.width > EPS else None
+ plate_slenderness = panel.width / panel.plate_thickness if panel.plate_thickness > EPS else None
+ web_slenderness = (
+ panel.stiffener_height / panel.web_thickness
+ if panel.web_thickness > EPS
+ else None
+ )
+ web_limit = (
+ limits["flatbar_web_slenderness_max"]
+ if panel.stiffener_type == "Flatbar"
+ else limits["open_profile_web_slenderness_max"]
+ )
+ flange_slenderness = None
+ flange_width_to_web_height = None
+ if panel.stiffener_type != "Flatbar" and panel.flange_width > EPS and panel.flange_thickness > EPS:
+ flange_slenderness = panel.flange_width / panel.flange_thickness
+ flange_width_to_web_height = panel.flange_width / max(panel.stiffener_height, EPS)
+ checks = {
+ "plate_slenderness": _limit_check(plate_slenderness, maximum=limits["plate_slenderness_max"]),
+ "aspect_ratio": _limit_check(
+ aspect_ratio,
+ minimum=limits["aspect_ratio_min"],
+ maximum=limits["aspect_ratio_max"],
+ ),
+ "web_slenderness": _limit_check(web_slenderness, maximum=web_limit),
+ "free_flange_slenderness": (
+ True
+ if panel.stiffener_type == "Flatbar"
+ else _limit_check(flange_slenderness, maximum=limits["free_flange_slenderness_max"])
+ ),
+ "flange_width_to_web_height": (
+ True
+ if panel.stiffener_type == "Flatbar"
+ else _limit_check(
+ flange_width_to_web_height,
+ minimum=limits["min_flange_width_to_web_height"],
+ )
+ ),
+ }
+ failed = [name for name, passed in checks.items() if passed is False]
+ unknown = [name for name, passed in checks.items() if passed is None]
+ return {
+ "source": limits["source"],
+ "manual_file_date": limits["manual_file_date"],
+ "panel_family": "S3",
+ "note": "manual reference limits are diagnostic only; solver covered-domain gates are unchanged",
+ "limits": {key: value for key, value in limits.items() if key not in {"source", "manual_file_date"}},
+ "values": {
+ "aspect_ratio": aspect_ratio,
+ "plate_slenderness": plate_slenderness,
+ "web_slenderness": web_slenderness,
+ "web_slenderness_limit": web_limit,
+ "free_flange_slenderness": flange_slenderness,
+ "flange_width_to_web_height": flange_width_to_web_height,
+ },
+ "checks": checks,
+ "failed": failed,
+ "unknown": unknown,
+ "within_manual_limits": not failed and not unknown,
+ }
+
+ limits = PULS_MANUAL_U3_LIMITS
+ short_side = min(panel.length, panel.width)
+ long_side = max(panel.length, panel.width)
+ long_to_short = long_side / max(short_side, EPS)
+ plate_slenderness = short_side / panel.plate_thickness if panel.plate_thickness > EPS else None
+ checks = {
+ "plate_slenderness": _limit_check(plate_slenderness, maximum=limits["plate_slenderness_max"]),
+ "long_to_short_aspect_ratio": _limit_check(
+ long_to_short,
+ maximum=limits["long_to_short_aspect_ratio_max"],
+ ),
+ }
+ failed = [name for name, passed in checks.items() if passed is False]
+ unknown = [name for name, passed in checks.items() if passed is None]
+ return {
+ "source": limits["source"],
+ "manual_file_date": limits["manual_file_date"],
+ "panel_family": "U3",
+ "note": "manual reference limits are diagnostic only; solver covered-domain gates are unchanged",
+ "limits": {key: value for key, value in limits.items() if key not in {"source", "manual_file_date"}},
+ "values": {
+ "long_to_short_aspect_ratio": long_to_short,
+ "plate_slenderness": plate_slenderness,
+ },
+ "checks": checks,
+ "failed": failed,
+ "unknown": unknown,
+ "within_manual_limits": not failed and not unknown,
+ }
+
+
+def _validation_domain(
+ panel: S3PanelInput | U3PanelInput,
+ config: S3SolverConfig,
+ panel_family: str,
+ reasons: Sequence[str] | None = None,
+) -> dict[str, Any]:
+ width = panel.width
+ plate_slenderness = width / panel.plate_thickness if panel.plate_thickness > EPS else None
+ aspect_ratio = panel.length / width if width > EPS else None
+ domain: dict[str, Any] = {
+ "panel_family": panel_family,
+ "aspect_ratio": aspect_ratio,
+ "aspect_ratio_limits": [config.min_aspect_ratio, config.max_aspect_ratio],
+ "plate_slenderness": plate_slenderness,
+ "max_plate_slenderness": config.max_plate_slenderness,
+ "in_plane_support": panel.in_plane_support,
+ "support_model": SUPPORTED_IN_PLANE_SUPPORTS.get(panel.in_plane_support),
+ "pressure": panel.pressure,
+ "pressure_category": "nonzero" if panel.pressure > EPS else "zero",
+ "load_family": _load_family(panel),
+ "reasons": list(reasons or ()),
+ "puls_manual_reference": _puls_manual_reference_domain(panel),
+ }
+ if isinstance(panel, S3PanelInput):
+ domain.update(
+ {
+ "stiffener_type": panel.stiffener_type,
+ "stiffener_boundary": panel.stiffener_boundary,
+ "web_slenderness": (
+ panel.stiffener_height / panel.web_thickness
+ if panel.web_thickness > EPS
+ else None
+ ),
+ "max_web_slenderness": config.max_web_slenderness,
+ "flange_slenderness": (
+ panel.flange_width / panel.flange_thickness
+ if panel.flange_thickness > EPS
+ else None
+ ),
+ "max_flange_slenderness": config.max_flange_slenderness,
+ "web_to_flange_ratio": (
+ panel.stiffener_height / max(panel.flange_width, panel.web_thickness)
+ if max(panel.flange_width, panel.web_thickness) > EPS
+ else None
+ ),
+ "max_web_to_flange_ratio": config.max_web_to_flange_ratio,
+ }
+ )
+ else:
+ domain.update(
+ {
+ "rotational_support": {
+ "x_edges": panel.rotational_support_1,
+ "y_edges": panel.rotational_support_2,
+ },
+ }
+ )
+ return domain
+
+
+def collect_s3_validation_reasons(panel: S3PanelInput, config: S3SolverConfig) -> list[str]:
+ """Return all explicit S3 domain/validity reasons in stable first-reason order."""
+
+ numeric_fields = {
+ "length": panel.length,
+ "stiffener_spacing": panel.stiffener_spacing,
+ "plate_thickness": panel.plate_thickness,
+ "stiffener_height": panel.stiffener_height,
+ "web_thickness": panel.web_thickness,
+ "flange_width": panel.flange_width,
+ "flange_thickness": panel.flange_thickness,
+ "yield_stress_plate": panel.yield_stress_plate,
+ "yield_stress_stiffener": panel.yield_stress_stiffener,
+ "elastic_modulus": panel.elastic_modulus,
+ "poisson_ratio": panel.poisson_ratio,
+ "axial_stress": panel.axial_stress,
+ "transverse_stress_1": panel.transverse_stress_1,
+ "transverse_stress_2": panel.transverse_stress_2,
+ "shear_stress": panel.shear_stress,
+ "pressure": panel.pressure,
+ }
+ reasons: list[str] = []
+ if any(not math.isfinite(value) for value in numeric_fields.values()):
+ return ["non-finite-input"]
+ if panel.poisson_ratio < 0.0 or panel.poisson_ratio >= 0.5:
+ reasons.append("unsupported-material")
+ positive_fields = {
+ "length": panel.length,
+ "stiffener_spacing": panel.stiffener_spacing,
+ "plate_thickness": panel.plate_thickness,
+ "stiffener_height": panel.stiffener_height,
+ "web_thickness": panel.web_thickness,
+ "yield_stress_plate": panel.yield_stress_plate,
+ "yield_stress_stiffener": panel.yield_stress_stiffener,
+ "elastic_modulus": panel.elastic_modulus,
+ }
+ if any(value <= 0.0 for value in positive_fields.values()):
+ reasons.append("non-positive-geometry-or-material")
+ if panel.pressure < 0.0:
+ reasons.append("negative-pressure-unsupported")
+ if panel.stiffener_type not in SUPPORTED_STIFFENER_TYPES:
+ reasons.append("unsupported-stiffener-type")
+ if panel.stiffener_boundary not in SUPPORTED_STIFFENER_BOUNDARIES:
+ reasons.append("unsupported-stiffener-boundary")
+ if panel.in_plane_support not in SUPPORTED_IN_PLANE_SUPPORTS:
+ reasons.append("unsupported-in-plane-support")
+ if panel.width > EPS:
+ aspect_ratio = panel.length / panel.width
+ if aspect_ratio < config.min_aspect_ratio or aspect_ratio > config.max_aspect_ratio:
+ reasons.append("aspect ratio")
+ if panel.plate_thickness > EPS and panel.width / panel.plate_thickness > config.max_plate_slenderness:
+ reasons.append("slenderness")
+ if panel.web_thickness > EPS and panel.stiffener_height / panel.web_thickness > config.max_web_slenderness:
+ reasons.append("slenderness")
+ if panel.stiffener_type != "Flatbar":
+ if panel.flange_width <= 0.0 or panel.flange_thickness <= 0.0:
+ reasons.append("web-flange-ratio")
+ elif panel.flange_width / panel.flange_thickness > config.max_flange_slenderness:
+ reasons.append("web-flange-ratio")
+ if (
+ panel.stiffener_type in {"Angle", "T-bar"}
+ and panel.stiffener_height / max(panel.flange_width, panel.web_thickness, EPS)
+ > config.max_web_to_flange_ratio
+ ):
+ reasons.append("web-flange-ratio")
+ return list(dict.fromkeys(reasons))
+
+
+def collect_u3_validation_reasons(panel: U3PanelInput, config: S3SolverConfig) -> list[str]:
+ """Return all explicit U3 domain/validity reasons in stable first-reason order."""
+
+ numeric_fields = {
+ "length": panel.length,
+ "width": panel.width,
+ "plate_thickness": panel.plate_thickness,
+ "yield_stress_plate": panel.yield_stress_plate,
+ "elastic_modulus": panel.elastic_modulus,
+ "poisson_ratio": panel.poisson_ratio,
+ "axial_stress_1": panel.axial_stress_1,
+ "axial_stress_2": panel.axial_stress_2,
+ "transverse_stress_1": panel.transverse_stress_1,
+ "transverse_stress_2": panel.transverse_stress_2,
+ "shear_stress": panel.shear_stress,
+ "pressure": panel.pressure,
+ }
+ reasons: list[str] = []
+ if any(not math.isfinite(value) for value in numeric_fields.values()):
+ return ["non-finite-input"]
+ if panel.poisson_ratio < 0.0 or panel.poisson_ratio >= 0.5:
+ reasons.append("unsupported-material")
+ positive_fields = {
+ "length": panel.length,
+ "width": panel.width,
+ "plate_thickness": panel.plate_thickness,
+ "yield_stress_plate": panel.yield_stress_plate,
+ "elastic_modulus": panel.elastic_modulus,
+ }
+ if any(value <= 0.0 for value in positive_fields.values()):
+ reasons.append("non-positive-geometry-or-material")
+ if panel.pressure < 0.0:
+ reasons.append("negative-pressure-unsupported")
+ if panel.in_plane_support not in SUPPORTED_IN_PLANE_SUPPORTS:
+ reasons.append("unsupported-in-plane-support")
+ if panel.rotational_support_1 not in SUPPORTED_ROTATIONAL_SUPPORTS:
+ reasons.append("unsupported-rotational-support")
+ if panel.rotational_support_2 not in SUPPORTED_ROTATIONAL_SUPPORTS:
+ reasons.append("unsupported-rotational-support")
+ if panel.width > EPS:
+ aspect_ratio = panel.length / panel.width
+ if aspect_ratio < config.min_aspect_ratio or aspect_ratio > config.max_aspect_ratio:
+ reasons.append("aspect ratio")
+ if panel.plate_thickness > EPS and panel.width / panel.plate_thickness > config.max_plate_slenderness:
+ reasons.append("slenderness")
+ return list(dict.fromkeys(reasons))
+
+
+def validate_s3_input(panel: S3PanelInput, config: S3SolverConfig) -> str | None:
+ """Validate the explicit covered domain before solving."""
+
+ reasons = collect_s3_validation_reasons(panel, config)
+ return reasons[0] if reasons else None
+
+
+def validate_u3_input(panel: U3PanelInput, config: S3SolverConfig) -> str | None:
+ """Validate the explicit U3 covered domain before solving."""
+
+ reasons = collect_u3_validation_reasons(panel, config)
+ return reasons[0] if reasons else None
+
+
+def _pressure_generalized_force(panel: S3PanelInput, m: int, n: int, share: float) -> float:
+ if panel.pressure <= 0.0 or m % 2 == 0 or n % 2 == 0:
+ return 0.0
+ return share * panel.pressure * 4.0 * panel.length * panel.width / (m * n * math.pi**2)
+
+
+def _mode_linear_terms(
+ panel: S3PanelInput,
+ stiffness: OrthotropicStiffness,
+ config: S3SolverConfig,
+ m: int,
+ n: int,
+) -> tuple[float, float, float, float]:
+ kx = m * math.pi / panel.length
+ ky = n * math.pi / panel.width
+ area_factor = panel.length * panel.width / 4.0
+ bending = (
+ stiffness.d11 * kx**4
+ + 2.0 * (stiffness.d12 + 2.0 * stiffness.d66) * kx * kx * ky * ky
+ + stiffness.d22 * ky**4
+ )
+
+ loads = normalized_load_components(panel)
+ geometric = (
+ loads["compression_nx"] * kx * kx
+ + loads["compression_ny"] * ky * ky
+ )
+ linear_stiffness = area_factor * bending
+ geometric_stiffness = area_factor * geometric
+ wave_norm = kx * kx + ky * ky
+ nonlinear_stiffness = (
+ config.nonlinear_membrane_factor
+ * _support_membrane_factor(panel)
+ * panel.elastic_modulus
+ * stiffness.membrane_thickness
+ * panel.length
+ * panel.width
+ * wave_norm
+ * wave_norm
+ / 16.0
+ )
+ return kx, ky, linear_stiffness, max(geometric_stiffness, 0.0), nonlinear_stiffness
+
+
+def _sin_cos_integral(sine_mode: int, cosine_mode: int, length: float) -> float:
+ """Return the Fourier integral int_0^L sin(p*pi*x/L) cos(q*pi*x/L) dx."""
+
+ def harmonic_integral(harmonic: int) -> float:
+ if harmonic == 0 or harmonic % 2 == 0:
+ return 0.0
+ return 2.0 / harmonic
+
+ return (
+ 0.5
+ * length
+ / math.pi
+ * (
+ harmonic_integral(sine_mode + cosine_mode)
+ + harmonic_integral(sine_mode - cosine_mode)
+ )
+ )
+
+
+def _rectangular_mode_shear_geometric_integral(
+ length: float,
+ width: float,
+ first: RitzMode,
+ second: RitzMode,
+) -> float:
+ """Return symmetric unit-Nxy geometric coupling between Ritz modes."""
+
+ first_x_second_y = (
+ first.kx
+ * second.ky
+ * _sin_cos_integral(second.m, first.m, length)
+ * _sin_cos_integral(first.n, second.n, width)
+ )
+ second_x_first_y = (
+ second.kx
+ * first.ky
+ * _sin_cos_integral(first.m, second.m, length)
+ * _sin_cos_integral(second.n, first.n, width)
+ )
+ return first_x_second_y + second_x_first_y
+
+
+def _mode_shear_geometric_integral(
+ panel: S3PanelInput,
+ first: RitzMode,
+ second: RitzMode,
+) -> float:
+ return _rectangular_mode_shear_geometric_integral(
+ panel.length,
+ panel.width,
+ first,
+ second,
+ )
+
+
+def build_ritz_modes(
+ panel: S3PanelInput,
+ section: S3SectionProperties,
+ config: S3SolverConfig,
+ global_stiffness_scale: float = 1.0,
+) -> list[RitzMode]:
+ modes: list[RitzMode] = []
+ imperfection = max(
+ config.initial_imperfection_floor_mm,
+ config.initial_imperfection_ratio * min(panel.length, panel.width),
+ )
+ global_pressure_share = (
+ 0.0
+ if config.use_separate_s3_pressure_modes
+ else config.pressure_global_share
+ )
+ for family, pressure_share in (
+ ("local", config.pressure_local_share),
+ ("global", global_pressure_share),
+ ):
+ stiffness = build_orthotropic_stiffness(panel, section, family, config)
+ if family == "global":
+ stiffness = _with_longitudinal_stiffness_scale(stiffness, global_stiffness_scale)
+ for m in config.longitudinal_modes:
+ for n in config.transverse_modes:
+ kx, ky, linear, geometric, nonlinear = _mode_linear_terms(
+ panel,
+ stiffness,
+ config,
+ m,
+ n,
+ )
+ modes.append(
+ RitzMode(
+ family=family,
+ m=m,
+ n=n,
+ kx=kx,
+ ky=ky,
+ linear_stiffness=linear,
+ geometric_stiffness=geometric,
+ pressure_force=_pressure_generalized_force(panel, m, n, pressure_share),
+ imperfection=imperfection,
+ nonlinear_stiffness=max(nonlinear, EPS),
+ )
+ )
+ if config.use_separate_s3_pressure_modes and config.pressure_global_share > EPS:
+ stiffness = build_orthotropic_stiffness(panel, section, "global", config)
+ stiffness = _with_longitudinal_stiffness_scale(stiffness, global_stiffness_scale)
+ stiffness = OrthotropicStiffness(
+ d11=stiffness.d11 * max(config.s3_pressure_mode_stiffness_factor, EPS),
+ d12=stiffness.d12 * max(config.s3_pressure_mode_stiffness_factor, EPS),
+ d22=stiffness.d22 * max(config.s3_pressure_mode_stiffness_factor, EPS),
+ d66=stiffness.d66 * max(config.s3_pressure_mode_stiffness_factor, EPS),
+ membrane_thickness=stiffness.membrane_thickness,
+ )
+ m = config.longitudinal_modes[0] if config.longitudinal_modes else 1
+ n = config.transverse_modes[0] if config.transverse_modes else 1
+ kx, ky, linear, _, nonlinear = _mode_linear_terms(panel, stiffness, config, m, n)
+ modes.append(
+ RitzMode(
+ family="global-pressure",
+ m=m,
+ n=n,
+ kx=kx,
+ ky=ky,
+ linear_stiffness=linear,
+ geometric_stiffness=0.0,
+ pressure_force=_pressure_generalized_force(
+ panel,
+ m,
+ n,
+ config.pressure_global_share,
+ ),
+ imperfection=0.0,
+ nonlinear_stiffness=max(nonlinear, EPS),
+ )
+ )
+ return modes
+
+
+def _isotropic_plate_stiffness(panel: U3PanelInput | S3PanelInput) -> OrthotropicStiffness:
+ d = _plate_bending_rigidity(panel)
+ return OrthotropicStiffness(
+ d11=d,
+ d12=panel.poisson_ratio * d,
+ d22=d,
+ d66=0.5 * (1.0 - panel.poisson_ratio) * d,
+ membrane_thickness=panel.plate_thickness,
+ )
+
+
+def build_u3_ritz_modes(panel: U3PanelInput, config: S3SolverConfig) -> list[RitzMode]:
+ """Return the plate-only Rayleigh-Ritz modes used by the U3 solver."""
+
+ modes: list[RitzMode] = []
+ imperfection = max(
+ config.initial_imperfection_floor_mm,
+ config.initial_imperfection_ratio * min(panel.length, panel.width),
+ )
+ stiffness = _isotropic_plate_stiffness(panel)
+ for m in config.longitudinal_modes:
+ for n in config.transverse_modes:
+ kx, ky, linear, geometric, nonlinear = _mode_linear_terms(
+ panel,
+ stiffness,
+ config,
+ m,
+ n,
+ )
+ modes.append(
+ RitzMode(
+ family="plate",
+ m=m,
+ n=n,
+ kx=kx,
+ ky=ky,
+ linear_stiffness=linear,
+ geometric_stiffness=geometric,
+ pressure_force=_pressure_generalized_force(panel, m, n, 1.0),
+ imperfection=imperfection,
+ nonlinear_stiffness=max(nonlinear, EPS),
+ )
+ )
+ return modes
+
+
+def ritz_combined_buckling_factor(
+ panel: S3PanelInput,
+ modes: Sequence[RitzMode],
+ family: str,
+) -> dict[str, Any] | None:
+ """Return a coupled linear Ritz factor for compression plus panel shear.
+
+ Normal compression is diagonal in the sine basis. Panel shear couples
+ opposite-parity Fourier terms and is the mechanism that lets a double
+ Fourier expansion describe inclined elastic shear shapes. This truncated
+ candidate makes that interaction explicit without replacing the classical
+ pure-shear plate diagnostic retained for a narrow reduced basis.
+ """
+
+ family_modes = [mode for mode in modes if mode.family == family]
+ if len(family_modes) < 2:
+ return None
+
+ linear = np.diag([mode.linear_stiffness for mode in family_modes])
+ geometric, coupling = _ritz_geometric_matrix(panel, family_modes)
+ if coupling["coupled_terms"] == 0:
+ return None
+
+ linear_diagonal = np.diag(linear)
+ inverse_sqrt_linear = np.diag(1.0 / np.sqrt(np.maximum(linear_diagonal, EPS)))
+ transformed = inverse_sqrt_linear @ geometric @ inverse_sqrt_linear
+ eigenvalues, eigenvectors = np.linalg.eigh(transformed)
+ positive_indices = [
+ index
+ for index, eigenvalue in enumerate(eigenvalues)
+ if eigenvalue > EPS and math.isfinite(float(eigenvalue))
+ ]
+ if not positive_indices:
+ return None
+
+ critical_index = max(positive_indices, key=lambda index: float(eigenvalues[index]))
+ eigenvalue = float(eigenvalues[critical_index])
+ transformed_vector = eigenvectors[:, critical_index]
+ physical_vector = inverse_sqrt_linear @ transformed_vector
+ modal_weights = np.abs(physical_vector)
+ weight_sum = float(np.sum(modal_weights))
+ composition = []
+ if weight_sum > EPS:
+ composition = [
+ {
+ "mode": mode.label,
+ "weight": float(weight / weight_sum),
+ }
+ for mode, weight in sorted(
+ zip(family_modes, modal_weights),
+ key=lambda item: float(item[1]),
+ reverse=True,
+ )
+ if weight > EPS
+ ]
+ return {
+ "factor": 1.0 / eigenvalue,
+ "eigenvalue": eigenvalue,
+ "family": family,
+ "signed_shear_resultant": coupling["signed_shear_resultant"],
+ "coupled_terms": coupling["coupled_terms"],
+ "mode_composition": composition[:4],
+ }
+
+
+def _ritz_geometric_matrix(
+ panel: S3PanelInput,
+ modes: Sequence[RitzMode],
+) -> tuple[np.ndarray, dict[str, Any]]:
+ """Return normal-plus-shear geometric stiffness for Ritz modes.
+
+ The local and distributed-global families share the trigonometric panel
+ coordinates but represent different reduced S3 displacement fields. Shear
+ coupling is therefore kept inside each family block.
+ """
+
+ geometric = np.diag([mode.geometric_stiffness for mode in modes])
+ signed_shear_resultant = panel.shear_stress * panel.plate_thickness
+ family_terms: dict[str, int] = {}
+ if abs(signed_shear_resultant) <= EPS or len(modes) < 2:
+ return geometric, {
+ "signed_shear_resultant": signed_shear_resultant,
+ "coupled_terms": 0,
+ "family_terms": family_terms,
+ }
+
+ coupled_terms = 0
+ for row, first in enumerate(modes):
+ for column in range(row + 1, len(modes)):
+ second = modes[column]
+ if first.family != second.family:
+ continue
+ coupling = (
+ signed_shear_resultant
+ * _mode_shear_geometric_integral(panel, first, second)
+ )
+ if abs(coupling) <= EPS:
+ continue
+ geometric[row, column] += coupling
+ geometric[column, row] += coupling
+ coupled_terms += 1
+ family_terms[first.family] = family_terms.get(first.family, 0) + 1
+ return geometric, {
+ "signed_shear_resultant": signed_shear_resultant,
+ "coupled_terms": coupled_terms,
+ "family_terms": family_terms,
+ }
+
+
+def _local_amplitude_ratio(panel: S3PanelInput, modes: Sequence[RitzMode], amplitudes: Sequence[float]) -> float:
+ local_amplitude = sum(abs(amplitude) for mode, amplitude in zip(modes, amplitudes) if mode.family == "local")
+ local_span = max(min(panel.length, panel.width), EPS)
+ return local_amplitude / local_span
+
+
+def local_global_stiffness_scale(
+ panel: S3PanelInput,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+) -> dict[str, float]:
+ """Return reduced global longitudinal stiffness from local response.
+
+ The Byklum model family uses local response to supply anisotropic stiffness
+ to a global orthotropic plate model. This reduced pass applies a scalar
+ longitudinal degradation driven by local-family elastic utilization so the
+ interaction is visible and configurable. Local amplitude is still
+ reported, but not used as a degradation driver because pressure deflection
+ in the reduced basis makes that scalar too noisy.
+ """
+
+ local_mode_factors = [
+ mode.linear_stiffness / mode.geometric_stiffness
+ for mode in modes
+ if mode.family == "local" and mode.geometric_stiffness > EPS
+ ]
+ local_elastic_utilization = (
+ load_factor / max(min(local_mode_factors), EPS)
+ if local_mode_factors
+ else 0.0
+ )
+ amplitude_ratio = _local_amplitude_ratio(panel, modes, amplitudes)
+ amplitude_utilization = amplitude_ratio / max(config.initial_imperfection_ratio, EPS)
+ interaction_driver = local_elastic_utilization
+ scale = 1.0 / (1.0 + config.local_global_coupling_gain * max(interaction_driver - 1.0, 0.0))
+ scale = min(1.0, max(config.local_global_coupling_floor, scale))
+ return {
+ "scale": scale,
+ "local_elastic_utilization": local_elastic_utilization,
+ "local_amplitude_ratio": amplitude_ratio,
+ "amplitude_utilization": amplitude_utilization,
+ "interaction_driver": interaction_driver,
+ }
+
+
+def _stiffener_column_factor(panel: S3PanelInput, section: S3SectionProperties) -> float | None:
+ compression = max(panel.axial_stress, 0.0)
+ if compression <= EPS:
+ return None
+ effective_length = panel.length * (0.70 if panel.stiffener_boundary == "Cont" else 1.0)
+ euler_force = math.pi**2 * panel.elastic_modulus * section.inertia_x / max(effective_length**2, EPS)
+ reference_force = compression * section.area
+ if reference_force <= EPS:
+ return None
+ return euler_force / reference_force
+
+
+def _plate_strip_shear_buckling(
+ panel: S3PanelInput | U3PanelInput,
+ length: float,
+ width: float,
+ thickness: float,
+ shear_stress: float,
+ capacity_factor: float = 1.0,
+) -> dict[str, float] | None:
+ """Return the elastic shear buckling factor for a plate strip.
+
+ Notes
+ -----
+ This uses the classical simply-supported plate shear buckling coefficient
+
+ k_tau = 5.34 + 4 / alpha^2
+
+ where alpha = long_side / short_side >= 1.
+
+ The classical elastic shear buckling stress is calculated directly as
+
+ tau_cr = k_tau * pi^2 * E / (12 * (1 - nu^2)) * (t / short_side)^2
+
+ The returned critical stress is then multiplied by the explicit
+ `capacity_factor`. S3 uses this as a visible reduced shear-interaction
+ control; U3 keeps the default factor of 1.0.
+ """
+
+ shear = abs(shear_stress)
+ if shear <= EPS:
+ return None
+
+ short_side = min(length, width)
+ long_side = max(length, width)
+ alpha = long_side / max(short_side, EPS)
+
+ shear_coefficient = 5.34 + 4.0 / max(alpha * alpha, EPS)
+
+ elastic_reference = (
+ math.pi**2
+ * panel.elastic_modulus
+ / (12.0 * (1.0 - panel.poisson_ratio**2))
+ * (thickness / max(short_side, EPS)) ** 2
+ )
+
+ applied_capacity_factor = max(float(capacity_factor), EPS)
+ classical_critical_stress = shear_coefficient * elastic_reference
+ critical_stress = applied_capacity_factor * classical_critical_stress
+
+ return {
+ "factor": critical_stress / shear,
+ "critical_stress": critical_stress,
+ "classical_critical_stress": classical_critical_stress,
+ "coefficient": shear_coefficient,
+ "capacity_factor": applied_capacity_factor,
+ "aspect_ratio": alpha,
+ "short_side": short_side,
+ "long_side": long_side,
+ }
+
+
+def _local_plate_shear_buckling(
+ panel: S3PanelInput,
+ config: S3SolverConfig,
+) -> dict[str, float] | None:
+ """Return the classical local plate shear factor for the unit bay.
+
+ Same-mode diagonal sine terms are not a sound representation of plate shear
+ buckling. The reduced solver therefore reports a separate simply
+ supported plate shear candidate while the nonlinear path keeps shear in
+ the yield stress state.
+ """
+
+ return _plate_strip_shear_buckling(
+ panel,
+ panel.length,
+ panel.width,
+ panel.plate_thickness,
+ panel.shear_stress,
+ config.s3_shear_buckling_capacity_factor,
+ )
+
+
+def _web_ritz_modes(
+ panel: S3PanelInput,
+ config: S3SolverConfig,
+ compression_stress: float,
+) -> list[RitzMode]:
+ """Return a bounded isotropic Ritz surface for open stiffener webs."""
+
+ bending_rigidity = panel.elastic_modulus * panel.web_thickness**3 / (
+ 12.0 * (1.0 - panel.poisson_ratio**2)
+ )
+ compression_resultant = max(compression_stress, 0.0) * panel.web_thickness
+ area_factor = panel.length * panel.stiffener_height / 4.0
+ modes: list[RitzMode] = []
+ for m in config.web_longitudinal_modes:
+ for n in config.web_depth_modes:
+ kx = m * math.pi / panel.length
+ ky = n * math.pi / panel.stiffener_height
+ wave_norm = kx * kx + ky * ky
+ modes.append(
+ RitzMode(
+ family="web",
+ m=m,
+ n=n,
+ kx=kx,
+ ky=ky,
+ linear_stiffness=area_factor * bending_rigidity * wave_norm * wave_norm,
+ geometric_stiffness=area_factor * compression_resultant * kx * kx,
+ pressure_force=0.0,
+ imperfection=0.0,
+ nonlinear_stiffness=EPS,
+ )
+ )
+ return modes
+
+
+def _ritz_factor_summary(
+ linear: np.ndarray,
+ geometric: np.ndarray,
+ modes: Sequence[RitzMode],
+ metadata: Mapping[str, Any],
+) -> dict[str, Any] | None:
+ """Return the positive critical factor for a Ritz geometric matrix."""
+
+ linear_diagonal = np.diag(linear)
+ inverse_sqrt_linear = np.diag(1.0 / np.sqrt(np.maximum(linear_diagonal, EPS)))
+ transformed = inverse_sqrt_linear @ geometric @ inverse_sqrt_linear
+ eigenvalues, eigenvectors = np.linalg.eigh(transformed)
+ positive_indices = [
+ index
+ for index, eigenvalue in enumerate(eigenvalues)
+ if eigenvalue > EPS and math.isfinite(float(eigenvalue))
+ ]
+ if not positive_indices:
+ return None
+
+ critical_index = max(positive_indices, key=lambda index: float(eigenvalues[index]))
+ eigenvalue = float(eigenvalues[critical_index])
+ physical_vector = inverse_sqrt_linear @ eigenvectors[:, critical_index]
+ modal_weights = np.abs(physical_vector)
+ weight_sum = float(np.sum(modal_weights))
+ composition = []
+ if weight_sum > EPS:
+ composition = [
+ {
+ "mode": mode.label,
+ "weight": float(weight / weight_sum),
+ }
+ for mode, weight in sorted(
+ zip(modes, modal_weights),
+ key=lambda item: float(item[1]),
+ reverse=True,
+ )
+ if weight > EPS
+ ]
+ return {
+ "factor": 1.0 / eigenvalue,
+ "eigenvalue": eigenvalue,
+ "mode_composition": composition[:4],
+ **metadata,
+ }
+
+
+def _web_ritz_buckling(
+ panel: S3PanelInput,
+ config: S3SolverConfig,
+ compression_demand: Mapping[str, Any],
+) -> dict[str, Any] | None:
+ """Return a web-surface Ritz factor for axial web compression and shear."""
+
+ modes = _web_ritz_modes(panel, config, float(compression_demand["stress"]))
+ if not modes:
+ return None
+
+ linear = np.diag([mode.linear_stiffness for mode in modes])
+ geometric = np.diag([mode.geometric_stiffness for mode in modes])
+ signed_shear_resultant = panel.shear_stress * panel.web_thickness
+ coupled_terms = 0
+ if abs(signed_shear_resultant) > EPS:
+ for row, first in enumerate(modes):
+ for column in range(row + 1, len(modes)):
+ second = modes[column]
+ coupling = (
+ signed_shear_resultant
+ * _rectangular_mode_shear_geometric_integral(
+ panel.length,
+ panel.stiffener_height,
+ first,
+ second,
+ )
+ )
+ if abs(coupling) <= EPS:
+ continue
+ geometric[row, column] += coupling
+ geometric[column, row] += coupling
+ coupled_terms += 1
+
+ return _ritz_factor_summary(
+ linear,
+ geometric,
+ modes,
+ {
+ "family": "web",
+ "signed_shear_resultant": signed_shear_resultant,
+ "coupled_terms": coupled_terms,
+ "mode_count": len(modes),
+ "longitudinal_modes": list(config.web_longitudinal_modes),
+ "depth_modes": list(config.web_depth_modes),
+ "surface": {
+ "length": panel.length,
+ "depth": panel.stiffener_height,
+ "thickness": panel.web_thickness,
+ },
+ "compression_demand": compression_demand,
+ },
+ )
+
+
+def _stiffener_web_reference_compression(
+ panel: S3PanelInput,
+ gross_section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ config: S3SolverConfig,
+) -> dict[str, Any]:
+ """Return reference web-edge compression for the local web check.
+
+ The panel axial stress is the base proportional compression driver. The
+ SI/PI yield branches keep the public sniped-stiffener axial eccentricity
+ moment. The reduced web branch carries that contribution as an explicit
+ sensitivity factor so the assumption remains visible in diagnostics and
+ can be disabled for comparison without changing the SI/PI yield path.
+ """
+
+ effective_axial = _stiffener_effective_axial_stress(
+ panel,
+ gross_section,
+ stiffener_section,
+ 1.0,
+ )
+ axial_compression = max(float(effective_axial["stress"]), 0.0)
+ sniped = _sniped_stiffener_eccentricity_moments(
+ panel,
+ gross_section,
+ stiffener_section,
+ 1.0,
+ config,
+ )
+ web_coordinates = {
+ "root": 0.5 * panel.plate_thickness,
+ "tip": 0.5 * panel.plate_thickness + panel.stiffener_height,
+ }
+ raw_absolute_moment = abs(float(sniped["absolute"]))
+ web_local_sniped_factor = max(float(config.web_local_sniped_eccentricity_factor), 0.0)
+ absolute_moment = raw_absolute_moment * web_local_sniped_factor
+ edge_bending = {
+ edge: absolute_moment
+ * abs(coordinate - stiffener_section.centroid_from_plate_midplane)
+ / max(stiffener_section.inertia_x, EPS)
+ for edge, coordinate in web_coordinates.items()
+ }
+ edge_compression = {
+ edge: axial_compression + bending
+ for edge, bending in edge_bending.items()
+ }
+ controlling_edge = max(edge_compression, key=edge_compression.get)
+ return {
+ "stress": edge_compression[controlling_edge],
+ "source": "stiffener-section-axial-plus-configured-sniped-web-edge-envelope",
+ "controlling_edge": controlling_edge,
+ "edge_compression": edge_compression,
+ "edge_bending_stress": edge_bending,
+ "effective_axial_stress": effective_axial,
+ "sniped_eccentricity_moment": sniped,
+ "web_local_sniped_eccentricity_factor": web_local_sniped_factor,
+ "applied_sniped_eccentricity_moment": absolute_moment,
+ }
+
+
+def _stiffener_web_local_buckling(
+ panel: S3PanelInput,
+ gross_section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ config: S3SolverConfig,
+) -> dict[str, Any] | None:
+ """Return an open-profile web compression-shear candidate.
+
+ The web is treated as a long simply supported plate strip under a reference
+ web-edge compression envelope and panel shear. The candidate keeps the
+ full PULS web/stiffener stress redistribution out of scope, but the explicit
+ interaction avoids treating tall loaded webs as compression-only strips.
+ """
+
+ compression_demand = _stiffener_web_reference_compression(
+ panel,
+ gross_section,
+ stiffener_section,
+ config,
+ )
+ compression = float(compression_demand["stress"])
+ compression_factor = None
+ compression_critical_stress = None
+ compression_coefficient = 4.0
+ if compression > EPS:
+ elastic_reference = (
+ math.pi**2
+ * panel.elastic_modulus
+ / (12.0 * (1.0 - panel.poisson_ratio**2))
+ * (panel.web_thickness / panel.stiffener_height) ** 2
+ )
+ compression_critical_stress = compression_coefficient * elastic_reference
+ compression_factor = compression_critical_stress / compression
+
+ shear = _plate_strip_shear_buckling(
+ panel,
+ panel.length,
+ panel.stiffener_height,
+ panel.web_thickness,
+ panel.shear_stress,
+ config.s3_shear_buckling_capacity_factor,
+ )
+ web_ritz = _web_ritz_buckling(panel, config, compression_demand)
+ factor_rows = [
+ factor
+ for factor in (
+ compression_factor,
+ None if shear is None else shear["factor"],
+ )
+ if factor is not None and factor > EPS and math.isfinite(factor)
+ ]
+ if not factor_rows:
+ return None
+
+ exponent = max(config.web_shear_interaction_exponent, EPS)
+ interaction_usage = sum((1.0 / factor) ** exponent for factor in factor_rows) ** (1.0 / exponent)
+ approximation_notes = [
+ "web modeled as an isolated simply supported compression-shear strip",
+ "S3 web buckling load shedding with plate, flange, and torsional displacement fields is not reproduced",
+ ]
+ return {
+ "factor": 1.0 / max(interaction_usage, EPS),
+ "critical_stress": compression_critical_stress,
+ "coefficient": compression_coefficient,
+ "compression_demand": compression_demand,
+ "compression_factor": compression_factor,
+ "shear_factor": None if shear is None else shear["factor"],
+ "shear_critical_stress": None if shear is None else shear["critical_stress"],
+ "shear_coefficient": None if shear is None else shear["coefficient"],
+ "interaction_exponent": exponent,
+ "factor_source": "strip-compression-shear-interaction",
+ "web_ritz": web_ritz,
+ "coverage": "reduced-strip-approximation",
+ "approximation_notes": approximation_notes,
+ }
+
+
+def _local_plate_web_interaction(
+ plate_shear: Mapping[str, Any] | None,
+ web_local: Mapping[str, Any] | None,
+ config: S3SolverConfig,
+) -> dict[str, Any] | None:
+ """Return a reduced mixed local interaction for plate and web response.
+
+ The public S3 description treats panel failure as a mixed local stiffened
+ response with load shedding between plating and primary stiffeners. The
+ first reduced solver still has separate plate-shear and web-local
+ candidates, so this explicit interaction candidate keeps those concurrent
+ local shear usages visible while the full coupled displacement family is
+ not yet implemented.
+ """
+
+ if plate_shear is None or web_local is None:
+ return None
+ plate_factor = _optional_float(plate_shear.get("factor"))
+ web_factor = _optional_float(web_local.get("factor"))
+ if (
+ plate_factor is None
+ or web_factor is None
+ or plate_factor <= EPS
+ or web_factor <= EPS
+ ):
+ return None
+
+ exponent = max(config.local_plate_web_interaction_exponent, EPS)
+ plate_usage = 1.0 / plate_factor
+ web_usage = 1.0 / web_factor
+ interaction_usage = (
+ plate_usage**exponent + web_usage**exponent
+ ) ** (1.0 / exponent)
+ return {
+ "factor": 1.0 / max(interaction_usage, EPS),
+ "interaction_usage": interaction_usage,
+ "interaction_exponent": exponent,
+ "plate_shear_factor": plate_factor,
+ "plate_shear_usage": plate_usage,
+ "web_local_factor": web_factor,
+ "web_local_usage": web_usage,
+ "factor_source": "plate-shear-web-local-usage-interaction",
+ "coverage": "reduced-local-interaction",
+ "approximation_notes": [
+ "mixed local interaction keeps separate plate-shear and web-local reduced candidates",
+ "full S3 plate, web, flange, and torsional local displacement coupling is not reproduced",
+ ],
+ }
+
+
+def _stiffener_torsional_buckling(
+ panel: S3PanelInput,
+ config: S3SolverConfig | None = None,
+) -> dict[str, float] | None:
+ """Return a reduced open-profile tripping/torsional stress candidate.
+
+ DNV-CG-0128 uses a torsional reference stress based on St. Venant torsion,
+ polar inertia, sectorial inertia, the stiffener span, and attachment
+ restraint. The reduced S3 solver uses the same ingredients in a
+ gross-section estimate about the web root with an explicit restraint
+ factor. It is exposed as an approximate candidate in diagnostics.
+ """
+
+ config = config or S3SolverConfig()
+ compression = max(panel.axial_stress, 0.0)
+ if compression <= EPS:
+ return None
+
+ web_height = panel.stiffener_height
+ web_thickness = panel.web_thickness
+ flange_width = 0.0 if panel.stiffener_type == "Flatbar" else max(panel.flange_width, 0.0)
+ flange_thickness = 0.0 if panel.stiffener_type == "Flatbar" else max(panel.flange_thickness, 0.0)
+ web_area = web_height * web_thickness
+ flange_area = flange_width * flange_thickness
+ shear_modulus = panel.elastic_modulus / (2.0 * (1.0 + panel.poisson_ratio))
+
+ torsion_constant = web_height * web_thickness**3 / 3.0
+ if flange_area > 0.0:
+ torsion_constant += flange_width * flange_thickness**3 / 3.0
+
+ polar_inertia = web_area * web_height**2 / 3.0
+ flange_offset = web_height + 0.5 * flange_thickness
+ if flange_area > 0.0:
+ polar_inertia += flange_area * (flange_offset**2 + flange_width**2 / 12.0)
+ polar_inertia = max(polar_inertia, EPS)
+
+ sectorial_inertia = 0.0
+ if flange_area > 0.0:
+ sectorial_inertia = flange_area * flange_width**2 * flange_offset**2 / 12.0
+
+ effective_length = panel.length * (0.70 if panel.stiffener_boundary == "Cont" else 1.0)
+ wave_number = math.pi / max(effective_length, EPS)
+ critical_stress = config.torsional_restraint_factor * (
+ shear_modulus * torsion_constant
+ + panel.elastic_modulus * sectorial_inertia * wave_number**2
+ ) / polar_inertia
+ return {
+ "factor": critical_stress / compression,
+ "critical_stress": critical_stress,
+ "torsion_constant": torsion_constant,
+ "polar_inertia": polar_inertia,
+ "sectorial_inertia": sectorial_inertia,
+ "effective_length": effective_length,
+ "half_waves": 1.0,
+ "restraint_factor": config.torsional_restraint_factor,
+ }
+
+
+def _global_stiffened_strip_capacity_adjustment(
+ panel: S3PanelInput,
+ section: S3SectionProperties,
+ raw_factor: float,
+ local_reference_factor: float | None,
+ config: S3SolverConfig,
+) -> dict[str, Any]:
+ """Return the reduced global-strip capacity adjustment and diagnostics."""
+
+ plate_reference_inertia = panel.width * panel.plate_thickness**3 / 12.0
+ section_inertia_ratio = section.inertia_x / max(plate_reference_inertia, EPS)
+ section_area_ratio = section.area / max(panel.width * panel.plate_thickness, EPS)
+ aspect_ratio = panel.length / max(panel.width, EPS)
+ plate_slenderness = panel.width / max(panel.plate_thickness, EPS)
+ local_interaction_ratio = (
+ raw_factor / max(local_reference_factor, EPS)
+ if local_reference_factor is not None and local_reference_factor > EPS
+ else None
+ )
+ load_family = _load_family(panel)
+
+ if config.global_stiffened_strip_capacity_factor is not None:
+ fixed_factor = max(float(config.global_stiffened_strip_capacity_factor), EPS)
+ return {
+ "raw_factor": raw_factor,
+ "local_reference_factor": local_reference_factor,
+ "local_interaction_ratio": local_interaction_ratio,
+ "capacity_factor": fixed_factor,
+ "mode": "fixed-override",
+ "section": {
+ "inertia_ratio": section_inertia_ratio,
+ "area_ratio": section_area_ratio,
+ "plate_slenderness": plate_slenderness,
+ "aspect_ratio": aspect_ratio,
+ },
+ "modifiers": {
+ "base": fixed_factor,
+ "support": 1.0,
+ "pressure": 1.0,
+ "load": 1.0,
+ "aspect": 1.0,
+ "slenderness": 1.0,
+ "section": 1.0,
+ "local_interaction": 1.0,
+ },
+ "notes": ["fixed global-stiffened-strip capacity override"],
+ }
+
+ notes = ["computed reduced global-stiffened-strip capacity adjustment"]
+ base = 0.55
+ support_modifier = {
+ "Integrated": 0.88,
+ "Girder - long": 0.95,
+ "Girder - trans": 0.85,
+ }.get(panel.in_plane_support, 1.0)
+ pressure_modifier = 0.92 if panel.pressure > EPS else 1.0
+
+ load_modifier = 1.0
+ if "shear" in load_family:
+ load_modifier *= 0.95
+ if "axial-tension" in load_family and "transverse-compression" in load_family:
+ load_modifier *= 0.95
+ if "pressure" in load_family and "shear" not in load_family:
+ load_modifier *= 0.95
+
+ if aspect_ratio >= 6.0:
+ aspect_modifier = 0.90
+ elif aspect_ratio >= 3.0:
+ aspect_modifier = 0.96
+ else:
+ aspect_modifier = 1.05
+
+ if plate_slenderness >= 80.0:
+ slenderness_modifier = 1.15
+ notes.append("very slender plate: global-strip reduction relaxed")
+ elif plate_slenderness >= 50.0:
+ slenderness_modifier = 1.00
+ else:
+ slenderness_modifier = 0.95
+
+ if section_inertia_ratio >= 1.0e5:
+ section_modifier = 0.95
+ elif section_inertia_ratio >= 1.0e4:
+ section_modifier = 0.98
+ else:
+ section_modifier = 0.92
+
+ if local_interaction_ratio is None:
+ local_modifier = 1.0
+ elif local_interaction_ratio >= 4.0:
+ local_modifier = 0.90
+ elif local_interaction_ratio >= 2.0:
+ local_modifier = 0.95
+ else:
+ local_modifier = 1.0
+
+ capacity_factor = (
+ base
+ * support_modifier
+ * pressure_modifier
+ * load_modifier
+ * aspect_modifier
+ * slenderness_modifier
+ * section_modifier
+ * local_modifier
+ )
+ capacity_factor = min(0.95, max(0.30, capacity_factor))
+ return {
+ "raw_factor": raw_factor,
+ "local_reference_factor": local_reference_factor,
+ "local_interaction_ratio": local_interaction_ratio,
+ "capacity_factor": capacity_factor,
+ "mode": "computed",
+ "section": {
+ "inertia_ratio": section_inertia_ratio,
+ "area_ratio": section_area_ratio,
+ "plate_slenderness": plate_slenderness,
+ "aspect_ratio": aspect_ratio,
+ },
+ "modifiers": {
+ "base": base,
+ "support": support_modifier,
+ "pressure": pressure_modifier,
+ "load": load_modifier,
+ "aspect": aspect_modifier,
+ "slenderness": slenderness_modifier,
+ "section": section_modifier,
+ "local_interaction": local_modifier,
+ },
+ "notes": notes,
+ }
+
+
+def elastic_buckling_factors(
+ panel: S3PanelInput,
+ section: S3SectionProperties,
+ modes: Sequence[RitzMode],
+ config: S3SolverConfig | None = None,
+ stiffener_section: S3SectionProperties | None = None,
+) -> dict[str, Any]:
+ config = config or S3SolverConfig()
+ stiffener_section = stiffener_section or section
+ factor_rows = []
+ for mode in modes:
+ if mode.geometric_stiffness <= EPS:
+ continue
+ raw_factor = mode.linear_stiffness / mode.geometric_stiffness
+ if mode.family == "global":
+ capacity_factor = 1.0
+ factor = raw_factor
+ failure_family = "global-stiffened-strip"
+ else:
+ capacity_factor = 1.0
+ factor = raw_factor
+ failure_family = "plate"
+ factor_rows.append(
+ {
+ "factor": factor,
+ "raw_factor": raw_factor,
+ "capacity_factor": capacity_factor,
+ "label": mode.label,
+ "family": mode.family,
+ "failure_family": failure_family,
+ }
+ )
+ column_factor = _stiffener_column_factor(panel, stiffener_section)
+ if column_factor is not None:
+ factor_rows.append(
+ {
+ "factor": column_factor,
+ "label": "stiffener-column",
+ "family": "stiffener-column",
+ "failure_family": "global-stiffener-cutoff",
+ }
+ )
+ shear_factor = _local_plate_shear_buckling(panel, config)
+ if shear_factor is not None:
+ factor_rows.append(
+ {
+ "factor": shear_factor["factor"],
+ "label": "local-plate-shear",
+ "family": "local-shear",
+ "failure_family": "plate-shear",
+ }
+ )
+ coupled_shear_factors = {
+ family: ritz_combined_buckling_factor(panel, modes, family)
+ for family in ("local", "global")
+ }
+ for family, factor in coupled_shear_factors.items():
+ if factor is None:
+ continue
+ factor_rows.append(
+ {
+ "factor": factor["factor"],
+ "raw_factor": factor["factor"],
+ "capacity_factor": 1.0,
+ "label": f"{family}-ritz-combined-shear",
+ "family": family,
+ "failure_family": "plate-shear" if family == "local" else "global-stiffened-strip",
+ }
+ )
+ web_factor = _stiffener_web_local_buckling(panel, section, stiffener_section, config)
+ if web_factor is not None:
+ factor_rows.append(
+ {
+ "factor": web_factor["factor"],
+ "label": "stiffener-web-local",
+ "family": "stiffener-web",
+ "failure_family": "web-local",
+ }
+ )
+ local_interaction = _local_plate_web_interaction(shear_factor, web_factor, config)
+ if local_interaction is not None:
+ factor_rows.append(
+ {
+ "factor": local_interaction["factor"],
+ "label": "local-plate-web-interaction",
+ "family": "local-interaction",
+ "failure_family": "plate-web-local-interaction",
+ }
+ )
+ torsional_factor = _stiffener_torsional_buckling(panel, config)
+ if torsional_factor is not None:
+ factor_rows.append(
+ {
+ "factor": torsional_factor["factor"],
+ "label": "stiffener-torsional",
+ "family": "stiffener-torsional",
+ "failure_family": "torsional-stiffener",
+ }
+ )
+ factor_rows = [
+ row
+ for row in factor_rows
+ if row["factor"] > EPS and math.isfinite(row["factor"])
+ ]
+ local_plate_rows = [
+ row
+ for row in factor_rows
+ if row["failure_family"] in {"plate", "plate-shear"}
+ ]
+ local_reference_factor = (
+ min(float(row["factor"]) for row in local_plate_rows)
+ if local_plate_rows
+ else None
+ )
+ elastic_global_coupling_rows: list[dict[str, float | str]] = []
+ if local_reference_factor is not None:
+ for row in factor_rows:
+ if row["failure_family"] != "global-stiffened-strip":
+ continue
+ raw_factor = float(row.get("raw_factor", row["factor"]))
+ adjustment = _global_stiffened_strip_capacity_adjustment(
+ panel,
+ section,
+ raw_factor,
+ local_reference_factor,
+ config,
+ )
+ capacity_factor = float(adjustment["capacity_factor"])
+ uncoupled_factor = raw_factor * capacity_factor
+ interaction_driver = uncoupled_factor / max(local_reference_factor, EPS)
+ scale = 1.0 / (
+ 1.0
+ + config.local_global_coupling_gain * max(interaction_driver - 1.0, 0.0)
+ )
+ scale = min(1.0, max(config.local_global_coupling_floor, scale))
+ row["raw_factor"] = raw_factor
+ row["capacity_factor"] = capacity_factor
+ row["global_capacity_adjustment"] = adjustment
+ row["uncoupled_factor"] = uncoupled_factor
+ row["elastic_coupling_scale"] = scale
+ row["factor"] = uncoupled_factor * scale
+ elastic_global_coupling_rows.append(
+ {
+ "mode": str(row["label"]),
+ "raw_factor": raw_factor,
+ "capacity_factor": capacity_factor,
+ "capacity_adjustment": adjustment,
+ "uncoupled_factor": uncoupled_factor,
+ "coupled_factor": float(row["factor"]),
+ "scale": scale,
+ "interaction_driver": interaction_driver,
+ }
+ )
+ if not factor_rows:
+ return {
+ "critical_factor": None,
+ "critical_mode": "no-compressive-or-shear-buckling-driver",
+ "critical_failure_family": "none",
+ "stiffener_column_factor": column_factor,
+ "local_plate_shear": shear_factor,
+ "ritz_combined_shear": coupled_shear_factors,
+ "stiffener_web_local": web_factor,
+ "local_plate_web_interaction": local_interaction,
+ "stiffener_torsional": torsional_factor,
+ "elastic_global_coupling": {
+ "local_reference_factor": local_reference_factor,
+ "global_modes": elastic_global_coupling_rows,
+ },
+ "modeled_failure_families": {},
+ "approximate_failure_families": [
+ "torsional-stiffener",
+ "web-local",
+ "plate-web-local-interaction",
+ ],
+ "unmodeled_failure_families": ["stiffener-local-global-coupling"],
+ }
+
+ family_minima: dict[str, dict[str, Any]] = {}
+ for row in factor_rows:
+ failure_family = str(row["failure_family"])
+ current = family_minima.get(failure_family)
+ if current is None or row["factor"] < current["factor"]:
+ family_minima[failure_family] = dict(row)
+
+ family_usages = {
+ name: 1.0 / max(float(row["factor"]), EPS)
+ for name, row in family_minima.items()
+ }
+ usage_total = sum(family_usages.values())
+ modeled_failure_families = {}
+ for name, row in sorted(family_minima.items()):
+ family_summary = {
+ "critical_factor": row["factor"],
+ "critical_mode": row["label"],
+ "usage_share_percent": 100.0 * family_usages[name] / max(usage_total, EPS),
+ }
+ if "uncoupled_factor" in row:
+ family_summary["uncoupled_factor"] = row["uncoupled_factor"]
+ family_summary["elastic_coupling_scale"] = row["elastic_coupling_scale"]
+ if "global_capacity_adjustment" in row:
+ family_summary["raw_factor"] = row["raw_factor"]
+ family_summary["capacity_factor"] = row["capacity_factor"]
+ family_summary["global_capacity_adjustment"] = row["global_capacity_adjustment"]
+ modeled_failure_families[name] = family_summary
+ critical = min(factor_rows, key=lambda item: item["factor"])
+ return {
+ "critical_factor": critical["factor"],
+ "critical_mode": critical["label"],
+ "critical_failure_family": critical["failure_family"],
+ "stiffener_column_factor": column_factor,
+ "local_plate_shear": shear_factor,
+ "ritz_combined_shear": coupled_shear_factors,
+ "stiffener_web_local": web_factor,
+ "local_plate_web_interaction": local_interaction,
+ "stiffener_torsional": torsional_factor,
+ "elastic_global_coupling": {
+ "local_reference_factor": local_reference_factor,
+ "global_modes": elastic_global_coupling_rows,
+ },
+ "modeled_failure_families": modeled_failure_families,
+ "approximate_failure_families": [
+ "torsional-stiffener",
+ "web-local",
+ "plate-web-local-interaction",
+ ],
+ "unmodeled_failure_families": ["stiffener-local-global-coupling"],
+ }
+
+
+def elastic_u3_buckling_factors(
+ panel: U3PanelInput,
+ modes: Sequence[RitzMode],
+ config: S3SolverConfig | None = None,
+) -> dict[str, Any]:
+ """Return elastic buckling candidates for the U3 unstiffened plate."""
+
+ config = config or S3SolverConfig()
+ factor_rows = [
+ {
+ "factor": mode.linear_stiffness / mode.geometric_stiffness,
+ "label": mode.label,
+ "family": mode.family,
+ "failure_family": "plate",
+ }
+ for mode in modes
+ if mode.geometric_stiffness > EPS
+ ]
+ shear_factor = _plate_strip_shear_buckling(
+ panel,
+ panel.length,
+ panel.width,
+ panel.plate_thickness,
+ panel.shear_stress,
+ )
+ if shear_factor is not None:
+ factor_rows.append(
+ {
+ "factor": shear_factor["factor"],
+ "label": "plate-shear",
+ "family": "plate-shear",
+ "failure_family": "plate-shear",
+ }
+ )
+ coupled_shear = {"plate": ritz_combined_buckling_factor(panel, modes, "plate")}
+ if coupled_shear["plate"] is not None:
+ factor_rows.append(
+ {
+ "factor": coupled_shear["plate"]["factor"],
+ "label": "plate-ritz-combined-shear",
+ "family": "plate",
+ "failure_family": "plate-shear",
+ }
+ )
+ factor_rows = [
+ row
+ for row in factor_rows
+ if row["factor"] > EPS and math.isfinite(row["factor"])
+ ]
+ if not factor_rows:
+ return {
+ "critical_factor": None,
+ "critical_mode": "no-compressive-or-shear-buckling-driver",
+ "critical_failure_family": "none",
+ "local_plate_shear": shear_factor,
+ "ritz_combined_shear": coupled_shear,
+ "modeled_failure_families": {},
+ "approximate_failure_families": [],
+ "unmodeled_failure_families": ["production-U3-elasto-plastic-postbuckling-calibration"],
+ }
+
+ family_minima: dict[str, dict[str, Any]] = {}
+ for row in factor_rows:
+ failure_family = str(row["failure_family"])
+ current = family_minima.get(failure_family)
+ if current is None or row["factor"] < current["factor"]:
+ family_minima[failure_family] = dict(row)
+
+ family_usages = {
+ name: 1.0 / max(float(row["factor"]), EPS)
+ for name, row in family_minima.items()
+ }
+ usage_total = sum(family_usages.values())
+ modeled_failure_families = {
+ name: {
+ "critical_factor": row["factor"],
+ "critical_mode": row["label"],
+ "usage_share_percent": 100.0 * family_usages[name] / max(usage_total, EPS),
+ }
+ for name, row in sorted(family_minima.items())
+ }
+ critical = min(factor_rows, key=lambda item: item["factor"])
+ return {
+ "critical_factor": critical["factor"],
+ "critical_mode": critical["label"],
+ "critical_failure_family": critical["failure_family"],
+ "local_plate_shear": shear_factor,
+ "ritz_combined_shear": coupled_shear,
+ "modeled_failure_families": modeled_failure_families,
+ "approximate_failure_families": [],
+ "unmodeled_failure_families": ["production-U3-elasto-plastic-postbuckling-calibration"],
+ }
+
+
+def _limit_newton_delta(
+ modes: Sequence[RitzMode],
+ amplitudes: np.ndarray,
+ delta: np.ndarray,
+) -> np.ndarray:
+ limited = delta.copy()
+ for index, mode in enumerate(modes):
+ max_delta = max(
+ 1.0,
+ 0.5 * abs(float(amplitudes[index])),
+ 0.10 / max(mode.kx, mode.ky, EPS),
+ )
+ if abs(float(limited[index])) > max_delta:
+ limited[index] = math.copysign(max_delta, float(limited[index]))
+ return limited
+
+
+def solve_equilibrium_amplitudes(
+ panel: S3PanelInput,
+ modes: Sequence[RitzMode],
+ load_factor: float,
+ previous_amplitudes: Sequence[float],
+ config: S3SolverConfig,
+) -> tuple[list[float], bool, int]:
+ """Solve the coupled reduced Ritz continuation equilibrium.
+
+ Normal resultants keep diagonal geometric terms in this basis. Panel shear
+ contributes off-diagonal geometric coupling between opposite-parity modes,
+ so the load-path residual and Newton tangent are assembled as vectors and
+ matrices instead of solving each amplitude independently.
+ """
+
+ if not modes:
+ return [], True, 0
+
+ q = np.asarray(previous_amplitudes, dtype=float)
+ if q.shape != (len(modes),):
+ q = np.zeros(len(modes), dtype=float)
+
+ linear = np.diag([mode.linear_stiffness for mode in modes])
+ geometric, _ = _ritz_geometric_matrix(panel, modes)
+ nonlinear = np.asarray([mode.nonlinear_stiffness for mode in modes], dtype=float)
+ pressure = np.asarray([mode.pressure_force for mode in modes], dtype=float)
+ imperfection = np.asarray([mode.imperfection for mode in modes], dtype=float)
+ force = pressure + load_factor * (geometric @ imperfection)
+ tangent_linear = linear - load_factor * geometric
+ if np.max(np.abs(force)) <= EPS and np.max(np.abs(q)) <= EPS:
+ return [0.0 for _ in modes], True, 0
+
+ for iteration in range(1, config.newton_max_iterations + 1):
+ nonlinear_response = nonlinear * q**3
+ residual = tangent_linear @ q + nonlinear_response - force
+ scale = max(
+ float(np.max(np.abs(force))),
+ float(np.max(np.abs(tangent_linear @ q))),
+ float(np.max(np.abs(nonlinear_response))),
+ 1.0,
+ )
+ if float(np.max(np.abs(residual))) <= config.newton_tolerance * scale:
+ return q.tolist(), True, iteration
+
+ tangent = tangent_linear + np.diag(3.0 * nonlinear * q * q)
+ try:
+ delta = np.linalg.solve(tangent, -residual)
+ except np.linalg.LinAlgError:
+ delta = np.linalg.lstsq(tangent, -residual, rcond=None)[0]
+ delta = _limit_newton_delta(modes, q, delta)
+ q += delta
+ if not np.all(np.isfinite(q)):
+ return list(previous_amplitudes), False, iteration
+
+ return q.tolist(), False, config.newton_max_iterations
+
+
+def _mode_amplitude_summary(
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+) -> dict[str, Any]:
+ family_maxima: dict[str, dict[str, float | str]] = {}
+ max_amplitude = 0.0
+ for mode, amplitude in zip(modes, amplitudes):
+ magnitude = abs(amplitude)
+ max_amplitude = max(max_amplitude, magnitude)
+ current = family_maxima.get(mode.family)
+ if current is None or magnitude > float(current["amplitude"]):
+ family_maxima[mode.family] = {
+ "mode": mode.label,
+ "amplitude": magnitude,
+ "signed_amplitude": amplitude,
+ }
+ return {
+ "max_amplitude": max_amplitude,
+ "families": family_maxima,
+ }
+
+
+def _stress_von_mises(sigma_x: float, sigma_y: float, tau_xy: float) -> float:
+ return math.sqrt(max(sigma_x**2 - sigma_x * sigma_y + sigma_y**2 + 3.0 * tau_xy**2, 0.0))
+
+
+def _mode_curvatures(
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ x: float,
+ y: float,
+ family: str | None = None,
+) -> tuple[float, float, float]:
+ d2x = 0.0
+ d2y = 0.0
+ dxy = 0.0
+ for mode, amplitude in zip(modes, amplitudes):
+ if family is not None and mode.family != family:
+ continue
+ sin_x = math.sin(mode.kx * x)
+ sin_y = math.sin(mode.ky * y)
+ cos_x = math.cos(mode.kx * x)
+ cos_y = math.cos(mode.ky * y)
+ d2x -= amplitude * mode.kx * mode.kx * sin_x * sin_y
+ d2y -= amplitude * mode.ky * mode.ky * sin_x * sin_y
+ dxy += amplitude * mode.kx * mode.ky * cos_x * cos_y
+ return d2x, d2y, dxy
+
+
+def _pressure_stiffener_bending_moment(panel: S3PanelInput) -> float:
+ if panel.pressure <= 0.0:
+ return 0.0
+ span_factor = 12.0 if panel.stiffener_boundary == "Cont" else 8.0
+ return panel.pressure * panel.width * panel.length**2 / span_factor
+
+
+def _sniped_stiffener_eccentricity_moments(
+ panel: S3PanelInput,
+ gross_section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ load_factor: float,
+ config: S3SolverConfig,
+) -> dict[str, float]:
+ """Return branch-signed axial eccentricity moments for sniped stiffeners."""
+
+ compression = max(load_factor * panel.axial_stress, 0.0)
+ if panel.stiffener_boundary != "Sniped" or compression <= EPS:
+ return {
+ "absolute": 0.0,
+ "stiffener_induced": 0.0,
+ "plate_induced": 0.0,
+ }
+ moment = (
+ config.sniped_eccentricity_factor
+ * abs(stiffener_section.centroid_from_plate_midplane)
+ * compression
+ * gross_section.area
+ )
+ return {
+ "absolute": moment,
+ "stiffener_induced": -moment,
+ "plate_induced": moment,
+ }
+
+
+def _stiffener_torsional_edge_distance(
+ panel: S3PanelInput,
+ stiffener_section: S3SectionProperties,
+) -> float:
+ """Return a reduced free-edge distance for SI torsional deformation."""
+
+ if panel.stiffener_type == "Flatbar":
+ return 0.5 * panel.web_thickness
+ if panel.stiffener_type == "T-bar":
+ return 0.5 * panel.flange_width
+
+ stiffener_area = max(stiffener_section.stiffener_area, EPS)
+ edge_distance = panel.flange_width - (
+ panel.stiffener_height * panel.web_thickness**2
+ + panel.flange_thickness * panel.flange_width**2
+ ) / (
+ 2.0 * stiffener_area
+ )
+ return max(edge_distance, EPS)
+
+
+def _stiffener_torsional_deformation_stress(
+ panel: S3PanelInput,
+ stiffener_section: S3SectionProperties,
+ effective_axial_stress: float,
+ config: S3SolverConfig,
+) -> dict[str, float]:
+ """Return the SI-only torsional deformation stress used in stiffener yield.
+
+ Public stiffener interaction rules add a stress amplification term for
+ stiffener-induced failure when the axial stress approaches the torsional
+ reference stress. This reduced pass uses the existing gross-section
+ torsional reference stress and one half-wave along the supported span.
+ """
+
+ axial_compression = max(effective_axial_stress, 0.0)
+ torsional = _stiffener_torsional_buckling(panel, config)
+ if axial_compression <= EPS or torsional is None:
+ return {
+ "stress": 0.0,
+ "edge_distance": 0.0,
+ "imperfection_rotation": 0.0,
+ "reference_stress": 0.0,
+ "stress_ratio": 0.0,
+ }
+
+ reference_stress = torsional["critical_stress"]
+ stress_ratio = min(axial_compression / max(reference_stress, EPS), 1.0 - 1.0e-6)
+ edge_distance = _stiffener_torsional_edge_distance(panel, stiffener_section)
+ half_waves = torsional["half_waves"]
+ effective_length = torsional["effective_length"]
+ imperfection_rotation = (
+ config.torsional_imperfection_scale
+ * effective_length
+ / max(half_waves * panel.stiffener_height, EPS)
+ * 1.0e-4
+ )
+ wave_number = half_waves * math.pi / max(effective_length, EPS)
+ stress = (
+ panel.elastic_modulus
+ * edge_distance
+ * imperfection_rotation
+ * wave_number**2
+ * (1.0 / max(1.0 - stress_ratio, EPS) - 1.0)
+ )
+ return {
+ "stress": stress,
+ "edge_distance": edge_distance,
+ "imperfection_rotation": imperfection_rotation,
+ "reference_stress": reference_stress,
+ "stress_ratio": stress_ratio,
+ }
+
+
+def _plate_yield_ratio(
+ panel: S3PanelInput,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+) -> float:
+ modulus = panel.elastic_modulus / (1.0 - panel.poisson_ratio**2)
+ shear_modulus = panel.elastic_modulus / (2.0 * (1.0 + panel.poisson_ratio))
+ z_values = (-0.5 * panel.plate_thickness, 0.5 * panel.plate_thickness)
+ max_ratio = 0.0
+ curvature_family = None if config.include_global_curvature_in_plate_yield else "local"
+
+ for x_fraction in config.hot_spot_grid:
+ x = panel.length * x_fraction
+ for y_fraction in config.hot_spot_grid:
+ y = panel.width * y_fraction
+ d2x, d2y, dxy = _mode_curvatures(
+ modes,
+ amplitudes,
+ x,
+ y,
+ family=curvature_family,
+ )
+ transverse_stress = (
+ panel.transverse_stress_1
+ + (panel.transverse_stress_2 - panel.transverse_stress_1) * y_fraction
+ )
+ for z in z_values:
+ bending_x = -modulus * z * (d2x + panel.poisson_ratio * d2y)
+ bending_y = -modulus * z * (d2y + panel.poisson_ratio * d2x)
+ bending_tau = -2.0 * shear_modulus * z * dxy
+ vm = _stress_von_mises(
+ load_factor * panel.axial_stress + bending_x,
+ load_factor * transverse_stress + bending_y,
+ load_factor * panel.shear_stress + bending_tau,
+ )
+ max_ratio = max(max_ratio, vm / panel.yield_stress_plate)
+ return max_ratio
+
+
+def _u3_plate_yield_ratio(
+ panel: U3PanelInput,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+) -> float:
+ modulus = panel.elastic_modulus / (1.0 - panel.poisson_ratio**2)
+ shear_modulus = panel.elastic_modulus / (2.0 * (1.0 + panel.poisson_ratio))
+ z_values = (-0.5 * panel.plate_thickness, 0.5 * panel.plate_thickness)
+ max_ratio = 0.0
+
+ for x_fraction in config.hot_spot_grid:
+ x = panel.length * x_fraction
+ axial_stress = (
+ panel.axial_stress_1
+ + (panel.axial_stress_2 - panel.axial_stress_1) * x_fraction
+ )
+ for y_fraction in config.hot_spot_grid:
+ y = panel.width * y_fraction
+ d2x, d2y, dxy = _mode_curvatures(modes, amplitudes, x, y)
+ transverse_stress = (
+ panel.transverse_stress_1
+ + (panel.transverse_stress_2 - panel.transverse_stress_1) * y_fraction
+ )
+ for z in z_values:
+ bending_x = -modulus * z * (d2x + panel.poisson_ratio * d2y)
+ bending_y = -modulus * z * (d2y + panel.poisson_ratio * d2x)
+ bending_tau = -2.0 * shear_modulus * z * dxy
+ vm = _stress_von_mises(
+ load_factor * axial_stress + bending_x,
+ load_factor * transverse_stress + bending_y,
+ load_factor * panel.shear_stress + bending_tau,
+ )
+ max_ratio = max(max_ratio, vm / panel.yield_stress_plate)
+ return max_ratio
+
+
+def u3_yield_utilization(
+ panel: U3PanelInput,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+) -> dict[str, Any]:
+ plate_ratio = _u3_plate_yield_ratio(panel, modes, amplitudes, load_factor, config)
+ return {
+ "max": plate_ratio,
+ "plate": plate_ratio,
+ "stiffener": None,
+ "stiffener_induced": None,
+ "plate_induced": None,
+ }
+
+
+def _stiffener_branch_stress_ratio(
+ axial_stress: float,
+ signed_bending_stress: float,
+ yield_stress: float,
+) -> dict[str, float]:
+ stress = axial_stress + signed_bending_stress
+ return {
+ "stress": stress,
+ "signed_bending_stress": signed_bending_stress,
+ "ratio": abs(stress) / max(yield_stress, EPS),
+ }
+
+
+def _stiffener_effective_axial_stress(
+ panel: S3PanelInput,
+ gross_section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ load_factor: float,
+) -> dict[str, float]:
+ """Return attached-plating effective axial stress for stiffener checks."""
+
+ nominal_stress = load_factor * panel.axial_stress
+ area_factor = gross_section.area / max(stiffener_section.area, EPS)
+ return {
+ "stress": nominal_stress * area_factor,
+ "nominal_stress": nominal_stress,
+ "area_factor": area_factor,
+ }
+
+
+def _global_slenderness_reduction(
+ panel: S3PanelInput,
+ global_elastic_factor: float | None,
+) -> dict[str, float | None]:
+ """Return the public stiffener lateral-deformation reduction terms."""
+
+ if global_elastic_factor is None or global_elastic_factor <= EPS:
+ return {
+ "gamma_reh": None,
+ "global_elastic_factor": global_elastic_factor,
+ "slenderness": None,
+ "reduction_factor": 0.0,
+ }
+
+ reference_stress = _stress_von_mises(
+ panel.axial_stress,
+ panel.mean_transverse_stress,
+ panel.shear_stress,
+ )
+ if reference_stress <= EPS:
+ return {
+ "gamma_reh": None,
+ "global_elastic_factor": global_elastic_factor,
+ "slenderness": None,
+ "reduction_factor": 0.0,
+ }
+
+ gamma_reh = min(panel.yield_stress_plate, panel.yield_stress_stiffener) / reference_stress
+ slenderness = math.sqrt(max(gamma_reh / global_elastic_factor, 0.0))
+ if slenderness <= 1.56:
+ reduction = 1.0 - slenderness**4 / 12.0
+ else:
+ reduction = 3.0 / max(4.0 * slenderness, EPS)
+ return {
+ "gamma_reh": gamma_reh,
+ "global_elastic_factor": global_elastic_factor,
+ "slenderness": slenderness,
+ "reduction_factor": min(1.0, max(reduction, 0.0)),
+ }
+
+
+def _stiffener_lateral_deformation_moment(
+ panel: S3PanelInput,
+ stiffener_section: S3SectionProperties,
+ load_factor: float,
+ global_elastic_factor: float | None,
+) -> dict[str, float | None]:
+ """Return guide-style stiffener moment from lateral deformation."""
+
+ slenderness = _global_slenderness_reduction(panel, global_elastic_factor)
+ if (
+ load_factor <= EPS
+ or global_elastic_factor is None
+ or global_elastic_factor <= load_factor + EPS
+ or slenderness["reduction_factor"] <= EPS
+ ):
+ return {
+ "moment": 0.0,
+ "ideal_elastic_buckling_force": 0.0,
+ "assumed_imperfection": panel.length / 1000.0,
+ "amplification": 0.0,
+ **slenderness,
+ }
+
+ effective_length = panel.length
+ ideal_elastic_force = (
+ math.pi**2
+ * panel.elastic_modulus
+ * stiffener_section.inertia_x
+ / max(effective_length**2, EPS)
+ )
+ amplification = load_factor / max(global_elastic_factor - load_factor, EPS)
+ assumed_imperfection = effective_length / 1000.0
+ moment = (
+ ideal_elastic_force
+ * float(slenderness["reduction_factor"])
+ * amplification
+ * assumed_imperfection
+ )
+ return {
+ "moment": moment,
+ "ideal_elastic_buckling_force": ideal_elastic_force,
+ "assumed_imperfection": assumed_imperfection,
+ "amplification": amplification,
+ **slenderness,
+ }
+
+
+def _stiffener_yield_ratios(
+ panel: S3PanelInput,
+ gross_section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+ global_elastic_factor: float | None = None,
+) -> dict[str, Any]:
+ pressure_moment = _pressure_stiffener_bending_moment(panel)
+ sniped_moments = _sniped_stiffener_eccentricity_moments(
+ panel,
+ gross_section,
+ stiffener_section,
+ load_factor,
+ config,
+ )
+ effective_axial = _stiffener_effective_axial_stress(
+ panel,
+ gross_section,
+ stiffener_section,
+ load_factor,
+ )
+ torsional_deformation = _stiffener_torsional_deformation_stress(
+ panel,
+ stiffener_section,
+ effective_axial["stress"],
+ config,
+ )
+ lateral_deformation = _stiffener_lateral_deformation_moment(
+ panel,
+ stiffener_section,
+ load_factor,
+ global_elastic_factor,
+ )
+ max_curvature = 0.0
+ for x_fraction in config.hot_spot_grid:
+ d2x, _, _ = _mode_curvatures(
+ modes,
+ amplitudes,
+ panel.length * x_fraction,
+ 0.5 * panel.width,
+ family="global",
+ )
+ max_curvature = max(max_curvature, abs(d2x))
+ si_deflection_stress = panel.elastic_modulus * stiffener_section.top_distance * max_curvature
+ pi_deflection_stress = panel.elastic_modulus * stiffener_section.bottom_distance * max_curvature
+ axial_stress = effective_axial["stress"]
+ common_moment = pressure_moment + float(lateral_deformation["moment"])
+ si_moment_stress = (
+ common_moment + sniped_moments["stiffener_induced"]
+ ) / max(stiffener_section.top_section_modulus, EPS)
+ pi_moment_stress = (
+ -common_moment + sniped_moments["plate_induced"]
+ ) / max(stiffener_section.attached_plate_section_modulus, EPS)
+ si_bending_stress = si_moment_stress + torsional_deformation["stress"]
+ pi_bending_stress = pi_moment_stress
+ si_stress = _stiffener_branch_stress_ratio(
+ axial_stress,
+ si_bending_stress,
+ panel.yield_stress_stiffener,
+ )
+ pi_stress = _stiffener_branch_stress_ratio(
+ axial_stress,
+ pi_bending_stress,
+ panel.yield_stress_plate,
+ )
+ si_ratio = si_stress["ratio"]
+ pi_ratio = pi_stress["ratio"]
+ return {
+ "max": max(si_ratio, pi_ratio),
+ "stiffener_induced": si_ratio,
+ "plate_induced": pi_ratio,
+ "effective_axial_stress": effective_axial,
+ "stiffener_induced_stress": si_stress,
+ "plate_induced_stress": pi_stress,
+ "signed_bending_stress": {
+ "stiffener_moment": si_moment_stress,
+ "plate_moment": pi_moment_stress,
+ "ritz_stiffener_deflection": si_deflection_stress,
+ "ritz_plate_deflection": -pi_deflection_stress,
+ },
+ "pressure_moment": pressure_moment,
+ "sniped_eccentricity_moment": sniped_moments,
+ "lateral_deformation_moment": lateral_deformation,
+ "torsional_deformation_stress": torsional_deformation["stress"],
+ "torsional_deformation": torsional_deformation,
+ }
+
+
+def yield_utilization(
+ panel: S3PanelInput,
+ section: S3SectionProperties,
+ stiffener_section: S3SectionProperties,
+ modes: Sequence[RitzMode],
+ amplitudes: Sequence[float],
+ load_factor: float,
+ config: S3SolverConfig,
+ global_elastic_factor: float | None = None,
+) -> dict[str, Any]:
+ plate_ratio = _plate_yield_ratio(panel, modes, amplitudes, load_factor, config)
+ stiffener_ratios = _stiffener_yield_ratios(
+ panel,
+ section,
+ stiffener_section,
+ modes,
+ amplitudes,
+ load_factor,
+ config,
+ global_elastic_factor,
+ )
+ return {
+ "max": max(plate_ratio, stiffener_ratios["max"]),
+ "plate": plate_ratio,
+ "stiffener": stiffener_ratios["max"],
+ "stiffener_induced": stiffener_ratios["stiffener_induced"],
+ "plate_induced": stiffener_ratios["plate_induced"],
+ "effective_axial_stress": stiffener_ratios["effective_axial_stress"],
+ "stiffener_induced_stress": stiffener_ratios["stiffener_induced_stress"],
+ "plate_induced_stress": stiffener_ratios["plate_induced_stress"],
+ "signed_bending_stress": stiffener_ratios["signed_bending_stress"],
+ "pressure_moment": stiffener_ratios["pressure_moment"],
+ "sniped_eccentricity_moment": stiffener_ratios["sniped_eccentricity_moment"],
+ "lateral_deformation_moment": stiffener_ratios["lateral_deformation_moment"],
+ "torsional_deformation_stress": stiffener_ratios["torsional_deformation_stress"],
+ "torsional_deformation": stiffener_ratios["torsional_deformation"],
+ }
+
+
+def _continuation_summary(
+ accepted_steps: int,
+ rejected_steps: int,
+ cutbacks: int,
+ last_accepted_load_factor: float,
+ min_accepted_step: float | None,
+ max_accepted_step: float | None,
+ current_step: float,
+ config: S3SolverConfig,
+) -> dict[str, Any]:
+ return {
+ "accepted_steps": accepted_steps,
+ "rejected_steps": rejected_steps,
+ "cutbacks": cutbacks,
+ "last_accepted_load_factor": last_accepted_load_factor,
+ "min_accepted_step": min_accepted_step,
+ "max_accepted_step": max_accepted_step,
+ "current_step": current_step,
+ "configured_initial_step": config.initial_load_step,
+ "configured_max_step": config.max_load_step,
+ "configured_min_step": config.min_load_step,
+ "configured_cutback": config.load_step_cutback,
+ "configured_max_cutbacks": config.max_load_step_cutbacks,
+ }
+
+
+def _interpolate_capacity(
+ previous_load: float,
+ previous_ratio: float,
+ load_factor: float,
+ ratio: float,
+ limit: float,
+) -> float:
+ if ratio <= previous_ratio + EPS:
+ return load_factor
+ fraction = (limit - previous_ratio) / (ratio - previous_ratio)
+ fraction = min(max(fraction, 0.0), 1.0)
+ return previous_load + fraction * (load_factor - previous_load)
+
+
+def _notes() -> list[str]:
+ return [
+ "regular S3 unit strip only; T1, K3, corrugation and FRP are outside this milestone",
+ "positive PULS CSV normal stress is compression; signed stresses scale while lateral pressure remains fixed",
+ "Rayleigh-Ritz sine modes use a reduced local/global strip basis, not the full production PULS basis",
+ "buckling usage is the reduced buckling-strength envelope over ultimate capacity and elastic local/global buckling limits; fixed-pressure material preload contribution is reported in ultimate diagnostics but excluded from buckling-strength control by default",
+ "shear-normal Ritz coupling is truncated in elastic and continuation checks; classical local plate shear remains a fallback candidate",
+ "web-local compression-shear uses a reduced stiffener-section web-edge compression envelope and a reduced local plate-web interaction; torsional stiffener remains a reduced gross-section estimate",
+ "stiffener yield exposes SI/PI section branches, lateral-deformation and sniped bending, SI-only torsional stress, and effective attached plate width",
+ "global longitudinal strip stiffness degrades from local elastic utilization on the nonlinear load path",
+ "PULS user manual S3 limits are reported as diagnostics; covered-domain gates remain controlled by solver config",
+ ]
+
+
+def _u3_notes() -> list[str]:
+ return [
+ "regular U3 unstiffened rectangular panels only; T1, K3, corrugation and FRP are outside this milestone",
+ "positive PULS export normal stress is compression; signed stresses scale while lateral pressure remains fixed",
+ "Rayleigh-Ritz sine modes use an isotropic plate basis with simply-supported trigonometric shapes",
+ "buckling usage is the reduced buckling-strength envelope over first-yield capacity and elastic plate/shear buckling limits",
+ "U3 end stresses are interpolated in yield checks and averaged in elastic buckling checks",
+ "PULS user manual U3 limits are reported as diagnostics; covered-domain gates remain controlled by solver config",
+ ]
+
+
+def _invalid_result(reason: str, diagnostics: dict[str, Any] | None = None) -> S3Result:
+ return S3Result(
+ buckling_usage_factor=None,
+ ultimate_usage_factor=None,
+ valid=False,
+ elastic_buckling_usage_factor=None,
+ invalid_reason=reason,
+ diagnostics=diagnostics or {},
+ covered_domain_notes=_notes(),
+ )
+
+
+def _invalid_u3_result(reason: str, diagnostics: dict[str, Any] | None = None) -> S3Result:
+ return S3Result(
+ buckling_usage_factor=None,
+ ultimate_usage_factor=None,
+ valid=False,
+ elastic_buckling_usage_factor=None,
+ invalid_reason=reason,
+ diagnostics=diagnostics or {},
+ covered_domain_notes=_u3_notes(),
+ )
+
+
+def _pressure_dominated_yield_limit(
+ panel: S3PanelInput,
+ pressure_yield: Mapping[str, Any],
+ final_yield: Mapping[str, Any],
+ config: S3SolverConfig,
+) -> bool:
+ if panel.pressure <= EPS:
+ return False
+ preload_max = float(pressure_yield.get("max") or 0.0)
+ if preload_max <= EPS:
+ return False
+ preload_share = preload_max / max(_s3_major_yield_utilization_limit(config), EPS)
+ return preload_share >= config.pressure_dominated_yield_preload_ratio
+
+
+def _s3_major_yield_utilization_limit(config: S3SolverConfig) -> float:
+ """Return the S3 utilization target for major yield/collapse detection."""
+
+ reserve = max(config.s3_major_yield_reserve_factor, EPS)
+ return max(config.yield_utilization_limit, EPS) * reserve
+
+
+def _relative_drift(reference: float | None, candidate: float | None) -> float | None:
+ if reference is None or candidate is None:
+ return None
+ return abs(candidate - reference) / max(abs(reference), EPS)
+
+
+def _mode_convergence_config(
+ config: S3SolverConfig,
+ longitudinal_modes: tuple[int, ...],
+ transverse_modes: tuple[int, ...],
+) -> S3SolverConfig:
+ return replace(
+ config,
+ longitudinal_modes=longitudinal_modes,
+ transverse_modes=transverse_modes,
+ check_mode_convergence=False,
+ )
+
+
+def _summarize_mode_convergence(
+ base_result: S3Result,
+ medium_result: S3Result,
+ high_result: S3Result,
+ config: S3SolverConfig,
+) -> dict[str, Any]:
+ medium_buckling = _relative_drift(
+ base_result.buckling_usage_factor,
+ medium_result.buckling_usage_factor,
+ )
+ medium_ultimate = _relative_drift(
+ base_result.ultimate_usage_factor,
+ medium_result.ultimate_usage_factor,
+ )
+ high_buckling = _relative_drift(
+ base_result.buckling_usage_factor,
+ high_result.buckling_usage_factor,
+ )
+ high_ultimate = _relative_drift(
+ base_result.ultimate_usage_factor,
+ high_result.ultimate_usage_factor,
+ )
+ finite_drifts = [
+ value
+ for value in (
+ medium_buckling,
+ medium_ultimate,
+ high_buckling,
+ high_ultimate,
+ )
+ if value is not None and math.isfinite(value)
+ ]
+ return {
+ "enabled": True,
+ "medium_basis": {
+ "longitudinal_modes": list(config.medium_longitudinal_modes),
+ "transverse_modes": list(config.medium_transverse_modes),
+ "valid": medium_result.valid,
+ "buckling_usage_factor": medium_result.buckling_usage_factor,
+ "ultimate_usage_factor": medium_result.ultimate_usage_factor,
+ "buckling_relative_drift": medium_buckling,
+ "ultimate_relative_drift": medium_ultimate,
+ },
+ "high_basis": {
+ "longitudinal_modes": list(config.high_longitudinal_modes),
+ "transverse_modes": list(config.high_transverse_modes),
+ "valid": high_result.valid,
+ "buckling_usage_factor": high_result.buckling_usage_factor,
+ "ultimate_usage_factor": high_result.ultimate_usage_factor,
+ "buckling_relative_drift": high_buckling,
+ "ultimate_relative_drift": high_ultimate,
+ },
+ "max_relative_drift": max(finite_drifts) if finite_drifts else None,
+ "high_confidence_drift_limit": config.high_confidence_drift_limit,
+ "medium_confidence_drift_limit": config.medium_confidence_drift_limit,
+ }
+
+
+def _classify_confidence(
+ result: S3Result,
+ validation_domain: Mapping[str, Any],
+ mode_convergence: Mapping[str, Any] | None,
+ config: S3SolverConfig,
+) -> tuple[str, list[str]]:
+ if not result.valid:
+ return "low", [f"invalid:{result.invalid_reason or 'unknown'}"]
+
+ reasons: list[str] = []
+ domain_reasons = list(validation_domain.get("reasons") or [])
+ if domain_reasons:
+ reasons.extend(f"domain:{reason}" for reason in domain_reasons)
+
+ if result.ultimate_usage_factor is not None and result.buckling_usage_factor is not None:
+ if result.ultimate_usage_factor > result.buckling_usage_factor + EPS:
+ reasons.append("usage-order:ultimate-exceeds-buckling")
+
+ if mode_convergence is None or not mode_convergence.get("enabled"):
+ reasons.append("mode-convergence:not-run")
+ return ("low" if any(reason.startswith("usage-order:") for reason in reasons) else "medium", reasons)
+
+ max_drift = _optional_float(mode_convergence.get("max_relative_drift"))
+ medium = mode_convergence.get("medium_basis", {})
+ high = mode_convergence.get("high_basis", {})
+ if not medium.get("valid") or not high.get("valid"):
+ reasons.append("mode-convergence:non-valid-refined-basis")
+ return "low", reasons
+ if max_drift is None:
+ reasons.append("mode-convergence:unavailable")
+ return "low", reasons
+ reasons.append(f"mode-convergence:max-drift={max_drift:.6g}")
+ if max_drift <= config.high_confidence_drift_limit:
+ return "high", reasons
+ if max_drift <= config.medium_confidence_drift_limit:
+ return "medium", reasons
+ return "low", reasons
+
+
+def _attach_reliability(
+ result: S3Result,
+ panel: S3PanelInput | U3PanelInput,
+ config: S3SolverConfig,
+ panel_family: str,
+ solver: Any,
+ validation_reasons: Sequence[str] | None = None,
+) -> S3Result:
+ validation_domain = _validation_domain(panel, config, panel_family, validation_reasons)
+ diagnostics = dict(result.diagnostics)
+ diagnostics["validation_domain"] = validation_domain
+ mode_convergence = diagnostics.get("mode_convergence")
+ if result.valid and config.check_mode_convergence:
+ medium_result = solver(
+ panel,
+ _mode_convergence_config(
+ config,
+ config.medium_longitudinal_modes,
+ config.medium_transverse_modes,
+ ),
+ )
+ high_result = solver(
+ panel,
+ _mode_convergence_config(
+ config,
+ config.high_longitudinal_modes,
+ config.high_transverse_modes,
+ ),
+ )
+ mode_convergence = _summarize_mode_convergence(
+ result,
+ medium_result,
+ high_result,
+ config,
+ )
+ elif mode_convergence is None:
+ mode_convergence = {"enabled": False}
+ diagnostics["mode_convergence"] = mode_convergence
+ confidence, confidence_reasons = _classify_confidence(
+ result,
+ validation_domain,
+ mode_convergence,
+ config,
+ )
+ diagnostics["confidence"] = confidence
+ diagnostics["confidence_reasons"] = confidence_reasons
+ return S3Result(
+ buckling_usage_factor=result.buckling_usage_factor,
+ ultimate_usage_factor=result.ultimate_usage_factor,
+ elastic_buckling_usage_factor=result.elastic_buckling_usage_factor,
+ valid=result.valid,
+ invalid_reason=result.invalid_reason,
+ diagnostics=diagnostics,
+ covered_domain_notes=result.covered_domain_notes,
+ confidence=confidence,
+ confidence_reasons=confidence_reasons,
+ )
+
+
+def solve_s3_panel(panel: S3PanelInput, config: S3SolverConfig | None = None) -> S3Result:
+ """Solve the reduced S3 load path and return usage-factor diagnostics."""
+
+ config = config or S3SolverConfig()
+ validation_reasons = collect_s3_validation_reasons(panel, config)
+ validation_error = validation_reasons[0] if validation_reasons else None
+ if validation_error is not None:
+ return _attach_reliability(
+ _invalid_result(validation_error),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+
+ if all(
+ abs(value) <= EPS
+ for value in (
+ panel.axial_stress,
+ panel.mean_transverse_stress,
+ panel.shear_stress,
+ )
+ ):
+ return _attach_reliability(
+ _invalid_result("zero-variable-load"),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ ["zero-variable-load"],
+ )
+
+ section = build_section_properties(panel)
+ stiffener_section, effective_width = build_effective_stiffener_section(panel, config)
+ modes = build_ritz_modes(panel, section, config)
+ amplitudes = [0.0 for _ in modes]
+ amplitudes, pressure_converged, pressure_iterations = solve_equilibrium_amplitudes(
+ panel,
+ modes,
+ 0.0,
+ amplitudes,
+ config,
+ )
+ if not pressure_converged:
+ return _attach_reliability(
+ _invalid_result(
+ "non-convergence",
+ {
+ "stage": "pressure-preload",
+ "iterations": pressure_iterations,
+ },
+ ),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+
+ pressure_yield = yield_utilization(panel, section, stiffener_section, modes, amplitudes, 0.0, config)
+ pressure_preload_response = {
+ "iterations": pressure_iterations,
+ "amplitudes": _mode_amplitude_summary(modes, amplitudes),
+ "yield_utilization": pressure_yield,
+ "controlling_yield_branch": max(
+ ("plate", "stiffener_induced", "plate_induced"),
+ key=lambda branch: float(pressure_yield[branch]),
+ ),
+ "pressure_mode_model": {
+ "separate_symmetric_pressure_modes": config.use_separate_s3_pressure_modes,
+ "pressure_family": (
+ "global-pressure"
+ if config.use_separate_s3_pressure_modes
+ else "global"
+ ),
+ "pressure_mode_stiffness_factor": (
+ config.s3_pressure_mode_stiffness_factor
+ if config.use_separate_s3_pressure_modes
+ else 1.0
+ ),
+ "manual_basis": (
+ "S3 user manual separates symmetric/clamped pressure modes from "
+ "asymmetric simply supported buckling modes"
+ ),
+ },
+ }
+ if pressure_yield["max"] >= config.pressure_yield_limit:
+ return _attach_reliability(
+ _invalid_result(
+ "pressure",
+ {
+ "stage": "pressure-preload",
+ "yield_utilization": pressure_yield,
+ "pressure_preload_response": pressure_preload_response,
+ "pressure_iterations": pressure_iterations,
+ },
+ ),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+
+ buckling = elastic_buckling_factors(panel, section, modes, config, stiffener_section)
+ buckling_factor = buckling["critical_factor"]
+ elastic_buckling_usage = None if buckling_factor is None else 1.0 / max(buckling_factor, EPS)
+ column_factor = buckling["stiffener_column_factor"]
+ global_family = buckling["modeled_failure_families"].get("global-stiffened-strip", {})
+ global_elastic_cutoff_factor = _optional_float(global_family.get("critical_factor"))
+ s3_major_yield_limit = _s3_major_yield_utilization_limit(config)
+
+ previous_load = 0.0
+ previous_yield = pressure_yield["max"]
+ yield_capacity_factor: float | None = None
+ max_iterations = pressure_iterations
+ collapse_state = "major-yield"
+ final_yield = pressure_yield
+ local_global_coupling = local_global_stiffness_scale(panel, modes, amplitudes, 0.0, config)
+
+ accepted_steps = 0
+ rejected_steps = 0
+ cutbacks = 0
+ min_accepted_step: float | None = None
+ max_accepted_step: float | None = None
+ current_step = min(
+ max(config.initial_load_step, config.min_load_step, EPS),
+ max(config.max_load_step, config.min_load_step, EPS),
+ )
+ cutback_factor = min(max(config.load_step_cutback, EPS), 0.95)
+
+ while previous_load < config.max_load_factor - EPS:
+ load_factor = min(previous_load + current_step, config.max_load_factor)
+ attempted_step = load_factor - previous_load
+ local_global_coupling = local_global_stiffness_scale(panel, modes, amplitudes, load_factor, config)
+ if local_global_coupling["scale"] < 1.0:
+ modes = build_ritz_modes(
+ panel,
+ section,
+ config,
+ global_stiffness_scale=local_global_coupling["scale"],
+ )
+ trial_amplitudes, converged, iterations = solve_equilibrium_amplitudes(
+ panel,
+ modes,
+ load_factor,
+ amplitudes,
+ config,
+ )
+ max_iterations = max(max_iterations, iterations)
+ if not converged:
+ rejected_steps += 1
+ next_step = current_step * cutback_factor
+ if (
+ next_step < max(config.min_load_step, EPS)
+ or cutbacks >= config.max_load_step_cutbacks
+ ):
+ continuation = _continuation_summary(
+ accepted_steps,
+ rejected_steps,
+ cutbacks,
+ previous_load,
+ min_accepted_step,
+ max_accepted_step,
+ current_step,
+ config,
+ )
+ continuation["attempted_load_factor"] = load_factor
+ continuation["attempted_step"] = attempted_step
+ continuation["next_cutback_step"] = next_step
+ continuation["newton_iterations"] = iterations
+ continuation["cutback_exhausted"] = True
+ return _attach_reliability(
+ _invalid_result(
+ "non-convergence",
+ {
+ "stage": "in-plane-continuation",
+ "load_factor": load_factor,
+ "iterations": iterations,
+ "buckling": buckling,
+ "continuation": continuation,
+ },
+ ),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+ current_step = next_step
+ cutbacks += 1
+ continue
+
+ amplitudes = trial_amplitudes
+ accepted_steps += 1
+ min_accepted_step = (
+ attempted_step
+ if min_accepted_step is None
+ else min(min_accepted_step, attempted_step)
+ )
+ max_accepted_step = (
+ attempted_step
+ if max_accepted_step is None
+ else max(max_accepted_step, attempted_step)
+ )
+ ultimate_yield_global_factor = (
+ global_elastic_cutoff_factor
+ if config.include_lateral_deformation_in_ultimate_yield
+ else None
+ )
+ final_yield = yield_utilization(
+ panel,
+ section,
+ stiffener_section,
+ modes,
+ amplitudes,
+ load_factor,
+ config,
+ ultimate_yield_global_factor,
+ )
+ if final_yield["max"] >= s3_major_yield_limit:
+ yield_capacity_factor = _interpolate_capacity(
+ previous_load,
+ previous_yield,
+ load_factor,
+ final_yield["max"],
+ s3_major_yield_limit,
+ )
+ previous_load = load_factor
+ break
+ previous_load = load_factor
+ previous_yield = final_yield["max"]
+ if previous_load >= 2.0:
+ current_step = min(current_step * config.load_step_growth, config.max_load_step)
+
+ continuation = _continuation_summary(
+ accepted_steps,
+ rejected_steps,
+ cutbacks,
+ previous_load,
+ min_accepted_step,
+ max_accepted_step,
+ current_step,
+ config,
+ )
+
+ ultimate_capacity_factor = yield_capacity_factor
+ if ultimate_capacity_factor is None:
+ if global_elastic_cutoff_factor is not None:
+ ultimate_capacity_factor = global_elastic_cutoff_factor
+ collapse_state = "global-elastic-cutoff"
+ if column_factor is not None and (
+ ultimate_capacity_factor is None or column_factor < ultimate_capacity_factor
+ ):
+ ultimate_capacity_factor = column_factor
+ collapse_state = "stiffener-column-cutoff"
+
+ if ultimate_capacity_factor is None or ultimate_capacity_factor <= EPS:
+ return _attach_reliability(
+ _invalid_result(
+ "no-collapse-within-load-range",
+ {
+ "max_load_factor": config.max_load_factor,
+ "buckling": buckling,
+ "yield_utilization": final_yield,
+ },
+ ),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+
+ pressure_dominated_yield = _pressure_dominated_yield_limit(
+ panel,
+ pressure_yield,
+ final_yield,
+ config,
+ )
+ include_ultimate_in_buckling_strength = (
+ config.include_pressure_dominated_yield_in_buckling_strength
+ or not pressure_dominated_yield
+ )
+ buckling_strength_limits = {}
+ excluded_buckling_strength_limits = {}
+ if include_ultimate_in_buckling_strength:
+ buckling_strength_limits["ultimate_capacity"] = ultimate_capacity_factor
+ else:
+ excluded_buckling_strength_limits[
+ "ultimate_capacity"
+ ] = "pressure-dominated-fixed-preload-yield"
+ if buckling_factor is not None:
+ buckling_strength_limits["elastic_buckling_envelope"] = buckling_factor
+ if not buckling_strength_limits:
+ buckling_strength_limits["ultimate_capacity"] = ultimate_capacity_factor
+ buckling_strength_control, buckling_strength_capacity_factor = min(
+ buckling_strength_limits.items(),
+ key=lambda item: float(item[1]),
+ )
+ raw_ultimate_capacity_factor = ultimate_capacity_factor
+ reported_ultimate_capacity_factor = max(
+ raw_ultimate_capacity_factor,
+ buckling_strength_capacity_factor,
+ )
+ ultimate_lifted_to_buckling_strength = (
+ reported_ultimate_capacity_factor > raw_ultimate_capacity_factor + EPS
+ )
+ buckling_strength = {
+ "capacity_factor": buckling_strength_capacity_factor,
+ "usage_factor": 1.0 / max(buckling_strength_capacity_factor, EPS),
+ "controlling_limit": buckling_strength_control,
+ "component_capacity_factors": buckling_strength_limits,
+ "excluded_component_capacity_factors": excluded_buckling_strength_limits,
+ "elastic_usage_factor": elastic_buckling_usage,
+ "ultimate_usage_factor": 1.0 / max(reported_ultimate_capacity_factor, EPS),
+ "raw_ultimate_usage_factor": 1.0 / max(raw_ultimate_capacity_factor, EPS),
+ "raw_ultimate_capacity_factor": raw_ultimate_capacity_factor,
+ "reported_ultimate_capacity_factor": reported_ultimate_capacity_factor,
+ "ultimate_lifted_to_buckling_strength": ultimate_lifted_to_buckling_strength,
+ "pressure_dominated_yield_limit": pressure_dominated_yield,
+ "ultimate_included": include_ultimate_in_buckling_strength,
+ }
+ diagnostics = {
+ "collapse_state": collapse_state,
+ "capacity_factor": reported_ultimate_capacity_factor,
+ "raw_capacity_factor": raw_ultimate_capacity_factor,
+ "yield_capacity_factor": yield_capacity_factor,
+ "s3_major_yield_utilization_limit": s3_major_yield_limit,
+ "s3_major_yield_reserve_factor": config.s3_major_yield_reserve_factor,
+ "ultimate_yield_includes_lateral_deformation": (
+ config.include_lateral_deformation_in_ultimate_yield
+ ),
+ "global_elastic_cutoff_factor": global_elastic_cutoff_factor,
+ "buckling": buckling,
+ "pressure_preload_yield_utilization": pressure_yield,
+ "pressure_preload_response": pressure_preload_response,
+ "final_yield_utilization": final_yield,
+ "max_newton_iterations": max_iterations,
+ "mode_count": len(modes),
+ "section": asdict(section),
+ "stiffener_section": asdict(stiffener_section),
+ "effective_stiffener_plate_width": effective_width,
+ "load_components": normalized_load_components(panel),
+ "support_model": SUPPORTED_IN_PLANE_SUPPORTS[panel.in_plane_support],
+ "local_global_coupling": local_global_coupling,
+ "continuation_geometric_coupling": _ritz_geometric_matrix(panel, modes)[1],
+ "continuation": continuation,
+ "buckling_strength": buckling_strength,
+ }
+ return _attach_reliability(
+ S3Result(
+ buckling_usage_factor=buckling_strength["usage_factor"],
+ ultimate_usage_factor=buckling_strength["ultimate_usage_factor"],
+ elastic_buckling_usage_factor=elastic_buckling_usage,
+ valid=True,
+ invalid_reason=None,
+ diagnostics=diagnostics,
+ covered_domain_notes=_notes(),
+ ),
+ panel,
+ config,
+ "S3",
+ solve_s3_panel,
+ validation_reasons,
+ )
+
+
+def solve_u3_panel(panel: U3PanelInput, config: S3SolverConfig | None = None) -> S3Result:
+ """Solve the reduced U3 load path and return usage-factor diagnostics."""
+
+ config = config or S3SolverConfig()
+ validation_reasons = collect_u3_validation_reasons(panel, config)
+ validation_error = validation_reasons[0] if validation_reasons else None
+ if validation_error is not None:
+ return _attach_reliability(
+ _invalid_u3_result(validation_error),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+
+ if all(
+ abs(value) <= EPS
+ for value in (
+ panel.axial_stress,
+ panel.mean_transverse_stress,
+ panel.shear_stress,
+ )
+ ):
+ return _attach_reliability(
+ _invalid_u3_result("zero-variable-load"),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ ["zero-variable-load"],
+ )
+
+ modes = build_u3_ritz_modes(panel, config)
+ amplitudes = [0.0 for _ in modes]
+ amplitudes, pressure_converged, pressure_iterations = solve_equilibrium_amplitudes(
+ panel,
+ modes,
+ 0.0,
+ amplitudes,
+ config,
+ )
+ if not pressure_converged:
+ return _attach_reliability(
+ _invalid_u3_result(
+ "non-convergence",
+ {
+ "stage": "pressure-preload",
+ "iterations": pressure_iterations,
+ },
+ ),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+
+ pressure_yield = u3_yield_utilization(panel, modes, amplitudes, 0.0, config)
+ pressure_preload_response = {
+ "iterations": pressure_iterations,
+ "amplitudes": _mode_amplitude_summary(modes, amplitudes),
+ "yield_utilization": pressure_yield,
+ "controlling_yield_branch": "plate",
+ }
+ if pressure_yield["max"] >= config.pressure_yield_limit:
+ return _attach_reliability(
+ _invalid_u3_result(
+ "pressure",
+ {
+ "stage": "pressure-preload",
+ "yield_utilization": pressure_yield,
+ "pressure_preload_response": pressure_preload_response,
+ "pressure_iterations": pressure_iterations,
+ },
+ ),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+
+ buckling = elastic_u3_buckling_factors(panel, modes, config)
+ buckling_factor = buckling["critical_factor"]
+ elastic_buckling_usage = None if buckling_factor is None else 1.0 / max(buckling_factor, EPS)
+
+ previous_load = 0.0
+ previous_yield = pressure_yield["max"]
+ yield_capacity_factor: float | None = None
+ max_iterations = pressure_iterations
+ collapse_state = "first-yield"
+ final_yield = pressure_yield
+
+ accepted_steps = 0
+ rejected_steps = 0
+ cutbacks = 0
+ min_accepted_step: float | None = None
+ max_accepted_step: float | None = None
+ current_step = min(
+ max(config.initial_load_step, config.min_load_step, EPS),
+ max(config.max_load_step, config.min_load_step, EPS),
+ )
+ cutback_factor = min(max(config.load_step_cutback, EPS), 0.95)
+
+ while previous_load < config.max_load_factor - EPS:
+ load_factor = min(previous_load + current_step, config.max_load_factor)
+ attempted_step = load_factor - previous_load
+ trial_amplitudes, converged, iterations = solve_equilibrium_amplitudes(
+ panel,
+ modes,
+ load_factor,
+ amplitudes,
+ config,
+ )
+ max_iterations = max(max_iterations, iterations)
+ if not converged:
+ rejected_steps += 1
+ next_step = current_step * cutback_factor
+ if (
+ next_step < max(config.min_load_step, EPS)
+ or cutbacks >= config.max_load_step_cutbacks
+ ):
+ continuation = _continuation_summary(
+ accepted_steps,
+ rejected_steps,
+ cutbacks,
+ previous_load,
+ min_accepted_step,
+ max_accepted_step,
+ current_step,
+ config,
+ )
+ continuation["attempted_load_factor"] = load_factor
+ continuation["attempted_step"] = attempted_step
+ continuation["next_cutback_step"] = next_step
+ continuation["newton_iterations"] = iterations
+ continuation["cutback_exhausted"] = True
+ return _attach_reliability(
+ _invalid_u3_result(
+ "non-convergence",
+ {
+ "stage": "in-plane-continuation",
+ "load_factor": load_factor,
+ "iterations": iterations,
+ "buckling": buckling,
+ "continuation": continuation,
+ },
+ ),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+ current_step = next_step
+ cutbacks += 1
+ continue
+
+ amplitudes = trial_amplitudes
+ accepted_steps += 1
+ min_accepted_step = (
+ attempted_step
+ if min_accepted_step is None
+ else min(min_accepted_step, attempted_step)
+ )
+ max_accepted_step = (
+ attempted_step
+ if max_accepted_step is None
+ else max(max_accepted_step, attempted_step)
+ )
+ final_yield = u3_yield_utilization(panel, modes, amplitudes, load_factor, config)
+ if final_yield["max"] >= config.yield_utilization_limit:
+ yield_capacity_factor = _interpolate_capacity(
+ previous_load,
+ previous_yield,
+ load_factor,
+ final_yield["max"],
+ config.yield_utilization_limit,
+ )
+ previous_load = load_factor
+ break
+ previous_load = load_factor
+ previous_yield = final_yield["max"]
+ if previous_load >= 2.0:
+ current_step = min(current_step * config.load_step_growth, config.max_load_step)
+
+ continuation = _continuation_summary(
+ accepted_steps,
+ rejected_steps,
+ cutbacks,
+ previous_load,
+ min_accepted_step,
+ max_accepted_step,
+ current_step,
+ config,
+ )
+
+ if yield_capacity_factor is None or yield_capacity_factor <= EPS:
+ return _attach_reliability(
+ _invalid_u3_result(
+ "no-collapse-within-load-range",
+ {
+ "max_load_factor": config.max_load_factor,
+ "buckling": buckling,
+ "yield_utilization": final_yield,
+ },
+ ),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+
+ buckling_strength_limits = {"ultimate_capacity": yield_capacity_factor}
+ if buckling_factor is not None:
+ buckling_strength_limits["elastic_buckling_envelope"] = buckling_factor
+ buckling_strength_control, buckling_strength_capacity_factor = min(
+ buckling_strength_limits.items(),
+ key=lambda item: float(item[1]),
+ )
+ raw_ultimate_capacity_factor = yield_capacity_factor
+ reported_ultimate_capacity_factor = max(
+ raw_ultimate_capacity_factor,
+ buckling_strength_capacity_factor,
+ )
+ ultimate_lifted_to_buckling_strength = (
+ reported_ultimate_capacity_factor > raw_ultimate_capacity_factor + EPS
+ )
+ buckling_strength = {
+ "capacity_factor": buckling_strength_capacity_factor,
+ "usage_factor": 1.0 / max(buckling_strength_capacity_factor, EPS),
+ "controlling_limit": buckling_strength_control,
+ "component_capacity_factors": buckling_strength_limits,
+ "excluded_component_capacity_factors": {},
+ "elastic_usage_factor": elastic_buckling_usage,
+ "ultimate_usage_factor": 1.0 / max(reported_ultimate_capacity_factor, EPS),
+ "raw_ultimate_usage_factor": 1.0 / max(raw_ultimate_capacity_factor, EPS),
+ "raw_ultimate_capacity_factor": raw_ultimate_capacity_factor,
+ "reported_ultimate_capacity_factor": reported_ultimate_capacity_factor,
+ "ultimate_lifted_to_buckling_strength": ultimate_lifted_to_buckling_strength,
+ "pressure_dominated_yield_limit": False,
+ "ultimate_included": True,
+ }
+ diagnostics = {
+ "panel_family": "U3",
+ "collapse_state": collapse_state,
+ "capacity_factor": reported_ultimate_capacity_factor,
+ "raw_capacity_factor": raw_ultimate_capacity_factor,
+ "yield_capacity_factor": yield_capacity_factor,
+ "buckling": buckling,
+ "pressure_preload_yield_utilization": pressure_yield,
+ "pressure_preload_response": pressure_preload_response,
+ "final_yield_utilization": final_yield,
+ "max_newton_iterations": max_iterations,
+ "mode_count": len(modes),
+ "load_components": normalized_load_components(panel),
+ "support_model": SUPPORTED_IN_PLANE_SUPPORTS[panel.in_plane_support],
+ "rotational_support": {
+ "x_edges": panel.rotational_support_1,
+ "y_edges": panel.rotational_support_2,
+ },
+ "continuation_geometric_coupling": _ritz_geometric_matrix(panel, modes)[1],
+ "continuation": continuation,
+ "buckling_strength": buckling_strength,
+ }
+ return _attach_reliability(
+ S3Result(
+ buckling_usage_factor=buckling_strength["usage_factor"],
+ ultimate_usage_factor=buckling_strength["ultimate_usage_factor"],
+ elastic_buckling_usage_factor=elastic_buckling_usage,
+ valid=True,
+ invalid_reason=None,
+ diagnostics=diagnostics,
+ covered_domain_notes=_u3_notes(),
+ ),
+ panel,
+ config,
+ "U3",
+ solve_u3_panel,
+ validation_reasons,
+ )
+
+
+
diff --git a/anystruct/current_comps.png b/anystruct/current_comps.png
index e5b5a222..df439658 100644
Binary files a/anystruct/current_comps.png and b/anystruct/current_comps.png differ
diff --git a/anystruct/example_data.py b/anystruct/example_data.py
index 8fdad194..500813b4 100644
--- a/anystruct/example_data.py
+++ b/anystruct/example_data.py
@@ -519,7 +519,10 @@ def get_grid_coord_from_points_coords(point_coord):
def get_section_list():
''' Returning a section list. '''
- import pl_stf_window as plstf
+ try:
+ import anystruct.pl_stf_window as plstf
+ except ModuleNotFoundError:
+ import ANYstructure.anystruct.pl_stf_window as plstf
return [plstf.Section(obj_dict), plstf.Section(obj_dict2), plstf.Section(obj_dict_L)]
diff --git a/anystruct/fatigue_window.py b/anystruct/fatigue_window.py
index 46eab5b4..cfe97143 100644
--- a/anystruct/fatigue_window.py
+++ b/anystruct/fatigue_window.py
@@ -3,9 +3,11 @@
try:
import anystruct.example_data as test
+ import anystruct.line_structure as line_structure
import anystruct.SN_curve_parameters as sn
except ModuleNotFoundError:
import ANYstructure.anystruct.example_data as test
+ import ANYstructure.anystruct.line_structure as line_structure
import ANYstructure.anystruct.SN_curve_parameters as sn
@@ -27,21 +29,22 @@ def __init__(self, master, app=None):
self._initial_fatigue_obj = test.get_fatigue_object()
else:
- if app._line_to_struc[app._active_line][0] is None:
+ active_bundle = app._line_to_struc[app._active_line]
+ if line_structure.structure(active_bundle) is None:
return
- elif app._line_to_struc[app._active_line][0].Stiffener is None:
+ elif not line_structure.has_stiffener(active_bundle):
return
self.app = app
self.active_line = app._active_line
points = app._line_dict[self.active_line]
coords = (app._point_dict['point'+str(points[0])], app._point_dict['point'+str(points[1])])
self.pressure_coords = self.get_pressure_point_coord_from_two_points(coords[0], coords[1])
- self._initial_structure_obj = app._line_to_struc[app._active_line][0].Stiffener
- self.load_objects = app._line_to_struc[app._active_line][3]
+ self._initial_structure_obj = line_structure.stiffener(active_bundle)
+ self.load_objects = line_structure.loads(active_bundle)
self.comp_objects = [app._tank_dict['comp'+str(comp_i)] for comp_i in
app.get_compartments_for_line(app._active_line)]
- self._initial_fatigue_obj = app._line_to_struc[self.active_line][2]
+ self._initial_fatigue_obj = line_structure.fatigue(active_bundle)
self._frame = master
@@ -349,4 +352,4 @@ def save_and_close(self):
if __name__ == '__main__':
root = tk.Tk()
my_app = CreateFatigueWindow(root,app=None)
- root.mainloop()
\ No newline at end of file
+ root.mainloop()
diff --git a/anystruct/gui.py b/anystruct/gui.py
index f133ad2d..6680ea43 100644
--- a/anystruct/gui.py
+++ b/anystruct/gui.py
@@ -1,24 +1,5 @@
-import sys, multiprocessing, ctypes, os
-import tkinter as tk
-from anystruct.main_application import Application
+from anystruct.__main__ import main
-def main(args=None):
- """The main routine."""
-
- if args is None:
- args = sys.argv[1:]
-
- multiprocessing.freeze_support()
- errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
- root = tk.Tk()
- width = root.winfo_screenwidth()
- height = root.winfo_screenheight()
- root.geometry(f'{width}x{height}')
- my_app = Application(root)
- root.mainloop()
-
- # Do argument parsing here (eg. with argparse) and anything else
- # you want your project to do.
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/anystruct/line_structure.py b/anystruct/line_structure.py
new file mode 100644
index 00000000..7a963582
--- /dev/null
+++ b/anystruct/line_structure.py
@@ -0,0 +1,96 @@
+"""Named accessors for the existing line-to-structure bundle format."""
+
+import copy
+from dataclasses import dataclass, field
+from typing import Any
+
+
+STRUCTURE = 0
+LEGACY_CALC_OBJECT = 1
+FATIGUE = 2
+LOADS = 3
+LOAD_COMBINATIONS = 4
+CYLINDER = 5
+
+
+@dataclass
+class LineStructureBundle:
+ """Typed adapter for the legacy line-to-structure list representation."""
+
+ line_structure: Any = None
+ legacy_calc_object: Any = None
+ fatigue: Any = None
+ loads: list[Any] = field(default_factory=list)
+ load_combinations: dict[str, Any] = field(default_factory=dict)
+ cylinder: Any = None
+ legacy_length: int = CYLINDER + 1
+
+ @classmethod
+ def from_legacy_bundle(cls, bundle):
+ return cls(
+ line_structure=None if len(bundle) <= STRUCTURE else bundle[STRUCTURE],
+ legacy_calc_object=None if len(bundle) <= LEGACY_CALC_OBJECT else bundle[LEGACY_CALC_OBJECT],
+ fatigue=None if len(bundle) <= FATIGUE else bundle[FATIGUE],
+ loads=[] if len(bundle) <= LOADS else bundle[LOADS],
+ load_combinations={} if len(bundle) <= LOAD_COMBINATIONS else bundle[LOAD_COMBINATIONS],
+ cylinder=None if len(bundle) <= CYLINDER else bundle[CYLINDER],
+ legacy_length=len(bundle),
+ )
+
+ def to_legacy_bundle(self):
+ bundle = [
+ self.line_structure,
+ self.legacy_calc_object,
+ self.fatigue,
+ self.loads,
+ self.load_combinations,
+ self.cylinder,
+ ]
+ return bundle[:self.legacy_length]
+
+
+def structure(bundle):
+ return LineStructureBundle.from_legacy_bundle(bundle).line_structure
+
+
+def plate(bundle):
+ line_structure = structure(bundle)
+ return None if line_structure is None else line_structure.Plate
+
+
+def stiffener(bundle):
+ line_structure = structure(bundle)
+ return None if line_structure is None else line_structure.Stiffener
+
+
+def girder(bundle):
+ line_structure = structure(bundle)
+ return None if line_structure is None else line_structure.Girder
+
+
+def fatigue(bundle):
+ return LineStructureBundle.from_legacy_bundle(bundle).fatigue
+
+
+def loads(bundle):
+ return LineStructureBundle.from_legacy_bundle(bundle).loads
+
+
+def load_combinations(bundle):
+ return LineStructureBundle.from_legacy_bundle(bundle).load_combinations
+
+
+def cylinder(bundle):
+ return LineStructureBundle.from_legacy_bundle(bundle).cylinder
+
+
+def has_cylinder(bundle):
+ return cylinder(bundle) is not None
+
+
+def has_stiffener(bundle):
+ return stiffener(bundle) is not None
+
+
+def copy_bundle(bundle):
+ return [copy.deepcopy(item) if item is not None else None for item in bundle]
diff --git a/anystruct/main_application.py b/anystruct/main_application.py
index 2199fabd..56f88792 100644
--- a/anystruct/main_application.py
+++ b/anystruct/main_application.py
@@ -5,14 +5,12 @@
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
-import decimal, pickle
+import decimal
from _tkinter import TclError
import multiprocessing
import ctypes
from matplotlib import pyplot as plt
import matplotlib
-from reportlab.lib.pagesizes import letter, landscape
-from reportlab.platypus import SimpleDocTemplate
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics._pairwise_distances_reduction import _datasets_pair,_middle_term_computer
@@ -34,9 +32,10 @@
import anystruct.stresses_window as stress
import anystruct.fatigue_window as fatigue
import anystruct.load_factor_window as load_factors
- from anystruct.report_generator import LetterMaker
- import anystruct.sesam_interface as sesam
- import anystruct.excel_inteface as excel_interface
+ import anystruct.api_helpers as api_helpers
+ import anystruct.ml_models as ml_models
+ import anystruct.project_application as project_application
+ import anystruct.project_services as project_services
except ModuleNotFoundError:
# This is due to pyinstaller issues.
from ANYstructure.anystruct.calc_structure import *
@@ -54,9 +53,10 @@
import ANYstructure.anystruct.stresses_window as stress
import ANYstructure.anystruct.fatigue_window as fatigue
import ANYstructure.anystruct.load_factor_window as load_factors
- from ANYstructure.anystruct.report_generator import LetterMaker
- import ANYstructure.anystruct.sesam_interface as sesam
- import ANYstructure.anystruct.excel_inteface as excel_interface
+ import ANYstructure.anystruct.api_helpers as api_helpers
+ import ANYstructure.anystruct.ml_models as ml_models
+ import ANYstructure.anystruct.project_application as project_application
+ import ANYstructure.anystruct.project_services as project_services
class Application():
'''
@@ -189,8 +189,6 @@ def __init__(self, parent):
sub_sesam = tk.Menu(menu)
menu.add_cascade(label = 'Interfaces', menu = sub_sesam)
sub_sesam.add_command(label = 'Export geometry to SESAM GeniE JS', command = self.export_to_js)
- sub_sesam.add_command(label='Run all PULS lines', command=self.puls_run_all_lines)
- sub_sesam.add_command(label='Delete all PULS results', command=self.puls_delete_all)
sub_sesam.add_command(label='Import excel file', command=self.open_excel_file)
sub_help = tk.Menu(menu)
@@ -327,58 +325,11 @@ def __init__(self, parent):
self.__previous_load_data = None # Used to compare loads before and after.
self.__copied_line_prop = None # Used to copy line properties to another.
- self._PULS_results = None # If a puls run is avaliable, it is stored here.
self._center_of_buoyancy = dict() # Center of buoyancy for all and for carious static drafts
# Example {8: (5,20), 22: (12,20), 'all': (16,20)}
- self._ML_buckling = {1.1 : dict(), 1.15: dict()}# Buckling machine learning algorithm
- for mat_fac in [1.1, 1.15]:
- for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
- 'cl SP ult int predictor', 'cl SP ult int scaler',
- 'cl SP buc GLGT predictor', 'cl SP buc GLGT scaler',
- 'cl SP ult GLGT predictor', 'cl SP ult GLGT scaler',
- 'cl UP buc int predictor', 'cl UP buc int scaler',
- 'cl UP ult int predictor', 'cl UP ult int scaler',
- 'cl UP buc GLGT predictor', 'cl UP buc GLGT scaler',
- 'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler',
- 'CSR predictor UP', 'CSR scaler UP',
- 'CSR predictor SP', 'CSR scaler SP'
- ],
- ["ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_CSR-Tank_req_cl_predictor",
- "ml_files\\CL_CSR-Tank_req_cl_scaler",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler"]):
-
- mat_fac_str = [str(round(mat_fac,2)).replace('.','')+'0'][0][0:3]
- self._ML_buckling[mat_fac][name] = None
- file_base = file_base.replace('XXX', mat_fac_str)
- if os.path.isfile(file_base + '.pickle'):
- file = open(file_base + '.pickle', 'rb')
- self._ML_buckling[mat_fac][name] = pickle.load(file)
- file.close()
- else:
- #file = open(self._root_dir +'\\' + file_base + '.pickle', 'rb')
-
- ml_file = os.path.join(self._root_dir, file_base + '.pickle')
- file = open(ml_file, 'rb')
- self._ML_buckling[mat_fac][name] = pickle.load(file)
- file.close()
+ self._ML_buckling = ml_models.load_buckling_models((self._root_dir,))
+ self._ML_classes = ml_models.default_ml_class_messages()
# Used to select parameter
self._stuctural_definition = ['mat_yield','mat_factor', 'span', 'spacing', 'plate_thk', 'stf_web_height',
@@ -950,14 +901,16 @@ def show_message():
self._ent_structure_type]
self._new_buckling_method = tk.StringVar()
- options = ['DNV-RP-C201 - prescriptive','DNV PULS','ML-CL (PULS based)']
+ options = [
+ 'DNV-RP-C201 - prescriptive',
+ 'ML-Numeric (PULS based)',
+ 'SemiAnalytical S3/U3',
+ ]
self._lab_buckling_method = ttk.Label(self._tab_prop, text='Set buckling method')
self._buckling_method = ttk.OptionMenu(self._tab_prop, self._new_buckling_method, options[0], *options,
command=self.update_frame)
- # PULS interface
- self._puls_run_all = ttk.Button(self._tab_prop, text='Run PULS -\nupdate results',
- command=self.puls_run_all_lines)
+ # SemiAnalytical and ML-Numeric share the historic buckling panel input parameters below.
self._ent_puls_uf = ttk.Entry(self._tab_prop, textvariable=self._new_puls_uf, width=int(ent_width * 1))
self._new_puls_uf.trace('w', self.trace_acceptance_change)
@@ -1017,12 +970,12 @@ def show_message():
ttk.Entry(self._tab_prop, textvariable=self._new_buckling_lf_stresses,
width=int(ent_width * 1))]
- self._lab_puls_acceptance= ttk.Label(self._tab_prop, text='PULS acceptance')
- self._lab_puls_uf = ttk.Label(self._tab_prop, text='PULS utilization factor:')
- self._lab_puls_int_gt = ttk.Label(self._tab_prop, text='PULS Int-integrated GL-free left/right GT-free top/bottom')
+ self._lab_puls_acceptance= ttk.Label(self._tab_prop, text='Buckling acceptance')
+ self._lab_puls_uf = ttk.Label(self._tab_prop, text='Utilization factor:')
+ self._lab_puls_int_gt = ttk.Label(self._tab_prop, text='Int-integrated GL-free left/right GT-free top/bottom')
self._lab_puls_cont_sniped = ttk.Label(self._tab_prop, text='Continous or Sniped',
font = self._text_size['Text 8'])
- self._lab_puls_up_supp = ttk.Label(self._tab_prop, text='PULS UP support - left,right,upper,lower\n'
+ self._lab_puls_up_supp = ttk.Label(self._tab_prop, text='UP support - left,right,upper,lower\n'
'S: simply supported C: Continuous',
font = self._text_size['Text 8'])
# self._zstar_label = ttk.Label(self._tab_prop, text='z* optimization (buckling RP-C201)',
@@ -1064,7 +1017,7 @@ def show_message():
chk_deltax = 0.1
chk_deltay = 0.025
- (ttk.Label(self._tab_information, text='Labelling options. For non-cylinder structure only. ', font="Text 9")
+ (ttk.Label(self._tab_information, text='Labelling and color code options ', font="Text 9")
.place(relx=0.02, rely=2*chk_deltay))
self._information_gui_chk_structure = [
ttk.Checkbutton(self._tab_information,
@@ -1470,8 +1423,12 @@ def show_message():
prop_vert_start = 0.01
types_start = 0.005208333
- options = list(CylinderAndCurvedPlate.geomeries.values()) # Shell geometry selection [string]
- self._shell_geometries_map = CylinderAndCurvedPlate.geomeries_map # Shell geometry selection string : int
+ options = ['Flat plate, stiffened', 'Flat plate, unstiffened', 'Flat plate, stiffened with girder'] + \
+ list(api_helpers.CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT)
+ self._shell_geometries_map = {
+ **api_helpers.FLAT_GEOMETRY_IDS,
+ **api_helpers.CYLINDER_GEOMETRY_IDS,
+ }
self._current_calculation_domain = 'Flat plate, stiffened'
self._unit_informations_dimensions = list()
@@ -1763,17 +1720,19 @@ def show_message():
ttk.Button(self._main_fr, text='Load factors', command=self.on_open_load_factor_window,style = "Bold.TButton")\
.place(relx=0.8225,rely=0.7, relwidth = 0.05)
- # PULS result information
- self._puls_information_button = ttk.Button(self._main_fr, text='PULS results for line',
- command=self.on_puls_results_for_line,style = "Bold.TButton")
- self._puls_information_button.place(relx=0.875,rely=0.7, relwidth = 0.075)
-
# Wight developement plot
self._weight_button = ttk.Button(self._main_fr, text='Weights',
command=self.on_plot_cog_dev,style = "Bold.TButton")
- self._weight_button.place(relx=0.9525,rely=0.7, relwidth = 0.038)
+ self._weight_button.place(relx=0.875,rely=0.7, relwidth = 0.038)
self.gui_structural_properties() # Initiating the flat panel structural properties
self.set_colors('default') # Setting colors theme
+
+ # Minimum practical size for the current Tkinter layout
+ parent.minsize(1200, 750)
+
+ self._resize_after_id = None
+ self._last_resize_size = (0, 0)
+
# self._current_theme = 'default'
def set_colors(self, theme):
@@ -1855,8 +1814,14 @@ def gui_structural_properties(self, flat_panel_stf_girder = False, flat_unstf =
vert_start = 0.04
hor_start = 0.02
- delta_y = 0.024
- delta_x = 0.13
+ width = max(self._parent.winfo_width(), 1)
+
+ if width < 1350:
+ delta_y = 0.028
+ delta_x = 0.12
+ else:
+ delta_y = 0.024
+ delta_x = 0.13
ent_relx = hor_start + 6*delta_x
@@ -1922,7 +1887,7 @@ def gui_structural_properties(self, flat_panel_stf_girder = False, flat_unstf =
idx += 1
self._flat_btn_load_info.place(relx=hor_start + 5 * delta_x,
rely=vert_start + (idx+1) * delta_y)
- self._flat_btn_fixation_info.place(relx=hor_start + 5 * delta_x,
+ self._flat_btn_fixation_info.place(relx=hor_start + 6 * delta_x,
rely=vert_start+ (idx-7.5) * delta_y)
self._button_str_type.place(relx=hor_start + 5 * delta_x,
rely=vert_start + (idx+3) * delta_y)
@@ -1966,8 +1931,6 @@ def gui_structural_properties(self, flat_panel_stf_girder = False, flat_unstf =
buckling_ent.place(relx=hor_start + 5 * delta_x, rely=vert_start + idx * delta_y)
idx += 1
- self._puls_run_all.place(relx=hor_start + 6 * delta_x, rely=vert_start + (idx-2) * delta_y)
-
# optimize buttons
for dom in ['Flat plate, unstiffened', 'Flat plate, stiffened', 'Flat plate, stiffened with girder',
@@ -2161,7 +2124,7 @@ def gui_structural_properties(self, flat_panel_stf_girder = False, flat_unstf =
rely=end_y + delta_y*other_count, relwidth=geo_ent_width*1.9)
other_count += 1
- if self._shell_geometries_map[self._new_calculation_domain.get()] in [1,5]:
+ if api_helpers.geometry_id_for_domain(self._new_calculation_domain.get()) in [1,5]:
self._lab_shell_en_cap_pressure.place(relx=hor_start,
rely= end_y + delta_y*other_count)
self._ent_shell_end_cap_pressure_included.place(relx=3 * delta_x,
@@ -2185,12 +2148,12 @@ def gui_structural_properties(self, flat_panel_stf_girder = False, flat_unstf =
def calculation_domain_selected(self, event = None):
'''
['Stiffened panel, flat', 'Unstiffened shell (Force input)', 'Unstiffened panel (Stress input)',
- 'Longitudinal Stiffened shell (Force input)', 'Longitudinal Stiffened panel (Stress input)',
+ 'Longitudinal Stiffened shell (Force input)', 'Longitudinal Stiffened panel (Stress input)',
'Ring Stiffened shell (Force input)', 'Ring Stiffened panel (Stress input)',
'Orthogonally Stiffened shell (Force input)', 'Orthogonally Stiffened panel (Stress input)']
'''
- to_process = [self._puls_run_all, self._lab_buckling_method,
+ to_process = [self._lab_buckling_method,
self._buckling_method, self._lab_yield,
self._lab_mat_fac,self._structure_types_label, self._button_str_type, self._ent_structure_type,
self._lab_structure_type, self._lab_kpp, self._lab_kps, self._lab_km1, self._lab_km2,
@@ -2222,7 +2185,7 @@ def calculation_domain_selected(self, event = None):
'''
geomeries = {1:'Unstiffened shell (Force input)',
2:'Unstiffened panel (Stress input)',
- 3:'Longitudinal Stiffened shell (Force input)',
+ 3:'Longitudinal Stiffened shell (Force input)',
4:'Longitudinal Stiffened panel (Stress input)',
5:'Ring Stiffened shell (Force input)',
6:'Ring Stiffened panel (Stress input)',
@@ -2246,7 +2209,7 @@ def calculation_domain_selected(self, event = None):
'Unstiffened panel (Stress input)']:
self.gui_structural_properties(flat_unstf=False, flat_stf = False, flat_panel_stf_girder = False,
shell=True, long_stf=False, ring_stf=False, ring_frame=False)
- elif self._new_calculation_domain.get() in ['Longitudinal Stiffened shell (Force input)',
+ elif self._new_calculation_domain.get() in ['Longitudinal Stiffened shell (Force input)',
'Longitudinal Stiffened panel (Stress input)']:
self.gui_structural_properties(flat_unstf=False, flat_stf = False, flat_panel_stf_girder = False,
shell=True, long_stf=True, ring_stf=False, ring_frame=False)
@@ -2294,69 +2257,6 @@ def calculation_domain_selected(self, event = None):
self._current_calculation_domain = self._new_calculation_domain.get()
# Setting the correct optmization buttons
- def puls_run_all_lines(self, line_given = None):
- progress = ttk.Progressbar(self._tab_prop, mode='indeterminate')
- progress.place(relx = 0.85, rely = 0.9, relwidth = 0.1)
- progress.start()
- if self._PULS_results is None:
- self._PULS_results = PULSpanel()
- if self._PULS_results.puls_sheet_location is None or not os.path.isfile(self._PULS_results.puls_sheet_location):
- tk.messagebox.showerror('No PULS excel sheet located', 'Set location of PULS excel sheet.\n'
- 'Note that PULS excel may require 32 bit '
- 'office.\n\n'
- 'A sheet may be provided but does not exist'
- ' in :\n'
- + str(self._PULS_results.puls_sheet_location) +
- '\n\n A file dialogue will pop up after this message.')
- self._PULS_results.puls_sheet_location= \
- tk.filedialog.askopenfilename(parent=self._main_fr,title='Set location of PULS excel sheet.')
- if self._PULS_results.puls_sheet_location == '':
- tk.messagebox.showerror('No valid PULS sheet', 'No excel sheet was provided. Cannot run PULS.\n'
- 'Note that PULS excel may require 32 bit office.')
- return
-
- dict_to_run = {}
- result_lines = list(self._PULS_results.get_run_results().keys())
-
- if line_given == None:
- current_button = self._puls_run_all
- for line, data in self._line_to_struc.items():
- if line not in result_lines:
- data[0].Plate.hw = 0 if data[0].Stiffener is None else data[0].Stiffener.hw
- data[0].Plate.tw = 0 if data[0].Stiffener is None else data[0].Stiffener.tw
- data[0].Plate.b = 0 if data[0].Stiffener is None else data[0].Stiffener.b
- data[0].Plate.tf = 0 if data[0].Stiffener is None else data[0].Stiffener.tf
- dict_to_run[line] = data[0].Plate.get_puls_input()
- dict_to_run[line]['Identification'] = line
- dict_to_run[line]['Pressure (fixed)'] = self.get_highest_pressure(line)['normal']/1e6
- else:
- current_button = self._puls_run_one
- if line_given == '':
- return
- if line_given not in result_lines:
- dict_to_run[line_given] = self._line_to_struc[line_given][0].Plate.get_puls_input()
- dict_to_run[line_given]['Identification'] = line_given
- dict_to_run[line_given]['Pressure (fixed)'] = self.get_highest_pressure(line_given)['normal'] / 1e6
-
-
- if len(dict_to_run) > 0:
-
- #current_button.config(relief = 'sunken')
- self._PULS_results.set_all_to_run(dict_to_run)
- self._PULS_results.run_all()
- #current_button.config(relief='raised')
- current_button.config(text='PULS run or\nupdate all lines' if line_given == None else 'PULS\nRun one line')
- #current_button.config(bg=self._button_bg_color)
- for key, value in self._line_to_struc.items():
- value[0].need_recalc = True
- else:
- tk.messagebox.showinfo('Results avaliable', 'PULS results is already avaliable for this line or no '
- 'lines need update.')
-
- progress.stop()
- progress.destroy()
- self.update_frame()
-
def stress_information_notebooks(self, info_type = 'shell'):
''' Shows stress information '''
text_m = tk.Toplevel(self._parent, background=self._general_color)
@@ -2416,10 +2316,6 @@ def stress_information_notebooks(self, info_type = 'shell'):
pass
text_widget.image_create('current', image=photo)
- def trace_puls_uf(self, *args):
- if self._PULS_results is not None:
- pass
-
def trace_material_factor(self, *args):
try:
self._new_puls_uf.set(1/self._new_material_factor.get())
@@ -2444,36 +2340,108 @@ def trace_puls_up_or_sp(self, event = None):
else:
self._ent_puls_up_boundary.place_forget()
- def puls_run_one_line(self):
- self.puls_run_all_lines(self._active_line)
- self.update_frame()
+ def resize(self, event=None):
+ """
+ Responsive GUI scaling.
- def puls_delete_all(self):
- '''
- Deletes all existing PULS results
- '''
- if self._PULS_results is not None:
- for key, val in self._line_to_struc.items():
- self._PULS_results.result_changed(key)
- val[0].need_recalc = True
+ The old implementation kept text_scale = 1 and did not call update_frame().
+ On small laptop screens, relative widget placement shrinks the available
+ widget area while the text remains full size, causing overlap.
+ """
- self.update_frame()
+ if event is not None and event.widget is not self._parent:
+ return
- def resize(self, event):
- self.text_scale = 1#self._main_fr.winfo_width()/1920
- self._text_size = {'Text 14 bold': 'Verdana '+str(int(14*self.text_scale))+' bold',
- 'Text 16 bold': 'Verdana ' + str(int(16 * self.text_scale)) + ' bold',
- 'Text 18 bold': 'Verdana ' + str(int(18 * self.text_scale)) + ' bold',
- 'Text 12 bold': 'Verdana ' + str(int(12 * self.text_scale)) + ' bold',
- 'Text 10 bold': 'Verdana '+str(int(10*self.text_scale))+' bold',
- 'Text 9 bold': 'Verdana ' + str(int(9 * self.text_scale)) + ' bold',
- 'Text 8 bold': 'Verdana ' + str(int(8 * self.text_scale)) + ' bold',
- 'Text 8': 'Verdana ' + str(int(8 * self.text_scale)),
- 'Text 9': 'Verdana ' + str(int(8 * self.text_scale)),
- 'Text 7': 'Verdana ' + str(int(7 * self.text_scale)),
- 'Text 10': 'Verdana ' + str(int(10 * self.text_scale)),
- 'Text 7 bold': 'Verdana ' + str(int(7 * self.text_scale)) + ' bold'}
- #self.update_frame()
+ width = max(self._parent.winfo_width(), 1)
+ height = max(self._parent.winfo_height(), 1)
+
+ if (width, height) == self._last_resize_size:
+ return
+
+ self._last_resize_size = (width, height)
+
+ # Scale against a comfortable reference layout.
+ # Clamp to avoid unreadably small or unnecessarily large fonts.
+ scale_w = width / 1600.0
+ scale_h = height / 900.0
+ self.text_scale = max(0.78, min(1.05, min(scale_w, scale_h)))
+
+ self._text_size = {
+ 'Text 14 bold': 'Verdana ' + str(max(9, int(14 * self.text_scale))) + ' bold',
+ 'Text 16 bold': 'Verdana ' + str(max(10, int(16 * self.text_scale))) + ' bold',
+ 'Text 18 bold': 'Verdana ' + str(max(11, int(18 * self.text_scale))) + ' bold',
+ 'Text 12 bold': 'Verdana ' + str(max(8, int(12 * self.text_scale))) + ' bold',
+ 'Text 10 bold': 'Verdana ' + str(max(7, int(10 * self.text_scale))) + ' bold',
+ 'Text 9 bold': 'Verdana ' + str(max(7, int(9 * self.text_scale))) + ' bold',
+ 'Text 8 bold': 'Verdana ' + str(max(6, int(8 * self.text_scale))) + ' bold',
+ 'Text 8': 'Verdana ' + str(max(6, int(8 * self.text_scale))),
+ 'Text 9': 'Verdana ' + str(max(6, int(9 * self.text_scale))),
+ 'Text 7': 'Verdana ' + str(max(6, int(7 * self.text_scale))),
+ 'Text 10': 'Verdana ' + str(max(7, int(10 * self.text_scale))),
+ 'Text 7 bold': 'Verdana ' + str(max(6, int(7 * self.text_scale))) + ' bold',
+ 'Text 6 bold': 'Verdana ' + str(max(6, int(6 * self.text_scale))) + ' bold',
+ }
+
+ self._apply_responsive_main_layout(width, height)
+
+ # Throttle redraws while user drags/resizes the window.
+ if self._resize_after_id is not None:
+ self._parent.after_cancel(self._resize_after_id)
+
+ self._resize_after_id = self._parent.after(150, self._finish_resize)
+
+ def _finish_resize(self):
+ self._resize_after_id = None
+
+ try:
+ self.update_frame()
+ except Exception:
+ # Avoid crashing during early initialization.
+ pass
+
+ def _apply_responsive_main_layout(self, width, height):
+ """
+ Adjust the major GUI regions for smaller laptop screens.
+
+ The old layout used:
+ tab width = 0.2585
+ main canvas start x = 0.26
+
+ That leaves too little room for the input notebook on small screens.
+ """
+
+ if width < 1350:
+ left_width = 0.34
+ elif width < 1600:
+ left_width = 0.30
+ else:
+ left_width = 0.2585
+
+ x_canvas_place = left_width + 0.005
+ remaining_width = 1.0 - x_canvas_place
+
+ self._tabControl.place(relwidth=left_width, relheight=1)
+
+ self._main_canvas.place(
+ relx=x_canvas_place,
+ rely=0,
+ relwidth=remaining_width * 0.70,
+ relheight=0.73,
+ )
+
+ self._prop_canvas.place(
+ relx=x_canvas_place,
+ rely=0.73,
+ relwidth=remaining_width * 0.52,
+ relheight=0.27,
+ )
+
+ self._result_canvas.place(
+ relx=x_canvas_place + remaining_width * 0.52,
+ rely=0.73,
+ relwidth=remaining_width * 0.48,
+ relheight=0.27,
+ )
def toggle_select_multiple(self, event = None):
if self._toggle_btn.config('relief')[-1] == 'sunken':
@@ -2846,7 +2814,7 @@ def add_to_combinations_dict(self,line):
def trace_update_load(self, *args):
try:
- self._line_to_struc[self._active_line][0].need_recalc = True
+ project_services.mark_line_for_recalculation(self._line_to_struc, self._active_line)
except BaseException as error:
pass
@@ -2859,8 +2827,7 @@ def trace_shift_change(self, *args):
def trace_acceptance_change(self, *args):
try:
self.update_frame()
- for key, val in self._line_to_struc.items():
- val[0].need_recalc = True
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
except (TclError, ZeroDivisionError):
pass
@@ -2874,14 +2841,181 @@ def update_frame(self, event = None, *args):
return state
- def get_color_and_calc_state(self, current_line = None, active_line_only = False):
+ def _ml_model_exists(self, mat_fac, key):
+ return (
+ mat_fac in self._ML_buckling
+ and key in self._ML_buckling[mat_fac]
+ and self._ML_buckling[mat_fac][key] is not None
+ )
+
+ def _predict_numeric_uf_pipeline(self, mat_fac, sp_or_up, boundary_type, buckling_ml_input):
+ """
+ Predict raw numeric buckling/ultimate UF using the numeric UF pipeline.
+
+ Important:
+ The numeric model is trained on Buckling/Ultimate UF at material factor = 1.0.
+ Material factor scaling is applied later in get_color_and_calc_state().
+ """
+ if sp_or_up == 'SP':
+ prefix = 'num SP int' if boundary_type == 'Int' else 'num SP GLGT'
+ else:
+ prefix = 'num UP int' if boundary_type == 'Int' else 'num UP GLGT'
+
+ valid_predictor_key = f'{prefix} validity predictor'
+ valid_xscaler_key = f'{prefix} validity xscaler'
+ reg_predictor_key = f'{prefix} UF reg predictor'
+ reg_xscaler_key = f'{prefix} UF reg xscaler'
+ reg_yscaler_key = f'{prefix} UF reg yscaler'
+
+ required_keys = [
+ valid_predictor_key,
+ valid_xscaler_key,
+ reg_predictor_key,
+ reg_xscaler_key,
+ reg_yscaler_key,
+ ]
+
+ missing = [key for key in required_keys if not self._ml_model_exists(mat_fac, key)]
+ if missing:
+ return {
+ 'available': False,
+ 'valid_prediction': None,
+ 'valid_label': 'numeric ML unavailable',
+ 'buckling_uf': float('inf'),
+ 'ultimate_uf': float('inf'),
+ 'buckling_uf_raw': float('inf'),
+ 'ultimate_uf_raw': float('inf'),
+ 'material_factor': mat_fac,
+ 'error': 'Missing numeric ML model(s): ' + ', '.join(missing),
+ }
+
+ try:
+ x_valid = self._ML_buckling[mat_fac][valid_xscaler_key].transform(
+ buckling_ml_input
+ )
+ valid_pred = self._ML_buckling[mat_fac][valid_predictor_key].predict(
+ x_valid
+ )[0]
+ valid_pred_int = int(valid_pred)
+
+ if valid_pred_int != 1:
+ return {
+ 'available': True,
+ 'valid_prediction': valid_pred_int,
+ 'valid_label': 'invalid/NaN UF predicted',
+ 'buckling_uf': float('inf'),
+ 'ultimate_uf': float('inf'),
+ 'buckling_uf_raw': float('inf'),
+ 'ultimate_uf_raw': float('inf'),
+ 'material_factor': mat_fac,
+ 'error': '',
+ }
+
+ x_reg = self._ML_buckling[mat_fac][reg_xscaler_key].transform(
+ buckling_ml_input
+ )
+ y_scaled = self._ML_buckling[mat_fac][reg_predictor_key].predict(
+ x_reg
+ )
+ y_numeric = self._ML_buckling[mat_fac][reg_yscaler_key].inverse_transform(
+ y_scaled
+ )[0]
+
+ buckling_uf_raw = float(y_numeric[0])
+ ultimate_uf_raw = float(y_numeric[1])
+
+ return {
+ 'available': True,
+ 'valid_prediction': valid_pred_int,
+ 'valid_label': 'valid numeric UF predicted',
+ 'buckling_uf': buckling_uf_raw,
+ 'ultimate_uf': ultimate_uf_raw,
+ 'buckling_uf_raw': buckling_uf_raw,
+ 'ultimate_uf_raw': ultimate_uf_raw,
+ 'material_factor': mat_fac,
+ 'error': '',
+ }
+
+ except Exception as e:
+ return {
+ 'available': False,
+ 'valid_prediction': None,
+ 'valid_label': 'numeric ML error',
+ 'buckling_uf': float('inf'),
+ 'ultimate_uf': float('inf'),
+ 'buckling_uf_raw': float('inf'),
+ 'ultimate_uf_raw': float('inf'),
+ 'material_factor': mat_fac,
+ 'error': str(e),
+ }
+
+ def _numeric_uf_color(self, uf, mat_fac=None):
+ """
+ Numeric UF is material-factored in get_color_and_calc_state().
+ Therefore the acceptance limit is 1.0.
+ """
+ try:
+ return 'green' if float(uf) <= 1.0 else 'red'
+ except Exception:
+ return 'red'
+
+ def _semi_analytical_uf_color(self, uf):
+ """SemiAnalytical UF is material-factored before pass/fail coloring."""
+ try:
+ return 'green' if float(uf) <= 1.0 else 'red'
+ except Exception:
+ return 'red'
+
+ @staticmethod
+ def _cylinder_buckling_uf(cylinder_results):
+ """Return the governing cylinder buckling UF used for GUI color coding."""
+ uf_values = []
+ for key in ['Unstiffened shell', 'Longitudinal stiffened shell',
+ 'Ring stiffened shell', 'Heavy ring frame']:
+ try:
+ if cylinder_results.get(key, None) is not None:
+ uf_values.append(float(cylinder_results[key]))
+ except Exception:
+ pass
+
+ try:
+ if cylinder_results.get('Need to check column buckling', False) is True and \
+ cylinder_results.get('Column stability UF', None) is not None:
+ uf_values.append(float(cylinder_results['Column stability UF']))
+ except Exception:
+ pass
+
+ return max(uf_values) if uf_values else 0.0
+
+ def get_color_and_calc_state(self, current_line=None, active_line_only=False):
''' Return calculations and colors for line and results. '''
- return_dict = {'colors': {}, 'section_modulus': {}, 'thickness': {}, 'shear_area': {}, 'buckling': {},
- 'fatigue': {}, 'pressure_uls': {}, 'pressure_fls': {},
- 'all_obj': {}, 'scant_calc_obj': {}, 'fatigue_obj': {}, 'utilization': {}, 'slamming': {},
- 'color code': {}, 'PULS colors': {}, 'ML buckling colors' : {}, 'ML buckling class' : {},
- 'weights': {}, 'cylinder': {}}
+ return_dict = {
+ 'colors': {},
+ 'section_modulus': {},
+ 'thickness': {},
+ 'shear_area': {},
+ 'buckling': {},
+ 'fatigue': {},
+ 'pressure_uls': {},
+ 'pressure_fls': {},
+ 'all_obj': {},
+ 'scant_calc_obj': {},
+ 'fatigue_obj': {},
+ 'utilization': {},
+ 'slamming': {},
+ 'color code': {},
+ 'ML buckling colors': {},
+ 'ML buckling class': {},
+ 'ML buckling numeric': {},
+ 'ML buckling numeric valid': {},
+ 'ML buckling numeric colors': {},
+ 'SemiAnalytical': {},
+ 'SemiAnalytical valid': {},
+ 'SemiAnalytical colors': {},
+ 'weights': {},
+ 'cylinder': {},
+ }
return_dict['slamming'][current_line] = {}
@@ -2908,40 +3042,52 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
all_cyl_thk = np.sort(all_cyl_thk)
for current_line in line_iterator:
- rec_for_color[current_line] = {}
+ rec_for_color[current_line] = {}
slamming_pressure = 0
if current_line in self._line_to_struc.keys():
-
if self._line_to_struc[current_line][5] is not None:
cyl_obj = self._line_to_struc[current_line][5]
- # cyl_radius = round(cyl_obj.ShellObj.radius * 1000, 2)
- # cyl_thickness = round(cyl_obj.ShellObj.thk * 1000, 2)
- # cyl_long_str = cyl_obj.LongStfObj.get_beam_string()
- # cyl_ring_stf = cyl_obj.LongStfObj.get_beam_string()
- # cyl_heavy_ring = cyl_obj.LongStfObj.get_beam_string()
- # cyl_span = round(cyl_obj.ShellObj.dist_between_rings, 1)
- # cyl_tot_length = round(cyl_obj.ShellObj.length_of_shell, 1)
- # cyl_tot_cyl = round(cyl_obj.ShellObj.tot_cyl_length, 1)
- # cyl_sigma_axial = cyl_obj.sasd / 1e6
- # cyl_sigma_bend = cyl_obj.smsd / 1e6
- # cyl_sigma_tors = cyl_obj.tTsd / 1e6
- # cyl_tau_xy = cyl_obj.tQsd / 1e6
- # cyl_lat_press = cyl_obj.psd / 1e6
- # cyl_sigma_hoop = cyl_obj.shsd / 1e6
cyl_results = cyl_obj.get_utilization_factors()
else:
cyl_thickness = 0
all_obj = self._line_to_struc[current_line][0]
- obj_scnt_calc_pl = all_obj.Plate #self._line_to_struc[current_line][1]
+ obj_scnt_calc_pl = all_obj.Plate # self._line_to_struc[current_line][1]
obj_scnt_calc_stf = all_obj.Stiffener # self._line_to_struc[current_line][1]
obj_scnt_calc_girder = all_obj.Girder # self._line_to_struc[current_line][1]
return_dict['all_obj'][current_line] = all_obj
if all_obj.need_recalc is False:
- return self._state_logger[current_line]
+ # Cached state may be stale when the selected ML material factor changes.
+ # Reuse cache only when the cached numeric ML material factor matches
+ # the current GUI-selected material factor.
+ cached_state = self._state_logger.get(current_line, None)
+ try:
+ selected_mat_fac = float(self._new_material_factor.get())
+ except Exception:
+ selected_mat_fac = obj_scnt_calc_pl.mat_factor
+
+ cached_mat_fac = None
+ cached_has_semi_analytical = False
+ if cached_state is not None:
+ cached_mat_fac = (
+ cached_state
+ .get('ML buckling numeric', {})
+ .get(current_line, {})
+ .get('material factor', None)
+ )
+ cached_has_semi_analytical = current_line in cached_state.get('SemiAnalytical', {})
+
+ try:
+ cached_mat_fac = None if cached_mat_fac is None else float(cached_mat_fac)
+ except Exception:
+ cached_mat_fac = None
+
+ if cached_state is not None and cached_mat_fac == selected_mat_fac and cached_has_semi_analytical:
+ return cached_state
+ # Otherwise continue and recalculate this line so numeric UF is updated.
try:
norm_and_slam = self.get_highest_pressure(current_line)
design_pressure = norm_and_slam['normal'] / 1000
@@ -2958,7 +3104,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
min_thk = obj_scnt_calc_pl.get_dnv_min_thickness(design_pressure)
color_thk = 'green' if obj_scnt_calc_pl.is_acceptable_pl_thk(design_pressure) else 'red'
rec_for_color[current_line]['plate thickness'] = (min_thk / 1000) / obj_scnt_calc_pl.get_pl_thk()
- all_obj.lat_press = design_pressure/1000
+ all_obj.lat_press = design_pressure / 1000
buckling = all_obj.plate_buckling()
all_buckling_uf_list = list()
@@ -2977,7 +3123,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
min_shear = obj_scnt_calc_stf.get_minimum_shear_area(design_pressure)
min_sec_mod = obj_scnt_calc_stf.get_dnv_min_section_modulus(design_pressure)
rec_for_color[current_line]['section modulus'] = min_sec_mod / min(sec_mod)
- rec_for_color[current_line]['shear'] = min_shear/shear_area
+ rec_for_color[current_line]['shear'] = min_shear / shear_area
return_dict['slamming'][current_line] = dict()
if slamming_pressure is not None and slamming_pressure > 0:
return_dict['slamming'][current_line]['state'] = True
@@ -3003,20 +3149,20 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
color_shear = 'green' if obj_scnt_calc_stf.is_acceptable_shear_area(shear_area, design_pressure) \
else 'red'
else:
- sec_mod = [0,0]
+ sec_mod = [0, 0]
rec_for_color[current_line]['section modulus'] = 0.0
rec_for_color[current_line]['shear'] = 0
return_dict['slamming'][current_line] = dict()
fatigue_obj, p_int, p_ext, damage, dff = [None for dummy in range(5)]
color_sec = 'green' if all_obj.Stiffener is None else 'black'
- color_shear = 'green' if all_obj.Stiffener is None else'black'
+ color_shear = 'green' if all_obj.Stiffener is None else 'black'
return_dict['slamming'][current_line]['state'] = False
if slamming_pressure is not None and slamming_pressure > 0 and obj_scnt_calc_stf is not None:
slamming_res = obj_scnt_calc_stf.calculate_slamming_stiffener(slamming_pressure,
- red_fac=slamming_red_fac_pl)
+ red_fac=slamming_red_fac_pl)
min_pl_slamming = obj_scnt_calc_stf.calculate_slamming_plate(slamming_pressure,
- red_fac=slamming_red_fac_stf)
+ red_fac=slamming_red_fac_stf)
if slamming_res['Zp_req'] is not None:
zpl = obj_scnt_calc_stf.get_net_effective_plastic_section_modulus()
@@ -3027,10 +3173,10 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
zpl_req = None
color_sec = 'red'
- color_shear = 'green' if round(obj_scnt_calc_stf.get_web_thk()* 1000,1) >= \
- round(slamming_res['tw_req'],1) else 'red'
- color_thk = 'green' if round(obj_scnt_calc_stf.get_pl_thk() * 1000,1) >= \
- round(min_pl_slamming,1) else 'red'
+ color_shear = 'green' if round(obj_scnt_calc_stf.get_web_thk() * 1000, 1) >= \
+ round(slamming_res['tw_req'], 1) else 'red'
+ color_thk = 'green' if round(obj_scnt_calc_stf.get_pl_thk() * 1000, 1) >= \
+ round(min_pl_slamming, 1) else 'red'
return_dict['slamming'][current_line]['zpl'] = zpl
return_dict['slamming'][current_line]['zpl_req'] = zpl_req
@@ -3046,156 +3192,489 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
if self._line_to_struc[current_line][5] is not None:
return_dict['cylinder'][current_line] = cyl_results
-
- '''
- PULS calculations
'''
- if self._PULS_results != None:
- res = self._PULS_results.get_puls_line_results(current_line)
- if res is not None:
- geo_problem = False
- if type(res['Ultimate capacity']['Actual usage Factor'][0]) != str:
- ufnum = res['Ultimate capacity']['Actual usage Factor'][0] / self._new_puls_uf.get()
- rec_for_color[current_line]['PULS ultimate']=ufnum
- col_ult = 'green' if ufnum < 1 else 'red'
- else:
- geo_problem = True
- col_ult = 'red'
- if res['Buckling strength']['Actual usage Factor'][0] is not None:
- bnum = res['Buckling strength']['Actual usage Factor'][0] / self._new_puls_uf.get()
- rec_for_color[current_line]['PULS buckling'] = bnum
- col_buc = 'green' if bnum < 1 else 'red'
- else:
- col_buc = 'red'
- if geo_problem:
- loc_geom = 'red'
- else:
- if obj_scnt_calc_stf is None:
- loc_label = 'Geom. Req (PULS validity limits)'
- else:
- loc_label = 'Local geom req (PULS validity limits)' if \
- obj_scnt_calc_stf.get_puls_sp_or_up() == 'SP' else 'Geom. Req (PULS validity limits)'
- loc_geom = 'green' if all([val[0] == 'Ok' for val in res[loc_label].values()]) else 'red'
- if obj_scnt_calc_stf is None:
- csr_label = 'CSR-Tank req'
- else:
- csr_label = 'CSR-Tank requirements (primary stiffeners)' if \
- obj_scnt_calc_stf.get_puls_sp_or_up() == 'SP' else'CSR-Tank req'
+ Machine learning buckling.
- csr_geom = 'green' if all([val[0] in ['Ok', '-'] for val in res[csr_label].values()]) else 'red'
- return_dict['PULS colors'][current_line] = {'ultimate': col_ult, 'buckling': col_buc,
- 'local geometry': loc_geom, 'csr': csr_geom}
- else:
- return_dict['PULS colors'][current_line] = {'ultimate': 'black', 'buckling': 'black',
- 'local geometry': 'black', 'csr': 'black'}
- else:
- return_dict['PULS colors'][current_line] = {'ultimate': 'black', 'buckling': 'black',
- 'local geometry': 'black', 'csr': 'black'}
+ Classification pipeline:
+ return_dict['ML buckling class']
+ return_dict['ML buckling colors']
+ Numeric UF pipeline:
+ return_dict['ML buckling numeric']
+ return_dict['ML buckling numeric valid']
+ return_dict['ML buckling numeric colors']
'''
- Machine learning buckling
- ['cl SP buc int predictor', 'cl SP buc int scaler',
- 'cl SP ult int predictor', 'cl SP ult int scaler',
- 'cl SP buc GLGT predictor', 'cl SP buc GLGT scaler',
- 'cl SP ult GLGT predictor', 'cl SP ult GLGT scaler']
- '''
+
mat_fac_error = ''
+
+ # -----------------------------------------------------------------------------
+ # Helper defaults in case ML cannot be evaluated
+ # -----------------------------------------------------------------------------
+ default_numeric_pred = {
+ 'available': False,
+ 'valid_prediction': None,
+ 'valid_label': 'numeric ML not available',
+ 'buckling_uf': float('inf'),
+ 'ultimate_uf': float('inf'),
+ 'buckling_uf_raw': float('inf'),
+ 'ultimate_uf_raw': float('inf'),
+ 'material_factor': None,
+ 'error': '',
+ }
+
+ def _apply_material_factor_to_numeric_pred(numeric_pred, mat_fac):
+ """
+ Numeric UF model predicts UF at material factor = 1.0.
+
+ The displayed/check UF shall be:
+ UF = predicted_UF * material_factor
+ """
+
+ numeric_pred = numeric_pred.copy()
+
+ if numeric_pred.get('valid_prediction', None) == 1:
+ buckling_uf_raw = float(numeric_pred.get('buckling_uf', float('inf')))
+ ultimate_uf_raw = float(numeric_pred.get('ultimate_uf', float('inf')))
+
+ numeric_pred['buckling_uf_raw'] = buckling_uf_raw
+ numeric_pred['ultimate_uf_raw'] = ultimate_uf_raw
+
+ numeric_pred['buckling_uf'] = buckling_uf_raw * mat_fac
+ numeric_pred['ultimate_uf'] = ultimate_uf_raw * mat_fac
+ numeric_pred['material_factor'] = mat_fac
+ else:
+ numeric_pred['buckling_uf_raw'] = float('inf')
+ numeric_pred['ultimate_uf_raw'] = float('inf')
+ numeric_pred['material_factor'] = mat_fac
+
+ return numeric_pred
+
if obj_scnt_calc_pl.get_puls_sp_or_up() == 'UP':
- buckling_ml_input = obj_scnt_calc_pl.get_buckling_ml_input(design_lat_press=design_pressure)
- mat_fac = obj_scnt_calc_pl.mat_factor
+ # =========================================================================
+ # UP / unstiffened panel ML
+ # =========================================================================
+ buckling_ml_input = obj_scnt_calc_pl.get_buckling_ml_input(
+ design_lat_press=design_pressure
+ )
+
+ try:
+ mat_fac = float(self._new_material_factor.get())
+ except Exception:
+ mat_fac = obj_scnt_calc_pl.mat_factor
mat_fac_error = ''
+
if mat_fac not in [1.1, 1.15]:
mat_fac_error = ' MATERIAL FACTOR MUST BE 1.1 or 1.15 -> using 1.15'
mat_fac = 1.15
- if obj_scnt_calc_pl.get_puls_boundary() == 'Int':
- if self._ML_buckling[mat_fac]['cl UP buc int predictor'] != None:
- x_buc = self._ML_buckling[mat_fac]['cl UP buc int scaler'].transform(buckling_ml_input)
- y_pred_buc = self._ML_buckling[mat_fac]['cl UP buc int predictor'].predict(x_buc)[0]
+
+ boundary_type = obj_scnt_calc_pl.get_puls_boundary()
+
+ # -------------------------------------------------------------------------
+ # Classification ML prediction
+ # -------------------------------------------------------------------------
+ if boundary_type == 'Int':
+ if self._ML_buckling[mat_fac]['cl UP buc int predictor'] is not None:
+ x_buc = self._ML_buckling[mat_fac]['cl UP buc int scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_buc = self._ML_buckling[mat_fac]['cl UP buc int predictor'].predict(
+ x_buc
+ )[0]
else:
- y_pred_buc = 0
- if self._ML_buckling[mat_fac]['cl UP ult int predictor'] != None:
- x_ult = self._ML_buckling[mat_fac]['cl UP ult int scaler'].transform(buckling_ml_input)
- y_pred_ult = self._ML_buckling[mat_fac]['cl UP ult int predictor'].predict(x_ult)[0]
+ y_pred_buc = 'ML buckling model missing'
+
+ if self._ML_buckling[mat_fac]['cl UP ult int predictor'] is not None:
+ x_ult = self._ML_buckling[mat_fac]['cl UP ult int scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_ult = self._ML_buckling[mat_fac]['cl UP ult int predictor'].predict(
+ x_ult
+ )[0]
else:
- y_pred_ult = 0
+ y_pred_ult = 'ML ultimate model missing'
+
else:
- if self._ML_buckling[mat_fac]['cl UP buc GLGT predictor'] != None:
- x_buc = self._ML_buckling[mat_fac]['cl UP buc GLGT scaler'].transform(buckling_ml_input)
- y_pred_buc = self._ML_buckling[mat_fac]['cl UP buc GLGT predictor'].predict(x_buc)[0]
- else:
- y_pred_buc = 0
- if self._ML_buckling[mat_fac]['cl UP ult GLGT predictor'] != None:
- x_ult = self._ML_buckling[mat_fac]['cl UP ult GLGT scaler'].transform(buckling_ml_input)
- y_pred_ult = self._ML_buckling[mat_fac]['cl UP ult GLGT predictor'].predict(x_ult)[0]
+ if self._ML_buckling[mat_fac]['cl UP buc GLGT predictor'] is not None:
+ x_buc = self._ML_buckling[mat_fac]['cl UP buc GLGT scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_buc = self._ML_buckling[mat_fac]['cl UP buc GLGT predictor'].predict(
+ x_buc
+ )[0]
else:
- y_pred_ult = 0
+ y_pred_buc = 'ML buckling model missing'
- x_csr = obj_scnt_calc_pl.get_buckling_ml_input(design_lat_press=design_pressure, csr = True)
- x_csr = self._ML_buckling[mat_fac]['CSR scaler UP'].transform(x_csr)
- csr_pl = self._ML_buckling[mat_fac]['CSR predictor UP'].predict(x_csr)[0]
+ if self._ML_buckling[mat_fac]['cl UP ult GLGT predictor'] is not None:
+ x_ult = self._ML_buckling[mat_fac]['cl UP ult GLGT scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_ult = self._ML_buckling[mat_fac]['cl UP ult GLGT predictor'].predict(
+ x_ult
+ )[0]
+ else:
+ y_pred_ult = 'ML ultimate model missing'
- if self._new_material_factor.get() == 1.1:
+ # -------------------------------------------------------------------------
+ # Numeric UF pipeline
+ # -------------------------------------------------------------------------
+ try:
+ numeric_pred = self._predict_numeric_uf_pipeline(
+ mat_fac=mat_fac,
+ sp_or_up='UP',
+ boundary_type=boundary_type,
+ buckling_ml_input=buckling_ml_input,
+ )
+ except Exception as e:
+ numeric_pred = default_numeric_pred.copy()
+ numeric_pred['valid_label'] = 'numeric ML error'
+ numeric_pred['error'] = str(e)
+
+ numeric_pred = _apply_material_factor_to_numeric_pred(numeric_pred, mat_fac)
+
+ # -------------------------------------------------------------------------
+ # CSR prediction
+ # -------------------------------------------------------------------------
+ try:
+ x_csr = obj_scnt_calc_pl.get_buckling_ml_input(
+ design_lat_press=design_pressure,
+ csr=True,
+ )
+ x_csr = self._ML_buckling[mat_fac]['CSR scaler UP'].transform(x_csr)
+ csr_pl = self._ML_buckling[mat_fac]['CSR predictor UP'].predict(x_csr)[0]
+ except Exception:
+ csr_pl = 0
+
+ if mat_fac == 1.1:
accept = 'below or equal 0.91'
else:
accept = 'below or equal 0.87'
- return_dict['ML buckling colors'][current_line] = \
- {'buckling': 'green' if y_pred_buc == accept else 'red',
- 'ultimate': 'green' if y_pred_ult == accept else 'red',
- 'CSR requirement': 'green' if csr_pl == 1 else 'red'}
+ return_dict['ML buckling colors'][current_line] = {
+ 'buckling': 'green' if y_pred_buc == accept else 'red',
+ 'ultimate': 'green' if y_pred_ult == accept else 'red',
+ 'CSR requirement': 'green' if csr_pl == 1 else 'red',
+ }
+
+ return_dict['ML buckling class'][current_line] = {
+ 'buckling': str(y_pred_buc) + mat_fac_error,
+ 'ultimate': str(y_pred_ult) + mat_fac_error,
+ 'CSR': [csr_pl, float('inf'), float('inf'), float('inf')],
+ }
+
+ # -------------------------------------------------------------------------
+ # Numeric UF color handling
+ # -------------------------------------------------------------------------
+ if numeric_pred.get('valid_prediction', None) == 1:
+ numeric_buc_color = self._numeric_uf_color(
+ numeric_pred['buckling_uf'],
+ mat_fac,
+ )
+ numeric_ult_color = self._numeric_uf_color(
+ numeric_pred['ultimate_uf'],
+ mat_fac,
+ )
+ else:
+ numeric_buc_color = 'red'
+ numeric_ult_color = 'red'
+
+ return_dict['ML buckling numeric'][current_line] = {
+ 'buckling UF': numeric_pred['buckling_uf'],
+ 'ultimate UF': numeric_pred['ultimate_uf'],
+ 'buckling UF raw': numeric_pred.get('buckling_uf_raw', float('inf')),
+ 'ultimate UF raw': numeric_pred.get('ultimate_uf_raw', float('inf')),
+ 'material factor': numeric_pred.get('material_factor', mat_fac),
+ 'error': numeric_pred['error'],
+ }
+
+ return_dict['ML buckling numeric valid'][current_line] = {
+ 'available': numeric_pred['available'],
+ 'valid prediction': numeric_pred['valid_prediction'],
+ 'valid label': numeric_pred['valid_label'],
+ }
+
+ return_dict['ML buckling numeric colors'][current_line] = {
+ 'buckling': numeric_buc_color,
+ 'ultimate': numeric_ult_color,
+ }
- return_dict['ML buckling class'][current_line] = {'buckling': y_pred_buc + mat_fac_error,
- 'ultimate': y_pred_ult + mat_fac_error,
- 'CSR': [csr_pl, float('inf'),
- float('inf'), float('inf')]}
else:
- buckling_ml_input = obj_scnt_calc_stf.get_buckling_ml_input(design_lat_press=design_pressure)
- mat_fac = obj_scnt_calc_pl.mat_factor
- mat_fac_error = ''
- if mat_fac not in [1.1, 1.15]:
- mat_fac = 1.15
- mat_fac_error = ' MATERIAL FACTOR MUST BE 1.1 or 1.15 -> using 1.15'
- if obj_scnt_calc_stf.get_puls_boundary() == 'Int':
- if self._ML_buckling[mat_fac]['cl SP buc int predictor'] != None:
- x_buc = self._ML_buckling[mat_fac]['cl SP buc int scaler'].transform(buckling_ml_input)
- y_pred_buc = self._ML_buckling[mat_fac]['cl SP buc int predictor'].predict(x_buc)[0]
+ # =========================================================================
+ # SP / stiffened panel ML
+ # =========================================================================
+
+ # Defensive guard: SP ML requires a stiffener object.
+ if obj_scnt_calc_stf is None:
+ try:
+ mat_fac = float(self._new_material_factor.get())
+ except Exception:
+ mat_fac = obj_scnt_calc_pl.mat_factor
+
+ if mat_fac not in [1.1, 1.15]:
+ mat_fac_error = ' MATERIAL FACTOR MUST BE 1.1 or 1.15 -> using 1.15'
+ mat_fac = 1.15
else:
- y_pred_buc = 0
- if self._ML_buckling[mat_fac]['cl SP ult int predictor'] != None:
- x_ult = self._ML_buckling[mat_fac]['cl SP ult int scaler'].transform(buckling_ml_input)
- y_pred_ult = self._ML_buckling[mat_fac]['cl SP ult int predictor'].predict(x_ult)[0]
- else:
- y_pred_ult = 0
+ mat_fac_error = ''
+
+ return_dict['ML buckling colors'][current_line] = {
+ 'buckling': 'red',
+ 'ultimate': 'red',
+ 'CSR requirement': 'red',
+ }
+
+ return_dict['ML buckling class'][current_line] = {
+ 'buckling': 'No stiffener - ML SP not available' + mat_fac_error,
+ 'ultimate': 'No stiffener - ML SP not available' + mat_fac_error,
+ 'CSR': [0, 0, 0, 0],
+ }
+
+ return_dict['ML buckling numeric'][current_line] = {
+ 'buckling UF': float('inf'),
+ 'ultimate UF': float('inf'),
+ 'buckling UF raw': float('inf'),
+ 'ultimate UF raw': float('inf'),
+ 'material factor': mat_fac,
+ 'error': 'No stiffener - numeric ML SP not available',
+ }
+
+ return_dict['ML buckling numeric valid'][current_line] = {
+ 'available': False,
+ 'valid prediction': None,
+ 'valid label': 'No stiffener - numeric ML SP not available',
+ }
+
+ return_dict['ML buckling numeric colors'][current_line] = {
+ 'buckling': 'red',
+ 'ultimate': 'red',
+ }
+
else:
- if self._ML_buckling[mat_fac]['cl SP buc GLGT predictor'] != None:
- x_buc = self._ML_buckling[mat_fac]['cl SP buc GLGT scaler'].transform(buckling_ml_input)
- y_pred_buc = self._ML_buckling[mat_fac]['cl SP buc GLGT predictor'].predict(x_buc)[0]
- else:
- y_pred_buc = 0
- if self._ML_buckling[mat_fac]['cl SP ult GLGT predictor'] != None:
- x_ult = self._ML_buckling[mat_fac]['cl SP ult GLGT scaler'].transform(buckling_ml_input)
- y_pred_ult = self._ML_buckling[mat_fac]['cl SP ult GLGT predictor'].predict(x_ult)[0]
- else:
- y_pred_ult = 0
+ buckling_ml_input = obj_scnt_calc_stf.get_buckling_ml_input(
+ design_lat_press=design_pressure
+ )
+
+ try:
+ mat_fac = float(self._new_material_factor.get())
+ except Exception:
+ mat_fac = obj_scnt_calc_pl.mat_factor
+ mat_fac_error = ''
+
+ if mat_fac not in [1.1, 1.15]:
+ mat_fac = 1.15
+ mat_fac_error = ' MATERIAL FACTOR MUST BE 1.1 or 1.15 -> using 1.15'
+
+ boundary_type = obj_scnt_calc_stf.get_puls_boundary()
+
+ # ---------------------------------------------------------------------
+ # Classification ML prediction
+ # ---------------------------------------------------------------------
+ if boundary_type == 'Int':
+ if self._ML_buckling[mat_fac]['cl SP buc int predictor'] is not None:
+ x_buc = self._ML_buckling[mat_fac]['cl SP buc int scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_buc = self._ML_buckling[mat_fac]['cl SP buc int predictor'].predict(
+ x_buc
+ )[0]
+ else:
+ y_pred_buc = 'ML buckling model missing'
+
+ if self._ML_buckling[mat_fac]['cl SP ult int predictor'] is not None:
+ x_ult = self._ML_buckling[mat_fac]['cl SP ult int scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_ult = self._ML_buckling[mat_fac]['cl SP ult int predictor'].predict(
+ x_ult
+ )[0]
+ else:
+ y_pred_ult = 'ML ultimate model missing'
- x_csr = obj_scnt_calc_stf.get_buckling_ml_input(design_lat_press=design_pressure, csr = True)
+ else:
+ if self._ML_buckling[mat_fac]['cl SP buc GLGT predictor'] is not None:
+ x_buc = self._ML_buckling[mat_fac]['cl SP buc GLGT scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_buc = self._ML_buckling[mat_fac]['cl SP buc GLGT predictor'].predict(
+ x_buc
+ )[0]
+ else:
+ y_pred_buc = 'ML buckling model missing'
+
+ if self._ML_buckling[mat_fac]['cl SP ult GLGT predictor'] is not None:
+ x_ult = self._ML_buckling[mat_fac]['cl SP ult GLGT scaler'].transform(
+ buckling_ml_input
+ )
+ y_pred_ult = self._ML_buckling[mat_fac]['cl SP ult GLGT predictor'].predict(
+ x_ult
+ )[0]
+ else:
+ y_pred_ult = 'ML ultimate model missing'
+
+ # ---------------------------------------------------------------------
+ # Numeric UF pipeline
+ # ---------------------------------------------------------------------
+ try:
+ numeric_pred = self._predict_numeric_uf_pipeline(
+ mat_fac=mat_fac,
+ sp_or_up='SP',
+ boundary_type=boundary_type,
+ buckling_ml_input=buckling_ml_input,
+ )
+ except Exception as e:
+ numeric_pred = default_numeric_pred.copy()
+ numeric_pred['valid_label'] = 'numeric ML error'
+ numeric_pred['error'] = str(e)
+
+ numeric_pred = _apply_material_factor_to_numeric_pred(numeric_pred, mat_fac)
+
+ # ---------------------------------------------------------------------
+ # CSR prediction
+ # ---------------------------------------------------------------------
+ try:
+ x_csr = obj_scnt_calc_stf.get_buckling_ml_input(
+ design_lat_press=design_pressure,
+ csr=True,
+ )
- x_csr = self._ML_buckling[mat_fac]['CSR scaler SP'].transform(x_csr)
- csr_pl, csr_web, csr_web_fl, csr_fl = self._ML_buckling[mat_fac]['CSR predictor SP'].predict(x_csr)[0]
- if obj_scnt_calc_pl.mat_factor == 1.1:
- accept = 'below or equal 0.91'
- else:
- accept = 'below or equal 0.87'
+ x_csr = self._ML_buckling[mat_fac]['CSR scaler SP'].transform(x_csr)
+ csr_pl, csr_web, csr_web_fl, csr_fl = self._ML_buckling[mat_fac][
+ 'CSR predictor SP'
+ ].predict(x_csr)[0]
+ except Exception:
+ csr_pl, csr_web, csr_web_fl, csr_fl = 0, 0, 0, 0
+ if mat_fac == 1.1:
+ accept = 'below or equal 0.91'
+ else:
+ accept = 'below or equal 0.87'
+
+ return_dict['ML buckling colors'][current_line] = {
+ 'buckling': 'green' if y_pred_buc == accept else 'red',
+ 'ultimate': 'green' if y_pred_ult == accept else 'red',
+ 'CSR requirement': 'green' if all(
+ [csr_pl == 1, csr_web == 1, csr_web_fl == 1, csr_fl == 1]
+ ) else 'red',
+ }
+
+ return_dict['ML buckling class'][current_line] = {
+ 'buckling': str(y_pred_buc) + mat_fac_error,
+ 'ultimate': str(y_pred_ult) + mat_fac_error,
+ 'CSR': [csr_pl, csr_web, csr_web_fl, csr_fl],
+ }
+
+ # ---------------------------------------------------------------------
+ # Numeric UF color handling
+ # ---------------------------------------------------------------------
+ if numeric_pred.get('valid_prediction', None) == 1:
+ numeric_buc_color = self._numeric_uf_color(
+ numeric_pred['buckling_uf'],
+ mat_fac,
+ )
+ numeric_ult_color = self._numeric_uf_color(
+ numeric_pred['ultimate_uf'],
+ mat_fac,
+ )
+ else:
+ numeric_buc_color = 'red'
+ numeric_ult_color = 'red'
+
+ return_dict['ML buckling numeric'][current_line] = {
+ 'buckling UF': numeric_pred['buckling_uf'],
+ 'ultimate UF': numeric_pred['ultimate_uf'],
+ 'buckling UF raw': numeric_pred.get('buckling_uf_raw', float('inf')),
+ 'ultimate UF raw': numeric_pred.get('ultimate_uf_raw', float('inf')),
+ 'material factor': numeric_pred.get('material_factor', mat_fac),
+ 'error': numeric_pred['error'],
+ }
+
+ return_dict['ML buckling numeric valid'][current_line] = {
+ 'available': numeric_pred['available'],
+ 'valid prediction': numeric_pred['valid_prediction'],
+ 'valid label': numeric_pred['valid_label'],
+ }
+
+ return_dict['ML buckling numeric colors'][current_line] = {
+ 'buckling': numeric_buc_color,
+ 'ultimate': numeric_ult_color,
+ }
+
+ # -------------------------------------------------------------------------
+ # SemiAnalytical solver.
+ #
+ # Store both raw solver UF and material-factored UF. The factored UF is used
+ # for display, colors, and utilization checks, matching ML-Numeric behavior.
+ # -------------------------------------------------------------------------
+ try:
+ semi_analytical_mat_fac = float(self._new_material_factor.get())
+ except Exception:
+ semi_analytical_mat_fac = obj_scnt_calc_pl.mat_factor
- return_dict['ML buckling colors'][current_line] = \
- {'buckling': 'green' if y_pred_buc == accept else 'red',
- 'ultimate': 'green' if y_pred_ult == accept else 'red',
- 'CSR requirement': 'green' if
- all([csr_pl == 1, csr_web == 1, csr_web_fl == 1, csr_fl == 1]) else 'red'}
- return_dict['ML buckling class'][current_line] = {'buckling': y_pred_buc + mat_fac_error ,
- 'ultimate': y_pred_ult + mat_fac_error,
- 'CSR': [csr_pl, csr_web, csr_web_fl, csr_fl]}
+ try:
+ semi_analytical_result = op.semi_analytical.solve_anystructure_panel(
+ [all_obj, None],
+ design_pressure,
+ )
+ semi_analytical_valid = int(semi_analytical_result.get('valid_prediction', 0)) == 1
+ except Exception as e:
+ semi_analytical_result = {
+ 'buckling_usage_factor': float('inf'),
+ 'ultimate_usage_factor': float('inf'),
+ 'panel_family': None,
+ 'confidence': 'low',
+ }
+ semi_analytical_valid = False
+ semi_analytical_error = str(e)
+ else:
+ semi_analytical_error = semi_analytical_result.get('invalid_reason') or ''
+
+ semi_analytical_buc_raw = (
+ float(semi_analytical_result.get('buckling_usage_factor', float('inf')))
+ if semi_analytical_valid else float('inf')
+ )
+ semi_analytical_ult_raw = (
+ float(semi_analytical_result.get('ultimate_usage_factor', float('inf')))
+ if semi_analytical_valid else float('inf')
+ )
+ semi_analytical_buc_uf = semi_analytical_buc_raw * semi_analytical_mat_fac
+ semi_analytical_ult_uf = semi_analytical_ult_raw * semi_analytical_mat_fac
+ semi_analytical_acceptance = 1.0
+
+ if semi_analytical_valid:
+ semi_analytical_buc_color = self._semi_analytical_uf_color(semi_analytical_buc_uf)
+ semi_analytical_ult_color = self._semi_analytical_uf_color(semi_analytical_ult_uf)
+ semi_analytical_valid_label = semi_analytical_result.get(
+ 'valid_label',
+ 'valid SemiAnalytical UF predicted',
+ )
+ else:
+ semi_analytical_buc_color = 'red'
+ semi_analytical_ult_color = 'red'
+ semi_analytical_valid_label = semi_analytical_result.get(
+ 'valid_label',
+ 'SemiAnalytical S3/U3 unsupported or invalid',
+ )
+
+ return_dict['SemiAnalytical'][current_line] = {
+ 'buckling UF': semi_analytical_buc_uf,
+ 'ultimate UF': semi_analytical_ult_uf,
+ 'buckling UF raw': semi_analytical_buc_raw,
+ 'ultimate UF raw': semi_analytical_ult_raw,
+ 'material factor': semi_analytical_mat_fac,
+ 'acceptance': semi_analytical_acceptance,
+ 'panel family': semi_analytical_result.get('panel_family', None),
+ 'confidence': semi_analytical_result.get('confidence', None),
+ 'error': semi_analytical_error,
+ }
+
+ return_dict['SemiAnalytical valid'][current_line] = {
+ 'available': semi_analytical_valid,
+ 'valid prediction': 1 if semi_analytical_valid else 0,
+ 'valid label': semi_analytical_valid_label,
+ }
+
+ return_dict['SemiAnalytical colors'][current_line] = {
+ 'buckling': semi_analytical_buc_color,
+ 'ultimate': semi_analytical_ult_color,
+ }
'''
Weight calculations for line.
@@ -3212,7 +3691,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
p1 = self._point_dict['point' + str(points[0])]
p2 = self._point_dict['point' + str(points[1])]
- mid_coord = [(p1[0]+p2[0])/2, (p1[1]+p2[1])/2]
+ mid_coord = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]
return_dict['weights'][current_line] = {'line weight': line_weight, 'mid_coord': mid_coord}
@@ -3226,7 +3705,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
return_dict['section_modulus'][current_line] = {'sec_mod': sec_mod, 'min_sec_mod': 0} if \
obj_scnt_calc_stf is None else {'sec_mod': sec_mod, 'min_sec_mod': min_sec_mod}
return_dict['shear_area'][current_line] = {'shear_area': 0, 'min_shear_area': 0} if \
- obj_scnt_calc_stf is None else{'shear_area': shear_area, 'min_shear_area': min_shear}
+ obj_scnt_calc_stf is None else {'shear_area': shear_area, 'min_shear_area': min_shear}
return_dict['thickness'][current_line] = {'thk': obj_scnt_calc_pl.get_pl_thk(), 'min_thk': min_thk}
return_dict['fatigue_obj'][current_line] = fatigue_obj
return_dict['color code'][current_line] = {}
@@ -3234,7 +3713,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
if fatigue_obj is not None:
return_dict['fatigue'][current_line] = {'damage': damage, 'dff': dff,
'curve': fatigue_obj.get_sn_curve()}
- rec_for_color[current_line]['fatigue'] = damage*dff
+ rec_for_color[current_line]['fatigue'] = damage * dff
else:
return_dict['fatigue'][current_line] = {'damage': None, 'dff': None, 'curve': None}
rec_for_color[current_line]['fatigue'] = 0
@@ -3245,21 +3724,44 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
sec_util = 0 if min(sec_mod) == 0 else min_sec_mod / min(sec_mod)
buc_util = 1 if float('inf') in buckling else max(all_buckling_uf_list)
rec_for_color[current_line]['rp buckling'] = max(all_buckling_uf_list)
+ selected_buckling_method = self._new_buckling_method.get()
+ semi_analytical_util = buc_util
+ if return_dict['SemiAnalytical valid'][current_line].get('valid prediction', None) == 1:
+ if op._puls_selected_method(obj_scnt_calc_pl.get_puls_method()) == 'ultimate':
+ semi_analytical_util = return_dict['SemiAnalytical'][current_line]['ultimate UF']
+ else:
+ semi_analytical_util = return_dict['SemiAnalytical'][current_line]['buckling UF']
+
+ numeric_util = buc_util
+ if return_dict['ML buckling numeric valid'][current_line].get('valid prediction', None) == 1:
+ if op._puls_selected_method(obj_scnt_calc_pl.get_puls_method()) == 'ultimate':
+ numeric_util = return_dict['ML buckling numeric'][current_line]['ultimate UF']
+ else:
+ numeric_util = return_dict['ML buckling numeric'][current_line]['buckling UF']
+
+ active_buckling_util = buc_util
+ if selected_buckling_method == 'SemiAnalytical S3/U3':
+ active_buckling_util = semi_analytical_util
+ elif selected_buckling_method == 'ML-Numeric (PULS based)':
+ active_buckling_util = numeric_util
+
return_dict['utilization'][current_line] = {'buckling': buc_util,
- 'PULS buckling': buc_util,
+ 'SemiAnalytical buckling': semi_analytical_util,
+ 'ML-Numeric buckling': numeric_util,
+ 'active buckling': active_buckling_util,
'fatigue': fat_util,
'section': sec_util,
'shear': shear_util,
'thickness': thk_util}
# Color coding state
- self._state_logger[current_line] = return_dict # Logging the current state of the line.
+ self._state_logger[current_line] = return_dict # Logging the current state of the line.
self._line_to_struc[current_line][0].need_recalc = False
else:
pass
- sec_in_model, idx, recorded_sections = dict(), 0, list()
- cyl_sec_in_model, idx_cyl, recorded_cyl_sections = dict(), 0, list()
+ sec_in_model, idx, recorded_sections = dict(), 0, list()
+ cyl_sec_in_model, idx_cyl, recorded_cyl_sections = dict(), 0, list()
for data in self._line_to_struc.values():
if data[0].Stiffener is not None:
@@ -3270,7 +3772,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
if data[5] is not None:
if data[5].LongStfObj is not None:
if data[5].LongStfObj.get_beam_string() not in recorded_cyl_sections:
- cyl_sec_in_model[ data[5].LongStfObj.get_beam_string()] = idx_cyl
+ cyl_sec_in_model[data[5].LongStfObj.get_beam_string()] = idx_cyl
recorded_cyl_sections.append(data[5].LongStfObj.get_beam_string())
idx_cyl += 1
@@ -3278,17 +3780,16 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
cyl_sec_in_model['length'] = len(recorded_cyl_sections)
if self._line_to_struc != {}:
- sec_mod_map = np.arange(0,1.1,0.1)
- fat_map = np.arange(0,1.1,0.1)
+ sec_mod_map = np.arange(0, 1.1, 0.1)
+ fat_map = np.arange(0, 1.1, 0.1)
all_thicknesses = [round(objs[0].Plate.get_pl_thk(), 5) for objs in self._line_to_struc.values()]
all_thicknesses = np.unique(all_thicknesses).tolist()
-
-
+
thickest_plate = max(all_thicknesses)
if len(all_thicknesses) > 1:
thk_map = np.arange(min(all_thicknesses), max(all_thicknesses) + (max(all_thicknesses) -
min(all_thicknesses)) / 10,
- (max(all_thicknesses) - min(all_thicknesses)) / 10)
+ (max(all_thicknesses) - min(all_thicknesses)) / 10)
else:
thk_map = all_thicknesses
@@ -3303,7 +3804,6 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
# else:
# thk_map_cyl = [1,]
-
try:
all_pressures = sorted([self.get_highest_pressure(line)['normal']
for line in list(self._line_dict.keys())])
@@ -3321,27 +3821,26 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
else:
press_map = all_pressures
- all_utils = [max(list(return_dict['utilization'][line].values()))
- for line in self._line_to_struc.keys()]
+ all_utils = []
+ for line in self._line_to_struc.keys():
+ if self._line_to_struc[line][5] is not None:
+ all_utils.append(self._cylinder_buckling_uf(return_dict['cylinder'].get(line, {})))
+ else:
+ all_utils.append(
+ return_dict['utilization'][line].get(
+ 'active buckling',
+ max(list(return_dict['utilization'][line].values())),
+ )
+ )
all_utils = np.unique(all_utils).tolist()
- if len(all_utils) >1:
- util_map = np.arange(0, 1.1, 0.1)
+ if len(all_utils) > 1:
+ util_map = np.arange(0, 1.1, 0.1)
else:
util_map = all_utils
- if self._PULS_results is not None:
- #puls_util_map = self._PULS_results.all_uf
- puls_util_map = list()
- for key, val in self._line_to_struc.items():
- puls_util_map.append(self._PULS_results.get_utilization(key, val[0].Plate.get_puls_method(),
- acceptance = self._new_puls_uf.get()))
- puls_util_map = np.arange(0, 1.1, 0.1)
- else:
- puls_util_map = None
-
sig_x = np.unique([self._line_to_struc[line][0].Plate.sigma_x1 for line in
self._line_to_struc.keys()]).tolist()
- if len(sig_x) > 1: # TODO color coding when using sig_x1 and sig_x2 (23.12.2021)
+ if len(sig_x) > 1: # TODO color coding when using sig_x1 and sig_x2 (23.12.2021)
sig_x_map = np.arange(min(sig_x), max(sig_x) + (max(sig_x) - min(sig_x)) / 10,
(max(sig_x) - min(sig_x)) / 10)
else:
@@ -3383,9 +3882,9 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
'section modulus map': sec_mod_map,
'fatigue map': fat_map,
'highest pressure': highest_pressure, 'lowest pressure': lowest_pressure,
- 'pressure map': press_map, 'all pressures':all_pressures,
+ 'pressure map': press_map, 'all pressures': all_pressures,
+ 'buckling method': self._new_buckling_method.get(),
'all utilizations': all_utils, 'utilization map': util_map,
- 'PULS utilization map': puls_util_map,
'max sigma x': max(sig_x), 'min sigma x': min(sig_x), 'sigma x map': sig_x_map,
'max sigma y1': max(sig_y1), 'min sigma y1': min(sig_y1),
'sigma y1 map': sig_y1_map,
@@ -3396,28 +3895,17 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
'sections in model': sec_in_model,
'cyl sections in model': cyl_sec_in_model,
'recorded sections': recorded_sections,
- 'recorded cylinder long sections' : recorded_cyl_sections,
+ 'recorded cylinder long sections': recorded_cyl_sections,
'spacings': spacing, 'max spacing': max(spacing), 'min spacing': min(spacing)}
- line_color_coding, puls_method_map, puls_sp_or_up_map = \
- {}, {None: 0, 'buckling': 0.5, 'ultimate': 1}, {None:0, 'SP': 0.5, 'UP': 1}
+ line_color_coding = {}
cmap_sections = plt.get_cmap('jet')
thk_sort_unique = return_dict['color code']['all thicknesses']
spacing_sort_unique = return_dict['color code']['spacings']
structure_type_unique = return_dict['color code']['structure types map']
- tot_weight, weight_mult_dist_x, weight_mult_dist_y = 0, 0,0
+ tot_weight, weight_mult_dist_x, weight_mult_dist_y = 0, 0, 0
for line, line_data in self._line_to_struc.items():
- if self._PULS_results is None:
- puls_color, buc_uf, puls_uf, puls_method, puls_sp_or_up = 'black', 0, 0, None, None
- elif self._PULS_results.get_utilization(line, self._line_to_struc[line][0].Plate.get_puls_method(),
- self._new_puls_uf.get()) == None:
- puls_color, buc_uf, puls_uf, puls_method, puls_sp_or_up = 'black', 0,0, None, None
- else:
- puls_method = self._line_to_struc[line][0].Plate.get_puls_method()
- puls_uf = self._PULS_results.get_utilization(
- line, puls_method,
- self._new_puls_uf.get())
- puls_color = matplotlib.colors.rgb2hex(cmap_sections(puls_uf))
- puls_sp_or_up = self._line_to_struc[line][0].Plate.get_puls_sp_or_up()
+ puls_method = line_data[0].Plate.get_puls_method()
+ puls_sp_or_up = line_data[0].Plate.get_puls_sp_or_up()
# Cylinders
if self._line_to_struc[line][5] is not None:
@@ -3441,14 +3929,7 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
# cyl_sigma_hoop = cyl_obj.shsd / 1e6
cyl_results = cyl_obj.get_utilization_factors()
- cyl_uf = max([round(0 if cyl_results['Unstiffened shell'] is None else
- cyl_results['Unstiffened shell'],2),
- round(0 if cyl_results['Longitudinal stiffened shell'] is None else
- cyl_results['Longitudinal stiffened shell'],2),
- round(0 if cyl_results['Ring stiffened shell'] is None else
- cyl_results['Ring stiffened shell'],2),
- round(0 if cyl_results['Heavy ring frame'] is None else
- cyl_results['Heavy ring frame'],2)])
+ cyl_uf = self._cylinder_buckling_uf(cyl_results)
else:
cyl_uf = 0
cyl_long_str = None
@@ -3459,9 +3940,6 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
tot_uf_rp = max([rec_for_color[line]['fatigue'], rp_uf,
rec_for_color[line]['section modulus'], rec_for_color[line]['shear'],
rec_for_color[line]['plate thickness']])
- tot_uf_puls = max([rec_for_color[line]['fatigue'], puls_uf,
- rec_for_color[line]['section modulus'], rec_for_color[line]['shear'],
- rec_for_color[line]['plate thickness']])
try:
this_pressure = self.get_highest_pressure(line)['normal']
except KeyError:
@@ -3471,82 +3949,79 @@ def get_color_and_calc_state(self, current_line = None, active_line_only = False
res = list()
for stress_list, this_stress in zip([sig_x, sig_y1, sig_y2, tau_xy],
- [line_data[0].Plate.sigma_x1, line_data[0].Plate.sigma_y1,
- line_data[0].Plate.sigma_y2, line_data[0].Plate.tau_xy]):
- if type(stress_list)==float:
+ [line_data[0].Plate.sigma_x1, line_data[0].Plate.sigma_y1,
+ line_data[0].Plate.sigma_y2, line_data[0].Plate.tau_xy]):
+ if type(stress_list) == float:
res.append(1)
elif len(stress_list) == 1:
res.append(1)
elif max(stress_list) == 0 and min(stress_list) == 0:
res.append(0)
elif this_stress < 0:
- res.append(this_stress /min(stress_list))
+ res.append(this_stress / min(stress_list))
elif this_stress >= 0:
- res.append(this_stress/ max(stress_list))
+ res.append(this_stress / max(stress_list))
- sig_x_uf, sig_y1_uf, sig_y2_uf , tau_xy_uf = res
+ sig_x_uf, sig_y1_uf, sig_y2_uf, tau_xy_uf = res
if type(all_cyl_thk) is not list:
all_cyl_thk = all_cyl_thk.tolist()
line_color_coding[line] = {'plate': matplotlib.colors.rgb2hex(cmap_sections(
- thk_sort_unique.index(round(line_data[0].Plate.get_pl_thk(),10))/len(thk_sort_unique))),
- 'spacing': 'black' if line_data[0].Stiffener is None else matplotlib.colors.rgb2hex(
- cmap_sections(spacing_sort_unique.index(round(line_data[0].Stiffener
- .get_s(), 10)) / len(
- spacing_sort_unique))),
- 'section': 'black' if line_data[0].Stiffener is None else
- matplotlib.colors.rgb2hex(cmap_sections(sec_in_model[line_data[0]
- .Stiffener.get_beam_string()]/
- len(list(recorded_sections)))),
- 'section cyl': 'black' if cyl_long_str is None else
- matplotlib.colors.rgb2hex(cmap_sections(cyl_sec_in_model[cyl_long_str] /
- len(list(recorded_cyl_sections)))),
- 'structure type': matplotlib.colors.rgb2hex(
- cmap_sections(structure_type_unique.index(line_data[0].Plate.get_structure_type())
- /len(structure_type_unique))),
- 'pressure color': 'black' if all_pressures in [[0],[0,1]] else matplotlib.colors.rgb2hex(cmap_sections(
- this_pressure/highest_pressure)),
- 'pressure': this_pressure,
- 'rp uf color': matplotlib.colors.rgb2hex(cmap_sections(rp_util)),
- 'rp uf': rp_util,
- 'PULS method': puls_method,
- 'PULS sp or up':puls_sp_or_up,
- 'section modulus color': matplotlib.colors.rgb2hex(
- cmap_sections(rec_for_color[line]['section modulus'])),
- 'fatigue color': matplotlib.colors.rgb2hex(
- cmap_sections(rec_for_color[line]['fatigue'])),
- 'Total uf color rp' : matplotlib.colors.rgb2hex(
- cmap_sections(tot_uf_rp)),
- 'Total uf rp': tot_uf_rp,
- 'Total uf color puls': matplotlib.colors.rgb2hex(
- cmap_sections(tot_uf_puls)),
- 'Total uf puls': tot_uf_puls,
- 'PULS uf': round(puls_uf,2),
- 'PULS uf color': puls_color,
- 'fatigue uf' : rec_for_color[line]['fatigue'],
- 'section uf' : rec_for_color[line]['section modulus'],
- 'sigma x': matplotlib.colors.rgb2hex(cmap_sections(sig_x_uf)),
- 'sigma y1': matplotlib.colors.rgb2hex(cmap_sections(sig_y1_uf)),
- 'sigma y2': matplotlib.colors.rgb2hex(cmap_sections(sig_y2_uf)),
- 'tau xy':matplotlib.colors.rgb2hex(cmap_sections(tau_xy_uf)),
+ thk_sort_unique.index(round(line_data[0].Plate.get_pl_thk(), 10)) / len(thk_sort_unique))),
+ 'spacing': 'black' if line_data[0].Stiffener is None else matplotlib.colors.rgb2hex(
+ cmap_sections(spacing_sort_unique.index(round(line_data[0].Stiffener
+ .get_s(), 10)) / len(
+ spacing_sort_unique))),
+ 'section': 'black' if line_data[0].Stiffener is None else
+ matplotlib.colors.rgb2hex(cmap_sections(sec_in_model[line_data[0]
+ .Stiffener.get_beam_string()] /
+ len(list(recorded_sections)))),
+ 'section cyl': 'black' if cyl_long_str is None else
+ matplotlib.colors.rgb2hex(cmap_sections(cyl_sec_in_model[cyl_long_str] /
+ len(list(recorded_cyl_sections)))),
+ 'structure type': matplotlib.colors.rgb2hex(
+ cmap_sections(structure_type_unique.index(line_data[0].Plate.get_structure_type())
+ / len(structure_type_unique))),
+ 'pressure color': 'black' if all_pressures in [[0], [0, 1]] else matplotlib.colors.rgb2hex(
+ cmap_sections(
+ this_pressure / highest_pressure)),
+ 'pressure': this_pressure,
+ 'rp uf color': matplotlib.colors.rgb2hex(cmap_sections(rp_util)),
+ 'rp uf': rp_util,
+ 'Buckling method': puls_method,
+ 'Buckling SP/UP': puls_sp_or_up,
+ 'section modulus color': matplotlib.colors.rgb2hex(
+ cmap_sections(rec_for_color[line]['section modulus'])),
+ 'fatigue color': matplotlib.colors.rgb2hex(
+ cmap_sections(rec_for_color[line]['fatigue'])),
+ 'Total uf color rp': matplotlib.colors.rgb2hex(
+ cmap_sections(tot_uf_rp)),
+ 'Total uf rp': tot_uf_rp,
+ 'fatigue uf': rec_for_color[line]['fatigue'],
+ 'section uf': rec_for_color[line]['section modulus'],
+ 'sigma x': matplotlib.colors.rgb2hex(cmap_sections(sig_x_uf)),
+ 'sigma y1': matplotlib.colors.rgb2hex(cmap_sections(sig_y1_uf)),
+ 'sigma y2': matplotlib.colors.rgb2hex(cmap_sections(sig_y2_uf)),
+ 'tau xy': matplotlib.colors.rgb2hex(cmap_sections(tau_xy_uf)),
'cylinder uf': matplotlib.colors.rgb2hex(cmap_sections(cyl_uf)),
- 'cylinder plate' : matplotlib.colors.rgb2hex
- (cmap_sections(0 if cyl_thickness is None else all_cyl_thk.index(cyl_thickness)/len(all_cyl_thk)))
+ 'cylinder uf value': cyl_uf,
+ 'cylinder plate': matplotlib.colors.rgb2hex
+ (cmap_sections(0 if cyl_thickness is None else all_cyl_thk.index(cyl_thickness) / len(all_cyl_thk)))
- }
+ }
return_dict['color code']['lines'] = line_color_coding
# COG calculations
# Steel
tot_weight += return_dict['weights'][line]['line weight']
- weight_mult_dist_x += return_dict['weights'][line]['line weight']\
- *return_dict['weights'][line]['mid_coord'][0]
- weight_mult_dist_y += return_dict['weights'][line]['line weight']\
- *return_dict['weights'][line]['mid_coord'][1]
+ weight_mult_dist_x += return_dict['weights'][line]['line weight'] \
+ * return_dict['weights'][line]['mid_coord'][0]
+ weight_mult_dist_y += return_dict['weights'][line]['line weight'] \
+ * return_dict['weights'][line]['mid_coord'][1]
- tot_cog = [weight_mult_dist_x/tot_weight, weight_mult_dist_y/tot_weight]
+ tot_cog = [weight_mult_dist_x / tot_weight, weight_mult_dist_y / tot_weight]
else:
- tot_cog = [0,0]
+ tot_cog = [0, 0]
tot_weight = 0
return_dict['COG'] = tot_cog
@@ -3720,41 +4195,56 @@ def draw_canvas(self, state = None, event = None):
all_cyl_chks.append(stf_val)
color = 'green' if all(all_cyl_chks) else 'red'
- elif self._new_buckling_method.get() == 'DNV PULS':
- if 'black' in state['PULS colors'][line].values():
- color = 'black'
- else:
- col1, col2 = state['PULS colors'][line]['buckling'], \
- state['PULS colors'][line]['ultimate']
-
- if self._line_to_struc[line][0].Plate.get_puls_method() == 'buckling':
- color = 'red' if any([col1 == 'red', col2 == 'red']) else 'green'
- else:
- color = col2
-
- if color == 'green':
- color = 'green' if all([state['colors'][line][key] == 'green' for key in
- ['fatigue', 'section', 'shear','thickness']]) else 'red'
-
elif self._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
color = 'red' if 'red' in state['colors'][line].values() else 'green'
- elif self._new_buckling_method.get() == 'ML-CL (PULS based)':
- if 'black' in state['ML buckling colors'][line].values():
+ elif self._new_buckling_method.get() == 'SemiAnalytical S3/U3':
+ semi_analytical_valid = state.get('SemiAnalytical valid', {}).get(line, {})
+ semi_analytical_colors = state.get('SemiAnalytical colors', {}).get(line, {})
+
+ if semi_analytical_valid.get('valid prediction', None) != 1:
+ color = 'red'
+ else:
+ puls_method = op._puls_selected_method(
+ self._line_to_struc[line][0].Plate.get_puls_method()
+ )
+ if puls_method == 'ultimate':
+ color = semi_analytical_colors.get('ultimate', 'red')
+ else:
+ color = semi_analytical_colors.get('buckling', 'red')
+
+ if color == 'green':
+ color = 'green' if all([
+ state['colors'][line][key] == 'green'
+ for key in ['fatigue', 'section', 'shear', 'thickness']
+ ]) else 'red'
+ elif self._new_buckling_method.get() in [
+ 'ML-Numeric (PULS based)',
+ ]:
+ ml_color_dict = state.get('ML buckling numeric colors', {}).get(line, {})
+
+ if ml_color_dict == {}:
+ color = 'black'
+ elif 'black' in ml_color_dict.values():
color = 'black'
else:
- col1, col2 = state['ML buckling colors'][line]['buckling'], \
- state['ML buckling colors'][line]['ultimate']
+ col_buc = ml_color_dict.get('buckling', 'black')
+ col_ult = ml_color_dict.get('ultimate', 'black')
if self._line_to_struc[line][0].Plate.get_puls_method() == 'buckling':
- color = col1
+ color = col_buc
else:
- color = col2
+ color = col_ult
+ # If the selected ML check is green, keep the existing
+ # fatigue/section/shear/thickness checks as additional
+ # requirements for the displayed line color.
if color == 'green':
- color = 'green' if all([state['colors'][line][key] == 'green' for key in
- ['fatigue', 'section', 'shear','thickness']]) else 'red'
+ color = 'green' if all([
+ state['colors'][line][key] == 'green'
+ for key in ['fatigue', 'section', 'shear', 'thickness']
+ ]) else 'red'
- except (KeyError, TypeError):
+ except (KeyError, TypeError, AttributeError):
color = 'black'
elif chk_box_active and state != None and self._line_to_struc != {}:
color = self.color_code_line(state, line, coord1, [coord2[0] - coord1[0], coord2[1] - coord1[1]])
@@ -3890,21 +4380,19 @@ def color_code_text(self, state):
fill=matplotlib.colors.rgb2hex(cmap_sections(0 if highest_pressure == 0
else press/highest_pressure)),
anchor="nw")
- elif all([self._new_colorcode_utilization.get() == True,
- self._line_to_struc != {}, self._new_buckling_method.get() != 'DNV PULS']):
+ elif self._new_colorcode_utilization.get() == True and self._line_to_struc != {}:
all_utils = cc_state['utilization map']
- for idx, uf in enumerate(cc_state['utilization map']):
- self._main_canvas.create_text(11, start_text_shift + 20 * idx, text=str('UF = ' +str(round(uf,1))),
- font=self._text_size["Text 10 bold"],
- fill='black',
- anchor="nw")
- self._main_canvas.create_text(10, start_text + 20 * idx, text=str('UF = ' +str(round(uf,1))),
- font=self._text_size["Text 10 bold"],
- fill=matplotlib.colors.rgb2hex(cmap_sections(uf/max(all_utils))),
- anchor="nw")
- elif all([self._new_colorcode_utilization.get() == True,
- self._line_to_struc != {}, self._new_buckling_method == 'DNV PULS']):
- all_utils = cc_state['PULS utilization map']
+ method_text = cc_state.get('buckling method', self._new_buckling_method.get())
+ if method_text == 'SemiAnalytical S3/U3':
+ title_text = 'SemiAnalytical UF'
+ elif method_text == 'ML-Numeric (PULS based)':
+ title_text = 'ML-Numeric UF'
+ else:
+ title_text = 'Buckling UF'
+ self._main_canvas.create_text(10, start_text - 20, text=title_text,
+ font=self._text_size["Text 10 bold"],
+ fill=self._color_text,
+ anchor="nw")
for idx, uf in enumerate(cc_state['utilization map']):
self._main_canvas.create_text(11, start_text_shift + 20 * idx, text=str('UF = ' +str(round(uf,1))),
font=self._text_size["Text 10 bold"],
@@ -4021,6 +4509,16 @@ def color_code_text(self, state):
def color_code_line(self, state, line, coord1, vector):
cc_state = state['color code']
+ cmap_sections = plt.get_cmap('jet')
+
+ def utilization_color(uf):
+ try:
+ uf = float(uf)
+ max_uf = max(cc_state.get('utilization map', [uf, 1.0]))
+ max_uf = 1.0 if max_uf == 0 else float(max_uf)
+ return matplotlib.colors.rgb2hex(cmap_sections(uf / max_uf))
+ except Exception:
+ return 'black'
if line not in state['color code']['lines'].keys():
return 'black'
@@ -4089,15 +4587,10 @@ def color_code_line(self, state, line, coord1, vector):
color = 'black'
this_text = 'N/A'
elif self._line_to_struc[line][5] is not None:
- cyl_obj = self._line_to_struc[line][5]
- results = cyl_obj.get_utilization_factors()
- ufs = [round(0 if results['Unstiffened shell'] is None else results['Unstiffened shell'], 2),
- round(0 if results['Longitudinal stiffened shell'] is None else results['Longitudinal stiffened shell'],2),
- round(0 if results['Ring stiffened shell'] is None else results['Ring stiffened shell'], 2),
- round(0 if results['Heavy ring frame'] is None else results['Heavy ring frame'], 2)]
-
- color = state['color code']['lines'][line]['cylinder uf']
- this_text = str(max(ufs))
+ results = state.get('cylinder', {}).get(line, self._line_to_struc[line][5].get_utilization_factors())
+ cyl_uf = self._cylinder_buckling_uf(results)
+ color = utilization_color(cyl_uf)
+ this_text = str(round(cyl_uf, 3))
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
text=this_text)
@@ -4107,22 +4600,69 @@ def color_code_line(self, state, line, coord1, vector):
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
text=round(state['color code']['lines'][line]['rp uf'],2))
- elif self._new_colorcode_utilization.get() == True and self._new_buckling_method.get() == 'DNV PULS':
- if self._line_to_struc[line][5] is not None:
- color = 'grey'
- this_text = 'N/A'
+ elif self._new_colorcode_utilization.get() == True and self._new_buckling_method.get() == 'SemiAnalytical S3/U3':
+ if line in self._line_to_struc and self._line_to_struc[line][5] is not None:
+ results = state.get('cylinder', {}).get(line, self._line_to_struc[line][5].get_utilization_factors())
+ cyl_uf = self._cylinder_buckling_uf(results)
+ color = utilization_color(cyl_uf)
+ this_text = str(round(cyl_uf, 3))
else:
- color = state['color code']['lines'][line]['PULS uf color']
- this_text = round(state['color code']['lines'][line]['PULS uf'],2)
+ semi_analytical = state.get('SemiAnalytical', {}).get(line, {})
+ semi_analytical_valid = state.get('SemiAnalytical valid', {}).get(line, {})
+
+ if semi_analytical_valid.get('valid prediction', None) == 1:
+ puls_method = state['color code']['lines'][line].get('Buckling method', None)
+ puls_method = op._puls_selected_method(puls_method)
+
+ if puls_method == 'ultimate':
+ uf = semi_analytical.get('ultimate UF', float('inf'))
+ color = utilization_color(uf)
+ this_text = 'ult UF=' + str(round(uf, 3))
+ else:
+ uf = semi_analytical.get('buckling UF', float('inf'))
+ color = utilization_color(uf)
+ this_text = 'buc UF=' + str(round(uf, 3))
+ else:
+ color = 'red'
+ this_text = semi_analytical_valid.get('valid label', 'invalid')
+
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
text=this_text)
+ elif self._new_colorcode_utilization.get() == True and self._new_buckling_method.get() == 'ML-Numeric (PULS based)':
+ if line in self._line_to_struc and self._line_to_struc[line][5] is not None:
+ results = state.get('cylinder', {}).get(line, self._line_to_struc[line][5].get_utilization_factors())
+ cyl_uf = self._cylinder_buckling_uf(results)
+ color = utilization_color(cyl_uf)
+ this_text = str(round(cyl_uf, 3))
+ else:
+ numeric = state.get('ML buckling numeric', {}).get(line, {})
+ numeric_valid = state.get('ML buckling numeric valid', {}).get(line, {})
+
+ if numeric_valid.get('valid prediction', None) == 1:
+ puls_method = state['color code']['lines'][line].get('Buckling method', None)
+
+ if puls_method == 'buckling':
+ uf = numeric.get('buckling UF', float('inf'))
+ color = utilization_color(uf)
+ this_text = 'buc UF=' + str(round(uf, 3))
+ elif puls_method == 'ultimate':
+ uf = numeric.get('ultimate UF', float('inf'))
+ color = utilization_color(uf)
+ this_text = 'ult UF=' + str(round(uf, 3))
+ else:
+ buc_uf = numeric.get('buckling UF', float('inf'))
+ ult_uf = numeric.get('ultimate UF', float('inf'))
+ uf = max(buc_uf, ult_uf)
+ color = utilization_color(uf)
+ this_text = 'UF=' + str(round(uf, 3))
+ else:
+ color = 'red'
+ this_text = numeric_valid.get('valid label', 'invalid/NaN')
- elif self._new_colorcode_utilization.get() == True and self._new_buckling_method.get() == 'ML-CL (PULS based)':
- color = 'black'
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
- text='N/A')
+ text=this_text)
elif self._new_colorcode_sigmax.get() == True:
if self._line_to_struc[line][5] is not None:
@@ -4212,255 +4752,109 @@ def color_code_line(self, state, line, coord1, vector):
elif self._new_colorcode_total.get() == True:
if self._line_to_struc[line][5] is not None:
- cyl_obj = self._line_to_struc[line][5]
- results = cyl_obj.get_utilization_factors()
- ufs = [round(0 if results['Unstiffened shell'] is None else results['Unstiffened shell'], 2),
- round(0 if results['Longitudinal stiffened shell'] is None else results['Longitudinal stiffened shell'],2),
- round(0 if results['Ring stiffened shell'] is None else results['Ring stiffened shell'], 2),
- round(0 if results['Heavy ring frame'] is None else results['Heavy ring frame'], 2)]
-
- color = state['color code']['lines'][line]['cylinder uf']
- this_text = str(max(ufs))
+ results = state.get('cylinder', {}).get(line, self._line_to_struc[line][5].get_utilization_factors())
+ cyl_uf = self._cylinder_buckling_uf(results)
+ color = utilization_color(cyl_uf)
+ this_text = str(round(cyl_uf, 3))
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
text=this_text)
- elif self._new_buckling_method.get() == 'DNV PULS':
- color = state['color code']['lines'][line]['Total uf color puls']
- if self._new_label_color_coding.get():
- self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
- text=round(state['color code']['lines'][line]['Total uf puls'],2))
elif self._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
color = state['color code']['lines'][line]['Total uf color rp']
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
text=round(state['color code']['lines'][line]['Total uf rp'],2))
- elif self._new_buckling_method.get() == 'ML-CL (PULS based)':
- color = 'black'
+ elif self._new_buckling_method.get() == 'SemiAnalytical S3/U3':
+ semi_analytical = state.get('SemiAnalytical', {}).get(line, {})
+ semi_analytical_valid = state.get('SemiAnalytical valid', {}).get(line, {})
+ semi_analytical_colors = state.get('SemiAnalytical colors', {}).get(line, {})
+
+ if semi_analytical_valid.get('valid prediction', None) == 1:
+ buc_uf = semi_analytical.get('buckling UF', float('inf'))
+ ult_uf = semi_analytical.get('ultimate UF', float('inf'))
+ total_uf = max(buc_uf, ult_uf)
+
+ color = 'green' if all([
+ semi_analytical_colors.get('buckling', 'red') == 'green',
+ semi_analytical_colors.get('ultimate', 'red') == 'green'
+ ]) else 'red'
+ this_text = 'max UF=' + str(round(total_uf, 3))
+ else:
+ color = 'red'
+ this_text = semi_analytical_valid.get('valid label', 'invalid')
+
+ if self._new_label_color_coding.get():
+ self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
+ text=this_text)
+ elif self._new_buckling_method.get() == 'ML-Numeric (PULS based)':
+ numeric = state.get('ML buckling numeric', {}).get(line, {})
+ numeric_valid = state.get('ML buckling numeric valid', {}).get(line, {})
+ numeric_colors = state.get('ML buckling numeric colors', {}).get(line, {})
+
+ if numeric_valid.get('valid prediction', None) == 1:
+ buc_uf = numeric.get('buckling UF', float('inf'))
+ ult_uf = numeric.get('ultimate UF', float('inf'))
+ total_uf = max(buc_uf, ult_uf)
+
+ color = 'green' if all([
+ numeric_colors.get('buckling', 'red') == 'green',
+ numeric_colors.get('ultimate', 'red') == 'green'
+ ]) else 'red'
+ this_text = 'max UF=' + str(round(total_uf, 3))
+ else:
+ color = 'red'
+ this_text = numeric_valid.get('valid label', 'invalid/NaN')
+
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
- text='N/A')
+ text=this_text)
elif self._new_colorcode_puls_acceptance.get():
- if state['color code']['lines'][line]['PULS method'] == None:
+ buckling_method = state['color code']['lines'][line].get('Buckling method', None)
+ if buckling_method == None:
color = 'black'
else:
- color = 'blue' if state['color code']['lines'][line]['PULS method'] == 'ultimate' else 'red'
+ color = 'blue' if buckling_method == 'ultimate' else 'red'
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
- text=state['color code']['lines'][line]['PULS method'])
+ text=buckling_method)
elif self._new_colorcode_puls_sp_or_up.get():
- if state['color code']['lines'][line]['PULS sp or up'] == None:
+ buckling_sp_or_up = state['color code']['lines'][line].get('Buckling SP/UP', None)
+ if buckling_sp_or_up == None:
color = 'black'
else:
- color = 'blue' if state['color code']['lines'][line]['PULS sp or up'] == 'SP' else 'red'
+ color = 'blue' if buckling_sp_or_up == 'SP' else 'red'
if self._new_label_color_coding.get():
self._main_canvas.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 - 10,
- text=state['color code']['lines'][line]['PULS sp or up'])
+ text=buckling_sp_or_up)
else:
color = 'black'
return color
- def draw_prop(self, event = None):
+ def draw_results(self, state = None):
+ '''
+ The properties canvas is created here.
+ state = {'colors': {}, 'section_modulus': {}, 'thickness': {}, 'shear_area': {}, 'buckling': {},
+ 'fatigue': {}, 'pressure_uls': {}, 'pressure_fls': {},
+ 'all_obj': {}, 'scant_calc_obj': {}, 'fatigue_obj': {}}
+ :return:
'''
- Prints the properties of the selected line to the bottom canvas.
- properties for line dicitonary:
+ self._result_canvas.delete('all')
- name of line : [ Structure class, calc scantling class, calc fatigue class, [load classes] ]
+ if state is None or self._active_line not in state['all_obj'].keys():
+ return
- '''
+ if self._line_is_active:
+ x, y, dx, dy = 0, 5, 15, 17
- self._prop_canvas.delete('all')
- canvas_width = self._prop_canvas.winfo_width()
- canvas_height = self._prop_canvas.winfo_height()
+ if self._active_line in self._line_to_struc and self._line_to_struc[self._active_line][5] is None:
- def checkered(line_distance, canvas):
- '''
- Grid lines in the properties canvas.
- :param line_distance:
- :return:
- '''
- # vertical lines at an interval of "line_distance" pixel
- for x in range(line_distance, canvas_width, line_distance):
- canvas.create_line(x, 0, x, canvas_width, stipple='gray50', activestipple='gray75')
- # horizontal lines at an interval of "line_distance" pixel
- for y in range(line_distance, canvas_height, line_distance):
- canvas.create_line(0, y, canvas_width, y, stipple='gray50', activestipple='gray75')
-
- if self._active_line in self._line_to_struc:
- self.set_selected_variables(self._active_line)
- # printing the properties to the active line
- if self._line_is_active and self._line_to_struc[self._active_line][5] is None:
- #checkered(10, self._prop_canvas)
- self._prop_canvas.create_text([canvas_width/2-canvas_width/20, canvas_height/20],
- text ='SELECTED: '+str(self._active_line),
- font=self._text_size["Text 10 bold"], fill='red')
- if all([self._line_to_struc[self._active_line][0].Stiffener is None,
- self._line_to_struc[self._active_line][0].Girder is None]):
- structure_obj = self._line_to_struc[self._active_line][0].Plate
- spacing = structure_obj.get_s() * self._prop_canvas_scale * 3
- plate_thk = structure_obj.get_pl_thk() * self._prop_canvas_scale * 3
- startx = 20
- starty = 225
- self._prop_canvas.create_text([startx + 100, 50],
- text='Plate with thickness ' +
- str(structure_obj.get_pl_thk()*1000) + ' mm' ,
- font=self._text_size["Text 10 bold"], fill='Black')
- self._prop_canvas.create_rectangle(startx + spacing,
- starty,
- startx + spacing + spacing,
- starty - plate_thk,
- fill='grey', activefill='yellow')
-
- for idx, structure_obj in enumerate([self._line_to_struc[self._active_line][0].Stiffener,
- self._line_to_struc[self._active_line][0].Girder]):
- mult = 1 if self._line_to_struc[self._active_line][0].Girder is not None else 2 # *(400/max_web)
- thk_mult = 2 # *(400/max_web)
- startx = 100 + 300 * idx
- starty = 225
-
- if structure_obj is not None:
- self._prop_canvas.create_text([startx +60, 50],
- text='Stiffener\n' +structure_obj.get_beam_string()
- if idx == 0 else 'Girder\n' + structure_obj.get_beam_string(),
- font=self._text_size["Text 9 bold"], fill='Black')
- if structure_obj is not None:
- self._prop_canvas.create_text([100, 20],
- text='Thickness scale x 2',
- font=self._text_size["Text 10 bold"], fill='grey')
- # drawing stiffener
- spacing = structure_obj.get_s()*self._prop_canvas_scale * mult
- stf_web_height = structure_obj.get_web_h()*self._prop_canvas_scale * mult
- stf_flange_width = structure_obj.get_fl_w() *self._prop_canvas_scale * mult
- plate_thk = structure_obj.get_pl_thk()*self._prop_canvas_scale*thk_mult * mult
- stf_web_thk = structure_obj.get_web_thk()*self._prop_canvas_scale*thk_mult * mult
- stf_flange_thk = structure_obj.get_fl_thk()*self._prop_canvas_scale*thk_mult * mult
-
- for count in [0,1,2] if idx == 0 else [0,]:
-
- self._prop_canvas.create_rectangle(startx + count*spacing,
- starty,
- startx+spacing+ count*spacing,
- starty- plate_thk ,
- fill = 'grey', activefill = 'yellow')
- self._prop_canvas.create_rectangle(startx+spacing*0.5+ count*spacing - stf_web_thk/2,
- starty - plate_thk,
- startx+spacing*0.5+ count*spacing + stf_web_thk/2,
- starty - stf_web_height - plate_thk,
- fill = 'grey', activefill = 'yellow')
-
- if structure_obj.get_stiffener_type() not in ['L', 'L-bulb']:
-
- self._prop_canvas.create_rectangle(startx+spacing*0.5-stf_flange_width/2+ count*spacing,
- starty - stf_web_height - plate_thk,
- startx + spacing * 0.5 + stf_flange_width / 2+ count*spacing,
- starty - stf_web_height - plate_thk- stf_flange_thk,
- fill = 'grey', activefill = 'yellow')
- else:
- self._prop_canvas.create_rectangle(startx+spacing*0.5-stf_web_thk/2+ count*spacing,
- starty-stf_web_height - plate_thk,
- startx + spacing * 0.5 + stf_flange_width + count*spacing,
- starty - stf_web_height - plate_thk - stf_flange_thk,
- fill = 'grey',
- activefill = 'yellow')
-
-
- elif self._line_is_active and self._line_to_struc[self._active_line][5] is not None:
- self.draw_cylinder(canvas = self._prop_canvas,CylObj = self._line_to_struc[self._active_line][5],
- height = 200, radius = 150, start_x_cyl = 500,start_y_cyl = 20,
- text_color= self._color_text)
-
- else:
- pass
-
- @staticmethod
- def draw_cylinder(text_size = None, canvas = None, CylObj: CylinderAndCurvedPlate = None,
- height = 150, radius = 150,
- start_x_cyl = 500,start_y_cyl = 20, acceptance_color = False, text_x = 200, text_y = 130,
- text_color = 'black'):
-
- canvas_width = canvas.winfo_width()
- canvas_height = canvas.winfo_height()
- if text_size == None:
- text_size = 'Verdana 8'
-
- canvas.create_text([text_x, text_y], text=CylObj, font=text_size,fill = text_color)
- # setting the input field to active line properties
- #self.set_selected_variables(self._active_line)
-
- offset_oval = 30
-
- coord1 = start_x_cyl, start_y_cyl, start_x_cyl + radius, start_y_cyl+offset_oval
- coord2 = start_x_cyl, start_y_cyl + height, start_x_cyl + radius,start_y_cyl+ offset_oval + height
-
- arc_1 = canvas.create_oval(coord1, width=5, fill='grey90')
- arc_2 = canvas.create_arc(coord2, extent=180, start=180, style=tk.ARC, width=3)
-
- line1 = canvas.create_line(coord1[0], coord1[1] + offset_oval / 2,
- coord1[0], coord1[1] + height + offset_oval / 2,
- width=3)
- line2 = canvas.create_line(coord1[0] + radius, coord1[1] + offset_oval / 2,
- coord1[0] + radius, coord1[1] + height + offset_oval / 2,
- width=3)
- if CylObj.LongStfObj is not None:
- long_obj = CylObj.LongStfObj
-
- num_stf = int(1000 * 2*math.pi*CylObj.ShellObj.radius / long_obj.spacing / 2)
- for line_num in range(1, num_stf, 1):
- angle = 180 - 180 / (num_stf) * line_num
- arc_x, arc_y = 1 * math.cos(math.radians(angle)), 0.5 * math.sin(math.radians(angle))
- arc_x = (arc_x + 1) / 2
-
- line1 = canvas.create_line(coord1[0] + radius * arc_x,
- coord1[1] + 1 * arc_y * offset_oval+offset_oval/2,
- coord1[0] + radius * arc_x,
- coord1[1] + height + 1 * arc_y * offset_oval+offset_oval/2,
- fill='blue')
- if CylObj.RingStfObj is not None:
- num_ring_stiff = CylObj.ShellObj.length_of_shell / \
- CylObj.ShellObj._dist_between_rings
- num_ring_stiff = int(num_ring_stiff)
-
- for ring_stf in range(1, num_ring_stiff+1, 1):
- coord3 = coord1[0], coord1[1] + (height / (num_ring_stiff + 1)) * ring_stf, \
- start_x_cyl + radius, coord1[3] + (height / (num_ring_stiff + 1)) * ring_stf,
- arc_2 = canvas.create_arc(coord3, extent=180, start=180, style=tk.ARC, width=2,
- fill='orange',
- outline='orange')
- if CylObj.RingFrameObj is not None:
- num_ring_girder = CylObj.ShellObj.length_of_shell / \
- CylObj.length_between_girders
- num_ring_girder = int(num_ring_girder)
- for ring_girder in range(1, num_ring_girder + 1, 1):
- coord3 = coord1[0], coord1[1] + (height / (num_ring_girder + 1)) * ring_girder, \
- start_x_cyl + radius, coord1[3] + (height / (num_ring_girder + 1)) * ring_girder,
- arc_2 = canvas.create_arc(coord3, extent=180, start=180, style=tk.ARC, width=4,
- fill='grey', outline='grey')
-
- def draw_results(self, state = None):
- '''
- The properties canvas is created here.
- state = {'colors': {}, 'section_modulus': {}, 'thickness': {}, 'shear_area': {}, 'buckling': {},
- 'fatigue': {}, 'pressure_uls': {}, 'pressure_fls': {},
- 'all_obj': {}, 'scant_calc_obj': {}, 'fatigue_obj': {}}
- :return:
- '''
-
- self._result_canvas.delete('all')
-
- if state is None or self._active_line not in state['all_obj'].keys():
- return
-
- if self._line_is_active:
- x, y, dx, dy = 0, 5, 15, 17
-
- if self._active_line in self._line_to_struc and self._line_to_struc[self._active_line][5] is None:
-
- m3_to_mm3 = float(math.pow(1000,3))
- m2_to_mm2 = float(math.pow(1000, 2))
+ m3_to_mm3 = float(math.pow(1000,3))
+ m2_to_mm2 = float(math.pow(1000, 2))
current_line = self._active_line
@@ -4588,71 +4982,8 @@ def draw_results(self, state = None):
# buckling results
start_y, y = 5, 10
- if self._PULS_results != None and self._new_buckling_method.get() == 'DNV PULS':
- line_results = state['PULS colors'][self._active_line]
- puls_res = self._PULS_results.get_puls_line_results(self._active_line)
- if puls_res != None:
- geo_problem = False
- if type(puls_res['Ultimate capacity']['Actual usage Factor'][0]) != str:
- ult_text = 'Ultimate capacity usage factor: ' + str(puls_res['Ultimate capacity']
- ['Actual usage Factor'][
- 0] / self._new_puls_uf.get())
- else:
- geo_problem = True
- ult_text = puls_res['Ultimate capacity']['Actual usage Factor'][0]
- if puls_res['Buckling strength']['Actual usage Factor'][0] != None:
- buc_text = 'Buckling capacity usage factor: ' + str(puls_res['Buckling strength']
- ['Actual usage Factor'][
- 0] / self._new_puls_uf.get())
- else:
- buc_text = 'Buckling capacity usage factor: None - geometric issue'
-
- loc_label = 'Local geom req (PULS validity limits)' if \
- obj_scnt_calc_pl.get_puls_sp_or_up() == 'SP' else 'Geom. Req (PULS validity limits)'
- csr_label = 'CSR-Tank requirements (primary stiffeners)' if \
- obj_scnt_calc_pl.get_puls_sp_or_up() == 'SP' else 'CSR-Tank req'
- if geo_problem:
- loc_geom = 'Not ok: '
- for key, value in puls_res[loc_label].items():
- if value[0] == 'Not ok':
- loc_geom += key + ' '
- else:
- loc_geom = 'Ok' if all(
- [val[0] == 'Ok' for val in puls_res[loc_label]
- .values()]) else 'Not ok'
- csr_geom = 'Ok' if all(
- [val[0] in ['Ok', '-'] for val in puls_res[csr_label]
- .values()]) else 'Not ok'
- loc_geom = loc_label + ': ' + loc_geom
- csr_geom = csr_label+': ' + csr_geom
- self._result_canvas.create_text([x * 1, y + (start_y+0) * dy], text='PULS results',
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill = self._color_text)
- self._result_canvas.create_text([x * 1, y + (start_y+1) * dy], text=buc_text,
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill=line_results['buckling'])
- self._result_canvas.create_text([x * 1, y + (start_y+2) * dy], text=ult_text,
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill=line_results['ultimate'])
- self._result_canvas.create_text([x * 1, y + (start_y+3) * dy], text=loc_geom,
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill=line_results['local geometry'])
- self._result_canvas.create_text([x * 1, y + (start_y+4) * dy], text=csr_geom,
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill=line_results['csr'])
- else:
- self._result_canvas.create_text([x * 1, y + (start_y+0) * dy],
- text='PULS results not avaliable for this line.\n'
- 'Run or update lines.',
- font=self._text_size['Text 9 bold'],
- anchor='nw',
- fill='Orange')
- elif self._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
+ fatigue_start_offset = 8
+ if self._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
'''
return {'Plate': {'Plate buckling': up_buckling}, 'Stiffener': {'Overpressure plate side': stf_buckling_pl_side,
'Overpressure stiffener side': stf_buckling_stf_side,
@@ -4774,45 +5105,269 @@ def draw_results(self, state = None):
# anchor='nw',fill=color_buckling)
- elif self._new_buckling_method.get() == 'ML-CL (PULS based)':
+ elif self._new_buckling_method.get() in [
+ 'SemiAnalytical S3/U3',
+ 'ML-Numeric (PULS based)',
+ ]:
+
+ print_semi_analytical_results = self._new_buckling_method.get() == 'SemiAnalytical S3/U3'
+ print_class_results = False
+ print_numeric_results = self._new_buckling_method.get() == 'ML-Numeric (PULS based)'
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + 0) * dy) * 1],
+ text='Buckling results ANYstructure SemiAnalytical/ML algorithm:',
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=self._color_text,
+ )
+
+ line_offset = 1
+
+ # -------------------------------------------------------------------------
+ # Built-in semi-analytical replacement result
+ # -------------------------------------------------------------------------
+ if print_semi_analytical_results:
+ semi_analytical = state.get('SemiAnalytical', {}).get(current_line, None)
+ semi_analytical_valid = state.get('SemiAnalytical valid', {}).get(current_line, {})
+ semi_analytical_colors = state.get('SemiAnalytical colors', {}).get(current_line, {})
+
+ if semi_analytical is not None:
+ semi_analytical_is_valid = semi_analytical_valid.get('valid prediction', None) == 1
+ if semi_analytical_is_valid:
+ buckling_uf = semi_analytical.get('buckling UF', float('inf'))
+ ultimate_uf = semi_analytical.get('ultimate UF', float('inf'))
+ buckling_uf_raw = semi_analytical.get('buckling UF raw', None)
+ ultimate_uf_raw = semi_analytical.get('ultimate UF raw', None)
+ semi_analytical_mat_fac = semi_analytical.get(
+ 'material factor',
+ self._new_material_factor.get(),
+ )
+
+ buckling_uf_txt = f"{buckling_uf:.3f}"
+ ultimate_uf_txt = f"{ultimate_uf:.3f}"
+
+ if buckling_uf_raw is not None:
+ buckling_uf_txt += f" ({buckling_uf_raw:.3f} x {semi_analytical_mat_fac:.2f})"
+
+ if ultimate_uf_raw is not None:
+ ultimate_uf_txt += f" ({ultimate_uf_raw:.3f} x {semi_analytical_mat_fac:.2f})"
+ else:
+ buckling_uf_txt = 'invalid/unsupported'
+ ultimate_uf_txt = 'invalid/unsupported'
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Buckling UF SemiAnalytical S3/U3: ' + buckling_uf_txt,
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=semi_analytical_colors.get('buckling', 'red'),
+ )
+ line_offset += 1
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Ultimate UF SemiAnalytical S3/U3: ' + ultimate_uf_txt,
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=semi_analytical_colors.get('ultimate', 'red'),
+ )
+ line_offset += 1
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='SemiAnalytical UF acceptance limit: <= 1.00',
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text,
+ )
+ line_offset += 1
+
+ semi_analytical_status = semi_analytical_valid.get('valid label', '')
+ if semi_analytical.get('error', ''):
+ semi_analytical_status += ' | ' + semi_analytical.get('error', '')
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='SemiAnalytical S3/U3 status: ' + semi_analytical_status,
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text if semi_analytical_is_valid else 'red',
+ )
+ line_offset += 1
- self._result_canvas.create_text([x * 1, (y+(start_y+0)*dy) * 1],
- text='Buckling results ANYstructure ML algorithm:',
- font=self._text_size["Text 9 bold"], anchor='nw',
- fill = self._color_text)
- self._result_canvas.create_text([x * 1, (y+(start_y+1)*dy) * 1],
- text='Buckling: ' + state['ML buckling class'][current_line]['buckling'],
- font=self._text_size["Text 9 bold"],
- anchor='nw', fill=state['ML buckling colors'][current_line]['buckling'])
- self._result_canvas.create_text([x * 1, (y+(start_y+2)*dy) * 1],
- text='Ultimate: ' +state['ML buckling class'][current_line]['ultimate'],
- font=self._text_size["Text 9 bold"],
- anchor='nw', fill=state['ML buckling colors'][current_line]['ultimate'])
- if obj_scnt_calc_pl.get_puls_sp_or_up() == 'SP':
- csr = state['ML buckling class'][current_line]['CSR']
- csr_str = ['Ok' if csr[0] == 1 else 'Not ok', 'Ok' if csr[1] == 1 else 'Not ok',
- 'Ok' if csr[2] == 1 else 'Not ok', 'Ok' if csr[3] == 1 else 'Not ok']
- self._result_canvas.create_text([x * 1, (y+(start_y+3)*dy) * 1],
- text='CSR requirements (stiffener): plate-'+ csr_str[0]+ ' web-'+
- csr_str[1] + ' web/flange ratio-'+ csr_str[2] +
- ' flange-'+ csr_str[3] ,
- font=self._text_size["Text 9"],
- anchor='nw',
- fill=state['ML buckling colors'][current_line]['CSR requirement'])
- else:
- csr = state['ML buckling class'][current_line]['CSR']
- csr_str = 'Ok' if csr[0] == 1 else 'Not ok'
- self._result_canvas.create_text([x * 1, (y+(start_y+3)*dy) * 1],
- text='CSR requirements (stiffener): Plate slenderness -'+
- csr_str,
- font=self._text_size["Text 9"],
- anchor='nw',
- fill=state['ML buckling colors'][current_line]['CSR requirement'])
+ else:
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='SemiAnalytical S3/U3: not available',
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text,
+ )
+ line_offset += 1
+
+ # -------------------------------------------------------------------------
+ # Classification pipeline result
+ # -------------------------------------------------------------------------
+ if print_class_results:
+ ml_class = state.get('ML buckling class', {}).get(current_line, {})
+ ml_colors = state.get('ML buckling colors', {}).get(current_line, {})
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Buckling class: ' + str(ml_class.get('buckling', 'N/A')),
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=ml_colors.get('buckling', 'red'),
+ )
+ line_offset += 1
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Ultimate class: ' + str(ml_class.get('ultimate', 'N/A')),
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=ml_colors.get('ultimate', 'red'),
+ )
+ line_offset += 1
+
+ # -------------------------------------------------------------------------
+ # Numeric UF pipeline result
+ #
+ # The numeric UF values stored in state are already material-factored
+ # in get_color_and_calc_state(). The raw values are the model output
+ # for material factor = 1.0.
+ # -------------------------------------------------------------------------
+ if print_numeric_results:
+ numeric = state.get('ML buckling numeric', {}).get(current_line, None)
+ numeric_valid = state.get('ML buckling numeric valid', {}).get(current_line, {})
+ numeric_colors = state.get('ML buckling numeric colors', {}).get(current_line, {})
+
+ if numeric is not None:
+ numeric_is_valid = numeric_valid.get('valid prediction', None) == 1
+
+ if numeric_is_valid:
+ buckling_uf = numeric.get('buckling UF', float('inf'))
+ ultimate_uf = numeric.get('ultimate UF', float('inf'))
+
+ buckling_uf_raw = numeric.get('buckling UF raw', None)
+ ultimate_uf_raw = numeric.get('ultimate UF raw', None)
+
+ # Use the currently selected GUI material factor for display.
+ # The value in state['ML buckling numeric'] should already be
+ # multiplied by the material factor in get_color_and_calc_state().
+ numeric_mat_fac = self._new_material_factor.get()
+ buckling_uf_txt = f"{buckling_uf:.3f}"
+ ultimate_uf_txt = f"{ultimate_uf:.3f}"
+
+ if buckling_uf_raw is not None:
+ buckling_uf_txt += f" ({buckling_uf_raw:.3f} x {numeric_mat_fac:.2f})"
+
+ if ultimate_uf_raw is not None:
+ ultimate_uf_txt += f" ({ultimate_uf_raw:.3f} x {numeric_mat_fac:.2f})"
+
+ else:
+ buckling_uf_txt = 'invalid/NaN'
+ ultimate_uf_txt = 'invalid/NaN'
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Buckling UF numeric: ' + buckling_uf_txt,
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=numeric_colors.get('buckling', 'red'),
+ )
+ line_offset += 1
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Ultimate UF numeric: ' + ultimate_uf_txt,
+ font=self._text_size["Text 9 bold"],
+ anchor='nw',
+ fill=numeric_colors.get('ultimate', 'red'),
+ )
+ line_offset += 1
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Numeric UF acceptance limit: <= 1.00',
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text,
+ )
+ line_offset += 1
+
+ numeric_status = numeric_valid.get('valid label', '')
+ if numeric.get('error', ''):
+ numeric_status += ' | ' + numeric.get('error', '')
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Numeric UF status: ' + numeric_status,
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text if numeric_is_valid else 'red',
+ )
+ line_offset += 1
+
+ else:
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='Numeric UF: not available',
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=self._color_text,
+ )
+ line_offset += 1
+
+ # -------------------------------------------------------------------------
+ # CSR requirement result, shown for both ML classification and numeric modes
+ # -------------------------------------------------------------------------
+ ml_class = state.get('ML buckling class', {}).get(current_line, {})
+ ml_colors = state.get('ML buckling colors', {}).get(current_line, {})
+ csr = ml_class.get('CSR', None)
+
+ if csr is not None:
+ if obj_scnt_calc_pl.get_puls_sp_or_up() == 'SP':
+ csr_str = [
+ 'Ok' if csr[0] == 1 else 'Not ok',
+ 'Ok' if csr[1] == 1 else 'Not ok',
+ 'Ok' if csr[2] == 1 else 'Not ok',
+ 'Ok' if csr[3] == 1 else 'Not ok',
+ ]
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text=(
+ 'CSR requirements (stiffener): plate-' + csr_str[0] +
+ ' web-' + csr_str[1] +
+ ' web/flange ratio-' + csr_str[2] +
+ ' flange-' + csr_str[3]
+ ),
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=ml_colors.get('CSR requirement', 'red'),
+ )
+ line_offset += 1
+ else:
+ csr_str = 'Ok' if csr[0] == 1 else 'Not ok'
+
+ self._result_canvas.create_text(
+ [x * 1, (y + (start_y + line_offset) * dy) * 1],
+ text='CSR requirements (plate): Plate slenderness - ' + csr_str,
+ font=self._text_size["Text 9"],
+ anchor='nw',
+ fill=ml_colors.get('CSR requirement', 'red'),
+ )
+ line_offset += 1
+
+ # Keep fatigue below the ML result block, with the old default as a minimum.
+ fatigue_start_offset = max(8, line_offset + 1)
# fatigue results
- self._result_canvas.create_text([x * 1, (y+(start_y+8)*dy) * 1],
+ self._result_canvas.create_text([x * 1, (y+(start_y+fatigue_start_offset)*dy) * 1],
text='Fatigue results (DNVGL-RP-C203): ',
font=self._text_size["Text 9 bold"], anchor='nw', fill = self._color_text)
@@ -4820,19 +5375,19 @@ def draw_results(self, state = None):
if state['fatigue'][current_line]['damage'] is not None:
damage = state['fatigue'][current_line]['damage']
dff = state['fatigue'][current_line]['dff']
- self._result_canvas.create_text([x * 1, (y + (start_y+9) * dy) * 1],
+ self._result_canvas.create_text([x * 1, (y + (start_y+fatigue_start_offset+1) * dy) * 1],
text='Total damage (DFF not included): '+str(round(damage,3)) +
' | With DFF = '+str(dff)+' --> Damage: '+
str(round(damage*dff,3)),
font=self._text_size["Text 9 bold"], anchor='nw',
fill=color_fatigue)
else:
- self._result_canvas.create_text([x * 1, (y + (start_y+9) * dy) * 1],
+ self._result_canvas.create_text([x * 1, (y + (start_y+fatigue_start_offset+1) * dy) * 1],
text='Total damage: NO RESULTS ',
font=self._text_size["Text 9 bold"],
anchor='nw', fill = self._color_text)
else:
- self._result_canvas.create_text([x * 1, (y + (start_y+9) * dy) * 1],
+ self._result_canvas.create_text([x * 1, (y + (start_y+fatigue_start_offset+1) * dy) * 1],
text='Total damage: NO RESULTS ',
font=self._text_size["Text 9 bold"],
anchor='nw', fill = self._color_text)
@@ -4910,45 +5465,244 @@ def draw_results(self, state = None):
fill=uf_col)
elif key == 'Stiffener check':
- if value is not None:
- y_location +=1
- self._result_canvas.create_text([x, dy*y_location],
- text='Stiffener requirement checks:',
- font=self._text_size['Text 10 bold'],
- anchor='nw',
- fill = self._color_text)
- y_location += 1
- idx_y, idx_x = 0, 0
+ if value is not None:
+ y_location +=1
+ self._result_canvas.create_text([x, dy*y_location],
+ text='Stiffener requirement checks:',
+ font=self._text_size['Text 10 bold'],
+ anchor='nw',
+ fill = self._color_text)
+ y_location += 1
+ idx_y, idx_x = 0, 0
+
+ for stf_type, chk_bool in value.items():
+ stf_text = stf_type
+ if stf_type == 'ring frame':
+ continue
+
+ chk_text = 'OK' if chk_bool == True else 'failed' if chk_bool == False else 'N/A'
+
+ self._result_canvas.create_text([15*dx*idx_x, dy*y_location],
+ text=stf_text, font=self._text_size['Text 10 bold'],
+ anchor='nw',
+ fill=self._color_text if not value else 'black')
+
+ self._result_canvas.create_text([15*dx*idx_x, y + (y_location+1)*dy],
+ text=chk_text, font=self._text_size['Text 10 bold'],
+ anchor='nw',
+ fill='green' if chk_bool == True else 'red' if
+ chk_bool == False else self._color_text)
+
+ self._result_canvas.create_text([15*dx*idx_x, y + (y_location+2)*dy],
+ text=results['Stiffener check detailed'][stf_type],
+ font=self._text_size['Text 10'],
+ anchor='nw',
+ fill='green' if chk_bool == True else 'red' if
+ chk_bool == False else self._color_text)
+ idx_y += 1
+ idx_x += 1
+
+
+ y_location += 1
+
+ def draw_prop(self, event = None):
+ '''
+ Prints the properties of the selected line to the bottom canvas.
+
+ properties for line dicitonary:
+
+ name of line : [ Structure class, calc scantling class, calc fatigue class, [load classes] ]
+
+ '''
+
+ self._prop_canvas.delete('all')
+ canvas_width = self._prop_canvas.winfo_width()
+ canvas_height = self._prop_canvas.winfo_height()
+
+ def checkered(line_distance, canvas):
+ '''
+ Grid lines in the properties canvas.
+ :param line_distance:
+ :return:
+ '''
+ # vertical lines at an interval of "line_distance" pixel
+ for x in range(line_distance, canvas_width, line_distance):
+ canvas.create_line(x, 0, x, canvas_width, stipple='gray50', activestipple='gray75')
+ # horizontal lines at an interval of "line_distance" pixel
+ for y in range(line_distance, canvas_height, line_distance):
+ canvas.create_line(0, y, canvas_width, y, stipple='gray50', activestipple='gray75')
+
+ if self._active_line in self._line_to_struc:
+ self.set_selected_variables(self._active_line)
+ # printing the properties to the active line
+ if self._line_is_active and self._line_to_struc[self._active_line][5] is None:
+ #checkered(10, self._prop_canvas)
+ self._prop_canvas.create_text([canvas_width/2-canvas_width/20, canvas_height/20],
+ text ='SELECTED: '+str(self._active_line),
+ font=self._text_size["Text 10 bold"], fill='red')
+ if all([self._line_to_struc[self._active_line][0].Stiffener is None,
+ self._line_to_struc[self._active_line][0].Girder is None]):
+ structure_obj = self._line_to_struc[self._active_line][0].Plate
+ spacing = structure_obj.get_s() * self._prop_canvas_scale * 3
+ plate_thk = structure_obj.get_pl_thk() * self._prop_canvas_scale * 3
+ startx = 20
+ starty = 225
+ self._prop_canvas.create_text([startx + 100, 50],
+ text='Plate with thickness ' +
+ str(structure_obj.get_pl_thk()*1000) + ' mm' ,
+ font=self._text_size["Text 10 bold"], fill='Black')
+ self._prop_canvas.create_rectangle(startx + spacing,
+ starty,
+ startx + spacing + spacing,
+ starty - plate_thk,
+ fill='grey', activefill='yellow')
+
+ for idx, structure_obj in enumerate([self._line_to_struc[self._active_line][0].Stiffener,
+ self._line_to_struc[self._active_line][0].Girder]):
+ mult = 1 if self._line_to_struc[self._active_line][0].Girder is not None else 2 # *(400/max_web)
+ thk_mult = 2 # *(400/max_web)
+ startx = 100 + 300 * idx
+ starty = 225
+
+ if structure_obj is not None:
+ self._prop_canvas.create_text([startx +60, 50],
+ text='Stiffener\n' +structure_obj.get_beam_string()
+ if idx == 0 else 'Girder\n' + structure_obj.get_beam_string(),
+ font=self._text_size["Text 9 bold"], fill='Black')
+ if structure_obj is not None:
+ self._prop_canvas.create_text([100, 20],
+ text='Thickness scale x 2',
+ font=self._text_size["Text 10 bold"], fill='grey')
+ # drawing stiffener
+ spacing = structure_obj.get_s()*self._prop_canvas_scale * mult
+ stf_web_height = structure_obj.get_web_h()*self._prop_canvas_scale * mult
+ stf_flange_width = structure_obj.get_fl_w() *self._prop_canvas_scale * mult
+ plate_thk = structure_obj.get_pl_thk()*self._prop_canvas_scale*thk_mult * mult
+ stf_web_thk = structure_obj.get_web_thk()*self._prop_canvas_scale*thk_mult * mult
+ stf_flange_thk = structure_obj.get_fl_thk()*self._prop_canvas_scale*thk_mult * mult
+
+ for count in [0,1,2] if idx == 0 else [0,]:
+
+ self._prop_canvas.create_rectangle(startx + count*spacing,
+ starty,
+ startx+spacing+ count*spacing,
+ starty- plate_thk ,
+ fill = 'grey', activefill = 'yellow')
+ self._prop_canvas.create_rectangle(startx+spacing*0.5+ count*spacing - stf_web_thk/2,
+ starty - plate_thk,
+ startx+spacing*0.5+ count*spacing + stf_web_thk/2,
+ starty - stf_web_height - plate_thk,
+ fill = 'grey', activefill = 'yellow')
+
+ if structure_obj.get_stiffener_type() not in ['L', 'L-bulb']:
+
+ self._prop_canvas.create_rectangle(startx+spacing*0.5-stf_flange_width/2+ count*spacing,
+ starty - stf_web_height - plate_thk,
+ startx + spacing * 0.5 + stf_flange_width / 2+ count*spacing,
+ starty - stf_web_height - plate_thk- stf_flange_thk,
+ fill = 'grey', activefill = 'yellow')
+ else:
+ self._prop_canvas.create_rectangle(startx+spacing*0.5-stf_web_thk/2+ count*spacing,
+ starty-stf_web_height - plate_thk,
+ startx + spacing * 0.5 + stf_flange_width + count*spacing,
+ starty - stf_web_height - plate_thk - stf_flange_thk,
+ fill = 'grey',
+ activefill = 'yellow')
+
+
+ elif self._line_is_active and self._line_to_struc[self._active_line][5] is not None:
+ self.draw_cylinder(canvas = self._prop_canvas,CylObj = self._line_to_struc[self._active_line][5],
+ height = 200, radius = 150, start_x_cyl = 500,start_y_cyl = 20,
+ text_color= self._color_text)
+
+ else:
+ pass
+
+
+ @staticmethod
+ def draw_cylinder(text_size = None, canvas = None, CylObj: CylinderAndCurvedPlate = None,
+ height = 150, radius = 150,
+ start_x_cyl = 500,start_y_cyl = 20, acceptance_color = False, text_x = 200, text_y = 130,
+ text_color = 'black'):
+
+ canvas_width = canvas.winfo_width()
+ canvas_height = canvas.winfo_height()
+ if text_size == None:
+ text_size = 'Verdana 8'
+
+ canvas.create_text([text_x, text_y], text=CylObj, font=text_size,fill = text_color)
+ # setting the input field to active line properties
+ #self.set_selected_variables(self._active_line)
+
+ offset_oval = 30
+
+ coord1 = start_x_cyl, start_y_cyl, start_x_cyl + radius, start_y_cyl+offset_oval
+ coord2 = start_x_cyl, start_y_cyl + height, start_x_cyl + radius,start_y_cyl+ offset_oval + height
- for stf_type, chk_bool in value.items():
- stf_text = stf_type
- if stf_type == 'ring frame':
- continue
+ arc_1 = canvas.create_oval(coord1, width=5, fill='grey90')
+ arc_2 = canvas.create_arc(coord2, extent=180, start=180, style=tk.ARC, width=3)
- chk_text = 'OK' if chk_bool == True else 'failed' if chk_bool == False else 'N/A'
+ line1 = canvas.create_line(coord1[0], coord1[1] + offset_oval / 2,
+ coord1[0], coord1[1] + height + offset_oval / 2,
+ width=3)
+ line2 = canvas.create_line(coord1[0] + radius, coord1[1] + offset_oval / 2,
+ coord1[0] + radius, coord1[1] + height + offset_oval / 2,
+ width=3)
+ if CylObj.LongStfObj is not None:
+ long_obj = CylObj.LongStfObj
- self._result_canvas.create_text([15*dx*idx_x, dy*y_location],
- text=stf_text, font=self._text_size['Text 10 bold'],
- anchor='nw',
- fill=self._color_text if not value else 'black')
+ num_stf = int(1000 * 2*math.pi*CylObj.ShellObj.radius / long_obj.spacing / 2)
+ for line_num in range(1, num_stf, 1):
+ angle = 180 - 180 / (num_stf) * line_num
+ arc_x, arc_y = 1 * math.cos(math.radians(angle)), 0.5 * math.sin(math.radians(angle))
+ arc_x = (arc_x + 1) / 2
- self._result_canvas.create_text([15*dx*idx_x, y + (y_location+1)*dy],
- text=chk_text, font=self._text_size['Text 10 bold'],
- anchor='nw',
- fill='green' if chk_bool == True else 'red' if
- chk_bool == False else self._color_text)
+ line1 = canvas.create_line(coord1[0] + radius * arc_x,
+ coord1[1] + 1 * arc_y * offset_oval+offset_oval/2,
+ coord1[0] + radius * arc_x,
+ coord1[1] + height + 1 * arc_y * offset_oval+offset_oval/2,
+ fill='blue')
+ if CylObj.RingStfObj is not None:
+ num_ring_stiff = CylObj.ShellObj.length_of_shell / \
+ CylObj.ShellObj._dist_between_rings
+ num_ring_stiff = int(num_ring_stiff)
- self._result_canvas.create_text([15*dx*idx_x, y + (y_location+2)*dy],
- text=results['Stiffener check detailed'][stf_type],
- font=self._text_size['Text 10'],
- anchor='nw',
- fill='green' if chk_bool == True else 'red' if
- chk_bool == False else self._color_text)
- idx_y += 1
- idx_x += 1
+ for ring_stf in range(1, num_ring_stiff+1, 1):
+ coord3 = coord1[0], coord1[1] + (height / (num_ring_stiff + 1)) * ring_stf, \
+ start_x_cyl + radius, coord1[3] + (height / (num_ring_stiff + 1)) * ring_stf,
+ arc_2 = canvas.create_arc(coord3, extent=180, start=180, style=tk.ARC, width=2,
+ fill='orange',
+ outline='orange')
+ if CylObj.RingFrameObj is not None:
+ num_ring_girder = CylObj.ShellObj.length_of_shell / \
+ CylObj.length_between_girders
+ num_ring_girder = int(num_ring_girder)
+ for ring_girder in range(1, num_ring_girder + 1, 1):
+ coord3 = coord1[0], coord1[1] + (height / (num_ring_girder + 1)) * ring_girder, \
+ start_x_cyl + radius, coord1[3] + (height / (num_ring_girder + 1)) * ring_girder,
+ arc_2 = canvas.create_arc(coord3, extent=180, start=180, style=tk.ARC, width=4,
+ fill='grey', outline='grey')
- y_location += 1
+ def _get_ml_classes(self):
+ return getattr(self, '_ML_classes', ml_models.default_ml_class_messages())
+
+ def _build_report_data_snapshot(self):
+ return project_services.ReportDataSnapshot(
+ project_information=self._project_information.get('1.0', tk.END),
+ buckling_method=self._new_buckling_method.get(),
+ points=self._point_dict,
+ lines=self._line_dict,
+ line_bundles=self._line_to_struc,
+ tanks=self._tank_dict,
+ loads=self._load_dict,
+ result_state=self.get_color_and_calc_state(),
+ highest_pressures={
+ line_name: self.get_highest_pressure(line_name)
+ for line_name in self._line_to_struc
+ },
+ ml_classes=self._get_ml_classes(),
+ )
def report_generate(self, autosave = False):
'''
@@ -4957,10 +5711,12 @@ def report_generate(self, autosave = False):
'''
if not autosave:
- save_file = filedialog.asksaveasfile(mode="w", defaultextension=".pdf")
- filename = save_file.name
- if save_file is None: # ask saveasfile return `None` if dialog closed with "cancel".
+ target = project_application.ProjectFileDialogService.selected_output_target(
+ filedialog.asksaveasfilename(defaultextension=".pdf")
+ )
+ if target is None:
return
+ filename = target.path
else:
filename = '../testrun.pdf'
@@ -4974,9 +5730,9 @@ def report_generate(self, autosave = False):
else:
self.grid_display_tanks(save=True)
- doc = LetterMaker(filename, "Section results", 10, self)
- doc.createDocument()
- doc.savePDF()
+ project_services.ReportRequestService.create_pdf(
+ project_services.ReportRequest(str(filename), "Section results", 10, self._build_report_data_snapshot()),
+ )
try:
os.startfile(filename)
except FileNotFoundError:
@@ -4989,10 +5745,12 @@ def report_generate(self, autosave = False):
def table_generate(self, autosave = False):
if not autosave:
- save_file = filedialog.asksaveasfile(mode="w", defaultextension=".pdf")
- filename = save_file.name
- if save_file is None: # ask saveasfile return `None` if dialog closed with "cancel".
+ target = project_application.ProjectFileDialogService.selected_output_target(
+ filedialog.asksaveasfilename(defaultextension=".pdf")
+ )
+ if target is None:
return
+ filename = target.path
else:
filename = '../testrun.pdf'
@@ -5000,10 +5758,9 @@ def table_generate(self, autosave = False):
tk.messagebox.showerror('No lines', 'No lines defined. Cannot make report.')
return
- doc_dat = LetterMaker(filename, "Section results", 10, self)
- doc = SimpleDocTemplate(filename, pagesize=landscape(letter))
- elements = doc_dat.createTable()
- doc.build(elements)
+ project_services.ReportRequestService.create_table(
+ project_services.ReportRequest(str(filename), "Section results", 10, self._build_report_data_snapshot()),
+ )
try:
os.startfile(filename)
except FileNotFoundError:
@@ -5027,8 +5784,7 @@ def create_accelerations(self):
for tank, data in self._tank_dict.items():
data.set_acceleration(self._accelerations_dict)
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
except TclError:
messagebox.showinfo(title='Input error', message='Input must be a number. Dots used not comma.')
@@ -5049,29 +5805,19 @@ def new_point(self,copy=False,move=False, redo = None):
x_coord = (self._new_point_x.get() / 1000)
y_coord = (self._new_point_y.get() / 1000)
+ project_editor = project_services.ProjectEditService(self._point_dict, self._line_dict)
# Finding name of the new point
- current_point = ''
if move:
current_point, current_coords = self._active_point, self._point_dict[self._active_point]
else:
- found_name = False
- if len(self._point_dict) == 0:
- current_point = 'point1'
- found_name = True
- else:
- counter = 1
- while not found_name:
- current_point = 'point'+str(counter)
- if current_point not in self._point_dict.keys():
- found_name = True
- else:
- counter += 1
+ current_point = project_editor.next_point_name()
self._new_line_p1.set(get_num(current_point))
# Creating the point
# No point is created if another point is already there
- if [x_coord,y_coord] not in self._point_dict.values():
- self._point_dict[current_point] = [x_coord, y_coord]
+ point_record = project_editor.move_point(current_point, (x_coord, y_coord)) if move else \
+ project_editor.add_point(current_point, (x_coord, y_coord))
+ if point_record is not None:
self._active_point = current_point
if move:
self.logger(point=current_point, move_coords=(current_coords,[x_coord, y_coord]))
@@ -5101,24 +5847,22 @@ def move_point(self, event = None, redo = None):
'''
if self._point_is_active:
self.new_point(move=True, redo=redo) # doing the actual moving
- for line,data in self._line_dict.items():
+ project_editor = project_services.ProjectEditService(self._point_dict, self._line_dict)
+ for line in project_editor.connected_line_names(self._active_point):
+ data = self._line_dict[line]
# updating the span and deleting compartments (if not WT)
- if get_num(self._active_point) in data:
- coord1 = self._point_dict['point'+str(data[0])]
- coord2 = self._point_dict['point'+str(data[1])]
- if line in self._line_to_struc.keys():
- self._line_to_struc[line][0].Plate.set_span(dist(coord1,coord2))
- self._line_to_struc[line][0].Plate.set_span(dist(coord1, coord2))
- if self._PULS_results is not None:
- self._PULS_results.result_changed(line)
- if self._line_to_struc[line][0].Plate.get_structure_type() not in ['GENERAL_INTERNAL_NONWT',
- 'FRAME']:
- self._tank_dict = {}
- self._main_grid.clear()
- self._compartments_listbox.delete(0, 'end')
-
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
+ coord1 = self._point_dict['point'+str(data[0])]
+ coord2 = self._point_dict['point'+str(data[1])]
+ if line in self._line_to_struc.keys():
+ self._line_to_struc[line][0].Plate.set_span(dist(coord1,coord2))
+ self._line_to_struc[line][0].Plate.set_span(dist(coord1, coord2))
+ if self._line_to_struc[line][0].Plate.get_structure_type() not in ['GENERAL_INTERNAL_NONWT',
+ 'FRAME']:
+ self._tank_dict = {}
+ self._main_grid.clear()
+ self._compartments_listbox.delete(0, 'end')
+
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
self.update_frame()
else:
messagebox.showinfo(title='Input error', message='A point must be selected (right click).')
@@ -5138,6 +5882,7 @@ def new_line(self, event = None, redo = None):
Adds line to line dictionary. Type is 'line1' = [p1,p2]
'''
+ current_name = None
try:
# if's ensure that the new line does not exist already and that the point input is not an invalid point.
if redo is None:
@@ -5145,36 +5890,571 @@ def new_line(self, event = None, redo = None):
'point' + str(self._new_line_p2.get())
else:
first_point, second_point = redo
- first_point_num, second_point_num = get_num(first_point), get_num(second_point)
-
- if first_point in self._point_dict.keys() and second_point in self._point_dict.keys() \
- and first_point != second_point:
- line_str, line_str_rev = self.make_point_point_line_string(first_point_num, second_point_num)
-
- if line_str and line_str_rev not in self._line_point_to_point_string:
- name = False
- counter = 1
- while not name:
- current_name = 'line' + str(counter)
- if current_name not in self._line_dict.keys():
- name = True
- counter += 1
-
- self._line_dict[current_name] = [first_point_num, second_point_num]
-
- self.update_frame()
- self.logger(line=[current_name, redo])
-
- # making stings from two points difining the lines, e.g. for line 1 string could be 'p1p2' and 'p2p1'
- self._line_point_to_point_string.append(line_str)
- self._line_point_to_point_string.append(line_str_rev)
- self.add_to_combinations_dict(current_name)
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
+ project_editor = project_services.ProjectEditService(self._point_dict, self._line_dict)
+ line_record = project_editor.add_line(first_point, second_point)
+
+ if line_record is not None:
+ current_name = line_record.name
+ self.update_frame()
+ self.logger(line=[current_name, redo])
+
+ # Keep the legacy point-to-point index synchronized while the GUI still reads it.
+ self._line_point_to_point_string.extend(line_record.endpoint_keys)
+ self.add_to_combinations_dict(current_name)
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
except TclError:
messagebox.showinfo(title='Input error', message='Input must be a line number.')
return current_name
+ def _is_flat_calculation_domain(self, calculation_domain):
+ return calculation_domain in api_helpers.FLAT_STRUCTURE_DOMAINS
+
+ def _build_flat_structure_property_request(self):
+ return project_services.FlatStructurePropertyRequest(
+ calculation_domain=self._new_calculation_domain.get(),
+ base_values={
+ 'material': self._new_material.get(),
+ 'material_factor': self._new_material_factor.get(),
+ 'span': self._new_field_len.get(),
+ 'spacing': self._new_stf_spacing.get(),
+ 'plate_thk': self._new_plate_thk.get(),
+ 'stf_web_h': self._new_stf_web_h.get(),
+ 'stf_web_t': self._new_stf_web_t.get(),
+ 'stf_fl_w': self._new_stf_fl_w.get(),
+ 'stf_fl_t': self._new_stf_fl_t.get(),
+ 'structure_type': self._new_stucture_type.get(),
+ 'stf_type': self._new_stf_type.get(),
+ 'sigma_y1': self._new_sigma_y1.get(),
+ 'sigma_y2': self._new_sigma_y2.get(),
+ 'sigma_x1': self._new_sigma_x1.get(),
+ 'sigma_x2': self._new_sigma_x2.get(),
+ 'tau_xy': self._new_tauxy.get(),
+ 'plate_kpp': self._new_plate_kpp.get(),
+ 'stf_kps': self._new_stf_kps.get(),
+ 'stf_km1': self._new_stf_km1.get(),
+ 'stf_km2': self._new_stf_km2.get(),
+ 'stf_km3': self._new_stf_km3.get(),
+ 'pressure_side': self._new_pressure_side.get(),
+ 'zstar_optimization': self._new_zstar_optimization.get(),
+ 'puls_method': self._new_puls_method.get(),
+ 'puls_boundary': self._new_puls_panel_boundary.get(),
+ 'puls_stiffener_end': self._new_buckling_stf_end_support.get(),
+ 'puls_sp_or_up': self._new_puls_sp_or_up.get(),
+ 'puls_up_boundary': self._new_puls_up_boundary.get(),
+ 'panel_or_shell': self._new_panel_or_shell.get(),
+ 'girder_lg': self._new_girder_length_LG.get(),
+ },
+ girder_values={
+ 'web_h': self._new_girder_web_h.get(),
+ 'web_t': self._new_girder_web_t.get(),
+ 'fl_w': self._new_girder_fl_w.get(),
+ 'fl_t': self._new_girder_fl_t.get(),
+ 'type': self._new_girder_type.get(),
+ },
+ buckling_values={
+ 'min_pressure_adjacent_spans': self._new_buckling_min_press_adj_spans.get(),
+ 'load_factor_stresses': self._new_buckling_lf_stresses.get(),
+ 'stiffener_end_support': self._new_buckling_stf_end_support.get(),
+ 'girder_end_support': self._new_buckling_girder_end_support.get(),
+ 'tension_field': self._new_buckling_tension_field.get(),
+ 'plate_effective_against_sigy': self._new_buckling_effective_against_sigy.get(),
+ 'buckling_length_factor_stf': self._new_buckling_length_factor_stf.get(),
+ 'buckling_length_factor_girder': self._new_buckling_length_factor_stf.get(),
+ 'km3': self._new_buckling_km3.get(),
+ 'km2': self._new_buckling_km2.get(),
+ 'girder_dist_lateral_support': self._new_buckling_girder_dist_bet_lat_supp.get(),
+ 'stiffener_dist_lateral_support': self._new_buckling_stf_dist_bet_lat_supp.get(),
+ 'panel_length': self._new_panel_length_Lp.get(),
+ 'fabrication_method_stiffener': self._new_buckling_fab_method_stf.get(),
+ 'fabrication_method_girder': self._new_buckling_fab_method_girder.get(),
+ },
+ structure_types=self._structure_types,
+ )
+
+ def _build_flat_structure_properties(self):
+ return project_services.FlatStructurePropertyService.build(
+ self._build_flat_structure_property_request()
+ )
+
+ def _build_flat_structure_property_request_from_excel_record(self, import_record, span):
+ plate_thk, spacing, web_h, web_t, flange_w, flange_t = import_record.plate_values
+ sigma_x1, sigma_x2, sigma_y1, sigma_y2, tau_xy = import_record.stress_values
+ girder_length, girder_web_h, girder_web_t, girder_fl_w, girder_fl_t, girder_type = \
+ import_record.girder_values
+ overpressure, structure_type, stf_end, girder_end, fab_stf, fab_girder, length_stf, length_girder, \
+ stf_lateral_support, girder_lateral_support, tension_field, effective_against_sigy = \
+ import_record.buckling_values
+
+ return project_services.FlatStructurePropertyRequest(
+ calculation_domain=import_record.calculation_domain,
+ base_values={
+ 'material': self._new_material.get(),
+ 'material_factor': self._new_material_factor.get(),
+ 'span': span,
+ 'spacing': spacing,
+ 'plate_thk': plate_thk,
+ 'stf_web_h': web_h,
+ 'stf_web_t': web_t,
+ 'stf_fl_w': flange_w,
+ 'stf_fl_t': flange_t,
+ 'structure_type': structure_type if structure_type is not None else self._new_stucture_type.get(),
+ 'stf_type': self._new_stf_type.get(),
+ 'sigma_y1': sigma_y1,
+ 'sigma_y2': sigma_y2,
+ 'sigma_x1': sigma_x1,
+ 'sigma_x2': sigma_x2,
+ 'tau_xy': tau_xy,
+ 'plate_kpp': self._new_plate_kpp.get(),
+ 'stf_kps': self._new_stf_kps.get(),
+ 'stf_km1': self._new_stf_km1.get(),
+ 'stf_km2': self._new_stf_km2.get(),
+ 'stf_km3': self._new_stf_km3.get(),
+ 'pressure_side': self._new_pressure_side.get(),
+ 'zstar_optimization': self._new_zstar_optimization.get(),
+ 'puls_method': self._new_puls_method.get(),
+ 'puls_boundary': self._new_puls_panel_boundary.get(),
+ 'puls_stiffener_end': stf_end if stf_end is not None else self._new_buckling_stf_end_support.get(),
+ 'puls_sp_or_up': self._new_puls_sp_or_up.get(),
+ 'puls_up_boundary': self._new_puls_up_boundary.get(),
+ 'panel_or_shell': self._new_panel_or_shell.get(),
+ 'girder_lg': girder_length,
+ },
+ girder_values={
+ 'web_h': girder_web_h,
+ 'web_t': girder_web_t,
+ 'fl_w': girder_fl_w,
+ 'fl_t': girder_fl_t,
+ 'type': girder_type if girder_type is not None else self._new_girder_type.get(),
+ },
+ buckling_values={
+ 'min_pressure_adjacent_spans': self._new_buckling_min_press_adj_spans.get(),
+ 'load_factor_stresses': self._new_buckling_lf_stresses.get(),
+ 'stiffener_end_support': stf_end if stf_end is not None else self._new_buckling_stf_end_support.get(),
+ 'girder_end_support': girder_end if girder_end is not None
+ else self._new_buckling_girder_end_support.get(),
+ 'tension_field': tension_field if tension_field is not None else self._new_buckling_tension_field.get(),
+ 'plate_effective_against_sigy': effective_against_sigy if effective_against_sigy is not None
+ else self._new_buckling_effective_against_sigy.get(),
+ 'buckling_length_factor_stf': length_stf if length_stf is not None
+ else self._new_buckling_length_factor_stf.get(),
+ 'buckling_length_factor_girder': length_girder if length_girder is not None
+ else self._new_buckling_length_factor_stf.get(),
+ 'km3': self._new_buckling_km3.get(),
+ 'km2': self._new_buckling_km2.get(),
+ 'girder_dist_lateral_support': girder_lateral_support if girder_lateral_support is not None
+ else self._new_buckling_girder_dist_bet_lat_supp.get(),
+ 'stiffener_dist_lateral_support': stf_lateral_support if stf_lateral_support is not None
+ else self._new_buckling_stf_dist_bet_lat_supp.get(),
+ 'panel_length': self._new_panel_length_Lp.get(),
+ 'fabrication_method_stiffener': fab_stf if fab_stf is not None
+ else self._new_buckling_fab_method_stf.get(),
+ 'fabrication_method_girder': fab_girder if fab_girder is not None
+ else self._new_buckling_fab_method_girder.get(),
+ },
+ structure_types=self._structure_types,
+ ), overpressure
+
+ def _build_flat_structure_property_request_from_cylinder_excel_record(self, import_record):
+ shell_thk = import_record.shell_values[0]
+ long_web_h, long_web_t, long_fl_w, long_fl_t, panel_spacing, long_type = \
+ import_record.longitudinal_values
+
+ return project_services.FlatStructurePropertyRequest(
+ calculation_domain=import_record.calculation_domain,
+ base_values={
+ 'material': self._new_material.get(),
+ 'material_factor': self._new_material_factor.get(),
+ 'span': hlp.dist(import_record.first_point, import_record.second_point),
+ 'spacing': panel_spacing,
+ 'plate_thk': shell_thk,
+ 'stf_web_h': long_web_h,
+ 'stf_web_t': long_web_t,
+ 'stf_fl_w': long_fl_w,
+ 'stf_fl_t': long_fl_t,
+ 'structure_type': self._new_stucture_type.get(),
+ 'stf_type': long_type,
+ 'sigma_y1': self._new_sigma_y1.get(),
+ 'sigma_y2': self._new_sigma_y2.get(),
+ 'sigma_x1': self._new_sigma_x1.get(),
+ 'sigma_x2': self._new_sigma_x2.get(),
+ 'tau_xy': self._new_tauxy.get(),
+ 'plate_kpp': self._new_plate_kpp.get(),
+ 'stf_kps': self._new_stf_kps.get(),
+ 'stf_km1': self._new_stf_km1.get(),
+ 'stf_km2': self._new_stf_km2.get(),
+ 'stf_km3': self._new_stf_km3.get(),
+ 'pressure_side': self._new_pressure_side.get(),
+ 'zstar_optimization': self._new_zstar_optimization.get(),
+ 'puls_method': self._new_puls_method.get(),
+ 'puls_boundary': self._new_puls_panel_boundary.get(),
+ 'puls_stiffener_end': self._new_buckling_stf_end_support.get(),
+ 'puls_sp_or_up': self._new_puls_sp_or_up.get(),
+ 'puls_up_boundary': self._new_puls_up_boundary.get(),
+ 'panel_or_shell': self._new_panel_or_shell.get(),
+ 'girder_lg': self._new_girder_length_LG.get(),
+ },
+ girder_values={
+ 'web_h': self._new_girder_web_h.get(),
+ 'web_t': self._new_girder_web_t.get(),
+ 'fl_w': self._new_girder_fl_w.get(),
+ 'fl_t': self._new_girder_fl_t.get(),
+ 'type': self._new_girder_type.get(),
+ },
+ buckling_values={
+ 'min_pressure_adjacent_spans': self._new_buckling_min_press_adj_spans.get(),
+ 'load_factor_stresses': self._new_buckling_lf_stresses.get(),
+ 'stiffener_end_support': self._new_buckling_stf_end_support.get(),
+ 'girder_end_support': self._new_buckling_girder_end_support.get(),
+ 'tension_field': self._new_buckling_tension_field.get(),
+ 'plate_effective_against_sigy': self._new_buckling_effective_against_sigy.get(),
+ 'buckling_length_factor_stf': self._new_buckling_length_factor_stf.get(),
+ 'buckling_length_factor_girder': self._new_buckling_length_factor_stf.get(),
+ 'km3': self._new_buckling_km3.get(),
+ 'km2': self._new_buckling_km2.get(),
+ 'girder_dist_lateral_support': self._new_buckling_girder_dist_bet_lat_supp.get(),
+ 'stiffener_dist_lateral_support': self._new_buckling_stf_dist_bet_lat_supp.get(),
+ 'panel_length': self._new_panel_length_Lp.get(),
+ 'fabrication_method_stiffener': self._new_buckling_fab_method_stf.get(),
+ 'fabrication_method_girder': self._new_buckling_fab_method_girder.get(),
+ },
+ structure_types=self._structure_types,
+ )
+
+ def _build_cylinder_structure_property_request(self):
+ return project_services.CylinderStructurePropertyRequest(
+ calculation_domain=self._new_calculation_domain.get(),
+ dummy_values={
+ 'span': self._new_field_len.get(),
+ 'plate_thk': self._new_plate_thk.get(),
+ 'structure_type': self._new_stucture_type.get(),
+ 'sigma_y1': self._new_sigma_y1.get(),
+ 'sigma_y2': self._new_sigma_y2.get(),
+ 'sigma_x1': self._new_sigma_x1.get(),
+ 'sigma_x2': self._new_sigma_x2.get(),
+ 'tau_xy': self._new_tauxy.get(),
+ 'plate_kpp': self._new_plate_kpp.get(),
+ 'stf_kps': self._new_stf_kps.get(),
+ 'stf_km1': self._new_stf_km1.get(),
+ 'stf_km2': self._new_stf_km2.get(),
+ 'stf_km3': self._new_stf_km3.get(),
+ 'pressure_side': self._new_pressure_side.get(),
+ 'zstar_optimization': self._new_zstar_optimization.get(),
+ 'puls_method': self._new_puls_method.get(),
+ 'puls_boundary': self._new_puls_panel_boundary.get(),
+ 'puls_stiffener_end': self._new_buckling_stf_end_support.get(),
+ 'puls_sp_or_up': self._new_puls_sp_or_up.get(),
+ 'puls_up_boundary': self._new_puls_up_boundary.get(),
+ 'panel_or_shell': self._new_panel_or_shell.get(),
+ 'material_factor': self._new_material_factor.get(),
+ 'spacing': self._new_stf_spacing.get(),
+ },
+ shell_values={
+ 'thickness': self._new_shell_thk.get(),
+ 'radius': self._new_shell_radius.get(),
+ 'distance_between_rings': self._new_shell_dist_rings.get(),
+ 'length': self._new_shell_length.get(),
+ 'total_length': self._new_shell_tot_length.get(),
+ 'k_factor': self._new_shell_k_factor.get(),
+ },
+ longitudinal_values={
+ 'spacing': self._new_stf_spacing.get(),
+ 'web_h': self._new_stf_web_h.get(),
+ 'web_t': self._new_stf_web_t.get(),
+ 'fl_w': self._new_stf_fl_w.get(),
+ 'fl_t': self._new_stf_fl_t.get(),
+ 'type': self._new_stf_type.get(),
+ },
+ ring_stiffener_values={
+ 'web_h': self._new_shell_ring_stf_hw.get(),
+ 'web_t': self._new_shell_ring_stf_tw.get(),
+ 'fl_w': self._new_shell_ring_stf_b.get(),
+ 'fl_t': self._new_shell_ring_stf_tf.get(),
+ 'type': self._new_shell_ring_stf_type.get(),
+ },
+ ring_frame_values={
+ 'web_h': self._new_shell_ring_frame_hw.get(),
+ 'web_t': self._new_shell_ring_frame_tw.get(),
+ 'fl_w': self._new_shell_ring_frame_b.get(),
+ 'fl_t': self._new_shell_ring_frame_tf.get(),
+ 'type': self._new_shell_ring_frame_type.get(),
+ },
+ load_input={
+ 'mode': self._new_shell_stress_or_force.get(),
+ 'Nsd': self._new_shell_Nsd.get(),
+ 'Msd': self._new_shell_Msd.get(),
+ 'Tsd': self._new_shell_Tsd.get(),
+ 'Qsd': self._new_shell_Qsd.get(),
+ 'sasd': self._new_shell_sasd.get(),
+ 'smsd': self._new_shell_smsd.get(),
+ 'tTsd': self._new_shell_tTsd.get(),
+ 'tQsd': self._new_shell_tQsd.get(),
+ 'psd': self._new_shell_psd.get(),
+ 'shsd': self._new_shell_shsd.get(),
+ },
+ main_values={
+ 'material_factor': self._new_shell_mat_factor.get(),
+ 'fab_method_ring_stiffener': self._new_shell_ring_stf_fab_method.get(),
+ 'fab_method_ring_frame': self._new_shell_ring_frame_fab_method.get(),
+ 'e_module': self._new_shell_e_module.get(),
+ 'poisson': self._new_shell_poisson.get(),
+ 'yield': self._new_shell_yield.get(),
+ 'length_between_girders': self._new_shell_ring_frame_length_between_girders.get(),
+ 'panel_spacing': self._new_shell_panel_spacing.get(),
+ 'ring_stiffener_excluded': self._new_shell_exclude_ring_stf.get(),
+ 'ring_frame_excluded': self._new_shell_exclude_ring_frame.get(),
+ 'uls_or_als': self._new_shell_uls_or_als.get(),
+ 'end_cap_pressure': self._new_shell_end_cap_pressure_included.get(),
+ },
+ structure_types=self._structure_types,
+ )
+
+ def _build_cylinder_excel_import_defaults(self):
+ return project_services.CylinderExcelImportDefaults(
+ plate_thk=self._new_plate_thk.get(),
+ structure_type=self._new_stucture_type.get(),
+ sigma_y1=self._new_sigma_y1.get(),
+ sigma_y2=self._new_sigma_y2.get(),
+ sigma_x1=self._new_sigma_x1.get(),
+ sigma_x2=self._new_sigma_x2.get(),
+ tau_xy=self._new_tauxy.get(),
+ plate_kpp=self._new_plate_kpp.get(),
+ stf_kps=self._new_stf_kps.get(),
+ stf_km1=self._new_stf_km1.get(),
+ stf_km2=self._new_stf_km2.get(),
+ stf_km3=self._new_stf_km3.get(),
+ pressure_side=self._new_pressure_side.get(),
+ zstar_optimization=self._new_zstar_optimization.get(),
+ puls_method=self._new_puls_method.get(),
+ puls_boundary=self._new_puls_panel_boundary.get(),
+ puls_stiffener_end=self._new_buckling_stf_end_support.get(),
+ puls_sp_or_up=self._new_puls_sp_or_up.get(),
+ puls_up_boundary=self._new_puls_up_boundary.get(),
+ panel_or_shell=self._new_panel_or_shell.get(),
+ material_factor=self._new_material_factor.get(),
+ design_pressure=self._new_shell_psd.get(),
+ shear_stress=self._new_shell_shsd.get(),
+ e_module=self._new_shell_e_module.get(),
+ poisson=self._new_shell_poisson.get(),
+ length_between_girders=self._new_shell_ring_frame_length_between_girders.get(),
+ fab_method_ring_stiffener=self._new_shell_ring_stf_fab_method.get(),
+ fab_method_ring_frame=self._new_shell_ring_frame_fab_method.get(),
+ end_cap_pressure=self._new_shell_end_cap_pressure_included.get(),
+ structure_types=self._structure_types,
+ ring_stiffener_type=self._new_shell_ring_stf_type.get(),
+ ring_frame_type=self._new_shell_ring_frame_type.get(),
+ )
+
+ def _create_cylinder_structure_from_property_result(self, result):
+ return self._create_cylinder_structure_from_properties(
+ result.main_dict,
+ result.shell_dict,
+ result.longitudinal_dict,
+ result.ring_stiffener_dict,
+ result.ring_frame_dict,
+ result.geometry,
+ )
+
+ def _build_cylinder_structure_properties(self):
+ result = project_services.CylinderStructurePropertyService.build(
+ self._build_cylinder_structure_property_request()
+ )
+
+ sasd, smsd, tTsd, tQsd, _ = result.derived_stresses
+ Nsd, Msd, Tsd, Qsd = result.derived_forces
+ if self._new_shell_stress_or_force.get() == 1:
+ self._new_shell_sasd.set(sasd)
+ self._new_shell_smsd.set(smsd)
+ self._new_shell_tTsd.set(tTsd)
+ self._new_shell_tQsd.set(tQsd)
+ else:
+ self._new_shell_Nsd.set(Nsd)
+ self._new_shell_Msd.set(Msd)
+ self._new_shell_Tsd.set(Tsd)
+ self._new_shell_Qsd.set(Qsd)
+
+ cylinder_obj = self._create_cylinder_structure_from_property_result(result)
+
+ return (
+ cylinder_obj,
+ result.main_dict,
+ result.shell_dict,
+ result.longitudinal_dict,
+ result.ring_stiffener_dict,
+ result.ring_frame_dict,
+ result.geometry,
+ )
+
+ def _structure_input_is_missing(self):
+ if not self._is_flat_calculation_domain(self._new_calculation_domain.get()):
+ return False
+ return any([self._new_stf_spacing.get() == 0, self._new_plate_thk.get() == 0,
+ self._new_stf_web_h.get() == 0, self._new_stf_web_t.get() == 0])
+
+ @staticmethod
+ def _show_missing_structure_input_warning():
+ tk.messagebox.showwarning('No propertied defined', 'No properties is defined for the line!\n'
+ 'Define spacing, web height, web thickness etc.\n'
+ 'Either press button with stiffener or input manually.', type='ok')
+
+ def _create_all_structure_from_properties(self, prop_dict):
+ calculation_domain = self._new_calculation_domain.get()
+ return AllStructure(Plate=CalcScantlings(prop_dict['Plate']),
+ Stiffener=None if calculation_domain == 'Flat plate, unstiffened'
+ else CalcScantlings(prop_dict['Stiffener']),
+ Girder=None if calculation_domain in ['Flat plate, unstiffened',
+ 'Flat plate, stiffened']
+ else CalcScantlings(prop_dict['Girder']),
+ main_dict=prop_dict['main dict'])
+
+ def _create_cylinder_structure_from_properties(self, main_dict_cyl, shell_dict, long_dict, ring_stf_dict,
+ ring_frame_dict, geometry):
+ ring_stf_excluded = main_dict_cyl.get('ring stf excluded', [self._new_shell_exclude_ring_stf.get()])[0]
+ ring_frame_excluded = main_dict_cyl.get('ring frame excluded', [self._new_shell_exclude_ring_frame.get()])[0]
+ return CylinderAndCurvedPlate(main_dict_cyl, Shell(shell_dict),
+ long_stf=None if geometry in [1, 2, 5, 6]
+ else Structure(long_dict),
+ ring_stf=None if any([geometry in [1, 2, 3, 4],
+ ring_stf_excluded])
+ else Structure(ring_stf_dict),
+ ring_frame=None if any([geometry in [1, 2, 3, 4],
+ ring_frame_excluded])
+ else Structure(ring_frame_dict))
+
+ def _clear_tanks_and_grid(self):
+ self._tank_dict = {}
+ self._main_grid.clear()
+ self._compartments_listbox.delete(0, 'end')
+
+ def _refresh_after_structure_change(self, suspend_recalc):
+ if not suspend_recalc:
+ # when changing multiple parameters, recalculations are suspended.
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
+ state = self.update_frame()
+ if state != None and self._line_is_active:
+ self._weight_logger['new structure']['COG'].append(self.get_color_and_calc_state()['COG'])
+ self._weight_logger['new structure']['weight'].append(self.get_color_and_calc_state()['Total weight'])
+ self._weight_logger['new structure']['time'].append(time.time())
+ self.cylinder_gui_mods()
+
+ self.get_unique_plates_and_beams()
+
+ def _resolve_new_structure_properties(self, pasted_structure=None, multi_return=None, toggle_multi=None,
+ cylinder_return=None):
+ CylinderObj = None
+ obj_dict_stf = None
+ main_dict_cyl = shell_dict = long_dict = ring_stf_dict = ring_frame_dict = geometry = None
+
+ if multi_return is not None:
+ prop_dict = multi_return[0].get_main_properties() # From optimizer.
+ elif isinstance(toggle_multi, tuple):
+ prop_dict, obj_dict_stf = toggle_multi
+ elif toggle_multi is not None:
+ prop_dict = toggle_multi
+ elif pasted_structure is None:
+ prop_dict, obj_dict_stf = self._build_flat_structure_properties()
+
+ if not self._is_flat_calculation_domain(self._new_calculation_domain.get()) and cylinder_return is None:
+ CylinderObj, main_dict_cyl, shell_dict, long_dict, ring_stf_dict, ring_frame_dict, geometry = \
+ self._build_cylinder_structure_properties()
+ elif cylinder_return is not None:
+ main_dict_cyl, shell_dict, long_dict, ring_stf_dict, ring_frame_dict = \
+ cylinder_return.get_all_properties()
+ geometry = main_dict_cyl['geometry'][0]
+ else:
+ prop_dict = pasted_structure.get_main_properties()
+
+ if cylinder_return is not None:
+ CylinderObj = cylinder_return
+ main_dict_cyl, shell_dict, long_dict, ring_stf_dict, ring_frame_dict = \
+ cylinder_return.get_all_properties()
+ geometry = main_dict_cyl['geometry'][0]
+
+ if obj_dict_stf is None and isinstance(prop_dict, dict):
+ obj_dict_stf = prop_dict.get('Stiffener')
+
+ return (prop_dict, obj_dict_stf, CylinderObj, main_dict_cyl, shell_dict, long_dict, ring_stf_dict,
+ ring_frame_dict, geometry)
+
+ def _add_structure_to_active_line(self, prop_dict, obj_dict_stf, CylinderObj, main_dict_cyl, shell_dict,
+ long_dict, ring_stf_dict, ring_frame_dict, geometry):
+ All = self._create_all_structure_from_properties(prop_dict)
+ line_structures = project_services.LineStructureService(self._line_to_struc)
+ line_structures.assign_structure(self._active_line, All, cylinder=CylinderObj)
+
+ self._sections = add_new_section(self._sections, struc.Section(obj_dict_stf)) # TODO error when pasting
+ if line_structures.structure(self._active_line).Plate.get_structure_type() not in \
+ self._structure_types['non-wt']:
+ self._clear_tanks_and_grid()
+ if not self._is_flat_calculation_domain(self._new_calculation_domain.get()):
+ if CylinderObj is None:
+ CylinderObj = self._create_cylinder_structure_from_properties(
+ main_dict_cyl, shell_dict, long_dict, ring_stf_dict, ring_frame_dict, geometry)
+ line_structures.set_cylinder(self._active_line, CylinderObj)
+
+ def _scale_existing_flat_structure_if_needed(self, prev_all_obj):
+ line_structures = project_services.LineStructureService(self._line_to_struc)
+ if self._new_scale_stresses.get() and prev_all_obj.get_main_properties() != \
+ line_structures.structure(self._active_line).get_main_properties():
+ if prev_all_obj.Stiffener is not None:
+ plate = line_structures.structure(self._active_line).Plate
+ stiffener = line_structures.structure(self._active_line).Stiffener
+ girder = line_structures.structure(self._active_line).Girder
+ calc_tup = (plate.get_s(), plate.get_pl_thk(), stiffener.get_web_h(), stiffener.get_web_thk(),
+ stiffener.get_fl_w(),
+ stiffener.get_fl_thk(), plate.span, stiffener.girder_lg if girder is None else
+ girder.girder_lg, stiffener.stiffener_type)
+ else:
+ calc_tup = line_structures.structure(self._active_line).Plate.get_tuple()
+ line_structures.replace_structure(
+ self._active_line,
+ op.create_new_calc_obj(prev_all_obj, calc_tup, fup=self._new_fup.get(), fdwn=self._new_fdwn.get())[0],
+ )
+
+ def _sync_fatigue_object_after_structure_update(self, prop_dict):
+ project_services.LineStructureService(self._line_to_struc).sync_fatigue_after_structure_update(
+ self._active_line, prop_dict)
+
+ def _sync_cylinder_object_after_structure_update(self, CylinderObj, cylinder_return):
+ line_structures = project_services.LineStructureService(self._line_to_struc)
+ if all([CylinderObj is None, cylinder_return is None,
+ line_structures.cylinder(self._active_line) is not None]):
+ line_structures.set_cylinder(self._active_line, None)
+ elif CylinderObj is not None:
+ if line_structures.cylinder(self._active_line) is not None and self._new_scale_stresses.get():
+ NewCylinderObj = op.create_new_cylinder_obj(line_structures.cylinder(self._active_line),
+ CylinderObj.get_x_opt())
+ NewCylinderObj.LongStfObj = None if CylinderObj.LongStfObj is None \
+ else NewCylinderObj.LongStfObj
+ NewCylinderObj.RingStfObj = None if CylinderObj.RingStfObj is None \
+ else NewCylinderObj.RingStfObj
+ NewCylinderObj.RingFrameObj = None if CylinderObj.RingFrameObj is None \
+ else NewCylinderObj.RingFrameObj
+ line_structures.set_cylinder(self._active_line, CylinderObj)
+ elif cylinder_return is not None:
+ line_structures.set_cylinder(self._active_line, cylinder_return)
+
+ def _update_existing_active_line_structure(self, prop_dict, CylinderObj, cylinder_return):
+ line_structures = project_services.LineStructureService(self._line_to_struc)
+ prev_type = line_structures.structure(self._active_line).Plate.get_structure_type()
+ prev_all_obj = copy.deepcopy(line_structures.structure(self._active_line))
+ line_structures.update_structure_properties(self._active_line, prop_dict)
+
+ self._scale_existing_flat_structure_if_needed(prev_all_obj)
+ self._sync_fatigue_object_after_structure_update(prop_dict)
+
+ if prev_type in self._structure_types['non-wt'] and prop_dict['Plate']['structure_type'][0] in \
+ self._structure_types['internals'] + self._structure_types['horizontal'] + \
+ self._structure_types['vertical']:
+ self._clear_tanks_and_grid()
+
+ self._sync_cylinder_object_after_structure_update(CylinderObj, cylinder_return)
+
+ def _calculate_load_combinations_after_structure_update(self):
+ try:
+ self.calculate_all_load_combinations_for_line_all_lines()
+ except (KeyError, AttributeError):
+ pass
+
def new_structure(self, event = None, pasted_structure = None, multi_return = None, toggle_multi = None,
suspend_recalc = False, cylinder_return = None):
'''
@@ -5193,368 +6473,30 @@ def new_structure(self, event = None, pasted_structure = None, multi_return = No
if multi_return is None:
self.save_no_dialogue(backup=True) #keeping a backup
- if all([pasted_structure == None, multi_return == None]):
- missing_input = False
- if self._new_calculation_domain.get() in ['Flat plate, stiffened', 'Flat plate, unstiffened',
- 'Flat plate, stiffened with girder']:
- if any([self._new_stf_spacing.get()==0, self._new_plate_thk.get()==0, self._new_stf_web_h.get()==0,
- self._new_stf_web_t.get()==0]): # TODO must account for calculation domain
- missing_input = True
-
- if missing_input:
- mess = tk.messagebox.showwarning('No propertied defined', 'No properties is defined for the line!\n'
- 'Define spacing, web height, web thickness etc.\n'
- 'Either press button with stiffener or input '
- 'manually.', type='ok')
+ if all([pasted_structure == None, multi_return == None, toggle_multi == None]):
+ if self._structure_input_is_missing():
+ self._show_missing_structure_input_warning()
return
if self._line_is_active or multi_return != None:
# structure dictionary: name of line : [ 0.Structure class, 1.calc scantling class,
# 2.calc fatigue class, 3.load object, 4.load combinations result ]
- CylinderObj = None
- if multi_return is not None:
- prop_dict = multi_return[0].get_main_properties() # From optimizer.
- elif toggle_multi is not None:
- prop_dict = toggle_multi
- elif pasted_structure is None:
- calc_dom = self._new_calculation_domain.get()
-
- obj_dict = {'mat_yield': [self._new_material.get()*1e6, 'Pa'],
- 'mat_factor': [self._new_material_factor.get(), ''],
- 'span': [self._new_field_len.get()/1000, 'm'],
- 'spacing': [self._new_stf_spacing.get()/1000, 'm'],
- 'plate_thk': [self._new_plate_thk.get()/1000, 'm'],
- 'stf_web_height': [self._new_stf_web_h.get()/1000, 'm'],
- 'stf_web_thk': [self._new_stf_web_t.get()/1000, 'm'],
- 'stf_flange_width': [self._new_stf_fl_w.get()/1000, 'm'],
- 'stf_flange_thk': [self._new_stf_fl_t.get()/1000, 'm'],
- 'structure_type': [self._new_stucture_type.get(), ''],
- 'stf_type': [self._new_stf_type.get(), ''],
- 'sigma_y1': [self._new_sigma_y1.get(), 'MPa'],
- 'sigma_y2': [self._new_sigma_y2.get(), 'MPa'],
- 'sigma_x1': [self._new_sigma_x1.get(), 'MPa'],
- 'sigma_x2': [self._new_sigma_x2.get(), 'MPa'],
- 'tau_xy': [self._new_tauxy.get(), 'MPa'],
- 'plate_kpp': [self._new_plate_kpp.get(), ''],
- 'stf_kps': [self._new_stf_kps.get(), ''],
- 'stf_km1': [self._new_stf_km1.get(), ''],
- 'stf_km2': [self._new_stf_km2.get(), ''],
- 'stf_km3': [self._new_stf_km3.get(), ''],
- 'press_side': [self._new_pressure_side.get(), ''],
- 'structure_types':[self._structure_types, ''],
- 'zstar_optimization': [self._new_zstar_optimization.get(), ''],
- 'puls buckling method': [self._new_puls_method.get(), ''],
- 'puls boundary': [self._new_puls_panel_boundary.get(), ''],
- 'puls stiffener end': [self._new_buckling_stf_end_support.get(), ''],
- 'puls sp or up': [self._new_puls_sp_or_up.get(), ''],
- 'puls up boundary': [self._new_puls_up_boundary.get(), ''],
- 'panel or shell': [self._new_panel_or_shell.get(), ''],
- 'girder_lg': [self._new_girder_length_LG.get()/1000, '']}
-
- obj_dict_pl = copy.copy(obj_dict)
- obj_dict_stf = copy.copy(obj_dict)
- obj_dict_girder = copy.copy(obj_dict)
-
- obj_dict_girder['stf_web_height'] = [self._new_girder_web_h.get()/1000, 'm']
- obj_dict_girder['stf_web_thk'] = [self._new_girder_web_t.get() / 1000, 'm']
- obj_dict_girder['stf_flange_width'] = [self._new_girder_fl_w.get() / 1000, 'm']
- obj_dict_girder['stf_flange_thk'] = [self._new_girder_fl_t.get() / 1000, 'm']
- obj_dict_girder['stf_type'] = [self._new_girder_type.get(), '']
-
- main_dict = dict()
- main_dict['minimum pressure in adjacent spans'] = [self._new_buckling_min_press_adj_spans.get(), '']
- main_dict['material yield'] = [self._new_material.get()*1e6, 'Pa']
- main_dict['load factor on stresses'] = [self._new_buckling_lf_stresses.get(), '']
- main_dict['load factor on pressure'] = [1, '']
- main_dict['buckling method'] = [self._new_puls_method.get(), '']
- main_dict['stiffener end support'] =[self._new_buckling_stf_end_support.get(), ''] # 'Continuous'
- main_dict['girder end support'] = [self._new_buckling_girder_end_support.get(), ''] # 'Continuous'
- main_dict['tension field'] = [self._new_buckling_tension_field.get(), ''] # 'not allowed'
- main_dict['plate effective agains sigy'] = [self._new_buckling_effective_against_sigy.get(), ''] # True
- main_dict['buckling length factor stf'] = [self._new_buckling_length_factor_stf.get(), '']
- main_dict['buckling length factor girder'] = [self._new_buckling_length_factor_stf.get(), '']
- main_dict['km3'] = [self._new_buckling_km3.get(), ''] # 12
- main_dict['km2'] = [self._new_buckling_km2.get(), ''] # 24
- main_dict['girder distance between lateral support'] = [self._new_buckling_girder_dist_bet_lat_supp.get(), '']
- main_dict['stiffener distance between lateral support'] = [self._new_buckling_stf_dist_bet_lat_supp.get(), '']
- main_dict['panel length, Lp'] = [self._new_panel_length_Lp.get(), '']
- main_dict['pressure side'] = [self._new_pressure_side.get(), ''] # either 'stiffener', 'plate', 'both'
- main_dict['fabrication method stiffener'] = [self._new_buckling_fab_method_stf.get(), '']
- main_dict['fabrication method girder'] = [self._new_buckling_fab_method_girder.get(), '']
- main_dict['calculation domain'] = [self._new_calculation_domain.get(), '']
-
-
- prop_dict = {'main dict': main_dict,
- 'Plate': obj_dict_pl,
- 'Stiffener': None if calc_dom == 'Flat plate, unstiffened' else obj_dict_stf,
- 'Girder': None if calc_dom in ['Flat plate, unstiffened', 'Flat plate, stiffened']
- else obj_dict_girder}
-
- if self._new_calculation_domain.get() not in ['Flat plate, stiffened','Flat plate, unstiffened',
- 'Flat plate, stiffened with girder'] and cylinder_return is None:
- '''
- Shell structure.
- 0:'Stiffened panel, flat', 1:'Unstiffened shell (Force input)', 2:'Unstiffened panel (Stress input)',
- 3:'Longitudinal Stiffened shell (Force input)', 4:'Longitudinal Stiffened panel (Stress input)',
- 5:'Ring Stiffened shell (Force input)', 6:'Ring Stiffened panel (Stress input)',
- 7:'Orthogonally Stiffened shell (Force input)', 8:'Orthogonally Stiffened panel (Stress input)'
- '''
- domain_string = self._new_calculation_domain.get()
- domain_int = self._shell_geometries_map[domain_string]
-
- dummy_data = {'span': [self._new_field_len.get()/1000, 'm'],
- 'plate_thk': [self._new_plate_thk.get()/1000, 'm'],
- 'structure_type': [self._new_stucture_type.get(), ''],
- 'sigma_y1': [self._new_sigma_y1.get(), 'MPa'],
- 'sigma_y2': [self._new_sigma_y2.get(), 'MPa'],
- 'sigma_x1': [self._new_sigma_x1.get(), 'MPa'],
- 'sigma_x2': [self._new_sigma_x2.get(), 'MPa'],
- 'tau_xy': [self._new_tauxy.get(), 'MPa'],
- 'plate_kpp': [self._new_plate_kpp.get(), ''],
- 'stf_kps': [self._new_stf_kps.get(), ''],
- 'stf_km1': [self._new_stf_km1.get(), ''],
- 'stf_km2': [self._new_stf_km2.get(), ''],
- 'stf_km3': [self._new_stf_km3.get(), ''],
- 'press_side': [self._new_pressure_side.get(), ''],
- 'structure_types':[self._structure_types, ''],
- 'zstar_optimization': [self._new_zstar_optimization.get(), ''],
- 'puls buckling method': [self._new_puls_method.get(), ''],
- 'puls boundary': [self._new_puls_panel_boundary.get(), ''],
- 'puls stiffener end': [self._new_buckling_stf_end_support.get(), ''],
- 'puls sp or up': [self._new_puls_sp_or_up.get(), ''],
- 'puls up boundary': [self._new_puls_up_boundary.get(), ''],
- 'panel or shell': [self._new_panel_or_shell.get(), ''],
- 'mat_factor': [self._new_material_factor.get(), '',],
- 'spacing': [self._new_stf_spacing.get()/1000, 'm'],}
-
- # Main class input
-
- # Shell data input
- shell_dict = {'plate_thk': [self._new_shell_thk.get() / 1000, 'm'],
- 'radius': [self._new_shell_radius.get() / 1000, 'm'],
- 'distance between rings, l': [self._new_shell_dist_rings.get() / 1000, 'm'],
- 'length of shell, L': [self._new_shell_length.get() / 1000, 'm'],
- 'tot cyl length, Lc': [self._new_shell_tot_length.get() / 1000, 'm'],
- 'eff. buckling lenght factor': [self._new_shell_k_factor.get(), ''],
- 'mat_yield': [self._new_shell_yield.get() * 1e6, 'Pa'],
- }
- # Longitudinal stiffener input
- long_dict = {'spacing': [self._new_stf_spacing.get() / 1000, 'm'],
- 'stf_web_height': [self._new_stf_web_h.get() / 1000, 'm'],
- 'stf_web_thk': [self._new_stf_web_t.get() / 1000, 'm'],
- 'stf_flange_width': [self._new_stf_fl_w.get() / 1000, 'm'],
- 'stf_flange_thk': [self._new_stf_fl_t.get() / 1000, 'm'],
- 'stf_type': [self._new_stf_type.get(), ''],
- 'span': [self._new_field_len.get()/1000, 'm'],
- 'mat_yield': [self._new_shell_yield.get() * 1e6, 'Pa'],
- 'panel or shell': ['shell', '']}
- ring_stf_dict = {'stf_web_height': [self._new_shell_ring_stf_hw.get() / 1000, 'm'],
- 'stf_web_thk': [self._new_shell_ring_stf_tw.get() / 1000, 'm'],
- 'stf_flange_width': [self._new_shell_ring_stf_b.get() / 1000, 'm'],
- 'stf_flange_thk': [self._new_shell_ring_stf_tf.get() / 1000, 'm'],
- 'stf_type': [self._new_shell_ring_stf_type.get(), ''],
- 'mat_yield': [self._new_shell_yield.get() * 1e6, 'Pa'],
- 'panel or shell': ['shell', '']}
- ring_frame_dict = {'stf_web_height': [self._new_shell_ring_frame_hw.get() / 1000, 'm'],
- 'stf_web_thk': [self._new_shell_ring_frame_tw.get() / 1000, 'm'],
- 'stf_flange_width': [self._new_shell_ring_frame_b.get() / 1000, 'm'],
- 'stf_flange_thk': [self._new_shell_ring_frame_tf.get() / 1000, 'm'],
- 'stf_type': [self._new_shell_ring_frame_type.get(), ''],
- 'span': [self._new_field_len.get()/1000, 'm'],
- 'mat_yield': [self._new_shell_yield.get() * 1e6, 'Pa'],
- 'panel or shell': ['shell', '']}
-
- geometry = self._shell_geometries_map[self._new_calculation_domain.get()]
-
- if self._new_shell_stress_or_force.get() == 1:
- forces = [self._new_shell_Nsd.get(), self._new_shell_Msd.get(),
- self._new_shell_Tsd.get(), self._new_shell_Qsd.get()]
- sasd, smsd, tTsd, tQsd, shsd = hlp.helper_cylinder_stress_to_force_to_stress(
- stresses=None, forces=forces, geometry=geometry, shell_t=self._new_shell_thk.get(),
- shell_radius=self._new_shell_radius.get(), shell_spacing=self._new_stf_spacing.get(),
- hw=self._new_stf_web_h.get(), tw=self._new_stf_web_t.get(), b=self._new_stf_fl_w.get(),
- tf=self._new_stf_fl_t.get(), CylinderAndCurvedPlate=CylinderAndCurvedPlate)
- self._new_shell_sasd.set(sasd)
- self._new_shell_smsd.set(smsd)
- self._new_shell_tTsd.set(tTsd)
- self._new_shell_tQsd.set(tQsd)
- #self._new_shell_shsd.set(0)
- else:
- stresses = [self._new_shell_sasd.get(), self._new_shell_smsd.get(),
- abs(self._new_shell_tTsd.get()),
- self._new_shell_tQsd.get(), self._new_shell_shsd.get()]
- sasd, smsd, tTsd, tQsd, shsd = stresses
- Nsd, Msd, Tsd, Qsd, shsd = hlp.helper_cylinder_stress_to_force_to_stress(
- stresses=stresses, geometry=geometry, shell_t=self._new_shell_thk.get(),
- shell_radius=self._new_shell_radius.get(), shell_spacing=self._new_stf_spacing.get(),
- hw=self._new_stf_web_h.get(), tw=self._new_stf_web_t.get(), b=self._new_stf_fl_w.get(),
- tf=self._new_stf_fl_t.get(), CylinderAndCurvedPlate=CylinderAndCurvedPlate)
- self._new_shell_Nsd.set(Nsd)
- self._new_shell_Msd.set(Msd)
- self._new_shell_Tsd.set(Tsd)
- self._new_shell_Qsd.set(Qsd)
-
- main_dict_cyl = {'sasd': [sasd*1e6, 'Pa'],
- 'smsd': [smsd*1e6, 'Pa'],
- 'tTsd': [tTsd*1e6, 'Pa'],
- 'tQsd': [tQsd*1e6, 'Pa'],
- 'psd': [self._new_shell_psd.get() *1e6, 'Pa'],
- 'shsd': [shsd *1e6, 'Pa'],
- 'geometry': [self._shell_geometries_map[self._new_calculation_domain.get()], ''],
- 'material factor': [self._new_shell_mat_factor.get(), ''],
- 'delta0': [0.005, ''],
- 'fab method ring stf': [self._new_shell_ring_stf_fab_method.get(), ''],
- 'fab method ring girder': [self._new_shell_ring_frame_fab_method.get(), ''],
- 'E-module': [self._new_shell_e_module.get(), 'Pa'],
- 'poisson': [self._new_shell_poisson.get(), ''],
- 'mat_yield': [self._new_shell_yield.get() *1e6, 'Pa'],
- 'length between girders': [self._new_shell_ring_frame_length_between_girders.get()/1000, 'm'],
- 'panel spacing, s': [self._new_shell_panel_spacing.get()/1000, 'm'],
- 'ring stf excluded': [self._new_shell_exclude_ring_stf.get(), ''],
- 'ring frame excluded': [self._new_shell_exclude_ring_frame.get(), '',],
- 'ULS or ALS': [self._new_shell_uls_or_als.get(), '',],
- 'end cap pressure': [self._new_shell_end_cap_pressure_included.get(), '']
- }
-
- for key, value in dummy_data.items():
- if key not in long_dict.keys():
- long_dict[key] = value
- if key not in ring_stf_dict.keys():
- ring_stf_dict[key] = value
- if key not in ring_frame_dict.keys():
- ring_frame_dict[key] = value
-
- CylinderObj = CylinderAndCurvedPlate(main_dict_cyl, Shell(shell_dict),
- long_stf=None if geometry in [1,2,5,6]
- else Structure(long_dict),
- ring_stf=None if any([geometry in [1,2,3,4],
- self._new_shell_exclude_ring_stf.get()])
- else Structure(ring_stf_dict),
- ring_frame=None if any([geometry in [1,2,3,4],
- self._new_shell_exclude_ring_frame.get()])
- else Structure(ring_frame_dict))
- elif cylinder_return is not None:
- main_dict_cyl, shell_dict, long_dict, ring_stf_dict, ring_frame_dict = \
- cylinder_return.get_all_properties()
- else:
- prop_dict = pasted_structure.get_main_properties()
-
+ prop_dict, obj_dict_stf, CylinderObj, main_dict_cyl, shell_dict, long_dict, ring_stf_dict, \
+ ring_frame_dict, geometry = self._resolve_new_structure_properties(
+ pasted_structure=pasted_structure, multi_return=multi_return, toggle_multi=toggle_multi,
+ cylinder_return=cylinder_return)
if self._active_line not in self._line_to_struc.keys() :
- self._line_to_struc[self._active_line] = [None, None, None, [None], {}, None]
- # First entry
- # Flat plate domains: 'Flat plate, stiffened with girder', 'Flat plate, stiffened', Flat plate, unstiffened'
- cdom = self._new_calculation_domain.get()
- All = AllStructure(Plate=CalcScantlings(prop_dict['Plate']),
- Stiffener=None if cdom == 'Flat plate, unstiffened'
- else CalcScantlings(prop_dict['Stiffener']),
- Girder=None if cdom in ['Flat plate, unstiffened', 'Flat plate, stiffened']
- else CalcScantlings(prop_dict['Girder']),
- main_dict=prop_dict['main dict'])
-
- self._sections = add_new_section(self._sections, struc.Section(obj_dict_stf)) # TODO error when pasting
- self._line_to_struc[self._active_line][0] = All
- self._line_to_struc[self._active_line][5] = CylinderObj
- if self._line_to_struc[self._active_line][0].Plate.get_structure_type() not in \
- self._structure_types['non-wt']:
- self._tank_dict = {}
- self._main_grid.clear()
- self._compartments_listbox.delete(0, 'end')
- if self._new_calculation_domain.get() not in ['Flat plate, stiffened','Flat plate, unstiffened',
- 'Flat plate, stiffened with girder']:
- CylinderObj = CylinderAndCurvedPlate(main_dict_cyl, Shell(shell_dict),
- long_stf=None if geometry in [1,2,5,6]
- else Structure(long_dict),
- ring_stf=None if any([geometry in [1,2,3,4],
- self._new_shell_exclude_ring_stf.get()])
- else Structure(ring_stf_dict),
- ring_frame=None if any([geometry in [1,2,3,4],
- self._new_shell_exclude_ring_frame.get()])
- else Structure(ring_frame_dict))
-
- self._line_to_struc[self._active_line][5] = CylinderObj
-
+ self._add_structure_to_active_line(prop_dict, obj_dict_stf, CylinderObj, main_dict_cyl, shell_dict,
+ long_dict, ring_stf_dict, ring_frame_dict, geometry)
else:
- # if self._new_calculation_domain.get() in ['Flat plate, stiffened','Flat plate, unstiffened',
- # 'Flat plate, stiffened with girder'] and \
- # self._line_to_struc[self._active_line][5] is not None:
- # self._line_to_struc[self._active_line][5] = None
-
- prev_type = self._line_to_struc[self._active_line][0].Plate.get_structure_type()
- prev_all_obj = copy.deepcopy(self._line_to_struc[self._active_line][0])
- self._line_to_struc[self._active_line][0].set_main_properties(prop_dict)
-
- if self._new_scale_stresses.get() and prev_all_obj.get_main_properties() != \
- self._line_to_struc[self._active_line][0].get_main_properties():
- if prev_all_obj.Stiffener is not None:
- plate = self._line_to_struc[self._active_line][0].Plate
- stiffener = self._line_to_struc[self._active_line][0].Stiffener
- girder = self._line_to_struc[self._active_line][0].Girder
- calc_tup = (plate.get_s(), plate.get_pl_thk(), stiffener.get_web_h(), stiffener.get_web_thk(),
- stiffener.get_fl_w(),
- stiffener.get_fl_thk(), plate.span, stiffener.girder_lg if girder is None else
- girder.girder_lg, stiffener.stiffener_type)
- else:
- calc_tup = self._line_to_struc[self._active_line][0].Plate.get_tuple()
- self._line_to_struc[self._active_line][0] = op.create_new_calc_obj(prev_all_obj, calc_tup,
- fup=self._new_fup.get(),
- fdwn=self._new_fdwn.get())[0]
-
- self._line_to_struc[self._active_line][0].need_recalc = True
-
- if self._line_to_struc[self._active_line][2] is not None:
- calc_dom = self._line_to_struc[self._active_line][0].calculation_domain
- if calc_dom == 'Flat plate, unstiffened':
- self._line_to_struc[self._active_line][2] = None
- else:
- self._line_to_struc[self._active_line][2].set_main_properties(prop_dict['Stiffener'])
-
- if prev_type in self._structure_types['non-wt'] and prop_dict['Plate']['structure_type'][0] in \
- self._structure_types['internals'] + self._structure_types['horizontal'] + \
- self._structure_types['vertical']:
- self._tank_dict = {}
- self._main_grid.clear()
- self._compartments_listbox.delete(0, 'end')
-
- if all([CylinderObj is None, cylinder_return is None,
- self._line_to_struc[self._active_line][5] is not None]):
- self._line_to_struc[self._active_line][5] = None
- elif CylinderObj is not None:
- if self._line_to_struc[self._active_line][5] is not None and self._new_scale_stresses.get():
- NewCylinderObj = op.create_new_cylinder_obj(self._line_to_struc[self._active_line][5],
- CylinderObj.get_x_opt())
- NewCylinderObj.LongStfObj = None if CylinderObj.LongStfObj is None \
- else NewCylinderObj.LongStfObj
- NewCylinderObj.RingStfObj = None if CylinderObj.RingStfObj is None \
- else NewCylinderObj.RingStfObj
- NewCylinderObj.RingFrameObj = None if CylinderObj.RingFrameObj is None \
- else NewCylinderObj.RingFrameObj
- self._line_to_struc[self._active_line][5] = CylinderObj
- elif cylinder_return is not None:
- self._line_to_struc[self._active_line][5] = cylinder_return
- try:
- self.calculate_all_load_combinations_for_line_all_lines()
- except (KeyError, AttributeError):
- pass
+ self._update_existing_active_line_structure(prop_dict, CylinderObj, cylinder_return)
+ self._calculate_load_combinations_after_structure_update()
else:
pass
- if self._PULS_results != None:
- self._PULS_results.result_changed(self._active_line)
-
- if not suspend_recalc:
- # when changing multiple parameters, recalculations are suspended.
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
- state = self.update_frame()
- if state != None and self._line_is_active:
- self._weight_logger['new structure']['COG'].append(self.get_color_and_calc_state()['COG'])
- self._weight_logger['new structure']['weight'].append(self.get_color_and_calc_state()['Total weight'])
- self._weight_logger['new structure']['time'].append(time.time())
- self.cylinder_gui_mods()
-
- self.get_unique_plates_and_beams()
+ self._refresh_after_structure_change(suspend_recalc)
def option_meny_structure_type_trace(self, event):
''' Updating of the values in the structure type option menu. '''
@@ -5639,42 +6581,11 @@ def calculate_all_load_combinations_for_line(self, line, limit_state = 'ULS', ge
self._load_conditions = ['loaded', 'ballast','tanktest']
:return:
'''
- if limit_state == 'FLS':
- return
- results = {} #dict - dnva/dnvb/tanktest/manual
- load_info = []
- # calculating for DNV a and DNV b
-
-
- for dnv_ab in ['dnva', 'dnvb']: #, load_factors in self._load_factors_dict.items():
- results[dnv_ab] = []
- for load_condition in self._load_conditions[0:2]:
- returned = self.calculate_one_load_combination(line, dnv_ab, load_condition)
- if returned != None:
- results[dnv_ab].append(returned[0])
- [load_info.append(val) for val in returned[1]]
-
- # calculating for tank test condition
- results['tanktest'] = []
- res_val = self.calculate_one_load_combination(line, "tanktest", 'tanktest')
- results['tanktest'].append(res_val[0])
- [load_info.append(val) for val in res_val[1]]
-
-
- # calculating for manual condition
- results['manual'] = []
- res_val = self.calculate_one_load_combination(line, 'manual', 'manual')
- results['manual'].append(res_val[0])
- [load_info.append(val) for val in res_val[1]]
-
- results['slamming'] = []
- res_val = self.calculate_one_load_combination(line, 'slamming', 'slamming')
- results['slamming'].append(res_val[0])
- [load_info.append(val) for val in res_val[1]]
-
- if get_load_info:
- return load_info
- return results
+ return project_services.LinePressureService.calculate_combinations(
+ self._build_line_pressure_input(line),
+ limit_state=limit_state,
+ get_load_info=get_load_info,
+ )
def calculate_one_load_combination(self, current_line, comb_name, load_condition):
'''
@@ -5684,36 +6595,29 @@ def calculate_one_load_combination(self, current_line, comb_name, load_condition
#load combination dictionary (comb,line,load) : [stat - DoubleVar(), dyn - DoubleVar], on/off - IntVar()]
:return:
'''
+ return project_services.LinePressureService.calculate_one(
+ self._build_line_pressure_input(current_line),
+ comb_name,
+ load_condition,
+ )
- defined_loads = []
- for load_obj in self._line_to_struc[current_line][3]:
- if load_obj is not None:
- if load_obj.get_limit_state() != 'FLS':
- defined_loads.append(load_obj)
+ def _build_line_pressure_input(self, current_line):
if self._tank_dict == {}:
- defined_tanks = []
- else:
- defined_tanks = [['comp'+str(int(tank_num)), self._tank_dict['comp'+str(int(tank_num))]]
- for tank_num in self.get_compartments_for_line_duplicates(current_line)]
-
- coord = (self.get_line_radial_mid(current_line), self.get_line_low_elevation(current_line))
-
- if load_condition not in ['tanktest','manual','slamming']:
- acc = (self._accelerations_dict['static'], self._accelerations_dict['dyn_'+str(load_condition)])
- else:
- acc = (self._accelerations_dict['static'], 0)
-
- load_factors_all = self._new_load_comb_dict
-
- current_line_obj = [current_line, self._line_to_struc[current_line][0].Plate]
-
- if self._line_to_struc[current_line][0].Plate.get_structure_type() in ['', 'FRAME','GENERAL_INTERNAL_NONWT']:
- return [0, '']
+ defined_tanks = ()
else:
- return_value = one_load_combination(current_line_obj, coord, defined_loads, load_condition,
- defined_tanks, comb_name, acc, load_factors_all)
-
- return return_value
+ defined_tanks = tuple(
+ ('comp' + str(int(tank_num)), self._tank_dict['comp' + str(int(tank_num))])
+ for tank_num in self.get_compartments_for_line_duplicates(current_line)
+ )
+
+ return project_services.LinePressureInput(
+ line_name=current_line,
+ line_bundle=self._line_to_struc[current_line],
+ coordinate=(self.get_line_radial_mid(current_line), self.get_line_low_elevation(current_line)),
+ defined_tanks=defined_tanks,
+ accelerations=self._accelerations_dict,
+ load_factors=project_services.load_factor_records(self._new_load_comb_dict),
+ )
def run_optimizer_for_line(self,line,goal,constrains):
'''
@@ -5737,10 +6641,7 @@ def update_tank(self):
current_tank.set_content(self._new_content_type.get())
current_tank.set_acceleration(self._accelerations_dict)
current_tank.set_density(self._new_density.get())
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
- if self._compartments_listbox.get('active') in self.get_compartments_for_line(line):
- self._PULS_results.result_changed(line)
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
def delete_line(self, event = None, undo = None, line = None):
'''
@@ -5755,19 +6656,19 @@ def delete_line(self, event = None, undo = None, line = None):
if line in self._line_dict.keys() or undo is not None:
line = line if undo is None else undo
- point_str = 'p' + str(self._line_dict[line][0]) + 'p' + str(self._line_dict[line][1])
- point_str_rev = 'p' + str(self._line_dict[line][1]) + 'p' + str(self._line_dict[line][0])
+ project_editor = project_services.ProjectEditService(self._point_dict, self._line_dict)
+ line_record = project_editor.line(line)
if line in self._line_dict.keys():
if line in self._line_to_struc.keys():
if self._line_to_struc[line][0].Plate.get_structure_type() not in self._structure_types['non-wt']:
self.delete_properties_pressed()
self.delete_all_tanks()
- self._line_dict.pop(line)
+ project_editor.remove_line(line)
if line in self._line_to_struc.keys():
self._line_to_struc.pop(line)
- self._line_point_to_point_string.pop(self._line_point_to_point_string.index(point_str))
- self._line_point_to_point_string.pop(self._line_point_to_point_string.index(point_str_rev))
+ for endpoint_key in line_record.endpoint_keys:
+ self._line_point_to_point_string.pop(self._line_point_to_point_string.index(endpoint_key))
self._active_line = ''
# Removing from load dict
@@ -5776,10 +6677,6 @@ def delete_line(self, event = None, undo = None, line = None):
for load in loads:
if line in self._load_dict[load][1]:
self._load_dict[load][1].pop(self._load_dict[load][1].index(line))
- # Removing from puls results
- if self._PULS_results is not None:
- self._PULS_results.result_changed(line)
-
self.update_frame()
else:
messagebox.showinfo(title='No line.', message='Input line does noe exist.')
@@ -5796,11 +6693,8 @@ def delete_point(self, event = None, undo = None, point = None):
point = 'point' + str(self._ent_delete_point.get()) if undo is None else undo
if point in self._point_dict.keys():
- line_to_delete = []
- # finding the lines that needs to be deleted
- for line, points in self._line_dict.items():
- if int(self._ent_delete_point.get()) in points:
- line_to_delete.append(line)
+ project_editor = project_services.ProjectEditService(self._point_dict, self._line_dict)
+ line_to_delete = project_editor.connected_line_names(point)
# deleting the lines and the connected properties. also deleting point to point string list items.
for line in list(line_to_delete):
self.delete_line(line = line)
@@ -5813,7 +6707,7 @@ def delete_point(self, event = None, undo = None, point = None):
# if line in self._line_to_struc.keys():
# self._line_to_struc.pop(line)
# at the en, the points is deleted from the point dict.
- self._point_dict.pop(point)
+ project_editor.remove_point(point)
self._active_point = ''
else:
messagebox.showinfo(title='No point.', message='Input point does not exist.')
@@ -5869,8 +6763,7 @@ def delete_properties_pressed(self, event = None, line = None):
action_taken = True
if action_taken:
- for line, obj in self._line_to_struc.items():
- obj[0].need_recalc = True
+ project_services.mark_lines_for_recalculation(self._line_to_struc)
self.update_frame()
def delete_all_tanks(self):
@@ -5985,7 +6878,7 @@ def set_selected_variables(self, line):
self._new_shell_psd.set(main_dict_cyl['psd'][0]/1e6)
self._new_shell_shsd.set(main_dict_cyl['shsd'][0]/1e6)
- self._new_calculation_domain.set(CylinderAndCurvedPlate.geomeries[main_dict_cyl['geometry'][0]])
+ self._new_calculation_domain.set(api_helpers.domain_for_geometry_id(main_dict_cyl['geometry'][0]))
self._new_shell_mat_factor.set(main_dict_cyl['material factor'][0])
self._new_shell_ring_stf_fab_method.set(main_dict_cyl['fab method ring stf'][0])
self._new_shell_ring_frame_fab_method.set(main_dict_cyl['fab method ring girder'][0])
@@ -6035,29 +6928,12 @@ def get_highest_pressure(self, line, limit_state = 'ULS'):
Returning the highest pressure of a line.
:return:
'''
- all_press = list()
- if limit_state == 'ULS':
- pressures = self.calculate_all_load_combinations_for_line(line)
- slm_red, psl, slm_red_pl, slm_red_stf = 1, 0, 1, 1
- for key, value in pressures.items():
- if key != 'slamming':
- all_press.append(max(value))
- else:
- if value is not None:
- for load in self._line_to_struc[line][3]:
- if load is not None:
- if load.get_load_condition() == 'slamming':
- slm_red_pl = load.get_slamming_reduction_plate()
- slm_red_stf = load.get_slamming_reduction_stf()
- psl = max(value)
-
-
- return {'normal':max(all_press), 'slamming': psl, 'slamming plate reduction factor': slm_red_pl,
- 'slamming stf reduction factor': slm_red_stf}
- elif limit_state == 'FLS':
- pass
- else:
- return {'normal':0, 'slamming': 0}
+ if limit_state == 'FLS':
+ return
+ return project_services.LinePressureService.highest_pressure(
+ self._build_line_pressure_input(line),
+ limit_state=limit_state,
+ )
def get_fatigue_pressures(self, line, accelerations = (0, 0, 0)):
''' Retruning a dictionary of internal and external pressures. '''
@@ -6324,7 +7200,6 @@ def reset(self):
self._line_point_to_point_string = [] # This one ensures that a line is not created on top of a line
self._accelerations_dict = {'static':9.81, 'dyn_loaded':0, 'dyn_ballast':0}
self._multiselect_lines = []
- self._PULS_results = None
self.update_frame()
# Initsializing the calculation grid used for tank definition
@@ -6535,8 +7410,8 @@ def cylinder_gui_mods(self):
if self._active_line in self._line_to_struc.keys():
if self._line_to_struc[self._active_line][5] is not None:
- self._new_calculation_domain.set(CylinderAndCurvedPlate
- .geomeries[self._line_to_struc[self._active_line][5].geometry])
+ self._new_calculation_domain.set(
+ api_helpers.domain_for_geometry_id(self._line_to_struc[self._active_line][5].geometry))
self._new_shell_exclude_ring_stf.set(self._line_to_struc[self._active_line][5]._ring_stiffener_excluded)
self._new_shell_exclude_ring_frame.set(self._line_to_struc[self._active_line][5]._ring_frame_excluded)
self.calculation_domain_selected()
@@ -6653,12 +7528,15 @@ def draw_point_frame(self):
def save_no_dialogue(self, event = None, backup = False):
if backup:
- self.savefile(filename=os.path.join(self._root_dir, '../backup.txt'), backup = backup)
+ target = project_application.ProjectFileDialogService.backup_save_target(self._root_dir)
+ self.savefile(filename=target.path, backup=backup)
return
- if self.__last_save_file is not None:
- self.savefile(filename=self.__last_save_file)
- else:
+
+ target = project_application.ProjectFileDialogService.remembered_save_target(self.__last_save_file)
+ if target is None:
tk.messagebox.showerror('Save error', 'No saves in this session yet.')
+ return
+ self.savefile(filename=target.path)
def savefile(self, filename = None, backup = False):
'''
@@ -6666,327 +7544,130 @@ def savefile(self, filename = None, backup = False):
'''
if filename is None:
- save_file = filedialog.asksaveasfile(mode="w", defaultextension=".txt")
- if save_file is None: # ask saveasfile return `None` if dialog closed with "cancel".
+ filename = filedialog.asksaveasfilename(defaultextension=".txt")
+ if not filename:
return
- if not backup:
- self.__last_save_file = save_file.name
- else:
- try:
- save_file = open(filename, mode='w')
- except FileNotFoundError:
- save_file = open(filename.replace('',''), mode='w')
-
- structure_properties = {}
- shell_structure_properties = {}
- for key, value in self._line_to_struc.items():
- structure_properties[key] = value[0].get_main_properties()
- shell_structure_properties[key] = None if value[5] is None else value[5].get_all_properties()
-
- fatigue_properties = {}
- for key, value in self._line_to_struc.items():
- if value[2] != None:
- try:
- fatigue_properties[key] = value[2].get_fatigue_properties()
- except AttributeError:
- fatigue_properties[key] = None
- else:
- fatigue_properties[key] = None
-
- load_properties = {}
- for load, data in self._load_dict.items():
- load_properties[load] = [data[0].get_load_parmeters(), data[1]]
+ save_target = project_application.ProjectFileDialogService.selected_save_target(filename, backup=backup)
+ if save_target.remember_as_last_save:
+ self.__last_save_file = str(save_target.path)
- tank_properties = {}
- tank_properties['grid'] = self._main_grid.export_grid()
- tank_properties['search_data'] = self._main_grid.bfs_search_data
- for tank,data in self._tank_dict.items():
- tank_properties[tank] = data.get_parameters()
+ try:
+ save_result = project_application.ProjectSaveService.save_path(
+ save_target.path,
+ self._build_project_save_input(),
+ )
+ except project_application.ProjectPersistenceError as error:
+ tk.messagebox.showerror('Save error', str(error))
+ return
- load_combiantions = {}
- counter = 0
- for name, data in self._new_load_comb_dict.items():
- load_combiantions[counter] = [name,data[0].get(),data[1].get(),data[2].get()]
- counter+=1
-
- export_all = {}
-
- export_all['project information'] = self._project_information.get('1.0', tk.END)
- export_all['theme'] = self._current_theme
- export_all['point_dict'] = self._point_dict
- export_all['line_dict'] = self._line_dict
- export_all['structure_properties'] = structure_properties
- export_all['shell structure properties'] = shell_structure_properties
- export_all['load_properties'] = load_properties
- export_all['accelerations_dict'] = self._accelerations_dict
- export_all['load_combinations'] = load_combiantions
- export_all['tank_properties'] = tank_properties
- export_all['fatigue_properties'] = fatigue_properties
- #export_all['buckling type'] = self._new_buckling_slider.get()
-
- export_all['buckling method'] = self._new_buckling_method.get()
-
- if self._PULS_results is not None:
- export_all['PULS results'] = self._PULS_results.get_run_results()
- export_all['PULS results']['sheet location'] = self._PULS_results.puls_sheet_location
- export_all['shifting'] = {'shifted checked': self._new_shifted_coords.get(),
- 'shift hor': self._new_shift_viz_coord_hor.get(),
- 'shift ver': self._new_shift_viz_coord_ver.get()}
-
- export_all['Weight and COG'] = self._weight_logger
-
- json.dump(export_all, save_file)#, sort_keys=True, indent=4)
- save_file.close()
if not backup:
- self._parent.wm_title('| ANYstructure | ' + save_file.name)
+ self._parent.wm_title('| ANYstructure | ' + str(save_result.path))
#self.update_frame()
- def openfile(self, defined = None, alone = False):
- '''
- Opens a file with data (JSON).
- '''
-
- if defined == None:
- imp_file = filedialog.askopenfile(mode='r', defaultextension=".txt")
- if imp_file is None: # asksaveasfile return `None` if dialog closed with "cancel".
- return
- else:
- imp_file = open(defined,'r')
-
- imported = json.load(imp_file)
-
- self.reset()
- if 'project information' in imported.keys():
- self._project_information.delete("1.0", tk.END)
- self._project_information.insert(1.0, imported['project information'])
+ def _build_project_save_input(self):
+ load_combinations = [
+ project_application.LoadCombinationRecord(name, data[0].get(), data[1].get(), data[2].get())
+ for name, data in self._new_load_comb_dict.items()
+ ]
+ return project_application.ProjectSaveInput(
+ project_information=self._project_information.get('1.0', tk.END),
+ theme=self._current_theme,
+ points=self._point_dict,
+ lines=self._line_dict,
+ line_bundles=self._line_to_struc,
+ load_assignments=self._load_dict,
+ accelerations=self._accelerations_dict,
+ load_combinations=load_combinations,
+ tanks=self._tank_dict,
+ tank_grid=self._main_grid.export_grid(),
+ tank_search_data=self._main_grid.bfs_search_data,
+ buckling_method=self._new_buckling_method.get(),
+ shifting={'shifted checked': self._new_shifted_coords.get(),
+ 'shift hor': self._new_shift_viz_coord_hor.get(),
+ 'shift ver': self._new_shift_viz_coord_ver.get()},
+ weight_and_cog=self._weight_logger,
+ )
+
+ def _build_project_hydration_defaults(self):
+ return project_application.ProjectHydrationDefaults(
+ structure_types=self._structure_types,
+ zstar_optimization=self._new_zstar_optimization.get(),
+ puls_buckling_method=self._new_puls_method.get(),
+ puls_boundary=self._new_puls_panel_boundary.get(),
+ puls_stiffener_end=self._new_buckling_stf_end_support.get(),
+ puls_sp_or_up=self._new_puls_sp_or_up.get(),
+ puls_up_boundary=self._new_puls_up_boundary.get(),
+ material_factor=self._new_material_factor.get(),
+ )
+
+ def _apply_open_project_text_and_theme(self, open_transfer):
+ self._project_information.delete("1.0", tk.END)
+ if open_transfer.project_information:
+ self._project_information.insert(1.0, open_transfer.project_information)
else:
- self._project_information.delete("1.0", tk.END)
self._project_information.insert(1.0, 'No information on project provided. Input here.')
- if 'shifting' in imported.keys():
- self._new_shifted_coords.set(imported['shifting']['shifted checked'])
- self._new_shift_viz_coord_hor.set(imported['shifting']['shift hor'])
- self._new_shift_viz_coord_ver.set(imported['shifting']['shift ver'])
- else:
- pass
-
- if 'theme' in imported.keys():
- self.set_colors(imported['theme'])
+ if open_transfer.shifting:
+ self._new_shifted_coords.set(open_transfer.shifting.get('shifted checked', False))
+ self._new_shift_viz_coord_hor.set(open_transfer.shifting.get('shift hor', 0))
+ self._new_shift_viz_coord_ver.set(open_transfer.shifting.get('shift ver', 0))
- self._point_dict = imported['point_dict']
- self._line_dict = imported['line_dict']
- struc_prop = imported['structure_properties']
- old_save_file = False
+ self.set_colors(open_transfer.theme)
- for line, lines_prop in struc_prop.items():
+ def _apply_open_project_geometry_and_objects(self, open_transfer, hydration):
+ self._point_dict = open_transfer.points
+ self._line_dict = open_transfer.lines
+ self._line_to_struc = hydration.line_bundles
+ self._load_dict = hydration.load_assignments
- if len(lines_prop) > 10:
- # Loading a file (pre 3.4)
- old_save_file = True
-
- self._line_to_struc[line] = [None, None, None, [], {}, None]
+ for line in self._line_to_struc:
self._line_point_to_point_string.append(
self.make_point_point_line_string(self._line_dict[line][0], self._line_dict[line][1])[0])
self._line_point_to_point_string.append(
self.make_point_point_line_string(self._line_dict[line][0], self._line_dict[line][1])[1])
+ for section_properties in hydration.section_properties:
+ self._sections = add_new_section(self._sections, struc.Section(section_properties))
- if 'structure_types' not in lines_prop.keys():
- lines_prop['structure_types'] = [self._structure_types, ' ']
- if 'zstar_optimization' not in lines_prop.keys():
- lines_prop['zstar_optimization'] = [self._new_zstar_optimization.get(), '']
- if 'puls buckling method' not in lines_prop.keys():
- lines_prop['puls buckling method'] = [self._new_puls_method.get(), '']
- if 'puls boundary' not in lines_prop.keys():
- lines_prop['puls boundary'] = [self._new_puls_panel_boundary.get(), '']
- if 'puls stiffener end' not in lines_prop.keys():
- lines_prop['puls stiffener end'] = [self._new_buckling_stf_end_support.get(), '']
- if 'puls sp or up' not in lines_prop.keys():
- lines_prop['puls sp or up'] = [self._new_puls_sp_or_up.get(), '']
- if 'puls up boundary' not in lines_prop.keys():
- lines_prop['puls up boundary'] = [self._new_puls_up_boundary.get(), '']
- if 'mat_factor' not in lines_prop.keys():
- lines_prop['mat_factor'] = [self._new_material_factor.get(), '']
-
- # Sigma x1/x2 is missing before 3.4
- if 'sigma_x' in lines_prop.keys():
- lines_prop['sigma_x1'] = lines_prop['sigma_x']
- lines_prop['sigma_x2'] = lines_prop['sigma_x']
- lines_prop.pop('sigma_x')
-
- if old_save_file: #need to get some basic information
- # Import issues
- try:
- import example_data as ex
- except ModuleNotFoundError:
- # This is due to pyinstaller issues.
- import anystruct.example_data as ex
- #import ANYstructure.anystruct.example_data as ex
-
- main_dict = ex.prescriptive_main_dict
- map_end = {'C': 'Continuous', 'S': 'Sniped'}
- lines_prop['puls stiffener end'] = [map_end[lines_prop['puls stiffener end'][0]],
- lines_prop['puls stiffener end'][1]]
- main_dict['material yield'] = [355e6, 'Pa']
- main_dict['load factor on stresses'] = [1, '']
- main_dict['load factor on pressure'] = [1, '']
- main_dict['buckling method'] = [lines_prop['puls buckling method'], '']
- main_dict['stiffener end support'] = lines_prop['puls stiffener end'] # 'Continuous'
- main_dict['girder end support'] = ['Continuous', ''] # 'Continuous'
- dom = 'Flat plate, stiffened' if lines_prop['puls sp or up'][0] == 'SP' else 'Flat plate, unstiffened'
-
- main_dict['calculation domain'] = [dom, '']
- map_side = {'p': 'plate side', 's': 'stiffener side'}
- if 'press_side' in lines_prop.keys():
- lines_prop['press_side'] = [map_side[lines_prop['press_side'][0]], '']
- else:
- lines_prop['press_side'] = 'both sides'
- lines_prop['panel or shell'] = 'panel'
- #lines_prop['tension field'] = 'allowed'
- self._line_to_struc[line][0] = AllStructure(Plate=CalcScantlings(lines_prop),
- Stiffener=None if dom == 'Flat plate, unstiffened'
- else CalcScantlings(lines_prop),
- Girder=None, main_dict=main_dict)
-
- if imported['fatigue_properties'][line] is not None:
- self._line_to_struc[line][2] = CalcFatigue(lines_prop,
- imported['fatigue_properties'][line])
- else:
- self._line_to_struc[line][2] = None
- # Recording sections.
- self._sections = add_new_section(self._sections, struc.Section(lines_prop))
-
- else:
- self._line_to_struc[line][0] = AllStructure(Plate=None if lines_prop['Plate'] is None
- else CalcScantlings(lines_prop['Plate']),
- Stiffener=None if lines_prop['Stiffener'] is None
- else CalcScantlings(lines_prop['Stiffener']),
- Girder=None if lines_prop['Girder'] is None
- else CalcScantlings(lines_prop['Girder']),
- main_dict=lines_prop['main dict'])
-
- if imported['fatigue_properties'][line] is not None:
- self._line_to_struc[line][2] = CalcFatigue(lines_prop['Stiffener'],
- imported['fatigue_properties'][line])
- else:
- self._line_to_struc[line][2] = None
- # Recording sections.
- if self._line_to_struc[line][0].Stiffener is not None:
- self._sections = add_new_section(self._sections, struc.Section(lines_prop['Stiffener']))
-
- if 'shell structure properties' in imported.keys():
- if imported['shell structure properties'][line] is not None:
- # need to correct the calcuation domain.
- #self._new_calculation_domain.set(imported_dict['Main class'][CylinderAndCurvedPlate.geomeries])
- imported_dict = imported['shell structure properties'][line]
- '''
- all_data = {'Main class': self.get_main_properties(),
- 'Shell': self._Shell.get_main_properties(),
- 'Long. stf.': self._LongStf.get_structure_prop(),
- 'Ring stf.': self.RingStfObj.get_structure_prop(),
- 'Ring frame': self._RingFrame.get_structure_prop()}
- '''
- for stuc_type in ['Long. stf.', 'Ring stf.', 'Ring frame']:
- if imported_dict[stuc_type] is not None:
- if 'sigma_x' in imported_dict[stuc_type].keys():
- imported_dict[stuc_type]['sigma_x1'] = imported_dict[stuc_type]['sigma_x']
- imported_dict[stuc_type]['sigma_x2'] = imported_dict[stuc_type]['sigma_x']
- imported_dict[stuc_type].pop('sigma_x')
- self._line_to_struc[line][5] = \
- CylinderAndCurvedPlate(imported_dict['Main class'], shell=None if imported_dict['Shell'] is None
- else Shell(imported_dict['Shell']), long_stf=None if imported_dict['Long. stf.'] is None
- else Structure(imported_dict['Long. stf.']), ring_stf=None if imported_dict['Ring stf.'] is None
- else Structure(imported_dict['Ring stf.']), ring_frame=None if imported_dict['Ring frame']
- is None
- else Structure(imported_dict['Ring frame']))
-
-
- # opening the loads
- variables = ['poly_third','poly_second', 'poly_first', 'poly_const', 'load_condition',
- 'structure_type', 'man_press', 'static_draft', 'name_of_load', 'limit_state',
- 'slamming mult pl', 'slamming mult stf']
-
- if len(imported['load_properties']) != 0:
- for load, data in imported['load_properties'].items():
- temp_dict = {}
- count_i = 0
- values = data[0]
- if len(values) != len(variables):
- # Adding slamming multiplication factors
- values.append(1)
- values.append(1)
- for value in values:
- temp_dict[variables[count_i]]= value
- count_i += 1
- self._load_dict[load] = [Loads(temp_dict), data[1]]
-
- if len(data[1]) != 0:
- for main_line in self._line_dict.keys():
- if main_line in data[1]:
- self._line_to_struc[main_line][3].append(self._load_dict[load][0])
-
- try:
- self._accelerations_dict = imported['accelerations_dict']
- except IndexError:
- self._accelerations_dict = {'static':9.81, 'dyn_loaded':0, 'dyn_ballast':0}
-
+ def _apply_open_project_accelerations(self, open_transfer):
+ self._accelerations_dict = open_transfer.accelerations
self._new_static_acc.set(self._accelerations_dict['static'])
self._new_dyn_acc_loaded.set(self._accelerations_dict['dyn_loaded'])
self._new_dyn_acc_ballast.set(self._accelerations_dict['dyn_ballast'])
- try:
- for data in imported['load_combinations'].values():
- name = tuple(data[0])
- self._new_load_comb_dict[name] = [tk.DoubleVar(),tk.DoubleVar(),tk.IntVar()]
- self._new_load_comb_dict[name][0].set(data[1]), self._new_load_comb_dict[name][1].set(data[2])
- self._new_load_comb_dict[name][2].set(data[3])
- except IndexError:
- for data in imported['load_combinations'].values():
- name = tuple(data[0])
- self._new_load_comb_dict[name] = [tk.DoubleVar(),tk.IntVar()]
- self._new_load_comb_dict[name][0].set(data[1]), self._new_load_comb_dict[name][1].set(data[2])
+ def _apply_open_project_load_combinations(self, open_transfer):
+ for load_combination in open_transfer.load_combinations:
+ name = load_combination.name
+ if load_combination.has_include:
+ self._new_load_comb_dict[name] = [tk.DoubleVar(), tk.DoubleVar(), tk.IntVar()]
+ self._new_load_comb_dict[name][0].set(load_combination.static_factor)
+ self._new_load_comb_dict[name][1].set(load_combination.dynamic_factor)
+ self._new_load_comb_dict[name][2].set(load_combination.include)
+ else:
+ self._new_load_comb_dict[name] = [tk.DoubleVar(), tk.IntVar()]
+ self._new_load_comb_dict[name][0].set(load_combination.static_factor)
+ self._new_load_comb_dict[name][1].set(load_combination.dynamic_factor)
+ def _apply_open_project_tanks(self, open_transfer):
try:
- self._main_grid.import_grid(imported['tank_properties']['grid'])
+ self._main_grid.import_grid(open_transfer.tank_grid)
self._grid_calc = grid_window.CreateGridWindow(self._main_grid, self._canvas_dim,
self._pending_grid_draw, self._canvas_base_origo)
- tank_inp = dict()
- if 'search_data' in imported['tank_properties'].keys():
- try:
- for key, value in imported['tank_properties']['search_data'].items():
- tank_inp[int(key)] = value
- self._main_grid.bfs_search_data = tank_inp
- self._grid_calc.bfs_search_data = tank_inp
- except AttributeError:
- self._main_grid.bfs_search_data = None
- self._grid_calc.bfs_search_data = None
- else:
- self._main_grid.bfs_search_data = None
- self._grid_calc.bfs_search_data = None
+ self._main_grid.bfs_search_data = open_transfer.tank_search_data
+ self._grid_calc.bfs_search_data = open_transfer.tank_search_data
- for comp_no in range(2, int(self._main_grid.get_highest_number_in_grid())+1):
- self._compartments_listbox.insert('end',comp_no)
- self._tank_dict['comp' + str(comp_no)] = Tanks(imported['tank_properties']['comp' + str(comp_no)])
+ for comp_no in range(2, int(self._main_grid.get_highest_number_in_grid()) + 1):
+ self._compartments_listbox.insert('end', comp_no)
+ tank_name = 'comp' + str(comp_no)
+ self._tank_dict[tank_name] = Tanks(open_transfer.tank_properties[tank_name])
except IndexError:
for line_name, point_no in self._line_dict.items():
point_coord_x = self._canvas_base_origo[0] + self._point_dict[point_no][0] * self._canvas_scale
point_coord_y = self._canvas_base_origo[1] - self._point_dict[point_no][1] * self._canvas_scale
- self.grid_operations(line_name, [point_coord_x,point_coord_y])
-
- if 'PULS results' in list(imported.keys()):
- self._PULS_results = PULSpanel()
- if 'sheet location' in imported['PULS results'].keys():
- self._PULS_results.puls_sheet_location = imported['PULS results']['sheet location']
- imported['PULS results'].pop('sheet location')
- self._PULS_results.set_run_results(imported['PULS results'])
-
- if 'buckling method' in list(imported.keys()):
- #options = ['DNV-RP-C201 - prescriptive', 'DNV PULS', 'ML-CL (PULS based)']
- self._new_buckling_method.set(imported['buckling method'])
-
- # Setting the scale of the canvas
+ self.grid_operations(line_name, [point_coord_x, point_coord_y])
+ def _apply_open_project_canvas_scale(self):
points = self._point_dict
if len(points) != 0:
highest_y = max([coord[1] for coord in points.values()])
@@ -6997,236 +7678,143 @@ def openfile(self, defined = None, alone = False):
if not any([highest_x == 0, highest_y == 0]):
self._canvas_scale = min(800 / highest_y, 800 / highest_x, 15)
- # if 'buckling type' in imported.keys():
- # self._new_buckling_slider.set(imported['buckling type'])
- # self._buckling_slider.set(imported['buckling type'])
-
- if 'Weight and COG' in imported.keys():
- self._weight_logger = imported['Weight and COG']
-
+ def _finalize_open_project(self, open_transfer, filename):
+ self._new_buckling_method.set(open_transfer.buckling_method)
+ self._weight_logger = open_transfer.weight_and_cog
self.get_cob()
- imp_file.close()
- self._parent.wm_title('| ANYstructure | ' + imp_file.name)
+ self._parent.wm_title('| ANYstructure | ' + str(filename))
self.update_frame()
+ def openfile(self, defined = None, alone = False):
+ '''
+ Opens a file with data (JSON).
+ '''
+
+ if defined == None:
+ target = project_application.ProjectFileDialogService.selected_open_target(
+ filedialog.askopenfilename(defaultextension=".txt")
+ )
+ else:
+ target = project_application.ProjectFileDialogService.selected_open_target(defined)
+ if target is None:
+ return
+
+ try:
+ opened_project = project_application.ProjectOpenService.open_path(
+ target.path,
+ self._build_project_hydration_defaults(),
+ )
+ except project_application.ProjectPersistenceError as error:
+ tk.messagebox.showerror('Open error', str(error))
+ return
+
+ open_transfer = opened_project.transfer
+ hydration = opened_project.hydration
+
+ self.reset()
+ self._apply_open_project_text_and_theme(open_transfer)
+ self._apply_open_project_geometry_and_objects(open_transfer, hydration)
+ self._apply_open_project_accelerations(open_transfer)
+ self._apply_open_project_load_combinations(open_transfer)
+ self._apply_open_project_tanks(open_transfer)
+ self._apply_open_project_canvas_scale()
+ self._finalize_open_project(open_transfer, target.path)
+
def restore_previous(self):
- if os.path.isfile(os.path.join(self._root_dir, '../backup.txt')):
- self.openfile(defined=os.path.join(self._root_dir, '../backup.txt'))
+ target = project_application.ProjectFileDialogService.restore_target(self._root_dir)
+ if target is not None:
+ self.openfile(defined=target.path)
def open_example(self, file_name = 'ship_section_example.txt'):
''' Open the example file. To be used in help menu. '''
- if os.path.isfile(file_name) :
- self.openfile(defined = file_name)
- else:
- self.openfile(defined= self._root_dir + '/' + file_name)
+ target = project_application.ProjectFileDialogService.example_open_target(file_name, self._root_dir)
+ self.openfile(defined=target.path)
def open_example_excel_file(self):
file_name = 'excel_input_example.xlsx'
+ target = project_application.ProjectFileDialogService.example_open_target(file_name, self._root_dir)
+ project_services.ExcelProjectImportService.open_example_path(target.path)
- if os.path.isfile(file_name) :
- XLB = excel_interface.ExcelInterface(file_name, visible=True, read_only=True)
- else:
- XLB = excel_interface.ExcelInterface(self._root_dir + '/' + file_name, visible=True, read_only=True)
+ def _sync_excel_import_geometry(self, geometry_import):
+ for point in geometry_import.created_points:
+ self._active_point = point.name
+ self.logger(point=point.name, move_coords=None)
+
+ for imported_line in geometry_import.imported_lines:
+ self._line_point_to_point_string.extend(imported_line.line.endpoint_keys)
+ self.add_to_combinations_dict(imported_line.line.name)
+ self.logger(line=[imported_line.line.name, None])
def open_excel_file(self):
''' Open an excel file with data to read into ANYstructure '''
- imp_file = filedialog.askopenfile(mode='r', defaultextension=".xlsx")
- if imp_file is None: # asksaveasfile return `None` if dialog closed with "cancel".
+ target = project_application.ProjectFileDialogService.selected_open_target(
+ filedialog.askopenfilename(defaultextension=".xlsx")
+ )
+ if target is None:
return
- data_wb = excel_interface.ExcelInterface(imp_file.name, visible=False, read_only=True)
- # if os.path.isfile(imp_file) : #kj
- # data_wb = excel_interface.ExcelInterface(imp_file, visible=False, read_only=True)
- # else:
- # return
- data_flat = data_wb.get_sheet_data('flat_plate')
- data_cyl = data_wb.get_sheet_data('cylinder')
- data_wb.close_book()
+ import_data = project_services.ExcelProjectImportService.read_path(target.path)
+ flat_plate_records = import_data.flat_plate_records
+ cylinder_records = import_data.cylinder_records
+
+ flat_geometry = project_services.ExcelProjectGeometryImportService.add_records(
+ self._point_dict,
+ self._line_dict,
+ flat_plate_records,
+ )
+ cylinder_geometry = project_services.ExcelProjectGeometryImportService.add_records(
+ self._point_dict,
+ self._line_dict,
+ cylinder_records,
+ )
+ self._sync_excel_import_geometry(flat_geometry)
+ self._sync_excel_import_geometry(cylinder_geometry)
+ self.update_frame()
# Flat
#--------------------------------------------------------------------------------------------------------------
- for row_data in data_flat[1:]:
- l1x, l1y, l2x, l2y = row_data[1:5]
- self._new_point_x.set(l1x)
- self._new_point_y.set(l1y)
- self.new_point()
- self._new_point_x.set(l2x)
- self._new_point_y.set(l2y)
- self.new_point()
- for row_data in data_cyl[1:]:
- l1x, l1y, l2x, l2y = row_data[1:5]
- self._new_point_x.set(l1x)
- self._new_point_y.set(l1y)
- self.new_point()
- self._new_point_x.set(l2x)
- self._new_point_y.set(l2y)
- self.new_point()
- # Creating lines
- all_points = self.get_points()
- for row_data in data_flat[1:]:
- l1x, l1y, l2x, l2y = row_data[1:5]
- p1_found = False
- p2_found = False
- for key, value in all_points.items():
- if l1x/1000 == value[0] and l1y/1000 == value[1]:
- p1 = get_num(key)
- p1_found = True
- if l2x/1000 == value[0] and l2y/1000 == value[1]:
- p2 = get_num(key)
- p2_found = True
- if p1_found and p2_found:
- self._new_line_p1.set(p1)
- self._new_line_p2.set(p2)
- this_line = self.new_line()
- self._active_line = this_line
- self._line_is_active = True
- self._new_calculation_domain.set(row_data[0])
- self._new_field_len.set(hlp.dist((l1x, l1y), (l2x, l2y)))
- self._new_plate_thk.set(row_data[5])
- self._new_stf_spacing.set(row_data[6])
- self._new_stf_web_h.set(row_data[7])
- self._new_stf_web_t.set(row_data[8])
- self._new_stf_fl_w.set(row_data[9])
- self._new_stf_fl_t.set(row_data[10])
- sig_start = 12
- self._new_sigma_x1.set(row_data[sig_start])
- self._new_sigma_x2.set(row_data[sig_start+1])
- self._new_sigma_y1.set(row_data[sig_start+2])
- self._new_sigma_y2.set(row_data[sig_start+3])
- self._new_tauxy.set(row_data[sig_start+4])
- gird_start = 18
- if row_data[0] == 'Flat plate, stiffened with girder':
- self._new_girder_length_LG.set(row_data[gird_start+0])
- self._new_girder_web_h.set(row_data[gird_start+1])
- self._new_girder_web_t.set(row_data[gird_start + 2])
- self._new_girder_fl_w.set(row_data[gird_start + 3])
- self._new_girder_fl_t.set(row_data[gird_start + 4])
- self._new_girder_type.set(row_data[gird_start + 5])
-
- end_data_start = 24
- for idx, var in enumerate([self._new_overpresure,self._new_stucture_type, self._new_buckling_stf_end_support,
- self._new_buckling_girder_end_support, self._new_buckling_fab_method_stf,
- self._new_buckling_fab_method_girder, self._new_buckling_length_factor_stf,
- self._new_buckling_length_factor_girder, self._new_buckling_stf_dist_bet_lat_supp,
- self._new_buckling_girder_dist_bet_lat_supp, self._new_buckling_tension_field,
- self._new_buckling_effective_against_sigy]):
- if row_data[end_data_start + idx] is not None:
- var.set(row_data[end_data_start + idx])
-
-
-
- # self._new_overpresure.set(row_data[end_data_start + 0])
- # self._new_stucture_type.set(row_data[end_data_start + 1])
- # self._new_buckling_stf_end_support.set(row_data[end_data_start + 2])
- # self._new_buckling_girder_end_support.set(row_data[end_data_start + 3])
- # self._new_buckling_fab_method_stf.set(row_data[end_data_start + 4])
- # self._new_buckling_fab_method_girder.set(row_data[end_data_start + 5])
- # self._new_buckling_length_factor_stf.set(row_data[end_data_start + 6])
- # self._new_buckling_length_factor_girder.set(row_data[end_data_start + 7])
- # self._new_buckling_stf_dist_bet_lat_supp.set(row_data[end_data_start + 8])
- # self._new_buckling_girder_dist_bet_lat_supp.set(row_data[end_data_start + 9])
- # self._new_buckling_tension_field.set(row_data[end_data_start + 10])
- # self._new_buckling_effective_against_sigy.set(row_data[end_data_start + 11])
-
- self.new_structure()
- self._new_load_comb_dict[('manual', this_line, 'manual')][0].set(row_data[16])
- self._new_load_comb_dict[('manual', this_line, 'manual')][1].set(1)
- self._new_load_comb_dict[('manual', this_line, 'manual')][2].set(1)
- self._line_to_struc[this_line][0].need_recalc = True
+ for imported_line in flat_geometry.imported_lines:
+ import_record = imported_line.record
+ this_line = imported_line.line.name
+ l1x, l1y = import_record.first_point
+ l2x, l2y = import_record.second_point
+ self._active_line = this_line
+ self._line_is_active = True
+ self._new_calculation_domain.set(import_record.calculation_domain)
+ flat_request, overpressure = self._build_flat_structure_property_request_from_excel_record(
+ import_record,
+ hlp.dist((l1x, l1y), (l2x, l2y)),
+ )
+ if overpressure is not None:
+ self._new_overpresure.set(overpressure)
+ self.new_structure(
+ toggle_multi=project_services.FlatStructurePropertyService.build(flat_request)
+ )
+ self._new_load_comb_dict[('manual', this_line, 'manual')][0].set(import_record.manual_pressure)
+ self._new_load_comb_dict[('manual', this_line, 'manual')][1].set(1)
+ self._new_load_comb_dict[('manual', this_line, 'manual')][2].set(1)
+ self._line_to_struc[this_line][0].need_recalc = True
# Cylinders
# ------------------------------------------------------------------------------------------------------------
- for row_data in data_cyl[1:]:
-
- l1x, l1y, l2x, l2y = row_data[1:5]
- p1_found = False
- p2_found = False
- for key, value in all_points.items():
- if l1x/1000 == value[0] and l1y/1000 == value[1]:
- p1 = get_num(key)
- p1_found = True
- if l2x/1000 == value[0] and l2y/1000 == value[1]:
- p2 = get_num(key)
- p2_found = True
-
- if p1_found and p2_found:
- self._new_line_p1.set(p1)
- self._new_line_p2.set(p2)
- this_line = self.new_line()
- self._active_line = this_line
- self._line_is_active = True
- self._new_calculation_domain.set(row_data[0])
- main_start = 5
- self._new_shell_thk.set(row_data[main_start + 0])
- self._new_shell_radius.set(row_data[main_start + 1])
- self._new_shell_dist_rings.set(row_data[main_start + 2])
- self._new_shell_length.set(row_data[main_start + 3])
- self._new_shell_tot_length.set(row_data[main_start + 4])
- self._new_shell_k_factor.set(row_data[main_start + 5])
- self._new_shell_mat_factor.set(row_data[main_start + 6])
-
- long_start = 12
- self._new_stf_web_h.set(row_data[long_start + 0])
- self._new_stf_web_t.set(row_data[long_start + 1])
- self._new_stf_fl_w.set(row_data[long_start + 2])
- self._new_stf_fl_t.set(row_data[long_start + 3])
- self._new_shell_panel_spacing.set(row_data[long_start + 4])
- self._new_stf_type.set(row_data[long_start + 5])
-
- ring_stf_start = 18
- if row_data[ring_stf_start] is None:
- self._new_shell_exclude_ring_stf.set(True)
- else:
- self._new_shell_exclude_ring_stf.set(False)
- self._new_shell_ring_stf_hw.set(row_data[ring_stf_start + 0])
- self._new_shell_ring_stf_tw.set(row_data[ring_stf_start + 1])
- self._new_shell_ring_stf_b.set(row_data[ring_stf_start + 2])
- self._new_shell_ring_stf_tf.set(row_data[ring_stf_start + 3])
- self._new_shell_ring_stf_type.set(row_data[ring_stf_start + 4])
-
- ring_frame = 23
- if row_data[ring_frame] is None:
- self._new_shell_exclude_ring_frame.set(True)
- else:
- self._new_shell_exclude_ring_frame.set(False)
- self._new_shell_ring_frame_hw.set(row_data[ring_frame + 0])
- self._new_shell_ring_frame_tw.set(row_data[ring_frame + 1])
- self._new_shell_ring_frame_b.set(row_data[ring_frame + 2])
- self._new_shell_ring_frame_tf.set(row_data[ring_frame + 3])
- self._new_shell_ring_frame_length_between_girders.set(row_data[ring_frame + 4])
- self._new_shell_ring_frame_type.set(row_data[ring_frame + 5])
-
- stress_start = 29
- if row_data[stress_start] is None:
- pass
- else:
- #mapper ={1: 'Force', 2: 'Stress'}
- self._new_shell_stress_or_force.set(2)
- self._new_shell_sasd.set(row_data[stress_start + 0])
- self._new_shell_smsd.set(row_data[stress_start + 1])
- self._new_shell_tTsd.set(row_data[stress_start + 2])
- self._new_shell_tQsd.set(row_data[stress_start + 3])
- self._new_shell_psd.set(row_data[stress_start + 4])
- self._new_shell_shsd.set(row_data[stress_start + 5])
-
- force_start = 35
- if row_data[force_start] is None:
- pass
- else:
- self._new_shell_stress_or_force.set(1)
- self._new_shell_Nsd.set(row_data[force_start + 0])
- self._new_shell_Msd.set(row_data[force_start + 1])
- self._new_shell_Tsd.set(row_data[force_start + 2])
- self._new_shell_Qsd.set(row_data[force_start + 3])
- self._new_shell_psd.set(row_data[stress_start + 4])
- end_data_start = 39
- self._new_shell_uls_or_als.set(row_data[end_data_start + 0])
- self._new_shell_yield.set(row_data[end_data_start + 1])
- self._new_shell_end_cap_pressure_included.set(row_data[end_data_start + 1])
- self._new_shell_fab_ring_stf.set(row_data[end_data_start + 2])
- self._new_shell_fab_ring_frame.set(row_data[end_data_start + 3])
- self.new_structure()
+ for imported_line in cylinder_geometry.imported_lines:
+ import_record = imported_line.record
+ this_line = imported_line.line.name
+ self._active_line = this_line
+ self._line_is_active = True
+ self._new_calculation_domain.set(import_record.calculation_domain)
+ cylinder_request = project_services.CylinderExcelImportPropertyService.build_request(
+ import_record,
+ self._build_cylinder_excel_import_defaults(),
+ )
+ cylinder_result = project_services.CylinderStructurePropertyService.build(cylinder_request)
+ cylinder_obj = self._create_cylinder_structure_from_property_result(cylinder_result)
+ flat_request = self._build_flat_structure_property_request_from_cylinder_excel_record(import_record)
+ self.new_structure(
+ toggle_multi=project_services.FlatStructurePropertyService.build(flat_request),
+ cylinder_return=cylinder_obj,
+ )
def button_load_info_click(self, event = None):
''' Get the load information for one line.'''
@@ -7332,43 +7920,6 @@ def on_open_load_factor_window(self):
lf_tkinter = tk.Toplevel(self._parent, background=self._general_color)
load_factors.CreateLoadFactorWindow(lf_tkinter, self)
- def on_puls_results_for_line(self):
- if not self._line_is_active:
- return
- if self._PULS_results is None:
- return
- elif self._PULS_results.get_puls_line_results(self._active_line) is None:
- return
- # if self._puls_information_button.config('relief')[-1] == 'sunken':
- # self.text_widget.forget()
- # self._puls_information_button.config(relief='raised')
- this_result = self._PULS_results.get_puls_line_results(self._active_line)
- this_string = ''
- for key, value in this_result.items():
- if type(value) == list:
- this_string += key + ' : ' + str(value[0]) + ' ' + str(value[1]) + '\n'
- elif type(value) == str:
- this_string += key + ' : ' + value + '\n'
- elif type(value) == dict:
- this_string += key + '\n'
- for subk, subv in value.items():
- this_string += ' ' + subk + ' : ' + str(subv[0]) + ' ' + str(subv[1] if subv[1] != None else '') + '\n'
-
- text_m = tk.Toplevel(self._parent, background=self._general_color)
- # Create the text widget
- text_widget = tk.Text(text_m , height=60, width=100)
- # Create a scrollbar
- scroll_bar = ttk.Scrollbar(text_m)
- # Pack the scroll bar
- # Place it to the right side, using tk.RIGHT
- scroll_bar.pack(side=tk.RIGHT)
- # Pack it into our tkinter application
- # Place the text widget to the left side
- text_widget.pack(side=tk.LEFT)
- long_text = this_string
- # Insert text into the text widget
- text_widget.insert(tk.END, long_text)
-
def on_show_loads(self):
'''
User can open a new window to specify loads
@@ -7521,32 +8072,12 @@ def on_close_load_window(self, returned_loads, counter, load_comb_dict):
self._new_load_comb_dict = load_comb_dict
temp_load = self.__previous_load_data
if len(returned_loads) != 0:
- need_to_recalc_puls = {}
for load, data in returned_loads.items():
#creating the loads objects dictionary
self._load_dict[load] = data
- # adding values to the line dictionary. resetting first.
- for key, value in self._line_to_struc.items():
- self._line_to_struc[key][3] = []
- self._line_to_struc[key][0].need_recalc = True # All lines need recalculations.
-
- for main_line in self._line_dict.keys():
- for load_obj, load_line in self._load_dict.values():
- if main_line in self._line_to_struc.keys():
- if load_obj.get_name() in temp_load.keys():
- if any([load_obj.__str__() != temp_load[load_obj.get_name()][0].__str__() and main_line in \
- load_line+temp_load[load_obj.get_name()][1],
- main_line in list(set(temp_load[load_obj.get_name()][1]).symmetric_difference(set(load_line)))]) :
- # The load has changed for this line.
- if self._PULS_results is not None:
- self._PULS_results.result_changed(main_line)
- elif main_line in load_line:
- # This is a new load for this line.
- if self._PULS_results is not None:
- self._PULS_results.result_changed(main_line)
- if main_line in load_line and main_line in self._line_to_struc.keys():
- self._line_to_struc[main_line][3].append(load_obj)
+ project_services.LineLoadService(self._line_to_struc).rebuild_line_loads(
+ self._line_dict.keys(), self._load_dict, temp_load)
# Storing the the returned data to temporary variable.
self.__returned_load_data = [returned_loads, counter, load_comb_dict]
@@ -7831,11 +8362,11 @@ def open_about(self):
Open a about messagebox.
:return:
'''
- messagebox.showinfo(title='Information', message='ANYstructure 5.x.x (Stable/Production)'
+ messagebox.showinfo(title='Information', message='ANYstructure 6.x.x (Stable/Production)'
'\n'
'\n'
'By Audun Arnesen Nyhus \n'
- '2023/2024\n\n'
+ '2026\n\n'
'All technical calculation based on \n'
'DNV RPs and standards')
@@ -7844,19 +8375,20 @@ def export_to_js(self):
Printing to a js file
:return:
'''
- save_file = filedialog.asksaveasfile(mode="w", defaultextension=".js")
- if save_file is None: # ask saveasfile return `None` if dialog closed with "cancel".
+ target = project_application.ProjectFileDialogService.selected_output_target(
+ filedialog.asksaveasfilename(defaultextension=".js")
+ )
+ if target is None:
return
- # Setting up interface class.
- JS = sesam.JSfile(self._point_dict, self._line_dict, self._sections, self._line_to_struc)
-
- JS.write_points()
- JS.write_lines()
- JS.write_sections()
- JS.write_beams()
-
- save_file.writelines(JS.output_lines)
- save_file.close()
+ project_services.SesamExportService.write_js_path(
+ project_services.SesamExportRequest(
+ points=self._point_dict,
+ lines=self._line_dict,
+ sections=self._sections,
+ line_bundles=self._line_to_struc,
+ ),
+ target.path,
+ )
if __name__ == '__main__':
@@ -7932,4 +8464,4 @@ def export_to_js(self):
my_app = Application(root)
root.mainloop()
- #Application.openfile(r'C:\Github\ANYstructure\ANYstructure\ship_section_example.txt', alone=True)
\ No newline at end of file
+ #Application.openfile(r'C:\Github\ANYstructure\ANYstructure\ship_section_example.txt', alone=True)
diff --git a/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_predictor_UP.pickle b/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_predictor_UP.pickle
new file mode 100644
index 00000000..f33d1872
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_predictor_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_scaler_UP.pickle b/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_scaler_UP.pickle
new file mode 100644
index 00000000..e06c3541
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_CSR-Tank_req_cl_scaler_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor_SP.pickle b/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor_SP.pickle
new file mode 100644
index 00000000..58d7c140
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler_SP.pickle b/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler_SP.pickle
new file mode 100644
index 00000000..bb42b952
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..6a7b60e4
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..3495c5b2
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..7e743c85
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..f4e505ee
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..5f9a8597
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..b48f8a0e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..f3d47448
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..ebef42e8
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..444964c5
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..3d8b1907
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..64bc8dd8
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..578b2ce6
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..5f9a8597
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..b48f8a0e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..f3d47448
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..ebef42e8
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..d286b666
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..273dd08a
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..8c1ed61f
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..7e282ea9
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..5f9a8597
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..b48f8a0e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..f3d47448
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..ebef42e8
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..f467c50e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..6172dbd1
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..f4a2db7d
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..176e7d2e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle
new file mode 100644
index 00000000..5f9a8597
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle
new file mode 100644
index 00000000..b48f8a0e
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle
new file mode 100644
index 00000000..f3d47448
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle differ
diff --git a/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle
new file mode 100644
index 00000000..ebef42e8
Binary files /dev/null and b/anystruct/ml_files/CLPIPE_CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle differ
diff --git a/anystruct/ml_files/CL_CSR-Tank_req_cl_predictor.pickle b/anystruct/ml_files/CL_CSR-Tank_req_cl_predictor.pickle
deleted file mode 100644
index a2de2479..00000000
Binary files a/anystruct/ml_files/CL_CSR-Tank_req_cl_predictor.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_CSR-Tank_req_cl_scaler.pickle b/anystruct/ml_files/CL_CSR-Tank_req_cl_scaler.pickle
deleted file mode 100644
index c84aa64a..00000000
Binary files a/anystruct/ml_files/CL_CSR-Tank_req_cl_scaler.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle b/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle
deleted file mode 100644
index 0e4d1148..00000000
Binary files a/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle b/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle
deleted file mode 100644
index 23b286b7..00000000
Binary files a/anystruct/ml_files/CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index cf66382c..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index 956507ea..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index f23818a8..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index bc21e32b..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_predictor_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 53d37fe9..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index d62314aa..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 40f0b3d0..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 3dfb1cbc..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_110_scaler_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index f9c237d3..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index 230af97b..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 9167b313..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 639f468b..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_predictor_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 53d37fe9..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index d62314aa..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 40f0b3d0..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 3dfb1cbc..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_buc_115_scaler_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 0f0f7d35..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index 16ada80e..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index b3cddf18..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index e6697373..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_predictor_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 53d37fe9..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index d62314aa..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 40f0b3d0..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 3dfb1cbc..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_110_scaler_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 7c4d4d0b..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index 87c69f3a..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 1dcbf6ad..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 88310e6b..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_predictor_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle
deleted file mode 100644
index 53d37fe9..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle
deleted file mode 100644
index d62314aa..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_1_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle
deleted file mode 100644
index 40f0b3d0..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_SP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle b/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle
deleted file mode 100644
index 3dfb1cbc..00000000
Binary files a/anystruct/ml_files/CL_output_cl_str_ult_115_scaler_In-plane_support_cl_2,_3_UP.pickle and /dev/null differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..693ec895
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..1e62cbaf
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..135c435d
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..147605bc
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..a31f9771
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..5074999c
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..3bbf489b
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..ad9a6527
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..35b391c8
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..a1e9a995
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..eaa91f16
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..772d1b67
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..043e66a7
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..577e78a9
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..5b4d11d9
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..1458089b
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..30328368
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..50d2df8d
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle b/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle
new file mode 100644
index 00000000..84547ce7
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1.pickle differ
diff --git a/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle b/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle
new file mode 100644
index 00000000..062ad36a
Binary files /dev/null and b/anystruct/ml_files/NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3.pickle differ
diff --git a/anystruct/ml_models.py b/anystruct/ml_models.py
new file mode 100644
index 00000000..d5c1a387
--- /dev/null
+++ b/anystruct/ml_models.py
@@ -0,0 +1,236 @@
+"""Machine-learning model file resolution and loading helpers."""
+
+import pickle
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, Iterable
+
+
+MATERIAL_FACTORS = (1.1, 1.15)
+ML_CLASS_MESSAGES = {
+ 0: "N/A",
+ 1: "A negative utilisation factor is found.",
+ 2: "At least one of the in-plane loads must be non-zero.",
+ 3: "Division by zero",
+ 4: "Overflow",
+ 5: "The aspect ratio exceeds the PULS code limit",
+ 6: "The global slenderness exceeds 4. Please reduce stiffener span or increase stiffener height.",
+ 7: "The applied pressure is too high for this plate field.",
+ 8: "web-flange-ratio",
+ 9: "UF below or equal 0.87",
+ 10: "UF between 0.87 and 1.0",
+ 11: "UF above 1.0",
+}
+
+
+def default_ml_class_messages():
+ """Return the ML classifier result messages used by GUI/report surfaces."""
+ return dict(ML_CLASS_MESSAGES)
+
+
+@dataclass(frozen=True)
+class ModelFileSpec:
+ """Named model with current and compatible legacy file locations."""
+
+ name: str
+ file_bases: tuple[str, ...]
+
+
+BUCKLING_MODEL_SPECS = (
+ # -------------------------------------------------------------------------
+ # Classification pipeline - existing CLPIPE models
+ # -------------------------------------------------------------------------
+ ModelFileSpec("cl SP buc int predictor", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_SP",)),
+ ModelFileSpec("cl SP buc int scaler", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_SP",)),
+ ModelFileSpec("cl SP ult int predictor", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_SP",)),
+ ModelFileSpec("cl SP ult int scaler", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_SP",)),
+
+ ModelFileSpec("cl SP buc GLGT predictor", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_SP",)),
+ ModelFileSpec("cl SP buc GLGT scaler", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_SP",)),
+ ModelFileSpec("cl SP ult GLGT predictor", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_SP",)),
+ ModelFileSpec("cl SP ult GLGT scaler", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_SP",)),
+
+ ModelFileSpec("cl UP buc int predictor", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_UP",)),
+ ModelFileSpec("cl UP buc int scaler", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_UP",)),
+ ModelFileSpec("cl UP ult int predictor", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_UP",)),
+ ModelFileSpec("cl UP ult int scaler", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_UP",)),
+
+ ModelFileSpec("cl UP buc GLGT predictor", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_UP",)),
+ ModelFileSpec("cl UP buc GLGT scaler", ("ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_UP",)),
+ ModelFileSpec("cl UP ult GLGT predictor", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_UP",)),
+ ModelFileSpec("cl UP ult GLGT scaler", ("ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_UP",)),
+
+ ModelFileSpec(
+ "CSR predictor UP",
+ (
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_predictor_UP",
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_predictor",
+ ),
+ ),
+ ModelFileSpec(
+ "CSR scaler UP",
+ (
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_scaler_UP",
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_scaler",
+ ),
+ ),
+ ModelFileSpec(
+ "CSR predictor SP",
+ (
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor_SP",
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
+ ),
+ ),
+ ModelFileSpec(
+ "CSR scaler SP",
+ (
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler_SP",
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler",
+ ),
+ ),
+
+ # -------------------------------------------------------------------------
+ # Numeric UF pipeline - validity classifier + two-output UF regressor
+ # -------------------------------------------------------------------------
+ # SP, integrated support
+ ModelFileSpec(
+ "num SP int validity predictor",
+ ("ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num SP int validity xscaler",
+ ("ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num SP int UF reg predictor",
+ ("ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num SP int UF reg xscaler",
+ ("ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num SP int UF reg yscaler",
+ ("ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1",),
+ ),
+
+ # SP, GL/GT support
+ ModelFileSpec(
+ "num SP GLGT validity predictor",
+ ("ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num SP GLGT validity xscaler",
+ ("ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num SP GLGT UF reg predictor",
+ ("ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num SP GLGT UF reg xscaler",
+ ("ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num SP GLGT UF reg yscaler",
+ ("ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+
+ # UP, integrated support
+ ModelFileSpec(
+ "num UP int validity predictor",
+ ("ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num UP int validity xscaler",
+ ("ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num UP int UF reg predictor",
+ ("ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num UP int UF reg xscaler",
+ ("ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1",),
+ ),
+ ModelFileSpec(
+ "num UP int UF reg yscaler",
+ ("ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1",),
+ ),
+
+ # UP, GL/GT support
+ ModelFileSpec(
+ "num UP GLGT validity predictor",
+ ("ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num UP GLGT validity xscaler",
+ ("ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num UP GLGT UF reg predictor",
+ ("ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num UP GLGT UF reg xscaler",
+ ("ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+ ModelFileSpec(
+ "num UP GLGT UF reg yscaler",
+ ("ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3",),
+ ),
+)
+
+
+def material_factor_token(material_factor: float) -> str:
+ """Return the token embedded in material-factor-specific model filenames."""
+ return (str(round(material_factor, 2)).replace(".", "") + "0")[:3]
+
+
+def resolve_model_pickle(
+ spec: ModelFileSpec,
+ search_roots: Iterable[str | Path],
+ material_factor: float,
+) -> Path:
+ """Find the first available current or legacy pickle for a model spec."""
+ attempted = []
+ roots = tuple(Path(root) for root in search_roots)
+ for file_base in spec.file_bases:
+ relative_path = _pickle_path(file_base, material_factor)
+ for candidate in _candidate_paths(relative_path, roots):
+ attempted.append(candidate)
+ if candidate.is_file():
+ return candidate
+
+ searched = ", ".join(str(path) for path in attempted)
+ raise FileNotFoundError(f"Could not find ML model {spec.name!r}. Checked: {searched}")
+
+
+def load_buckling_models(
+ search_roots: Iterable[str | Path],
+ material_factors: Iterable[float] = MATERIAL_FACTORS,
+) -> dict[float, dict[str, Any]]:
+ """Load the buckling and CSR classifier models used by the main workspace."""
+ roots = tuple(search_roots)
+ models = {}
+ for material_factor in material_factors:
+ models[material_factor] = {}
+ for spec in BUCKLING_MODEL_SPECS:
+ model_path = resolve_model_pickle(spec, roots, material_factor)
+ with model_path.open("rb") as model_file:
+ models[material_factor][spec.name] = pickle.load(model_file)
+ return models
+
+
+def _pickle_path(file_base: str, material_factor: float) -> Path:
+ path = file_base.replace("XXX", material_factor_token(material_factor)).replace("\\", "/")
+ return Path(path + ".pickle")
+
+
+def _candidate_paths(relative_path: Path, roots: tuple[Path, ...]) -> tuple[Path, ...]:
+ candidates = [relative_path]
+ for root in roots:
+ candidates.append(root / relative_path)
+ if relative_path.parts and relative_path.parts[0] == "ml_files":
+ candidates.append(root / Path(*relative_path.parts[1:]))
+ return tuple(dict.fromkeys(candidates))
diff --git a/anystruct/optimize.py b/anystruct/optimize.py
index 9935e167..ddf38b5d 100644
--- a/anystruct/optimize.py
+++ b/anystruct/optimize.py
@@ -1,4 +1,4 @@
-#Optimize structure
+# Optimize structure
import numpy as np
import itertools as it
@@ -15,20 +15,64 @@
try:
import anystruct.calc_structure as calc
import anystruct.helper as hlp
+ import anystruct.calculate_semianalytical as semi_analytical
except ModuleNotFoundError:
import ANYstructure.anystruct.calc_structure as calc
import ANYstructure.anystruct.helper as hlp
+ import ANYstructure.anystruct.calculate_semianalytical as semi_analytical
+
+
+def _set_material_factor_on_structure(obj, material_factor):
+ """
+ Apply selected material factor to Plate/Stiffener/Girder objects.
+
+ This is important for the ML-Numeric optimizer because the numeric UF
+ is converted as:
+ UF_checked = UF_predicted * material_factor
+ and get_filtered_results reads the material factor from init_stuc_obj.Plate.
+
+ obj may be a single AllStructure-like object or a list/tuple of such objects
+ for geometric optimization.
+ """
+ if material_factor is None:
+ return obj
+
+ try:
+ mat_fac = float(material_factor)
+ except Exception:
+ return obj
+
+ def _apply(one_obj):
+ for attr_name in ('Plate', 'Stiffener', 'Girder'):
+ try:
+ part = getattr(one_obj, attr_name)
+ except Exception:
+ part = None
+ if part is not None:
+ try:
+ part.mat_factor = mat_fac
+ except Exception:
+ pass
+ return one_obj
+
+ if isinstance(obj, (list, tuple)):
+ for one_obj in obj:
+ _apply(one_obj)
+ else:
+ _apply(obj)
+
+ return obj
def run_optmizataion(initial_structure_obj=None, min_var=None, max_var=None, lateral_pressure=None,
deltas=None, algorithm='anysmart', trials=30000, side='p',
- const_chk = (True,True,True,True,True,True, True, False, False, False),
- pso_options = (100,0.5,0.5,0.5,100,1e-8,1e-8), is_geometric=False, fatigue_obj = None ,
- fat_press_ext_int = None,
- min_max_span = (2,6), tot_len = None, frame_height = 2.5, frame_distance = None,
- slamming_press = 0, predefined_stiffener_iter = None, processes = None, use_weight_filter = True,
- load_pre = False, opt_girder_prop = None, puls_sheet = None, puls_acceptance = 0.87,
- fdwn = 1, fup = 0.5, ml_algo = None, cylinder = False):
+ const_chk=(True, True, True, True, True, True, True, False, False, False),
+ pso_options=(100, 0.5, 0.5, 0.5, 100, 1e-8, 1e-8), is_geometric=False, fatigue_obj=None,
+ fat_press_ext_int=None,
+ min_max_span=(2, 6), tot_len=None, frame_height=2.5, frame_distance=None,
+ slamming_press=0, predefined_stiffener_iter=None, processes=None, use_weight_filter=True,
+ load_pre=False, opt_girder_prop=None, puls_sheet=None, puls_acceptance=0.87,
+ fdwn=1, fup=0.5, ml_algo=None, cylinder=False, material_factor=None):
'''
The optimazation is initiated here. It is called from optimize_window.
:param initial_structure_obj:
@@ -41,6 +85,16 @@ def run_optmizataion(initial_structure_obj=None, min_var=None, max_var=None, lat
:param pso_options:
:return:
'''
+ if puls_sheet is not None:
+ raise NotImplementedError(
+ "External Excel-sheet PULS optimization was removed. Use the built-in SemiAnalytical replacement, "
+ "prescriptive, or ML-Numeric buckling checks."
+ )
+
+ # Make material factor explicit for all optimizer variants.
+ # Single optimization receives one structure object; geometric optimization receives a list.
+ initial_structure_obj = _set_material_factor_on_structure(initial_structure_obj, material_factor)
+
init_filter_weight = float('inf')
if is_geometric:
@@ -58,44 +112,44 @@ def run_optmizataion(initial_structure_obj=None, min_var=None, max_var=None, lat
init_filter_weight = get_initial_weight(obj=initial_structure_obj,
lat_press=lateral_pressure,
min_var=min_var, max_var=max_var, deltas=deltas,
- trials= 30000 if predefined_stiffener_iter is None else
+ trials=30000 if predefined_stiffener_iter is None else
len(predefined_stiffener_iter),
fat_dict=fat_dict,
fat_press=None if fat_press_ext_int is None else fat_press_ext_int,
- predefined_stiffener_iter = predefined_stiffener_iter,
- slamming_press=slamming_press, fdwn = fdwn, fup = fup,
- ml_algo = ml_algo)
+ predefined_stiffener_iter=predefined_stiffener_iter,
+ slamming_press=slamming_press, fdwn=fdwn, fup=fup,
+ ml_algo=ml_algo)
if cylinder:
to_return = any_smart_loop_cylinder(min_var=min_var, max_var=max_var, deltas=deltas,
initial_structure_obj=initial_structure_obj,
- use_weight_filter = use_weight_filter,
+ use_weight_filter=use_weight_filter,
predefiened_stiffener_iter=predefined_stiffener_iter)
return to_return
elif algorithm == 'anysmart' and not is_geometric:
to_return = any_smart_loop(min_var, max_var, deltas, initial_structure_obj, lateral_pressure,
init_filter_weight, side=side, const_chk=const_chk, fat_dict=fat_dict,
- fat_press=fat_press_ext_int,slamming_press=slamming_press,
- predefiened_stiffener_iter=predefined_stiffener_iter, puls_sheet = puls_sheet,
- puls_acceptance = puls_acceptance, fdwn = fdwn, fup = fup, ml_algo=ml_algo)
+ fat_press=fat_press_ext_int, slamming_press=slamming_press,
+ predefiened_stiffener_iter=predefined_stiffener_iter, puls_sheet=puls_sheet,
+ puls_acceptance=puls_acceptance, fdwn=fdwn, fup=fup, ml_algo=ml_algo)
return to_return
elif algorithm == 'anysmart' and is_geometric:
- return geometric_summary_search(min_var= min_var, max_var=max_var, deltas= deltas,
- initial_structure_obj= initial_structure_obj, lateral_pressure=lateral_pressure,
- init_filter= init_filter_weight, side= side, const_chk= const_chk,
- fat_obj= fatigue_obj, fat_press= fat_press_ext_int, min_max_span= min_max_span,
- tot_len= tot_len, frame_distance = frame_distance,
- algorithm= 'anysmart', predefiened_stiffener_iter=predefined_stiffener_iter,
- slamming_press = slamming_press, load_pre = load_pre,
- opt_girder_prop = opt_girder_prop, ml_algo=ml_algo)
+ return geometric_summary_search(min_var=min_var, max_var=max_var, deltas=deltas,
+ initial_structure_obj=initial_structure_obj, lateral_pressure=lateral_pressure,
+ init_filter=init_filter_weight, side=side, const_chk=const_chk,
+ fat_obj=fatigue_obj, fat_press=fat_press_ext_int, min_max_span=min_max_span,
+ tot_len=tot_len, frame_distance=frame_distance,
+ algorithm='anysmart', predefiened_stiffener_iter=predefined_stiffener_iter,
+ slamming_press=slamming_press, load_pre=load_pre,
+ opt_girder_prop=opt_girder_prop, ml_algo=ml_algo)
elif algorithm == 'anydetail' and not is_geometric:
- return any_optimize_loop(min_var, max_var, deltas, initial_structure_obj, lateral_pressure,init_filter_weight,
+ return any_optimize_loop(min_var, max_var, deltas, initial_structure_obj, lateral_pressure, init_filter_weight,
side=side, const_chk=const_chk, fat_dict=fat_dict, fat_press=fat_press_ext_int,
slamming_press=slamming_press)
elif algorithm == 'random' and not is_geometric:
- return get_random_result(initial_structure_obj,lateral_pressure,min_var,max_var,deltas,trials=trials,
- side=side, const_chk=const_chk, fat_dict=fat_dict,fat_press=fat_press_ext_int)
+ return get_random_result(initial_structure_obj, lateral_pressure, min_var, max_var, deltas, trials=trials,
+ side=side, const_chk=const_chk, fat_dict=fat_dict, fat_press=fat_press_ext_int)
elif algorithm == 'random_no_delta' and not is_geometric:
return get_random_result_no_bounds(initial_structure_obj, lateral_pressure, min_var, max_var, trials=trials,
side=side, const_chk=const_chk)
@@ -107,9 +161,10 @@ def run_optmizataion(initial_structure_obj=None, min_var=None, max_var=None, lat
else:
return None
-def any_optimize_loop(min_var,max_var,deltas,initial_structure_obj,lateral_pressure, init_filter = float('inf'),
- side='p',const_chk=(True,True,True,True,True,False), fat_dict = None, fat_press = None,
- slamming_press = 0):
+
+def any_optimize_loop(min_var, max_var, deltas, initial_structure_obj, lateral_pressure, init_filter=float('inf'),
+ side='p', const_chk=(True, True, True, True, True, False), fat_dict=None, fat_press=None,
+ slamming_press=0):
'''
Calulating initial values.
:param min:
@@ -117,7 +172,7 @@ def any_optimize_loop(min_var,max_var,deltas,initial_structure_obj,lateral_press
:return:
'''
ass_var = []
- plot_x,plot_y = [],[]
+ plot_x, plot_y = [], []
plt.xlabel('#')
plt.ylabel('weigth [kg]')
plt.title('ANYdetail brute force results')
@@ -126,22 +181,22 @@ def any_optimize_loop(min_var,max_var,deltas,initial_structure_obj,lateral_press
iter_count = 0
min_weight = init_filter
main_fail = list()
- for spacing in np.arange(min_var[0],max_var[0]+deltas[0],deltas[0]):
- for plate_thk in np.arange(min_var[1],max_var[1]+deltas[1],deltas[1]):
- for stf_web_h in np.arange(min_var[2],max_var[2]+deltas[2],deltas[2]):
- for stf_web_thk in np.arange(min_var[3],max_var[3]+deltas[3],deltas[3]):
- for stf_flange_width in np.arange(min_var[4],max_var[4]+deltas[4],deltas[4]):
- for stf_flange_thk in np.arange(min_var[5],max_var[5]+deltas[5],deltas[5]):
+ for spacing in np.arange(min_var[0], max_var[0] + deltas[0], deltas[0]):
+ for plate_thk in np.arange(min_var[1], max_var[1] + deltas[1], deltas[1]):
+ for stf_web_h in np.arange(min_var[2], max_var[2] + deltas[2], deltas[2]):
+ for stf_web_thk in np.arange(min_var[3], max_var[3] + deltas[3], deltas[3]):
+ for stf_flange_width in np.arange(min_var[4], max_var[4] + deltas[4], deltas[4]):
+ for stf_flange_thk in np.arange(min_var[5], max_var[5] + deltas[5], deltas[5]):
var_x = np.array([spacing, plate_thk, stf_web_h, stf_web_thk, stf_flange_width,
- stf_flange_thk,min_var[6],min_var[7]])
- check = any_constraints_all(var_x,initial_structure_obj,lat_press=lateral_pressure,
- init_weight=min_weight,side=side,chk=const_chk,
- fat_dict = fat_dict, fat_press = fat_press,
+ stf_flange_thk, min_var[6], min_var[7]])
+ check = any_constraints_all(var_x, initial_structure_obj, lat_press=lateral_pressure,
+ init_weight=min_weight, side=side, chk=const_chk,
+ fat_dict=fat_dict, fat_press=fat_press,
slamming_press=slamming_press)
if check[0] is not False:
current_weight = calc_weight(var_x)
if current_weight <= min_weight:
- iter_count+=1
+ iter_count += 1
min_weight = current_weight
ass_var = var_x
main_fail.append(check)
@@ -150,16 +205,17 @@ def any_optimize_loop(min_var,max_var,deltas,initial_structure_obj,lateral_press
if ass_var is None:
return None, None, None, False, main_fail
- new_struc_obj = create_new_structure_obj(initial_structure_obj,[item for item in ass_var])
- new_calc_obj = create_new_calc_obj(initial_structure_obj,[item for item in ass_var])[0]
+ new_struc_obj = create_new_structure_obj(initial_structure_obj, [item for item in ass_var])
+ new_calc_obj = create_new_calc_obj(initial_structure_obj, [item for item in ass_var])[0]
return new_struc_obj, new_calc_obj, fat_dict, True, main_fail
-def any_smart_loop(min_var,max_var,deltas,initial_structure_obj,lateral_pressure, init_filter = float('inf'),
- side='p',const_chk=(True,True,True,True,True,True,True, False, False,False), fat_dict = None,
- fat_press = None,
- slamming_press = 0, predefiened_stiffener_iter = None, processes = None,
- puls_sheet = None, puls_acceptance = 0.87, fdwn = 1, fup = 0.5, ml_algo = None):
+
+def any_smart_loop(min_var, max_var, deltas, initial_structure_obj, lateral_pressure, init_filter=float('inf'),
+ side='p', const_chk=(True, True, True, True, True, True, True, False, False, False), fat_dict=None,
+ fat_press=None,
+ slamming_press=0, predefiened_stiffener_iter=None, processes=None,
+ puls_sheet=None, puls_acceptance=0.87, fdwn=1, fup=0.5, ml_algo=None):
'''
Trying to be smart
:param min_var:
@@ -172,18 +228,19 @@ def any_smart_loop(min_var,max_var,deltas,initial_structure_obj,lateral_pressure
if predefiened_stiffener_iter is None:
structure_to_check = any_get_all_combs(min_var, max_var, deltas)
else:
- structure_to_check = any_get_all_combs(min_var, max_var, deltas,predef_stiffeners=[item.get_tuple() for item
- in predefiened_stiffener_iter])
+ structure_to_check = any_get_all_combs(min_var, max_var, deltas, predef_stiffeners=[item.get_tuple() for item
+ in
+ predefiened_stiffener_iter])
- main_result = get_filtered_results(structure_to_check, initial_structure_obj,lateral_pressure,
- init_filter_weight=init_filter, side=side,chk=const_chk, fat_dict=fat_dict,
+ main_result = get_filtered_results(structure_to_check, initial_structure_obj, lateral_pressure,
+ init_filter_weight=init_filter, side=side, chk=const_chk, fat_dict=fat_dict,
fat_press=fat_press, slamming_press=slamming_press, processes=processes,
- puls_sheet = puls_sheet, puls_acceptance = puls_acceptance, ml_algo=ml_algo)
+ puls_sheet=puls_sheet, puls_acceptance=puls_acceptance, ml_algo=ml_algo)
main_iter = main_result[0]
main_fail = main_result[1]
- ass_var=None
+ ass_var = None
current_weight = float('inf')
for item in main_iter:
main_fail.append(item)
@@ -207,9 +264,9 @@ def any_smart_loop(min_var,max_var,deltas,initial_structure_obj,lateral_pressure
calc_object_stf = None if initial_structure_obj.Stiffener is None \
else create_new_calc_obj(initial_structure_obj.Stiffener, ass_var,
- fat_dict, fdwn=fdwn, fup=fup)
+ fat_dict, fdwn=fdwn, fup=fup)
calc_object_pl = create_new_calc_obj(initial_structure_obj.Plate, ass_var, fat_dict,
- fdwn=fdwn, fup=fup)
+ fdwn=fdwn, fup=fup)
calc_object = calc.AllStructure(Plate=calc_object_pl[0],
Stiffener=None if initial_structure_obj.Stiffener is None else calc_object_stf[0],
Girder=None,
@@ -218,18 +275,19 @@ def any_smart_loop(min_var,max_var,deltas,initial_structure_obj,lateral_pressure
return calc_object, fat_dict, True, main_fail
-def any_smart_loop_cylinder(min_var,max_var,deltas,initial_structure_obj,lateral_pressure = None,
- init_filter = float('inf'),
- side='p',const_chk=(True,True,True,True,True,True,True, False, False,False), fat_dict = None,
- fat_press = None, slamming_press = 0, predefiened_stiffener_iter = None, processes = None,
- fdwn = 1, fup = 0.5, ml_algo = None, use_weight_filter = True):
+def any_smart_loop_cylinder(min_var, max_var, deltas, initial_structure_obj, lateral_pressure=None,
+ init_filter=float('inf'),
+ side='p', const_chk=(True, True, True, True, True, True, True, False, False, False),
+ fat_dict=None,
+ fat_press=None, slamming_press=0, predefiened_stiffener_iter=None, processes=None,
+ fdwn=1, fup=0.5, ml_algo=None, use_weight_filter=True):
combs = list()
# TODO first optmize for long then ring components. Find the overall smallest weight.
# Creating the individual combinations for Shell, LongStf, RingStf and RingFrame
for idx, str_type in enumerate(range(len(min_var))):
if sum(min_var[idx]) == 0:
- structure_to_check = [(0, 0, 0, 0, 0, 0, 0, 0),]
+ structure_to_check = [(0, 0, 0, 0, 0, 0, 0, 0), ]
else:
if any([predefiened_stiffener_iter is None, idx == 0]):
initial_structure_obj.LongStfObj.stiffener_type = 'T'
@@ -237,8 +295,8 @@ def any_smart_loop_cylinder(min_var,max_var,deltas,initial_structure_obj,lateral
else:
structure_to_check = any_get_all_combs(min_var[idx], max_var[idx], deltas[idx],
- predef_stiffeners= [item.get_tuple() for item in
- predefiened_stiffener_iter])
+ predef_stiffeners=[item.get_tuple() for item in
+ predefiened_stiffener_iter])
# TODO add stifffener type
# [list(item).append('T') for item in structure_to_check]
@@ -260,7 +318,7 @@ def any_smart_loop_cylinder(min_var,max_var,deltas,initial_structure_obj,lateral
to_check = [random.choice(final_comb) + [float('inf')] for dummy in range(10000)]
with Pool(processes=max(cpu_count() - 1, 1)) as my_process:
res_pre = my_process.starmap(any_constraints_cylinder, to_check)
- for chk_res in res_pre :
+ for chk_res in res_pre:
if chk_res[0]:
current_weight = calc_weight_cylinder(chk_res[2])
if current_weight < min_weight:
@@ -273,7 +331,7 @@ def any_smart_loop_cylinder(min_var,max_var,deltas,initial_structure_obj,lateral
final_comb_inc_weight.append(val + [min_weight])
t1 = time.time()
- with Pool(processes = max(cpu_count()-1,1)) as my_process:
+ with Pool(processes=max(cpu_count() - 1, 1)) as my_process:
res_pre = my_process.starmap(any_constraints_cylinder, final_comb_inc_weight)
check_ok, check_not_ok = list(), list()
@@ -302,31 +360,32 @@ def any_smart_loop_cylinder(min_var,max_var,deltas,initial_structure_obj,lateral
# Checking ring stiffeners and frames
- #return new_struc_obj, new_calc_obj, fat_dict, True, main_fail
+ # return new_struc_obj, new_calc_obj, fat_dict, True, main_fail
return new_cylinder_obj, main_fail
-def any_smart_loop_geometric(min_var,max_var,deltas,initial_structure_obj,lateral_pressure, init_filter = float('inf'),
- side='p',const_chk=(True,True,True,True,True,True), fat_obj = None, fat_press = None,
- slamming_press = None, predefiened_stiffener_iter=None, processes = None, ml_algo = None):
+def any_smart_loop_geometric(min_var, max_var, deltas, initial_structure_obj, lateral_pressure,
+ init_filter=float('inf'),
+ side='p', const_chk=(True, True, True, True, True, True), fat_obj=None, fat_press=None,
+ slamming_press=None, predefiened_stiffener_iter=None, processes=None, ml_algo=None):
''' Searching multiple sections using the smart loop. '''
all_obj = []
idx = 0
for struc_obj, lat_press, fatigue_obj, fatigue_press, slam_press in zip(initial_structure_obj, lateral_pressure,
fat_obj, fat_press, slamming_press):
- #print(predefiened_stiffener_iter)
+ # print(predefiened_stiffener_iter)
if predefiened_stiffener_iter is not None:
- this_predefiened_objects = hlp.helper_read_section_file(predefiened_stiffener_iter, struc_obj)
+ this_predefiened_objects = hlp.helper_read_section_file(predefiened_stiffener_iter, struc_obj.Stiffener)
else:
this_predefiened_objects = None
- opt_obj = any_smart_loop(min_var = min_var,max_var = max_var,deltas = deltas,initial_structure_obj = struc_obj,
- lateral_pressure = lat_press, init_filter = init_filter, side=side,
+ opt_obj = any_smart_loop(min_var=min_var, max_var=max_var, deltas=deltas, initial_structure_obj=struc_obj,
+ lateral_pressure=lat_press, init_filter=init_filter, side=side,
const_chk=const_chk,
- fat_dict = None if fatigue_obj is None else fatigue_obj.get_fatigue_properties(),
- fat_press = None if fatigue_press is None else fatigue_press,
- slamming_press = 0 if slam_press is None else slam_press,
+ fat_dict=None if fatigue_obj is None else fatigue_obj.get_fatigue_properties(),
+ fat_press=None if fatigue_press is None else fatigue_press,
+ slamming_press=0 if slam_press is None else slam_press,
predefiened_stiffener_iter=this_predefiened_objects, processes=processes,
ml_algo=ml_algo)
@@ -335,24 +394,24 @@ def any_smart_loop_geometric(min_var,max_var,deltas,initial_structure_obj,latera
return all_obj
-def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_structure_obj=None,lateral_pressure=None,
- init_filter = float('inf'),side='p',const_chk=(True,True,True,True, True, True),
- pso_options=(100,0.5,0.5,0.5,100,1e-8,1e-8), fat_obj = None, fat_press = None,
- min_max_span = (2,6), tot_len = None, frame_distance = None,
- algorithm = 'anysmart', predefiened_stiffener_iter=None, reiterate = True,
- processes = None, slamming_press = None, load_pre = False, opt_girder_prop = None,
- ml_algo = None):
+def geometric_summary_search(min_var=None, max_var=None, deltas=None, initial_structure_obj=None, lateral_pressure=None,
+ init_filter=float('inf'), side='p', const_chk=(True, True, True, True, True, True),
+ pso_options=(100, 0.5, 0.5, 0.5, 100, 1e-8, 1e-8), fat_obj=None, fat_press=None,
+ min_max_span=(2, 6), tot_len=None, frame_distance=None,
+ algorithm='anysmart', predefiened_stiffener_iter=None, reiterate=True,
+ processes=None, slamming_press=None, load_pre=False, opt_girder_prop=None,
+ ml_algo=None):
'''Geometric optimization of all relevant sections. '''
# Checking the number of initial objects and adding if number of fraction is to be changed.
# print('Min/max span is', min_max_span)
found_max, found_min = False, False
- for frames in range(1,100):
+ for frames in range(1, 100):
frame_count = frames
- if tot_len/frames <= min_max_span[1] and found_min is False:
+ if tot_len / frames <= min_max_span[1] and found_min is False:
min_frame_count = frame_count - 1
found_min = True
- if tot_len/frames <= min_max_span[0] and found_max is False:
+ if tot_len / frames <= min_max_span[0] and found_max is False:
max_frame_count = frame_count - 1
found_max = True
if found_min and found_max:
@@ -368,13 +427,13 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
working_fatigue_press = {}
working_slamming = {}
- for no_of_fractions in range(min_frame_count+1, max_frame_count+1):
+ for no_of_fractions in range(min_frame_count + 1, max_frame_count + 1):
# Create fraction varables
- frac_var,min_frac,max_frac = [], [], []
+ frac_var, min_frac, max_frac = [], [], []
for var in range(no_of_fractions):
# Frame height is a interpolation between height at start and end.
- frac_var.append(1/no_of_fractions)
+ frac_var.append(1 / no_of_fractions)
working_objects[no_of_fractions] = list(initial_structure_obj)
working_lateral[no_of_fractions] = list(lateral_pressure)
working_fatigue[no_of_fractions] = list(fat_obj)
@@ -383,9 +442,9 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
similar_count = len(working_objects[no_of_fractions])
tick_tock = True
- while similar_count != no_of_fractions*2:
+ while similar_count != no_of_fractions * 2:
- if similar_count > no_of_fractions*2:
+ if similar_count > no_of_fractions * 2:
for var_dict in [working_objects, working_lateral, working_fatigue,
working_fatigue_press, working_slamming]:
if tick_tock:
@@ -403,25 +462,25 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
else:
if tick_tock:
lower_idx = 0
- upper_idx = int(len(working_objects[no_of_fractions])/2)
+ upper_idx = int(len(working_objects[no_of_fractions]) / 2)
tick_tock = False
else:
- lower_idx = int(len(working_objects[no_of_fractions])/2) - 1
+ lower_idx = int(len(working_objects[no_of_fractions]) / 2) - 1
upper_idx = -1
tick_tock = True
- #print(no_of_fractions, int(ceil(len(working_objects[no_of_fractions])/2)))
+ # print(no_of_fractions, int(ceil(len(working_objects[no_of_fractions])/2)))
- obj_start, obj_stop = copy.deepcopy(working_objects[no_of_fractions][lower_idx]),\
- copy.deepcopy(working_objects[no_of_fractions][upper_idx])
+ obj_start, obj_stop = copy.deepcopy(working_objects[no_of_fractions][lower_idx]), \
+ copy.deepcopy(working_objects[no_of_fractions][upper_idx])
fat_obj_start, fat_obj_stop = copy.deepcopy(working_fatigue[no_of_fractions][lower_idx]), \
- copy.deepcopy(working_fatigue[no_of_fractions][upper_idx])
+ copy.deepcopy(working_fatigue[no_of_fractions][upper_idx])
lat_start, lat_stop = working_lateral[no_of_fractions][lower_idx], \
- working_lateral[no_of_fractions][upper_idx]
+ working_lateral[no_of_fractions][upper_idx]
fat_press_start, fat_press_stop = working_fatigue_press[no_of_fractions][lower_idx], \
- working_fatigue_press[no_of_fractions][upper_idx]
+ working_fatigue_press[no_of_fractions][upper_idx]
slam_start, slam_stop = working_slamming[no_of_fractions][lower_idx], \
- working_slamming[no_of_fractions][upper_idx]
+ working_slamming[no_of_fractions][upper_idx]
# if no_of_fractions == 11:
# print('Tick/tock', tick_tock, 'lower/opper idx', lower_idx, upper_idx)
@@ -447,7 +506,7 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
# print('')
for no_of_fractions, struc_objects in working_objects.items():
for struc_obj in struc_objects:
- struc_obj.Plate.set_span(tot_len/no_of_fractions)
+ struc_obj.Plate.set_span(tot_len / no_of_fractions)
struc_obj.Stiffener.set_span(tot_len / no_of_fractions)
solution_found, iterations = False, 0
@@ -455,8 +514,8 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
while not solution_found:
iterations += 1
if iterations != 1:
- min_var[0:6] += deltas/2
- max_var[0:6] -= deltas/2
+ min_var[0:6] += deltas / 2
+ max_var[0:6] -= deltas / 2
if algorithm == 'anysmart':
if load_pre:
@@ -465,62 +524,61 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
opt_objects = pickle.load(file)[no_of_fractions][1]
else:
- opt_objects = any_smart_loop_geometric(min_var=min_var,max_var=max_var,deltas=deltas,
+ opt_objects = any_smart_loop_geometric(min_var=min_var, max_var=max_var, deltas=deltas,
initial_structure_obj=working_objects[no_of_fractions],
lateral_pressure=working_lateral[no_of_fractions],
- init_filter = init_filter,side=side,const_chk=const_chk,
- fat_obj = working_fatigue[no_of_fractions],
- slamming_press = working_slamming[no_of_fractions],
+ init_filter=init_filter, side=side, const_chk=const_chk,
+ fat_obj=working_fatigue[no_of_fractions],
+ slamming_press=working_slamming[no_of_fractions],
fat_press=working_fatigue_press[no_of_fractions],
- predefiened_stiffener_iter = predefiened_stiffener_iter,
+ predefiened_stiffener_iter=predefiened_stiffener_iter,
ml_algo=ml_algo)
-
# Finding weight of this solution.
tot_weight, frame_spacings, valid, width, weight_details = 0, [None for dummy in range(len(opt_objects))], \
- True, 10, {'frames': list(), 'objects': list(),
- 'scales': list()}
+ True, 10, {'frames': list(), 'objects': list(),
+ 'scales': list()}
- #print('Weight for', no_of_fractions)
+ # print('Weight for', no_of_fractions)
for count, opt in enumerate(opt_objects):
obj = opt[0]
if opt[3]:
- weigth_to_add = calc_weight((obj.Plate.get_s(),obj.Plate.get_pl_thk(),obj.Stiffener.get_web_h(),
+ weigth_to_add = calc_weight((obj.Plate.get_s(), obj.Plate.get_pl_thk(), obj.Stiffener.get_web_h(),
obj.Stiffener.get_web_thk(),
- obj.Stiffener.get_fl_w(),obj.Stiffener.get_fl_thk(),
- obj.Plate.span,width), prt=False)
+ obj.Stiffener.get_fl_w(), obj.Stiffener.get_fl_thk(),
+ obj.Plate.span, width), prt=False)
tot_weight += weigth_to_add
weight_details['objects'].append(weigth_to_add)
if frame_spacings[count // 2] is None:
frame_spacings[count // 2] = obj.Plate.get_s()
- #print('added normal weight', weigth_to_add)
+ # print('added normal weight', weigth_to_add)
else:
# In this case there are no applicable solutions found in the specified dimension ranges.
tot_weight += float('inf')
valid = False
if valid:
- #print(frame_distance)
- for frame in range(no_of_fractions-1):
+ # print(frame_distance)
+ for frame in range(no_of_fractions - 1):
frame_height = 2.5 if frame_distance is None else frame_distance['start_dist'] + \
- (frame_distance['stop_dist']-
+ (frame_distance['stop_dist'] -
frame_distance['start_dist']) * \
- ((frame+1)/no_of_fractions)
+ ((frame + 1) / no_of_fractions)
- #pl_area, stf_area = 0.018 * width, 0.25 * 0.015 * (width//frame_spacings[frame])
+ # pl_area, stf_area = 0.018 * width, 0.25 * 0.015 * (width//frame_spacings[frame])
this_x = (frame_spacings[frame], opt_girder_prop[0], opt_girder_prop[1], opt_girder_prop[2],
opt_girder_prop[3], opt_girder_prop[4], None, width)
- this_weight = sum(get_field_tot_area(this_x))* frame_height * 7850
+ this_weight = sum(get_field_tot_area(this_x)) * frame_height * 7850
scale_max, scale_min = opt_girder_prop[5], opt_girder_prop[6]
- this_scale = scale_min + (scale_max-scale_min) * (abs((max_frame_count-(count+1)/2))/
- (max_frame_count-min_frame_count))
- #print('Number of fractions', no_of_fractions, 'Scale', this_scale)
+ this_scale = scale_min + (scale_max - scale_min) * (abs((max_frame_count - (count + 1) / 2)) /
+ (max_frame_count - min_frame_count))
+ # print('Number of fractions', no_of_fractions, 'Scale', this_scale)
tot_weight += this_weight * this_scale
solution_found = True
- #print('added frame weight', this_weight * this_scale)
+ # print('added frame weight', this_weight * this_scale)
weight_details['frames'].append(this_weight * this_scale)
weight_details['scales'].append(this_scale)
elif iterations == 2:
@@ -535,6 +593,7 @@ def geometric_summary_search(min_var=None,max_var=None,deltas = None, initial_st
# print(val)
return results
+
def any_find_min_weight_var(var):
'''
Find the minimum weight of the inpu
@@ -545,10 +604,11 @@ def any_find_min_weight_var(var):
return min(map(calc_weight))
-def any_constraints_cylinder(x,obj: calc.CylinderAndCurvedPlate,init_weight, lat_press = None,side='p',
- chk=(True,True,True,True, True, True, True, False, False, False),
- fat_dict = None, fat_press = None, slamming_press = 0,fdwn = 1, fup = 0.5,
- ml_results = None):
+
+def any_constraints_cylinder(x, obj: calc.CylinderAndCurvedPlate, init_weight, lat_press=None, side='p',
+ chk=(True, True, True, True, True, True, True, False, False, False),
+ fat_dict=None, fat_press=None, slamming_press=0, fdwn=1, fup=0.5,
+ ml_results=None):
'''
Checking all constraints defined.
@@ -558,9 +618,9 @@ def any_constraints_cylinder(x,obj: calc.CylinderAndCurvedPlate,init_weight, lat
:return:
'''
- all_checks = [0,0,0,0,0,0,0,0]
- check_map = {'weight': 0, 'UF unstiffened': 1, 'Column stability': 2, 'UF longitudinal stiffeners':3,
- 'Stiffener check': 4, 'UF ring stiffeners':5, 'UF ring frame': 6, 'Check OK': 7}
+ all_checks = [0, 0, 0, 0, 0, 0, 0, 0]
+ check_map = {'weight': 0, 'UF unstiffened': 1, 'Column stability': 2, 'UF longitudinal stiffeners': 3,
+ 'Stiffener check': 4, 'UF ring stiffeners': 5, 'UF ring frame': 6, 'Check OK': 7}
calc_obj = create_new_cylinder_obj(obj, x)
@@ -569,13 +629,13 @@ def any_constraints_cylinder(x,obj: calc.CylinderAndCurvedPlate,init_weight, lat
if init_weight != False:
this_weight = calc_weight_cylinder(x)
if this_weight > init_weight:
- results = calc_obj.get_utilization_factors(optimizing=optimizing, empty_result_dict = True)
+ results = calc_obj.get_utilization_factors(optimizing=optimizing, empty_result_dict=True)
results['Weight'] = this_weight
all_checks[0] += 1
return False, 'Weight filter', x, all_checks, calc_obj
if chk[0]:
- results = calc_obj.get_utilization_factors(optimizing = optimizing)
+ results = calc_obj.get_utilization_factors(optimizing=optimizing)
if results[0]:
all_checks[check_map[results[1]]] += 1
return True, results[1], x, all_checks, calc_obj
@@ -584,59 +644,357 @@ def any_constraints_cylinder(x,obj: calc.CylinderAndCurvedPlate,init_weight, lat
return False, results[1], x, all_checks, calc_obj
-def any_constraints_all(x,obj,lat_press,init_weight,side='p',chk=(True,True,True,True, True, True, True, False,
- False, False),
- fat_dict = None, fat_press = None, slamming_press = 0, PULSrun: calc.PULSpanel = None,
- print_result = False, fdwn = 1, fup = 0.5, ml_results = None, random_result_return = False):
+def _get_ml_input_for_optimization(calc_object, lat_press):
+ """
+ Return the correct ML input row for an optimization candidate.
+
+ UP models use the plate object input.
+ SP models use the stiffener object input, because SP numeric/regression models
+ were trained with stiffener geometry included.
+ """
+ all_structure = calc_object[0] if isinstance(calc_object, (list, tuple)) else calc_object
+
+ if all_structure.Plate.get_puls_sp_or_up() == 'UP':
+ return all_structure.Plate.get_buckling_ml_input(lat_press, alone=False)
+
+ if all_structure.Stiffener is not None:
+ return all_structure.Stiffener.get_buckling_ml_input(lat_press, alone=False)
+
+ # Defensive fallback. SP without stiffener should normally not occur.
+ return all_structure.Plate.get_buckling_ml_input(lat_press, alone=False)
+
+
+def _get_numeric_pipeline_prefix(calc_object):
+ """Return numeric ML pipeline prefix for this optimization candidate."""
+ all_structure = calc_object[0] if isinstance(calc_object, (list, tuple)) else calc_object
+
+ sp_or_up = all_structure.Plate.get_puls_sp_or_up()
+
+ if sp_or_up == 'UP':
+ boundary = all_structure.Plate.get_puls_boundary()
+ return 'num UP int' if boundary == 'Int' else 'num UP GLGT'
+
+ if all_structure.Stiffener is not None:
+ boundary = all_structure.Stiffener.get_puls_boundary()
+ else:
+ boundary = all_structure.Plate.get_puls_boundary()
+
+ return 'num SP int' if boundary == 'Int' else 'num SP GLGT'
+
+
+def _predict_numeric_uf_group(ml_algo, input_rows, prefix, mat_fac):
+ """
+ Batch-predict numeric UF for optimization.
+
+ Returns an array with columns:
+ [buckling_uf_material_factored, ultimate_uf_material_factored, valid_prediction]
+
+ The numeric regressor predicts UF at material factor = 1.0.
+ For optimization checks, the values are converted using:
+ UF = predicted_UF * mat_fac
+ and then compared against 1.0.
+ """
+ result = np.full((len(input_rows), 3), np.inf, dtype=float)
+
+ if len(input_rows) == 0:
+ return result
+
+ result[:, 2] = 0.0
+
+ required_keys = [
+ f'{prefix} validity predictor',
+ f'{prefix} validity xscaler',
+ f'{prefix} UF reg predictor',
+ f'{prefix} UF reg xscaler',
+ f'{prefix} UF reg yscaler',
+ ]
+
+ for key in required_keys:
+ if ml_algo is None or key not in ml_algo or ml_algo[key] is None:
+ return result
+
+ try:
+ x_valid = ml_algo[f'{prefix} validity xscaler'].transform(input_rows)
+ valid_pred = ml_algo[f'{prefix} validity predictor'].predict(x_valid)
+ valid_pred = np.asarray(valid_pred, dtype=float).ravel()
+ result[:, 2] = valid_pred
+
+ valid_idx = np.where(valid_pred == 1)[0]
+ if len(valid_idx) == 0:
+ return result
+
+ valid_inputs = [input_rows[idx] for idx in valid_idx]
+ x_reg = ml_algo[f'{prefix} UF reg xscaler'].transform(valid_inputs)
+ y_scaled = ml_algo[f'{prefix} UF reg predictor'].predict(x_reg)
+ y_raw = ml_algo[f'{prefix} UF reg yscaler'].inverse_transform(y_scaled)
+
+ # Material factor correction. The regressor output is for mat. factor = 1.0.
+ y_factored = y_raw * float(mat_fac)
+
+ result[valid_idx, 0] = y_factored[:, 0]
+ result[valid_idx, 1] = y_factored[:, 1]
+
+ return result
+
+ except Exception:
+ # If numeric prediction fails, mark all as invalid so optimization rejects them.
+ return result
+
+
+def _semi_analytical_float(value, default=0.0):
+ try:
+ if value is None:
+ return default
+ return float(value)
+ except Exception:
+ return default
+
+
+def _semi_analytical_stiffener_type(value):
+ return {
+ 'T': 'T-bar',
+ 'T-bar': 'T-bar',
+ 'L': 'Angle',
+ 'Angle': 'Angle',
+ 'L-bulb': 'L-bulb',
+ 'FB': 'Flatbar',
+ 'F': 'Flatbar',
+ 'Flatbar': 'Flatbar',
+ }.get(value, value)
+
+
+def _semi_analytical_stiffener_boundary(value):
+ return {
+ 'C': 'Cont',
+ 'Cont': 'Cont',
+ 'Continuous': 'Cont',
+ 'S': 'Sniped',
+ 'Sniped': 'Sniped',
+ }.get(value, value)
+
+
+def _semi_analytical_in_plane_support(value):
+ return {
+ 'Int': 'Integrated',
+ 'Integrated': 'Integrated',
+ 'GL': 'Girder - long',
+ 'Girder - long': 'Girder - long',
+ 'GT': 'Girder - trans',
+ 'Girder - trans': 'Girder - trans',
+ }.get(value, value)
+
+
+def _puls_selected_method(value):
+ text = str(value).strip().lower()
+ if text in ('1', 'buckling'):
+ return 'buckling'
+ if text in ('2', 'ultimate'):
+ return 'ultimate'
+ return text
+
+
+def _get_semi_analytical_input_for_optimization(calc_object, lat_press):
+ """
+ Build the reduced SemiAnalytical input from an optimization candidate.
+
+ The replacement currently covers regular stiffened SP panels. It follows
+ the same candidate object and pressure convention as the ML-Numeric SP
+ path: geometry/stresses are in the candidate object, and lateral pressure
+ is converted from kPa to MPa for the PULS-style input surface.
+ """
+ all_structure = calc_object[0] if isinstance(calc_object, (list, tuple)) else calc_object
+
+ if all_structure.Plate.get_puls_sp_or_up() != 'SP':
+ return None
+ if all_structure.Stiffener is None:
+ return None
+
+ stiffener = all_structure.Stiffener
+ puls_boundary = stiffener.get_puls_boundary()
+ sig_x1 = stiffener.sigma_x1
+ sig_x2 = stiffener.sigma_x2
+ if sig_x1 * sig_x2 >= 0:
+ sigxd = sig_x1 if abs(sig_x1) > abs(sig_x2) else sig_x2
+ else:
+ sigxd = max(sig_x1, sig_x2)
+
+ pressure_mpa = _semi_analytical_float(lat_press) / 1000.0
+ return semi_analytical.S3PanelInput(
+ length=stiffener.span * 1000.0,
+ stiffener_spacing=stiffener.spacing,
+ plate_thickness=stiffener.t,
+ stiffener_type=_semi_analytical_stiffener_type(stiffener.get_stiffener_type()),
+ stiffener_boundary=_semi_analytical_stiffener_boundary(stiffener.get_puls_stf_end()),
+ stiffener_height=stiffener.hw,
+ web_thickness=stiffener.tw,
+ flange_width=stiffener.b,
+ flange_thickness=stiffener.tf,
+ yield_stress_plate=stiffener.mat_yield / 1e6,
+ yield_stress_stiffener=stiffener.mat_yield / 1e6,
+ axial_stress=0.0 if puls_boundary == 'GT' else sigxd,
+ transverse_stress_1=0.0 if puls_boundary == 'GL' else stiffener.sigma_y1,
+ transverse_stress_2=0.0 if puls_boundary == 'GL' else stiffener.sigma_y2,
+ shear_stress=stiffener.tau_xy,
+ pressure=pressure_mpa,
+ in_plane_support=_semi_analytical_in_plane_support(puls_boundary),
+ elastic_modulus=210000.0,
+ poisson_ratio=0.3,
+ )
+
+
+def _predict_semi_analytical_uf(calc_object, lat_press, default_acceptance=0.87):
+ """
+ Return [buckling_uf, ultimate_uf, valid_prediction, acceptance_limit].
+ """
+ result = np.array([float('inf'), float('inf'), 0.0, float(default_acceptance)], dtype=float)
+
+ try:
+ if hasattr(semi_analytical, 'predict_anystructure_uf_with_acceptance'):
+ return semi_analytical.predict_anystructure_uf_with_acceptance(
+ calc_object,
+ lat_press,
+ default_acceptance=default_acceptance,
+ )
+ if hasattr(semi_analytical, 'predict_anystructure_uf'):
+ result[0:3] = semi_analytical.predict_anystructure_uf(calc_object, lat_press)
+ return result
+
+ panel = _get_semi_analytical_input_for_optimization(calc_object, lat_press)
+ if panel is None:
+ return result
+
+ solved = semi_analytical.solve_s3_panel(panel)
+ if (
+ solved.valid
+ and solved.buckling_usage_factor is not None
+ and solved.ultimate_usage_factor is not None
+ ):
+ result[0] = float(solved.buckling_usage_factor)
+ result[1] = float(solved.ultimate_usage_factor)
+ result[2] = 1.0
+ except Exception:
+ pass
+
+ return result
+
+
+def any_constraints_all(x, obj, lat_press, init_weight, side='p', chk=(True, True, True, True, True, True, True, False,
+ False, False),
+ fat_dict=None, fat_press=None, slamming_press=0, PULSrun: calc.PULSpanel = None,
+ print_result=False, fdwn=1, fup=0.5, ml_results=None, random_result_return=False):
'''
Checking all constraints defined.
- iter_var = ((item,init_stuc_obj,lat_press,init_filter_weight,side,chk,fat_dict,fat_press,slamming_press, PULSrun)
- for item in iterable_all)
- :param x:
- :return:
+ ml_results is used for both ML pipelines:
+ chk[7] SemiAnalytical replacement:
+ ml_results[0] = buckling UF
+ ml_results[1] = ultimate UF
+ ml_results[2] = valid prediction flag, 1 = valid, 0 = invalid/unsupported
+ ml_results[3] = SemiAnalytical acceptance limit
+ accepted if selected UF / acceptance < 1.0
+
+ chk[8] ML-CL:
+ deactivated; ML-Numeric has replaced this classifier path
+
+ chk[9] ML-Numeric:
+ ml_results[0] = material-factored buckling UF
+ ml_results[1] = material-factored ultimate UF
+ ml_results[2] = valid numeric prediction flag, 1 = valid, 0 = invalid/NaN
+ accepted if selected UF <= 1.0
'''
if random_result_return:
# Skip all calculations
- if random.choice([True,False,False,False,False,False,False]):
- return True, 'Check OK', x, [0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5]
+ if random.choice([True, False, False, False, False, False, False]):
+ return True, 'Check OK', x, [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
else:
- return False, 'Random result', x, [1.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5]
- all_checks = [0,0,0,0,0,0,0,0,0,0,0]
+ return False, 'Random result', x, [1.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
+
+ all_checks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
print_result = False
- calc_object_stf = None if obj.Stiffener is None else create_new_calc_obj(obj.Stiffener, x, fat_dict, fdwn = fdwn, fup = fup)
+
+ calc_object_stf = None if obj.Stiffener is None else create_new_calc_obj(obj.Stiffener, x, fat_dict, fdwn=fdwn,
+ fup=fup)
calc_object_pl = create_new_calc_obj(obj.Plate, x, fat_dict, fdwn=fdwn, fup=fup)
calc_object = [calc.AllStructure(Plate=calc_object_pl[0],
- Stiffener=None if obj.Stiffener is None else calc_object_stf[0], Girder=None,
+ Stiffener=None if obj.Stiffener is None else calc_object_stf[0],
+ Girder=None,
main_dict=obj.get_main_properties()['main dict']), calc_object_pl[1]]
calc_object[0].lat_press = lat_press
- # PULS buckling check
- if chk[7] and PULSrun is not None:
- x_id = x_to_string(x)
- if calc_object[0].Plate.get_puls_method() == 'buckling':
- puls_uf = PULSrun.get_puls_line_results(x_id)["Buckling strength"]["Actual usage Factor"][0]
- elif calc_object[0].Plate.get_puls_method() == 'ultimate':
- puls_uf = PULSrun.get_puls_line_results(x_id)["Ultimate capacity"]["Actual usage Factor"][0]
+ # SemiAnalytical buckling check
+ if chk[7]:
+ if ml_results is not None:
+ try:
+ valid_prediction = int(ml_results[2]) if len(ml_results) > 2 else 0
+ puls_acceptance = float(ml_results[3]) if len(ml_results) > 3 else 0.87
+
+ if valid_prediction != 1:
+ return False, 'SemiAnalytical', x, all_checks
+
+ puls_method = _puls_selected_method(calc_object[0].Plate.get_puls_method())
+ if puls_method == 'buckling':
+ puls_uf = float(ml_results[0])
+ elif puls_method == 'ultimate':
+ puls_uf = float(ml_results[1])
+ else:
+ puls_uf = None
+ except Exception:
+ return False, 'SemiAnalytical', x, all_checks
+ elif PULSrun is not None:
+ x_id = x_to_string(x)
+ puls_acceptance = PULSrun.puls_acceptance
+ puls_method = _puls_selected_method(calc_object[0].Plate.get_puls_method())
+ if puls_method == 'buckling':
+ puls_uf = PULSrun.get_puls_line_results(x_id)["Buckling strength"]["Actual usage Factor"][0]
+ elif puls_method == 'ultimate':
+ puls_uf = PULSrun.get_puls_line_results(x_id)["Ultimate capacity"]["Actual usage Factor"][0]
+ else:
+ puls_uf = None
+ else:
+ return False, 'SemiAnalytical', x, all_checks
+
if type(puls_uf) == str or puls_uf is None:
- return False, 'PULS', x, all_checks
- all_checks[8] = puls_uf/PULSrun.puls_acceptance
- if puls_uf/PULSrun.puls_acceptance >= 1:
+ return False, 'SemiAnalytical', x, all_checks
+
+ all_checks[8] = puls_uf / puls_acceptance
+ if puls_uf / puls_acceptance >= 1:
if print_result:
- print('PULS', calc_object[0].get_one_line_string(), False)
- return False, 'PULS', x, all_checks
+ print('SemiAnalytical', calc_object[0].get_one_line_string(), False)
+ return False, 'SemiAnalytical', x, all_checks
- # Buckling ml-cl
if chk[8]:
- if any([calc_object[0].Plate.get_puls_method() == 'buckling' and ml_results[0] != 9,
- calc_object[0].Plate.get_puls_method() == 'ultimate' and ml_results[1] != 9]):
- if print_result:
- print('Buckling ML-CL', calc_object[0].Stiffener.get_one_line_string(), False)
- return False, 'Buckling ML-CL', x, all_checks
+ return False, 'Buckling ML-CL deactivated', x, all_checks
- # Buckling ml-reg
+ # Buckling ML-Numeric
if chk[9]:
- pass
+ numeric_ok = False
+ numeric_uf = float('inf')
+
+ try:
+ valid_prediction = int(ml_results[2]) if ml_results is not None and len(ml_results) > 2 else 0
+
+ if valid_prediction == 1:
+ puls_method = _puls_selected_method(calc_object[0].Plate.get_puls_method())
+ if puls_method == 'buckling':
+ numeric_uf = float(ml_results[0])
+ elif puls_method == 'ultimate':
+ numeric_uf = float(ml_results[1])
+ else:
+ numeric_uf = float('inf')
+
+ numeric_ok = numeric_uf <= 1.0
+ except Exception:
+ numeric_ok = False
+ numeric_uf = float('inf')
+
+ all_checks[10] = numeric_uf
+
+ if not numeric_ok:
+ if print_result:
+ stf_text = calc_object[0].Stiffener.get_one_line_string() if calc_object[
+ 0].Stiffener is not None else 'No stiffener'
+ print('Buckling ML-Numeric', stf_text, False)
+ return False, 'Buckling ML-Numeric', x, all_checks
this_weight = calc_weight(x)
@@ -644,120 +1002,97 @@ def any_constraints_all(x,obj,lat_press,init_weight,side='p',chk=(True,True,True
weigt_frac = this_weight / init_weight
if print_result:
pass
- # print('Weights', calc_weight(x), ' > ', init_weight,
- # calc_object[0].get_one_line_string(), init_weight, False)
all_checks[0] = weigt_frac
return False, 'Weight filter', x, all_checks
# Section modulus
if chk[0] and calc_object[0].Stiffener is not None:
section_modulus = min(calc_object[0].Stiffener.get_section_modulus())
- min_section_modulus = calc_object[0].Stiffener.get_dnv_min_section_modulus(lat_press*1000)
+ min_section_modulus = calc_object[0].Stiffener.get_dnv_min_section_modulus(lat_press * 1000)
section_frac = section_modulus / min_section_modulus
- #print(section_modulus, min_section_modulus, section_frac, lat_press)
all_checks[1] = section_frac
- if not section_modulus > min_section_modulus :
+ if not section_modulus > min_section_modulus:
if print_result:
- print('Section modulus',calc_object[0].get_one_line_string(), False)
+ print('Section modulus', calc_object[0].get_one_line_string(), False)
return False, 'Section modulus', x, all_checks
-
# Local stiffener buckling
if chk[6] and calc_object[0].Stiffener is not None:
buckling_local = calc_object[0].local_buckling(optimizing=True)
check = all([buckling_local['Stiffener'][0] < calc_object[0].Stiffener.hw,
buckling_local['Stiffener'][1] < calc_object[0].Stiffener.b])
all_checks[2] = max([0 if buckling_local['Stiffener'][0] == 0 else
- calc_object[0].Stiffener.hw/buckling_local['Stiffener'][0],
- 0 if buckling_local['Stiffener'][1] == 0 else
- calc_object[0].Stiffener.b/buckling_local['Stiffener'][1]])
+ calc_object[0].Stiffener.hw / buckling_local['Stiffener'][0],
+ 0 if buckling_local['Stiffener'][1] == 0 else
+ calc_object[0].Stiffener.b / buckling_local['Stiffener'][1]])
if not check:
if print_result:
- print('Local stiffener buckling',calc_object[0].get_one_line_string(), False)
+ print('Local stiffener buckling', calc_object[0].get_one_line_string(), False)
return False, 'Local stiffener buckling', x, all_checks
# Buckling
if chk[3]:
- '''
- {'Plate': {'Plate buckling': up_buckling}, 'Stiffener': {'Overpressure plate side': stf_buckling_pl_side,
- 'Overpressure stiffener side': stf_buckling_stf_side,
- 'Resistance between stiffeners': stf_plate_resistance,
- 'Shear capacity': stf_shear_capacity},
- 'Girder': {'Overpressure plate side': girder_buckling_pl_side,
- 'Overpressure girder side': girder_buckling_girder_side,
- 'Shear capacity': girder_shear_capacity},
- 'Local buckling': local_buckling}
- '''
buckling_results = calc_object[0].plate_buckling(optimizing=True)
- res = [buckling_results['Plate']['Plate buckling'],]
+ res = [buckling_results['Plate']['Plate buckling'], ]
for val in buckling_results['Stiffener'].values():
res.append(val)
- # for val in buckling_results['Girder'].values():
- # res.append(val)
buckling_results = res
- # print(buckling_results)
all_checks[3] = max(buckling_results)
- if not all([uf<=1 for uf in buckling_results]):
+ if not all([uf <= 1 for uf in buckling_results]):
if print_result:
- print('Buckling',calc_object[0].get_one_line_string(), False)
+ print('Buckling', calc_object[0].get_one_line_string(), False)
return False, 'Buckling', x, all_checks
-
# Minimum plate thickness
if chk[1]:
act_pl_thk = calc_object[0].Plate.get_pl_thk()
- min_pl_thk = calc_object[0].Plate.get_dnv_min_thickness(lat_press*1000)/1000
+ min_pl_thk = calc_object[0].Plate.get_dnv_min_thickness(lat_press * 1000) / 1000
plate_frac = min_pl_thk / act_pl_thk
all_checks[4] = plate_frac
if not act_pl_thk > min_pl_thk:
if print_result:
- print('Minimum plate thickeness',calc_object[0].get_one_line_string(), False)
+ print('Minimum plate thickeness', calc_object[0].get_one_line_string(), False)
return False, 'Minimum plate thickness', x, all_checks
# Shear area
if chk[2]:
pass
- # calc_shear_area = calc_object[0].Stiffener.get_shear_area()
- # min_shear_area = calc_object[0].Stiffener.get_minimum_shear_area(lat_press)
- # shear_frac = min_shear_area / calc_shear_area
- # all_checks[5] = shear_frac
- # if not calc_shear_area > min_shear_area:
- # if print_result:
- # print('Shear area',calc_object[0].Stiffener.get_one_line_string(), False)
- # return False, 'Shear area', x, all_checks
# Fatigue
if chk[4] and fat_dict is not None and fat_press is not None:
-
fatigue_uf = calc_object[1].get_total_damage(ext_press=fat_press[0],
- int_press=fat_press[1])*calc_object[1].get_dff()
+ int_press=fat_press[1]) * calc_object[1].get_dff()
all_checks[6] = fatigue_uf
if fatigue_uf > 1:
if print_result:
- print('Fatigue',calc_object[0].Stiffener.get_one_line_string(), False)
+ print('Fatigue', calc_object[0].Stiffener.get_one_line_string(), False)
return False, 'Fatigue', x, all_checks
# Slamming
-
if chk[5] and slamming_press != 0 and calc_object[0].Stiffener is not None:
slam_check = calc_object[0].Stiffener.check_all_slamming(slamming_press)
all_checks[7] = slam_check[1]
if slam_check[0] is False:
if print_result:
- print('Slamming',calc_object[0].Stiffener.get_one_line_string(), False)
+ print('Slamming', calc_object[0].Stiffener.get_one_line_string(), False)
return False, 'Slamming', x, all_checks
if print_result:
- print('OK Section', calc_object[0].Stiffener.get_one_line_string(), True)
+ stf_text = calc_object[0].Stiffener.get_one_line_string() if calc_object[
+ 0].Stiffener is not None else 'No stiffener'
+ print('OK Section', stf_text, True)
return True, 'Check OK', x, all_checks
+
def constraint_geometric(fractions, *args):
return sum(fractions) == 1
-def pso_constraint_geometric(x,*args):
+
+def pso_constraint_geometric(x, *args):
''' The sum of the fractions must be 1.'''
- return 1-sum(x)
+ return 1 - sum(x)
+
def create_new_cylinder_obj(init_obj, x_new):
'''
@@ -775,20 +1110,20 @@ def create_new_cylinder_obj(init_obj, x_new):
t1, r1, s2, hw2, tw2, b2, tf2 = x2
'''
- x_old = shell_obj.thk, shell_obj.radius, \
- init_obj.panel_spacing if long_obj is None else long_obj.s/1000, \
- 0 if long_obj is None else long_obj.hw/1000, \
- 0 if long_obj is None else long_obj.tw/1000,\
- 0 if long_obj is None else long_obj.b/1000,\
- 0 if long_obj is None else long_obj.tf/1000,
+ x_old = shell_obj.thk, shell_obj.radius, \
+ init_obj.panel_spacing if long_obj is None else long_obj.s / 1000, \
+ 0 if long_obj is None else long_obj.hw / 1000, \
+ 0 if long_obj is None else long_obj.tw / 1000, \
+ 0 if long_obj is None else long_obj.b / 1000, \
+ 0 if long_obj is None else long_obj.tf / 1000,
x_new_stress_scaling = x_new[0][0] if not np.isnan(x_new[0][0]) else shell_obj.thk, \
- x_new[0][1] if not np.isnan(x_new[0][1]) else shell_obj.radius,\
- x_new[0][5] if long_obj is None else x_new[1][0], \
- 0 if long_obj is None else x_new[1][2], \
- 0 if long_obj is None else x_new[1][3],\
- 0 if long_obj is None else x_new[1][4],\
- 0 if long_obj is None else x_new[1][5]
+ x_new[0][1] if not np.isnan(x_new[0][1]) else shell_obj.radius, \
+ x_new[0][5] if long_obj is None else x_new[1][0], \
+ 0 if long_obj is None else x_new[1][2], \
+ 0 if long_obj is None else x_new[1][3], \
+ 0 if long_obj is None else x_new[1][4], \
+ 0 if long_obj is None else x_new[1][5]
new_stresses = stress_scaling_cylinder(x_old, x_new_stress_scaling, stress_press)
new_obj = copy.deepcopy(init_obj)
@@ -798,17 +1133,18 @@ def create_new_cylinder_obj(init_obj, x_new):
if long_obj is None:
new_obj.panel_spacing = x_new[0][5]
else:
- new_obj.LongStfObj.s = x_new[1][0]*1000
- new_obj.LongStfObj.hw = x_new[1][2]*1000
- new_obj.LongStfObj.tw = x_new[1][3]*1000
- new_obj.LongStfObj.b = x_new[1][4]*1000
- new_obj.LongStfObj.tf = x_new[1][5]*1000
+ new_obj.LongStfObj.s = x_new[1][0] * 1000
+ new_obj.LongStfObj.hw = x_new[1][2] * 1000
+ new_obj.LongStfObj.tw = x_new[1][3] * 1000
+ new_obj.LongStfObj.b = x_new[1][4] * 1000
+ new_obj.LongStfObj.tf = x_new[1][5] * 1000
- #new_obj.LongStfObj.stiffener_type = x_new[1][7] # TODO should be 8
+ # new_obj.LongStfObj.stiffener_type = x_new[1][7] # TODO should be 8
return new_obj
-def create_new_calc_obj(init_obj,x, fat_dict=None, fdwn = 1, fup = 0.5):
+
+def create_new_calc_obj(init_obj, x, fat_dict=None, fdwn=1, fup=0.5):
'''
Returns a new calculation object to be used in optimization
:param init_obj:
@@ -822,43 +1158,44 @@ def create_new_calc_obj(init_obj,x, fat_dict=None, fdwn = 1, fup = 0.5):
stiffener = init_obj.Stiffener
girder = init_obj.Girder
x_old = (plate.get_s(), plate.get_pl_thk(), stiffener.get_web_h(), stiffener.get_web_thk(),
- stiffener.get_fl_w(),
- stiffener.get_fl_thk(), plate.span, stiffener.girder_lg if girder is None else
- girder.girder_lg, stiffener.stiffener_type)
+ stiffener.get_fl_w(),
+ stiffener.get_fl_thk(), plate.span, stiffener.girder_lg if girder is None else
+ girder.girder_lg, stiffener.stiffener_type)
else:
x_old = init_obj.Plate.get_tuple()
-
- sigma_y1_new = stress_scaling(init_obj.Plate.sigma_y1, init_obj.Plate.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- sigma_y2_new = stress_scaling(init_obj.Plate.sigma_y2, init_obj.Plate.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- tau_xy_new = stress_scaling(init_obj.Plate.tau_xy, init_obj.Plate.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
+
+ sigma_y1_new = stress_scaling(init_obj.Plate.sigma_y1, init_obj.Plate.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ sigma_y2_new = stress_scaling(init_obj.Plate.sigma_y2, init_obj.Plate.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ tau_xy_new = stress_scaling(init_obj.Plate.tau_xy, init_obj.Plate.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
sigma_x1_new = stress_scaling_area(init_obj.Plate.sigma_x1,
- sum(get_field_tot_area(x_old)),
- sum(get_field_tot_area(x)), fdwn = fdwn, fup = fup)
+ sum(get_field_tot_area(x_old)),
+ sum(get_field_tot_area(x)), fdwn=fdwn, fup=fup)
sigma_x2_new = stress_scaling_area(init_obj.Plate.sigma_x2,
- sum(get_field_tot_area(x_old)),
- sum(get_field_tot_area(x)), fdwn = fdwn, fup = fup)
+ sum(get_field_tot_area(x_old)),
+ sum(get_field_tot_area(x)), fdwn=fdwn, fup=fup)
try:
stf_type = x[8]
except IndexError:
stf_type = init_obj.plate.get_stiffener_type()
-
- main_dict = {'mat_yield': [init_obj.Plate.get_fy(), 'Pa'],'mat_factor': [init_obj.Plate.mat_factor, ''],
+
+ main_dict = {'mat_yield': [init_obj.Plate.get_fy(), 'Pa'], 'mat_factor': [init_obj.Plate.mat_factor, ''],
'span': [init_obj.Plate.span, 'm'],
- 'spacing': [x[0], 'm'],'plate_thk': [x[1], 'm'],'stf_web_height':[ x[2], 'm'],
- 'stf_web_thk': [x[3], 'm'],'stf_flange_width': [x[4], 'm'],
- 'stf_flange_thk': [x[5], 'm'],'structure_type': [init_obj.Plate.get_structure_type(), ''],
- 'stf_type': [stf_type, ''],'sigma_y1': [sigma_y1_new, 'MPa'],
- 'sigma_y2': [sigma_y2_new, 'MPa'],'sigma_x1': [sigma_x1_new, 'MPa'],'sigma_x2': [sigma_x2_new, 'MPa'],
- 'tau_xy': [tau_xy_new, 'MPa'],'plate_kpp': [init_obj.Plate.get_kpp(), ''],
- 'stf_kps': [init_obj.Plate.get_kps(), ''],'stf_km1': [init_obj.Plate.get_km1(), ''],
- 'stf_km2': [init_obj.Plate.get_km2(), ''],'stf_km3': [init_obj.Plate.get_km3(), ''],
- 'structure_types':[init_obj.Plate.get_structure_types(), ''],
+ 'spacing': [x[0], 'm'], 'plate_thk': [x[1], 'm'], 'stf_web_height': [x[2], 'm'],
+ 'stf_web_thk': [x[3], 'm'], 'stf_flange_width': [x[4], 'm'],
+ 'stf_flange_thk': [x[5], 'm'], 'structure_type': [init_obj.Plate.get_structure_type(), ''],
+ 'stf_type': [stf_type, ''], 'sigma_y1': [sigma_y1_new, 'MPa'],
+ 'sigma_y2': [sigma_y2_new, 'MPa'], 'sigma_x1': [sigma_x1_new, 'MPa'],
+ 'sigma_x2': [sigma_x2_new, 'MPa'],
+ 'tau_xy': [tau_xy_new, 'MPa'], 'plate_kpp': [init_obj.Plate.get_kpp(), ''],
+ 'stf_kps': [init_obj.Plate.get_kps(), ''], 'stf_km1': [init_obj.Plate.get_km1(), ''],
+ 'stf_km2': [init_obj.Plate.get_km2(), ''], 'stf_km3': [init_obj.Plate.get_km3(), ''],
+ 'structure_types': [init_obj.Plate.get_structure_types(), ''],
'zstar_optimization': [init_obj.Plate.get_z_opt(), ''],
- 'puls buckling method':[init_obj.Plate.get_puls_method(),''],
- 'puls boundary':[init_obj.Plate.get_puls_boundary(),''],
- 'puls stiffener end':[init_obj.Plate.get_puls_stf_end(),''],
- 'puls sp or up':[init_obj.Plate.get_puls_sp_or_up(),''],
- 'puls up boundary':[init_obj.Plate.get_puls_up_boundary(),''],
+ 'puls buckling method': [init_obj.Plate.get_puls_method(), ''],
+ 'puls boundary': [init_obj.Plate.get_puls_boundary(), ''],
+ 'puls stiffener end': [init_obj.Plate.get_puls_stf_end(), ''],
+ 'puls sp or up': [init_obj.Plate.get_puls_sp_or_up(), ''],
+ 'puls up boundary': [init_obj.Plate.get_puls_up_boundary(), ''],
'panel or shell': [init_obj.Plate.panel_or_shell, '']}
all_dict = init_obj.get_main_properties()
all_dict['Plate'] = main_dict
@@ -867,78 +1204,80 @@ def create_new_calc_obj(init_obj,x, fat_dict=None, fdwn = 1, fup = 0.5):
if fat_dict == None:
return calc.AllStructure(Plate=None if all_dict['Plate'] is None
- else calc.CalcScantlings(all_dict['Plate']),
- Stiffener=None if all_dict['Stiffener'] is None
- else calc.CalcScantlings(all_dict['Stiffener']),
- Girder=None if all_dict['Girder'] is None
- else calc.CalcScantlings(all_dict['Girder']),
- main_dict=all_dict['main dict']), None
+ else calc.CalcScantlings(all_dict['Plate']),
+ Stiffener=None if all_dict['Stiffener'] is None
+ else calc.CalcScantlings(all_dict['Stiffener']),
+ Girder=None if all_dict['Girder'] is None
+ else calc.CalcScantlings(all_dict['Girder']),
+ main_dict=all_dict['main dict']), None
else:
return calc.AllStructure(Plate=None if all_dict['Plate'] is None
- else calc.CalcScantlings(all_dict['Plate']),
- Stiffener=None if all_dict['Stiffener'] is None
- else calc.CalcScantlings(all_dict['Stiffener']),
- Girder=None if all_dict['Girder'] is None
- else calc.CalcScantlings(all_dict['Girder']),
- main_dict=all_dict['main dict']), \
- calc.CalcFatigue(main_dict, fat_dict)
+ else calc.CalcScantlings(all_dict['Plate']),
+ Stiffener=None if all_dict['Stiffener'] is None
+ else calc.CalcScantlings(all_dict['Stiffener']),
+ Girder=None if all_dict['Girder'] is None
+ else calc.CalcScantlings(all_dict['Girder']),
+ main_dict=all_dict['main dict']), \
+ calc.CalcFatigue(main_dict, fat_dict)
else:
- x_old = [init_obj.get_s(), init_obj.get_pl_thk(), init_obj.get_web_h() , init_obj.get_web_thk(),
- init_obj.get_fl_w(),init_obj.get_fl_thk(), init_obj.span, init_obj.girder_lg]
+ x_old = [init_obj.get_s(), init_obj.get_pl_thk(), init_obj.get_web_h(), init_obj.get_web_thk(),
+ init_obj.get_fl_w(), init_obj.get_fl_thk(), init_obj.span, init_obj.girder_lg]
- sigma_y1_new = stress_scaling(init_obj.sigma_y1, init_obj.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- sigma_y2_new = stress_scaling(init_obj.sigma_y2, init_obj.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- tau_xy_new = stress_scaling(init_obj.tau_xy, init_obj.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
+ sigma_y1_new = stress_scaling(init_obj.sigma_y1, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ sigma_y2_new = stress_scaling(init_obj.sigma_y2, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ tau_xy_new = stress_scaling(init_obj.tau_xy, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
sigma_x1_new = stress_scaling_area(init_obj.sigma_x1,
- sum(get_field_tot_area(x_old)),
- sum(get_field_tot_area(x)), fdwn = fdwn, fup = fup)
+ sum(get_field_tot_area(x_old)),
+ sum(get_field_tot_area(x)), fdwn=fdwn, fup=fup)
sigma_x2_new = stress_scaling_area(init_obj.sigma_x2,
- sum(get_field_tot_area(x_old)),
- sum(get_field_tot_area(x)), fdwn = fdwn, fup = fup)
+ sum(get_field_tot_area(x_old)),
+ sum(get_field_tot_area(x)), fdwn=fdwn, fup=fup)
try:
stf_type = x[8]
except IndexError:
stf_type = init_obj.get_stiffener_type()
- main_dict = {'mat_yield': [init_obj.get_fy(), 'Pa'],'mat_factor': [init_obj.mat_factor, ''],
+ main_dict = {'mat_yield': [init_obj.get_fy(), 'Pa'], 'mat_factor': [init_obj.mat_factor, ''],
'span': [init_obj.span, 'm'],
- 'spacing': [x[0], 'm'],'plate_thk': [x[1], 'm'],'stf_web_height':[ x[2], 'm'],
- 'stf_web_thk': [x[3], 'm'],'stf_flange_width': [x[4], 'm'],
- 'stf_flange_thk': [x[5], 'm'],'structure_type': [init_obj.get_structure_type(), ''],
- 'stf_type': [stf_type, ''],'sigma_y1': [sigma_y1_new, 'MPa'],
- 'sigma_y2': [sigma_y2_new, 'MPa'],'sigma_x1': [sigma_x1_new, 'MPa'],'sigma_x2': [sigma_x2_new, 'MPa'],
- 'tau_xy': [tau_xy_new, 'MPa'],'plate_kpp': [init_obj.get_kpp(), ''],
- 'stf_kps': [init_obj.get_kps(), ''],'stf_km1': [init_obj.get_km1(), ''],
- 'stf_km2': [init_obj.get_km2(), ''],'stf_km3': [init_obj.get_km3(), ''],
- 'structure_types':[init_obj.get_structure_types(), ''],
+ 'spacing': [x[0], 'm'], 'plate_thk': [x[1], 'm'], 'stf_web_height': [x[2], 'm'],
+ 'stf_web_thk': [x[3], 'm'], 'stf_flange_width': [x[4], 'm'],
+ 'stf_flange_thk': [x[5], 'm'], 'structure_type': [init_obj.get_structure_type(), ''],
+ 'stf_type': [stf_type, ''], 'sigma_y1': [sigma_y1_new, 'MPa'],
+ 'sigma_y2': [sigma_y2_new, 'MPa'], 'sigma_x1': [sigma_x1_new, 'MPa'],
+ 'sigma_x2': [sigma_x2_new, 'MPa'],
+ 'tau_xy': [tau_xy_new, 'MPa'], 'plate_kpp': [init_obj.get_kpp(), ''],
+ 'stf_kps': [init_obj.get_kps(), ''], 'stf_km1': [init_obj.get_km1(), ''],
+ 'stf_km2': [init_obj.get_km2(), ''], 'stf_km3': [init_obj.get_km3(), ''],
+ 'structure_types': [init_obj.get_structure_types(), ''],
'zstar_optimization': [init_obj.get_z_opt(), ''],
- 'puls buckling method':[init_obj.get_puls_method(),''],
- 'puls boundary':[init_obj.get_puls_boundary(),''],
- 'puls stiffener end':[init_obj.get_puls_stf_end(),''],
- 'puls sp or up':[init_obj.get_puls_sp_or_up(),''],
- 'puls up boundary':[init_obj.get_puls_up_boundary(),''],
+ 'puls buckling method': [init_obj.get_puls_method(), ''],
+ 'puls boundary': [init_obj.get_puls_boundary(), ''],
+ 'puls stiffener end': [init_obj.get_puls_stf_end(), ''],
+ 'puls sp or up': [init_obj.get_puls_sp_or_up(), ''],
+ 'puls up boundary': [init_obj.get_puls_up_boundary(), ''],
'panel or shell': [init_obj.panel_or_shell, '']}
if fat_dict == None:
return calc.CalcScantlings(main_dict), None
else:
return calc.CalcScantlings(main_dict), calc.CalcFatigue(main_dict, fat_dict)
-def create_new_structure_obj(init_obj, x, fat_dict=None, fdwn = 1, fup = 0.5):
+
+def create_new_structure_obj(init_obj, x, fat_dict=None, fdwn=1, fup=0.5):
'''
Returns a new calculation object to be used in optimization
:param init_obj:
:return:
'''
- x_old = [init_obj.get_s(), init_obj.get_pl_thk(), init_obj.get_web_h() , init_obj.get_web_thk(),
- init_obj.get_fl_w() ,init_obj.get_fl_thk(), init_obj.span, init_obj.girder_lg]
+ x_old = [init_obj.get_s(), init_obj.get_pl_thk(), init_obj.get_web_h(), init_obj.get_web_thk(),
+ init_obj.get_fl_w(), init_obj.get_fl_thk(), init_obj.span, init_obj.girder_lg]
- sigma_y1_new = stress_scaling(init_obj.sigma_y1, init_obj.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- sigma_y2_new = stress_scaling(init_obj.sigma_y2, init_obj.get_pl_thk(), x[1], fdwn = fdwn, fup = fup)
- tau_xy_new = stress_scaling(init_obj.tau_xy, init_obj.get_pl_thk(), x[1],fdwn = fdwn, fup = fup)
- sigma_x1_new = stress_scaling_area(init_obj.sigma_x1,sum(get_field_tot_area(x_old)),sum(get_field_tot_area(x)),
- fdwn = fdwn, fup = fup)
- sigma_x2_new = stress_scaling_area(init_obj.sigma_x2,sum(get_field_tot_area(x_old)),sum(get_field_tot_area(x)),
- fdwn = fdwn, fup = fup)
+ sigma_y1_new = stress_scaling(init_obj.sigma_y1, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ sigma_y2_new = stress_scaling(init_obj.sigma_y2, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ tau_xy_new = stress_scaling(init_obj.tau_xy, init_obj.get_pl_thk(), x[1], fdwn=fdwn, fup=fup)
+ sigma_x1_new = stress_scaling_area(init_obj.sigma_x1, sum(get_field_tot_area(x_old)), sum(get_field_tot_area(x)),
+ fdwn=fdwn, fup=fup)
+ sigma_x2_new = stress_scaling_area(init_obj.sigma_x2, sum(get_field_tot_area(x_old)), sum(get_field_tot_area(x)),
+ fdwn=fdwn, fup=fup)
try:
stf_type = x[8]
@@ -947,14 +1286,15 @@ def create_new_structure_obj(init_obj, x, fat_dict=None, fdwn = 1, fup = 0.5):
main_dict = {'mat_yield': [init_obj.get_fy(), 'Pa'], 'span': [init_obj.span, 'm'],
'mat_factor': [init_obj.Plate.mat_factor, ''],
- 'spacing': [x[0], 'm'], 'plate_thk': [x[1], 'm'], 'stf_web_height': [x[2], 'm'],
- 'stf_web_thk': [x[3], 'm'], 'stf_flange_width': [x[4], 'm'],
- 'stf_flange_thk': [x[5], 'm'], 'structure_type': [init_obj.get_structure_type(), ''],
- 'stf_type': [stf_type, ''], 'sigma_y1': [sigma_y1_new, 'MPa'],
- 'sigma_y2': [sigma_y2_new, 'MPa'], 'sigma_x1': [sigma_x1_new, 'MPa'],'sigma_x2': [sigma_x2_new, 'MPa'],
- 'tau_xy': [tau_xy_new, 'MPa'], 'plate_kpp': [init_obj.get_kpp(), ''],
- 'stf_kps': [init_obj.get_kps(), ''], 'stf_km1': [init_obj.get_km1(), ''],
- 'stf_km2': [init_obj.get_km2(), ''], 'stf_km3': [init_obj.get_km3(), ''],
+ 'spacing': [x[0], 'm'], 'plate_thk': [x[1], 'm'], 'stf_web_height': [x[2], 'm'],
+ 'stf_web_thk': [x[3], 'm'], 'stf_flange_width': [x[4], 'm'],
+ 'stf_flange_thk': [x[5], 'm'], 'structure_type': [init_obj.get_structure_type(), ''],
+ 'stf_type': [stf_type, ''], 'sigma_y1': [sigma_y1_new, 'MPa'],
+ 'sigma_y2': [sigma_y2_new, 'MPa'], 'sigma_x1': [sigma_x1_new, 'MPa'],
+ 'sigma_x2': [sigma_x2_new, 'MPa'],
+ 'tau_xy': [tau_xy_new, 'MPa'], 'plate_kpp': [init_obj.get_kpp(), ''],
+ 'stf_kps': [init_obj.get_kps(), ''], 'stf_km1': [init_obj.get_km1(), ''],
+ 'stf_km2': [init_obj.get_km2(), ''], 'stf_km3': [init_obj.get_km3(), ''],
'structure_types': [init_obj.get_structure_types(), ''],
'zstar_optimization': [init_obj.get_z_opt(), ''],
'puls buckling method': [init_obj.get_puls_method(), ''],
@@ -964,9 +1304,10 @@ def create_new_structure_obj(init_obj, x, fat_dict=None, fdwn = 1, fup = 0.5):
'puls up boundary': [init_obj.get_puls_up_boundary(), ''],
}
- #if fat_dict == None:
+ # if fat_dict == None:
return calc.Structure(main_dict)
+
def get_field_tot_area(x):
''' Total area of a plate field. '''
@@ -974,12 +1315,13 @@ def get_field_tot_area(x):
width = 10
else:
width = x[7]
- plate_area = width*x[1]
- stiff_area = (x[2] * x[3]+ x[4] * x[5]) * (width//x[0])
+ plate_area = width * x[1]
+ stiff_area = (x[2] * x[3] + x[4] * x[5]) * (width // x[0])
return plate_area, stiff_area
-def calc_weight(x, prt = False):
+
+def calc_weight(x, prt=False):
'''
Calculating the current weight
:param current_dict:
@@ -993,7 +1335,8 @@ def calc_weight(x, prt = False):
span * 7850 * (plate_area + stiff_area))
return span * 7850 * (plate_area + stiff_area)
-def calc_weight_pso(x,*args):
+
+def calc_weight_pso(x, *args):
'''
Calculating the current weight
:param current_dict:
@@ -1003,16 +1346,17 @@ def calc_weight_pso(x,*args):
width = args[5]
span = args[6]
- plate_area = width*x[1]
- stiff_area = (x[2] * x[3]+ x[4] * x[5]) * (width//x[0])
+ plate_area = width * x[1]
+ stiff_area = (x[2] * x[3] + x[4] * x[5]) * (width // x[0])
return span * 7850 * (plate_area + stiff_area)
-def calc_weight_pso_section(x,*args):
+
+def calc_weight_pso_section(x, *args):
'''
Calculating the weight of a complete section.
- :param x:
- :param args:
- :return:
+ :param x:
+ :param args:
+ :return:
'''
stru_objects = args[1]
tot_length = args[2]
@@ -1022,17 +1366,18 @@ def calc_weight_pso_section(x,*args):
tot_weight = 0
for dummy_i in range(len(stru_objects)):
- tot_weight += frame_section_area*frame_height*7850
+ tot_weight += frame_section_area * frame_height * 7850
count = 0
for stru_object in stru_objects:
- span = tot_length*x[count]
+ span = tot_length * x[count]
stru_object.Plate.set_span(span)
stru_object.Stiffener.set_span(span)
tot_weight += stru_object.Stiffener.get_weight_width_lg()
return tot_weight
+
def calc_weight_cylinder(x):
'''
Calculation of total weigth.
@@ -1043,29 +1388,30 @@ def calc_weight_cylinder(x):
ring (nan, nan, 0.7, 0.02, 0.2, 0.02, nan, nan)]
'''
if sum(x[1][0:8]) != 0:
- num_long_stf = 2*math.pi*x[0][1]/x[1][0]
- long_stf_area = x[1][2]*x[1][3]+x[1][4]*x[1][5]
+ num_long_stf = 2 * math.pi * x[0][1] / x[1][0]
+ long_stf_area = x[1][2] * x[1][3] + x[1][4] * x[1][5]
long_stf_volume = long_stf_area * x[0][4] * num_long_stf
else:
long_stf_volume = 0
if sum(x[2][0:8]) != 0:
num_ring_stf = x[0][4] / x[0][2]
- ring_stf_volume = math.pi*(math.pow(x[0][1],2)-math.pow(x[0][1]-x[2][2],2))*x[2][3] + \
- 2*math.pi*(x[0][1]-x[2][2]) * x[2][4] * x[2][5]
+ ring_stf_volume = math.pi * (math.pow(x[0][1], 2) - math.pow(x[0][1] - x[2][2], 2)) * x[2][3] + \
+ 2 * math.pi * (x[0][1] - x[2][2]) * x[2][4] * x[2][5]
ring_stf_tot_vol = ring_stf_volume * num_ring_stf
else:
ring_stf_tot_vol = 0
if sum(x[3][0:8]) != 0:
num_ring_girder = x[0][4] / x[0][3]
- ring_frame_volume = math.pi*(math.pow(x[0][1],2)-math.pow(x[0][1]-x[3][2],2))*x[3][3] + \
- 2*math.pi*(x[0][1]-x[3][2])*x[3][4]*x[3][5]
- tot_ring_frame_vol = ring_frame_volume*num_ring_girder
+ ring_frame_volume = math.pi * (math.pow(x[0][1], 2) - math.pow(x[0][1] - x[3][2], 2)) * x[3][3] + \
+ 2 * math.pi * (x[0][1] - x[3][2]) * x[3][4] * x[3][5]
+ tot_ring_frame_vol = ring_frame_volume * num_ring_girder
else:
tot_ring_frame_vol = 0
shell_volume = 2 * math.pi * x[0][1] * x[0][0] * x[0][4]
- return (long_stf_volume+ring_stf_tot_vol+tot_ring_frame_vol+shell_volume)*7850
+ return (long_stf_volume + ring_stf_tot_vol + tot_ring_frame_vol + shell_volume) * 7850
+
def stress_scaling_cylinder(x1, x2, stress1):
'''
@@ -1083,7 +1429,7 @@ def stress_scaling_cylinder(x1, x2, stress1):
t1, r1, s1, hw1, tw1, b1, tf1 = x1
t2, r2, s2, hw2, tw2, b2, tf2 = x2
-
+
sasd1, smsd1, tTsd1, tQsd1, shsd1 = stress1
A1 = hw1 * tw1 + b1 * tf1
@@ -1100,81 +1446,73 @@ def stress_scaling_cylinder(x1, x2, stress1):
# Torsional, shear and hoop changes by cylinder thickness.
- return sasd1*(thk_eq1/thk_eq2), smsd1*(Itot1/Itot2), tTsd1*(t1/t2), tQsd1*(t1/t2), shsd1*(t1/t2)
+ return sasd1 * (thk_eq1 / thk_eq2), smsd1 * (Itot1 / Itot2), tTsd1 * (t1 / t2), tQsd1 * (t1 / t2), shsd1 * (t1 / t2)
-def stress_scaling(sigma_old,t_old,t_new, fdwn = 1, fup = 0.5):
- if t_new <= t_old: #decreasing the thickness
- sigma_new = sigma_old*(t_old/(t_old-fdwn*abs((t_old-t_new))))
+def stress_scaling(sigma_old, t_old, t_new, fdwn=1, fup=0.5):
+ if t_new <= t_old: # decreasing the thickness
+ sigma_new = sigma_old * (t_old / (t_old - fdwn * abs((t_old - t_new))))
# assert sigma_new >= sigma_old, 'ERROR no stress increase: \n' \
# 't_old '+str(t_old)+' sigma_old '+str(sigma_old)+ \
# '\nt_new '+str(t_new)+' sigma_new '+str(sigma_new)
- else: #increasing the thickness
+ else: # increasing the thickness
- sigma_new = sigma_old*(t_old/(t_old+fup*abs((t_old-t_new))))
+ sigma_new = sigma_old * (t_old / (t_old + fup * abs((t_old - t_new))))
# assert sigma_new <= sigma_old, 'ERROR no stress reduction: \n' \
# 't_old '+str(t_old)+' sigma_old '+str(sigma_old)+ \
# '\nt_new '+str(t_new)+' sigma_new '+str(sigma_new)
return sigma_new
-def stress_scaling_area(sigma_old,a_old,a_new, fdwn = 1, fup = 0.5):
+
+def stress_scaling_area(sigma_old, a_old, a_new, fdwn=1, fup=0.5):
''' Scale stresses using input area '''
- if a_new <= a_old: #decreasing the thickness
- sigma_new = sigma_old*(a_old/(a_old-fdwn*abs((a_old-a_new))))
+ if a_new <= a_old: # decreasing the thickness
+ sigma_new = sigma_old * (a_old / (a_old - fdwn * abs((a_old - a_new))))
# assert sigma_new >= sigma_old, 'ERROR no stress increase: \n' \
# 't_old '+str(a_old)+' sigma_old '+str(sigma_old)+ \
# '\nt_new '+str(a_new)+' sigma_new '+str(sigma_new)
- else: #increasing the thickness
- sigma_new = sigma_old*(a_old/(a_old+fup*abs((a_old-a_new))))
+ else: # increasing the thickness
+ sigma_new = sigma_old * (a_old / (a_old + fup * abs((a_old - a_new))))
# assert sigma_new <= sigma_old, 'ERROR no stress reduction: \n' \
# 't_old '+str(a_old)+' sigma_old '+str(sigma_old)+ \
# '\nt_new '+str(a_new)+' sigma_new '+str(sigma_new)
- #print('a_old', a_old, 'sigma_old', sigma_old, '|', 'a_new', a_new, 'sigma_new',sigma_new)
+ # print('a_old', a_old, 'sigma_old', sigma_old, '|', 'a_new', a_new, 'sigma_new',sigma_new)
return sigma_new
+
def x_to_string(x):
ret = ''
for val in x:
ret += str(val) + '_'
return ret
-def get_filtered_results(iterable_all,init_stuc_obj,lat_press,init_filter_weight,side='p',
- chk=(True,True,True,True,True,True,True, False),fat_dict = None, fat_press = None,
- slamming_press=None, processes = None, puls_sheet = None, puls_acceptance = 0.87,
- fdwn = 1, fup = 0.5, ml_algo = None):
+
+def get_filtered_results(iterable_all, init_stuc_obj, lat_press, init_filter_weight, side='p',
+ chk=(True, True, True, True, True, True, True, False), fat_dict=None, fat_press=None,
+ slamming_press=None, processes=None, puls_sheet=None, puls_acceptance=0.87,
+ fdwn=1, fup=0.5, ml_algo=None):
'''
Using multiprocessing to return list of applicable results.
- :param iterable_all:
- :param init_stuc_obj:
- :param lat_press:
- :param init_filter_weight:
- :param side:
- :param chk:
- :return:
- '''
- #print('Init filter weight', init_filter_weight)
- '''
- x,obj,lat_press,init_weight,side='p',chk=(True,True,True,True, True, True, True, False),
- fat_dict = None, fat_press = None, slamming_press = 0, , puls_results = None, print_result = False
+ Supports:
+ chk[7] = built-in SemiAnalytical replacement
+ chk[8] = ML-CL classification pipeline, deactivated
+ chk[9] = ML-Numeric UF pipeline
'''
+ if len(chk) > 8 and chk[8]:
+ raise NotImplementedError('ML-CL buckling is deactivated. Use ML-Numeric or SemiAnalytical.')
+
if chk[7]:
- # PULS to be used.
- #calc.PULSpanel
- '''
- dict_to_run[line_given] = self._line_to_struc[line_given][1].get_puls_input()
- dict_to_run[line_given]['Identification'] = line_given
- dict_to_run[line_given]['Pressure (fixed)'] = self.get_highest_pressure(line_given)['normal'] / 1e6
- '''
-
- dict_to_run = {}
- for x in iterable_all:
- x_id = x_to_string(x)
- # calc_object = create_new_calc_obj(init_stuc_obj, x, fat_dict, fdwn = fdwn, fup = fup)
+ # Built-in SemiAnalytical replacement. Columns: buckling UF, ultimate UF,
+ # valid prediction, acceptance limit.
+ sort_again = np.full([len(iterable_all), 4], np.inf, dtype=float)
+ sort_again[:, 2] = 0.0
+ sort_again[:, 3] = float(puls_acceptance)
+ for idx, x in enumerate(iterable_all):
calc_object_stf = None if init_stuc_obj.Stiffener is None else create_new_calc_obj(init_stuc_obj.Stiffener,
x, fat_dict,
fdwn=fdwn, fup=fup)
@@ -1185,27 +1523,16 @@ def get_filtered_results(iterable_all,init_stuc_obj,lat_press,init_filter_weight
main_dict=init_stuc_obj.get_main_properties()['main dict']),
calc_object_pl[1]]
- dict_to_run[x_id] = calc_object[0].Plate.get_puls_input()
- dict_to_run[x_id]['Identification'] = x_id
- dict_to_run[x_id]['Pressure (fixed)'] = lat_press # PULS sheet to have pressure in MPa
+ sort_again[idx, 0:4] = _predict_semi_analytical_uf(calc_object, lat_press, puls_acceptance)
+
+ PULSrun = None
- PULSrun = calc.PULSpanel(dict_to_run, puls_sheet_location=puls_sheet, puls_acceptance=puls_acceptance)
- PULSrun.run_all()
- sort_again = None
elif chk[8]:
+ # ML-CL is deactivated.
+ sp_int, sp_gl_gt, up_int, up_gl_gt = list(), list(), list(), list()
+ sp_int_idx, sp_gl_gt_idx, up_int_idx, up_gl_gt_idx = list(), list(), list(), list()
- # ML-CL to be used.
- # Buckling ml-cl
- sp_int, sp_gl_gt, up_int, up_gl_gt, \
- sp_int_idx, sp_gl_gt_idx, up_int_idx, up_gl_gt_idx = \
- list(), list(), list(),list(),list(), list(), list(),list()
-
- # Create iterator
- idx_count = 0
for idx, x in enumerate(iterable_all):
- idx_count += 1
- # calc_object = create_new_calc_obj(init_stuc_obj, x, fat_dict, fdwn=fdwn, fup=fup)
-
calc_object_stf = None if init_stuc_obj.Stiffener is None else create_new_calc_obj(init_stuc_obj.Stiffener,
x, fat_dict,
fdwn=fdwn, fup=fup)
@@ -1213,79 +1540,138 @@ def get_filtered_results(iterable_all,init_stuc_obj,lat_press,init_filter_weight
calc_object = [calc.AllStructure(Plate=calc_object_pl[0],
Stiffener=None if init_stuc_obj.Stiffener is None else calc_object_stf[0],
Girder=None,
- main_dict=init_stuc_obj.get_main_properties()['main dict']), calc_object_pl[1]]
+ main_dict=init_stuc_obj.get_main_properties()['main dict']),
+ calc_object_pl[1]]
if calc_object[0].Plate.get_puls_sp_or_up() == 'UP':
if calc_object[0].Plate.get_puls_boundary() == 'Int':
- up_int.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone = False))
+ up_int.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone=False))
up_int_idx.append(idx)
else:
- up_gl_gt.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone = False))
+ up_gl_gt.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone=False))
up_gl_gt_idx.append(idx)
else:
+ ml_input = _get_ml_input_for_optimization(calc_object, lat_press)
if calc_object[0].Plate.get_puls_boundary() == 'Int':
- sp_int.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone = False))
+ sp_int.append(ml_input)
sp_int_idx.append(idx)
else:
- sp_gl_gt.append(calc_object[0].Plate.get_buckling_ml_input(lat_press, alone = False))
+ sp_gl_gt.append(ml_input)
sp_gl_gt_idx.append(idx)
- # Predict
- sort_again = np.zeros([len(iterable_all),2])
+ # Existing classification result container.
+ sort_again = np.zeros([len(iterable_all), 2])
if len(sp_int) != 0:
sp_int_res = [ml_algo['cl SP buc int predictor'].predict(ml_algo['cl SP buc int scaler']
.transform(sp_int)),
- ml_algo['cl SP ult int predictor'].predict(ml_algo['cl SP buc int scaler']
+ ml_algo['cl SP ult int predictor'].predict(ml_algo['cl SP ult int scaler']
.transform(sp_int))]
- for idx, res_buc, res_ult in zip(sp_int_idx, sp_int_res[0],sp_int_res[1]):
+ for idx, res_buc, res_ult in zip(sp_int_idx, sp_int_res[0], sp_int_res[1]):
sort_again[idx] = [res_buc, res_ult]
if len(sp_gl_gt) != 0:
sp_gl_gt_res = [ml_algo['cl SP buc GLGT predictor'].predict(ml_algo['cl SP buc GLGT scaler']
.transform(sp_gl_gt)),
- ml_algo['cl SP buc GLGT predictor'].predict(ml_algo['cl SP buc GLGT scaler']
- .transform(sp_gl_gt))]
- for idx, res_buc, res_ult in zip(sp_gl_gt_idx, sp_gl_gt_res[0],sp_gl_gt_res[1]):
+ ml_algo['cl SP ult GLGT predictor'].predict(ml_algo['cl SP ult GLGT scaler']
+ .transform(sp_gl_gt))]
+ for idx, res_buc, res_ult in zip(sp_gl_gt_idx, sp_gl_gt_res[0], sp_gl_gt_res[1]):
sort_again[idx] = [res_buc, res_ult]
+
if len(up_int) != 0:
up_int_res = [ml_algo['cl UP buc int predictor'].predict(ml_algo['cl UP buc int scaler']
.transform(up_int)),
- ml_algo['cl UP ult int predictor'].predict(ml_algo['cl UP buc int scaler']
+ ml_algo['cl UP ult int predictor'].predict(ml_algo['cl UP ult int scaler']
.transform(up_int))]
- for idx, res_buc, res_ult in zip(up_int_idx, up_int_res[0],up_int_res[1]):
+ for idx, res_buc, res_ult in zip(up_int_idx, up_int_res[0], up_int_res[1]):
sort_again[idx] = [res_buc, res_ult]
+
if len(up_gl_gt) != 0:
- up_gl_gt_res =[ml_algo['cl UP buc GLGT predictor'].predict(ml_algo['cl UP buc GLGT scaler']
+ up_gl_gt_res = [ml_algo['cl UP buc GLGT predictor'].predict(ml_algo['cl UP buc GLGT scaler']
.transform(up_gl_gt)),
- ml_algo['cl UP buc GLGT predictor'].predict(ml_algo['cl UP buc GLGT scaler']
- .transform(up_gl_gt))]
- for idx, res_buc, res_ult in zip(up_gl_gt_idx, up_gl_gt_res[0],up_gl_gt_res[1]):
+ ml_algo['cl UP ult GLGT predictor'].predict(ml_algo['cl UP ult GLGT scaler']
+ .transform(up_gl_gt))]
+ for idx, res_buc, res_ult in zip(up_gl_gt_idx, up_gl_gt_res[0], up_gl_gt_res[1]):
sort_again[idx] = [res_buc, res_ult]
+
PULSrun = None
+
+ elif chk[9]:
+ # ML-Numeric UF to be used.
+ numeric_groups = {
+ 'num SP int': {'inputs': [], 'indices': []},
+ 'num SP GLGT': {'inputs': [], 'indices': []},
+ 'num UP int': {'inputs': [], 'indices': []},
+ 'num UP GLGT': {'inputs': [], 'indices': []},
+ }
+
+ for idx, x in enumerate(iterable_all):
+ calc_object_stf = None if init_stuc_obj.Stiffener is None else create_new_calc_obj(init_stuc_obj.Stiffener,
+ x, fat_dict,
+ fdwn=fdwn, fup=fup)
+ calc_object_pl = create_new_calc_obj(init_stuc_obj.Plate, x, fat_dict, fdwn=fdwn, fup=fup)
+ calc_object = [calc.AllStructure(Plate=calc_object_pl[0],
+ Stiffener=None if init_stuc_obj.Stiffener is None else calc_object_stf[0],
+ Girder=None,
+ main_dict=init_stuc_obj.get_main_properties()['main dict']),
+ calc_object_pl[1]]
+
+ prefix = _get_numeric_pipeline_prefix(calc_object)
+ ml_input = _get_ml_input_for_optimization(calc_object, lat_press)
+ numeric_groups[prefix]['inputs'].append(ml_input)
+ numeric_groups[prefix]['indices'].append(idx)
+
+ # columns: buckling UF, ultimate UF, valid prediction
+ sort_again = np.full([len(iterable_all), 3], np.inf, dtype=float)
+ sort_again[:, 2] = 0.0
+
+ try:
+ mat_fac = float(init_stuc_obj.Plate.mat_factor)
+ except Exception:
+ mat_fac = 1.15
+
+ for prefix, group in numeric_groups.items():
+ if len(group['inputs']) == 0:
+ continue
+
+ group_res = _predict_numeric_uf_group(
+ ml_algo=ml_algo,
+ input_rows=group['inputs'],
+ prefix=prefix,
+ mat_fac=mat_fac,
+ )
+
+ for idx, res in zip(group['indices'], group_res):
+ sort_again[idx] = res
+
+ PULSrun = None
+
else:
PULSrun = None
- idx_count = 0
- for x in iterable_all:
- idx_count += 1
+ for _ in iterable_all:
+ pass
sort_again = None
iter_var = list()
- for idx,item in enumerate(iterable_all):
- iter_var.append((item,init_stuc_obj,lat_press,init_filter_weight,side,chk,fat_dict,fat_press,slamming_press,
- PULSrun, False,fdwn, fup, sort_again[idx] if chk[8] == True else None))
+ for idx, item in enumerate(iterable_all):
+ if chk[7] or chk[8] or chk[9]:
+ this_ml_result = sort_again[idx]
+ else:
+ this_ml_result = None
+
+ iter_var.append((item, init_stuc_obj, lat_press, init_filter_weight, side, chk, fat_dict, fat_press,
+ slamming_press, PULSrun, False, fdwn, fup, this_ml_result))
iter_var = tuple(iter_var)
- #res_pre = it.starmap(any_constraints_all, iter_var)
if processes is None:
- processes = max(cpu_count()-1,1)
+ processes = max(cpu_count() - 1, 1)
- with Pool(processes) as my_process:
- # res_pre = m
- # y_process.starmap_async(any_constraints_all, iter_var).get()
- # print('Done calculating')
- res_pre = my_process.starmap(any_constraints_all, iter_var)
+ if processes == 1:
+ res_pre = [any_constraints_all(*args) for args in iter_var]
+ else:
+ with Pool(processes) as my_process:
+ res_pre = my_process.starmap(any_constraints_all, iter_var)
check_ok, check_not_ok = list(), list()
for item in res_pre:
@@ -1296,7 +1682,8 @@ def get_filtered_results(iterable_all,init_stuc_obj,lat_press,init_filter_weight
return check_ok, check_not_ok
-def any_get_all_combs(min_var, max_var,deltas, init_weight = float('inf'), predef_stiffeners = None, stf_type = None):
+
+def any_get_all_combs(min_var, max_var, deltas, init_weight=float('inf'), predef_stiffeners=None, stf_type=None):
'''
Calulating initial values.
:param min:
@@ -1321,14 +1708,14 @@ def any_get_all_combs(min_var, max_var,deltas, init_weight = float('inf'), prede
ring_frame_lower_bounds = np.array( [None, None, 0.5, 0.02, 0.2, 0.020])
'''
if min_var[0] is not None:
- spacing_array = (np.arange(min_var[0], max_var[0]+ deltas[0], deltas[0])) if min_var[0] != max_var[0] \
+ spacing_array = (np.arange(min_var[0], max_var[0] + deltas[0], deltas[0])) if min_var[0] != max_var[0] \
else np.array([min_var[0]])
spacing_array = spacing_array[spacing_array <= max_var[0]]
else:
spacing_array = np.array([np.nan])
if min_var[1] is not None:
- pl_thk_array = (np.arange(min_var[1], max_var[1]+ deltas[1], deltas[1])) if min_var[1] != max_var[1] \
+ pl_thk_array = (np.arange(min_var[1], max_var[1] + deltas[1], deltas[1])) if min_var[1] != max_var[1] \
else np.array([min_var[1]])
pl_thk_array = pl_thk_array[pl_thk_array <= max_var[1]]
else:
@@ -1347,21 +1734,20 @@ def any_get_all_combs(min_var, max_var,deltas, init_weight = float('inf'), prede
return predef_iterable
-
- web_h_array = (np.arange(min_var[2], max_var[2]+ deltas[2], deltas[2])) if min_var[2] != max_var[2] \
+ web_h_array = (np.arange(min_var[2], max_var[2] + deltas[2], deltas[2])) if min_var[2] != max_var[2] \
else np.array([min_var[2]])
web_h_array = web_h_array[web_h_array <= max_var[2]]
- web_thk_array = (np.arange(min_var[3], max_var[3]+ deltas[3], deltas[3])) if min_var[3] != max_var[3] \
+ web_thk_array = (np.arange(min_var[3], max_var[3] + deltas[3], deltas[3])) if min_var[3] != max_var[3] \
else np.array([min_var[3]])
web_thk_array = web_thk_array[web_thk_array <= max_var[3]]
- flange_w_array = (np.arange(min_var[4], max_var[4]+ deltas[4], deltas[4])) if min_var[4] != max_var[4] \
+ flange_w_array = (np.arange(min_var[4], max_var[4] + deltas[4], deltas[4])) if min_var[4] != max_var[4] \
else np.array([min_var[4]])
flange_w_array = flange_w_array[flange_w_array <= max_var[4]]
if min_var[5] is not None:
- flange_thk_array = (np.arange(min_var[5], max_var[5]+ deltas[5], deltas[5])) if min_var[5] != max_var[5] \
+ flange_thk_array = (np.arange(min_var[5], max_var[5] + deltas[5], deltas[5])) if min_var[5] != max_var[5] \
else np.array([min_var[5]])
flange_thk_array = flange_thk_array[flange_thk_array <= max_var[5]]
else:
@@ -1380,12 +1766,13 @@ def any_get_all_combs(min_var, max_var,deltas, init_weight = float('inf'), prede
girder_array = np.array([np.nan])
comb = it.product(spacing_array, pl_thk_array, web_h_array, web_thk_array, flange_w_array, flange_thk_array,
- span_array,girder_array)
+ span_array, girder_array)
return list(comb)
-def get_initial_weight(obj,lat_press,min_var,max_var,deltas,trials,fat_dict,fat_press, predefined_stiffener_iter,
- slamming_press, fdwn = 1, fup = 0.5, ml_algo = None):
+
+def get_initial_weight(obj, lat_press, min_var, max_var, deltas, trials, fat_dict, fat_press, predefined_stiffener_iter,
+ slamming_press, fdwn=1, fup=0.5, ml_algo=None):
'''
Return a guess of the initial weight used to filter the constraints.
Only aim is to reduce running time of the algorithm.
@@ -1395,21 +1782,23 @@ def get_initial_weight(obj,lat_press,min_var,max_var,deltas,trials,fat_dict,fat_
if predefined_stiffener_iter is None:
combs = any_get_all_combs(min_var, max_var, deltas)
else:
- combs = any_get_all_combs(min_var, max_var, deltas,predef_stiffeners=[item.get_tuple() for item in
- predefined_stiffener_iter])
+ combs = any_get_all_combs(min_var, max_var, deltas, predef_stiffeners=[item.get_tuple() for item in
+ predefined_stiffener_iter])
trial_selection = random_product(combs, repeat=trials)
obj.lat_press = lat_press
for x in trial_selection:
- if any_constraints_all(x=x,obj=obj,lat_press=lat_press,init_weight=min_weight,
- fat_dict=fat_dict,fat_press = fat_press,slamming_press=slamming_press,
- fdwn = fdwn, fup = fup)[0]:
+ if any_constraints_all(x=x, obj=obj, lat_press=lat_press, init_weight=min_weight,
+ fat_dict=fat_dict, fat_press=fat_press, slamming_press=slamming_press,
+ fdwn=fdwn, fup=fup)[0]:
current_weight = calc_weight(x)
if current_weight < min_weight:
min_weight = current_weight
return min_weight
-def get_random_result(obj,lat_press,min_var,max_var,deltas,trials=10000,side='p',const_chk=(True,True,True,True,True),
+
+def get_random_result(obj, lat_press, min_var, max_var, deltas, trials=10000, side='p',
+ const_chk=(True, True, True, True, True),
fat_dict=None, fat_press=None):
'''
Return random results
@@ -1417,10 +1806,10 @@ def get_random_result(obj,lat_press,min_var,max_var,deltas,trials=10000,side='p'
min_weight = float('inf')
ass_var = None
combs = any_get_all_combs(min_var, max_var, deltas)
- trial_selection = random_product(combs,repeat=trials)
+ trial_selection = random_product(combs, repeat=trials)
for x in trial_selection:
- if any_constraints_all(x=x,obj=obj,lat_press=lat_press,init_weight=min_weight,side=side,chk=const_chk,
- fat_dict = fat_dict, fat_press = fat_press)[0] is not False:
+ if any_constraints_all(x=x, obj=obj, lat_press=lat_press, init_weight=min_weight, side=side, chk=const_chk,
+ fat_dict=fat_dict, fat_press=fat_press)[0] is not False:
current_weight = calc_weight(x)
if current_weight < min_weight:
min_weight = current_weight
@@ -1428,9 +1817,11 @@ def get_random_result(obj,lat_press,min_var,max_var,deltas,trials=10000,side='p'
if ass_var == None:
return ass_var
return create_new_structure_obj(obj, [round(item, 5) for item in ass_var]), \
- create_new_calc_obj(obj, [round(item, 5) for item in ass_var])[0]
+ create_new_calc_obj(obj, [round(item, 5) for item in ass_var])[0]
+
-def get_random_result_no_bounds(obj,lat_press,min_var,max_var,trials=10000,side='p',const_chk=(True,True,True,True,True)
+def get_random_result_no_bounds(obj, lat_press, min_var, max_var, trials=10000, side='p',
+ const_chk=(True, True, True, True, True)
, fat_dict=None, fat_press=None):
'''
Return random results, ignoring the deltas
@@ -1438,15 +1829,15 @@ def get_random_result_no_bounds(obj,lat_press,min_var,max_var,trials=10000,side=
min_weight = float('inf')
ass_var = None
for trial in range(trials):
- spacing = random.randrange(int(min_var[0]*1000),int(max_var[0]*1000),1)/1000
- pl_thk = random.randrange(int(min_var[1]*1000),int(max_var[1]*1000),1)/1000
- web_h = random.randrange(int(min_var[2]*1000),int(max_var[2]*1000),1)/1000
- web_thk = random.randrange(int(min_var[3]*1000),int(max_var[3]*1000),1)/1000
- fl_w = random.randrange(int(min_var[4]*1000),int(max_var[4]*1000),1)/1000
- fl_thk = random.randrange(int(min_var[5]*1000),int(max_var[5]*1000),1)/1000
- x = (spacing,pl_thk,web_h,web_thk,fl_w,fl_thk,min_var[6],min_var[7])
- if any_constraints_all(x=x,obj=obj,lat_press=lat_press,init_weight=min_weight,side=side,chk=const_chk,
- fat_dict = fat_dict, fat_press = fat_press)[0]:
+ spacing = random.randrange(int(min_var[0] * 1000), int(max_var[0] * 1000), 1) / 1000
+ pl_thk = random.randrange(int(min_var[1] * 1000), int(max_var[1] * 1000), 1) / 1000
+ web_h = random.randrange(int(min_var[2] * 1000), int(max_var[2] * 1000), 1) / 1000
+ web_thk = random.randrange(int(min_var[3] * 1000), int(max_var[3] * 1000), 1) / 1000
+ fl_w = random.randrange(int(min_var[4] * 1000), int(max_var[4] * 1000), 1) / 1000
+ fl_thk = random.randrange(int(min_var[5] * 1000), int(max_var[5] * 1000), 1) / 1000
+ x = (spacing, pl_thk, web_h, web_thk, fl_w, fl_thk, min_var[6], min_var[7])
+ if any_constraints_all(x=x, obj=obj, lat_press=lat_press, init_weight=min_weight, side=side, chk=const_chk,
+ fat_dict=fat_dict, fat_press=fat_press)[0]:
current_weight = calc_weight(x)
if current_weight < min_weight:
min_weight = current_weight
@@ -1454,31 +1845,33 @@ def get_random_result_no_bounds(obj,lat_press,min_var,max_var,trials=10000,side=
if ass_var == None:
return ass_var
return create_new_structure_obj(obj, [round(item, 5) for item in ass_var]), \
- create_new_calc_obj(obj, [round(item, 5) for item in ass_var])[0]
+ create_new_calc_obj(obj, [round(item, 5) for item in ass_var])[0]
+
def random_product(*args, repeat=1):
"Random selection from itertools.product(*args, **kwds)"
pools = [tuple(pool) for pool in args] * repeat
return tuple(random.choice(pool) for pool in pools)
-def product_any(*args, repeat=1,weight=float('inf')):
+
+def product_any(*args, repeat=1, weight=float('inf')):
# product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
# product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
pools = [tuple(pool) for pool in args] * repeat
result = [[]]
for pool in pools:
- result = [x+[y] for x in result for y in pool]
+ result = [x + [y] for x in result for y in pool]
for prod in result:
if calc_weight(prod) < weight:
yield tuple(prod)
-def plot_optimization_results(results, multiple = False):
+def plot_optimization_results(results, multiple=False):
check_ok_array, check_array, section_array = list(), list(), list()
save_to_csv = asksaveasfilename()
if save_to_csv != '':
- csv_file = open(save_to_csv,'w', newline='')
+ csv_file = open(save_to_csv, 'w', newline='')
csv_writer = csv.writer(csv_file)
csv_writer.writerow(['Is OK', 'Check info', 'pl b', 'pl thk', 'web h', 'web thk', 'fl b', 'fl thk', 'span',
'girder width', 'stiffener type', 'uf weight', 'uf sec mod', 'uf loc stf buc',
@@ -1493,7 +1886,7 @@ def plot_optimization_results(results, multiple = False):
to_write.append(check_ok)
to_write.append(check)
[to_write.append(item) for item in section]
- if(len(section) == 8):
+ if (len(section) == 8):
to_write.append('T')
[to_write.append(item) for item in ufres]
csv_writer.writerow(to_write)
@@ -1502,8 +1895,8 @@ def plot_optimization_results(results, multiple = False):
csv_file.close()
check_ok_array, check_array, section_array = np.array(check_ok_array), \
- np.array(check_array), \
- np.array(section_array)
+ np.array(check_array), \
+ np.array(section_array)
x_label = np.unique(check_array)
y = [np.count_nonzero(check_array == item) for item in np.unique(check_array)]
@@ -1518,38 +1911,39 @@ def plot_optimization_results(results, multiple = False):
plt.show()
+
if __name__ == '__main__':
import example_data as ex
from calc_structure import CylinderAndCurvedPlate, Structure, Shell
shell_main_dict = ex.shell_main_dict
shell_main_dict['geometry'] = [7, '']
- #Structure(ex.obj_dict_cyl_ring)
- #Structure(ex.obj_dict_cyl_heavy_ring)
+ # Structure(ex.obj_dict_cyl_ring)
+ # Structure(ex.obj_dict_cyl_heavy_ring)
# my_cyl = CylinderAndCurvedPlate(main_dict = ex.shell_main_dict, shell= Shell(ex.shell_dict),
# long_stf= Structure(ex.obj_dict_cyl_long2),
# ring_stf = Structure(ex.obj_dict_cyl_ring2),
# ring_frame= Structure(ex.obj_dict_cyl_heavy_ring2))
- my_cyl = CylinderAndCurvedPlate(main_dict = ex.shell_main_dict, shell= Shell(ex.shell_dict),
- long_stf= Structure(ex.obj_dict_cyl_long2),
- ring_stf = None,# Structure(ex.obj_dict_cyl_ring2),
- ring_frame= None)#Structure(ex.obj_dict_cyl_heavy_ring2))
+ my_cyl = CylinderAndCurvedPlate(main_dict=ex.shell_main_dict, shell=Shell(ex.shell_dict),
+ long_stf=Structure(ex.obj_dict_cyl_long2),
+ ring_stf=None, # Structure(ex.obj_dict_cyl_ring2),
+ ring_frame=None) # Structure(ex.obj_dict_cyl_heavy_ring2))
- shell_upper_bounds = np.array( [0.03, 5, 5, 5, 10, None, None, None])
- shell_deltas = np.array( [0.005, 0.5, 1, 0.1,1, None, None, None])
- shell_lower_bounds = np.array( [0.02, 5, 5, 5, 10, None, None, None])
+ shell_upper_bounds = np.array([0.03, 5, 5, 5, 10, None, None, None])
+ shell_deltas = np.array([0.005, 0.5, 1, 0.1, 1, None, None, None])
+ shell_lower_bounds = np.array([0.02, 5, 5, 5, 10, None, None, None])
- long_upper_bounds = np.array( [0.8, None, 0.5, 0.02, 0.2, 0.03, None, None])
- long_deltas = np.array( [0.1, None, 0.1, 0.01, 0.1, 0.01, None, None])
- long_lower_bounds = np.array( [0.7, None, 0.3, 0.01, 0.1, 0.01, None, None])
+ long_upper_bounds = np.array([0.8, None, 0.5, 0.02, 0.2, 0.03, None, None])
+ long_deltas = np.array([0.1, None, 0.1, 0.01, 0.1, 0.01, None, None])
+ long_lower_bounds = np.array([0.7, None, 0.3, 0.01, 0.1, 0.01, None, None])
- ring_stf_upper_bounds = np.array( [None, None, 0.5, 0.018, 0.2, 0.03, None, None])
- ring_stf_deltas = np.array( [None, None, 0.1, 0.004, 0.1, 0.01, None, None])
- ring_stf_lower_bounds = np.array( [None, None, 0.3, 0.010, 0.1, 0.010, None, None])
+ ring_stf_upper_bounds = np.array([None, None, 0.5, 0.018, 0.2, 0.03, None, None])
+ ring_stf_deltas = np.array([None, None, 0.1, 0.004, 0.1, 0.01, None, None])
+ ring_stf_lower_bounds = np.array([None, None, 0.3, 0.010, 0.1, 0.010, None, None])
- ring_frame_upper_bounds = np.array( [None, None, 0.9, 0.04, 0.3, 0.04, None, None])
- ring_frame_deltas = np.array( [None, None, 0.2, 0.01, 0.1, 0.01, None, None])
- ring_frame_lower_bounds = np.array( [None, None, 0.7, 0.02, 0.2, 0.02, None, None])
+ ring_frame_upper_bounds = np.array([None, None, 0.9, 0.04, 0.3, 0.04, None, None])
+ ring_frame_deltas = np.array([None, None, 0.2, 0.01, 0.1, 0.01, None, None])
+ ring_frame_lower_bounds = np.array([None, None, 0.7, 0.02, 0.2, 0.02, None, None])
max_var = [shell_upper_bounds, long_upper_bounds, ring_stf_upper_bounds, ring_frame_upper_bounds]
deltas = [shell_deltas, long_deltas, ring_stf_deltas, ring_frame_deltas]
@@ -1557,9 +1951,12 @@ def plot_optimization_results(results, multiple = False):
results = run_optmizataion(initial_structure_obj=my_cyl, min_var=min_var, max_var=max_var, deltas=deltas,
cylinder=True, use_weight_filter=True)
- shell = ['Shell thk. [mm]', 'Shell radius [mm]' , 'l rings [mm]', 'L shell [mm]', 'L tot. [mm]', 'N/A - future', 'N/A - future', 'N/A - future']
- stf_long = ['Spacing [mm]', 'Plate thk. [mm]', 'Web height [mm]', 'Web thk. [mm]', 'Flange width [mm]', 'Flange thk. [mm]', 'N/A - future', 'N/A - future']
- stf_ring = ['N/A', 'Plate thk. [mm]', 'Web height [mm]', 'Web thk. [mm]', 'Flange width [mm]', 'Flange thk. [mm]', 'N/A - future', 'N/A - future']
+ shell = ['Shell thk. [mm]', 'Shell radius [mm]', 'l rings [mm]', 'L shell [mm]', 'L tot. [mm]', 'N/A - future',
+ 'N/A - future', 'N/A - future']
+ stf_long = ['Spacing [mm]', 'Plate thk. [mm]', 'Web height [mm]', 'Web thk. [mm]', 'Flange width [mm]',
+ 'Flange thk. [mm]', 'N/A - future', 'N/A - future']
+ stf_ring = ['N/A', 'Plate thk. [mm]', 'Web height [mm]', 'Web thk. [mm]', 'Flange width [mm]', 'Flange thk. [mm]',
+ 'N/A - future', 'N/A - future']
# obj_dict = ex.obj_dict_sec_error
# fat_obj = ex.get_fatigue_object_problematic()
@@ -1640,8 +2037,6 @@ def plot_optimization_results(results, multiple = False):
# plt.legend(loc=2)
# plt.show()
-
-
# for swarm_size in [100, 1000, 10000, 100000, 1000000]:
# t1 = time.time()
#
@@ -1678,3 +2073,4 @@ def plot_optimization_results(results, multiple = False):
+
diff --git a/anystruct/optimize_cylinder.py b/anystruct/optimize_cylinder.py
index d1f8cee3..340601d2 100644
--- a/anystruct/optimize_cylinder.py
+++ b/anystruct/optimize_cylinder.py
@@ -12,11 +12,13 @@
import anystruct.optimize as op
import anystruct.example_data as test
import anystruct.helper as hlp
+ import anystruct.line_structure as line_structure
except ModuleNotFoundError:
import ANYstructure.anystruct.main_application as main_application
import ANYstructure.anystruct.optimize as op
import ANYstructure.anystruct.example_data as test
import ANYstructure.anystruct.helper as hlp
+ import ANYstructure.anystruct.line_structure as line_structure
class CreateOptimizeCylinderWindow():
'''
@@ -34,45 +36,128 @@ def __init__(self,master,app=None):
self._fatigue_pressure = test.get_fatigue_pressures()
self._slamming_pressure = test.get_slamming_pressure()
image_dir = os.path.dirname(__file__)+'\\images\\'
- self._PULS_object = None
- self._puls_acceptance = 0.87
-
self._initial_cylinder_obj = calc.CylinderAndCurvedPlate(main_dict=test.shell_main_dict,
shell=calc.Shell(test.shell_dict),
long_stf=calc.Structure(test.obj_dict_cyl_long2),
ring_stf=None,#calc.Structure(test.obj_dict_cyl_ring2),
ring_frame=None)#calc.Structure(test.obj_dict_cyl_heavy_ring2))
- self._ML_buckling = dict() # Buckling machine learning algorithm
- for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
- 'cl SP ult int predictor', 'cl SP ult int scaler',
- 'cl SP buc GLGT predictor', 'cl SP buc GLGT scaler',
- 'cl SP ult GLGT predictor', 'cl SP ult GLGT scaler',
- 'cl UP buc int predictor', 'cl UP buc int scaler',
- 'cl UP ult int predictor', 'cl UP ult int scaler',
- 'cl UP buc GLGT predictor', 'cl UP buc GLGT scaler',
- 'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler'
- ],
- ["ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_CSR-Tank_req_cl_predictor",
- "ml_files\\CL_CSR-Tank_req_cl_UP_scaler",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
+ self._ML_buckling = dict() # Buckling machine learning algorithms
+
+ for name, file_base in zip(
+ [
+ # ---------------------------------------------------------------------
+ # Classification pipeline
+ # ---------------------------------------------------------------------
+ 'cl SP buc int predictor',
+ 'cl SP buc int scaler',
+ 'cl SP ult int predictor',
+ 'cl SP ult int scaler',
+
+ 'cl SP buc GLGT predictor',
+ 'cl SP buc GLGT scaler',
+ 'cl SP ult GLGT predictor',
+ 'cl SP ult GLGT scaler',
+
+ 'cl UP buc int predictor',
+ 'cl UP buc int scaler',
+ 'cl UP ult int predictor',
+ 'cl UP ult int scaler',
+
+ 'cl UP buc GLGT predictor',
+ 'cl UP buc GLGT scaler',
+ 'cl UP ult GLGT predictor',
+ 'cl UP ult GLGT scaler',
+
+ 'CSR predictor UP',
+ 'CSR scaler UP',
+ 'CSR predictor SP',
+ 'CSR scaler SP',
+
+ # ---------------------------------------------------------------------
+ # Numeric UF pipeline
+ # ---------------------------------------------------------------------
+ 'num SP int validity predictor',
+ 'num SP int validity xscaler',
+ 'num SP int UF reg predictor',
+ 'num SP int UF reg xscaler',
+ 'num SP int UF reg yscaler',
+
+ 'num SP GLGT validity predictor',
+ 'num SP GLGT validity xscaler',
+ 'num SP GLGT UF reg predictor',
+ 'num SP GLGT UF reg xscaler',
+ 'num SP GLGT UF reg yscaler',
+
+ 'num UP int validity predictor',
+ 'num UP int validity xscaler',
+ 'num UP int UF reg predictor',
+ 'num UP int UF reg xscaler',
+ 'num UP int UF reg yscaler',
+
+ 'num UP GLGT validity predictor',
+ 'num UP GLGT validity xscaler',
+ 'num UP GLGT UF reg predictor',
+ 'num UP GLGT UF reg xscaler',
+ 'num UP GLGT UF reg yscaler',
+ ],
+ [
+ # ---------------------------------------------------------------------
+ # Classification pipeline
+ # ---------------------------------------------------------------------
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_SP",
+
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_SP",
+
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_UP",
+
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_UP",
+
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_predictor_UP",
+ "ml_files\\CLPIPE_CL_CSR-Tank_req_cl_scaler_UP",
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor_SP",
+ "ml_files\\CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler_SP",
+
+ # ---------------------------------------------------------------------
+ # Numeric UF pipeline
+ # ---------------------------------------------------------------------
+ "ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1",
+
+ "ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3",
+
+ "ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1",
+ "ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1",
+
+ "ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3",
+ "ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3",
+ ],
+ ):
self._ML_buckling[name] = None
if os.path.isfile(file_base + '.pickle'):
file = open(file_base + '.pickle', 'rb')
@@ -90,10 +175,11 @@ def __init__(self,master,app=None):
9: 'UF below or equal 0.87', 10: 'UF between 0.87 and 1.0', 11: 'UF above 1.0'}
else:
self.app = app
- self._initial_structure_obj = app._line_to_struc[app._active_line][0]
- self._initial_calc_obj = app._line_to_struc[app._active_line][1]
- self._initial_cylinder_obj = app._line_to_struc[app._active_line][5]
- self._fatigue_object = app._line_to_struc[app._active_line][2]
+ active_bundle = app._line_to_struc[app._active_line]
+ self._initial_structure_obj = line_structure.structure(active_bundle)
+ self._initial_calc_obj = line_structure.structure(active_bundle)
+ self._initial_cylinder_obj = line_structure.cylinder(active_bundle)
+ self._fatigue_object = line_structure.fatigue(active_bundle)
try:
self._fatigue_pressure = app.get_fatigue_pressures(app._active_line,
self._fatigue_object.get_accelerations())
@@ -113,9 +199,6 @@ def __init__(self,master,app=None):
self._slamming_pressure = 0
image_dir = app._root_dir +'\\images\\'
self._root_dir = app._root_dir
- self._PULS_object = app._PULS_results
- self._puls_acceptance = self.app._new_puls_uf.get()
-
self._ML_buckling = app._ML_buckling
diff --git a/anystruct/optimize_geometry.py b/anystruct/optimize_geometry.py
index de52cbfd..741f3801 100644
--- a/anystruct/optimize_geometry.py
+++ b/anystruct/optimize_geometry.py
@@ -4,6 +4,10 @@
from tkinter.ttk import Progressbar
from tkinter import messagebox
import pickle
+import os
+import time
+import copy
+import numpy as np
from tkinter.filedialog import askopenfilenames
from multiprocessing import cpu_count
from tkinter import filedialog
@@ -13,6 +17,7 @@
import anystruct.main_application
import anystruct.optimize as op
import anystruct.example_data as test
+ import anystruct.line_structure as line_structure
from anystruct.calc_structure import *
import anystruct.calc_structure
from anystruct.helper import *
@@ -20,6 +25,7 @@
import ANYstructure.anystruct.main_application
import ANYstructure.anystruct.optimize as op
import ANYstructure.anystruct.example_data as test
+ import ANYstructure.anystruct.line_structure as line_structure
from ANYstructure.anystruct.calc_structure import *
import ANYstructure.anystruct.calc_structure
from ANYstructure.anystruct.helper import *
@@ -30,6 +36,76 @@ class CreateOptGeoWindow():
This class initiates the MultiOpt window.
'''
+ def _get_selected_ml_buckling(self):
+ """Return the flat ML model dictionary expected by optimize.py."""
+ ml_algo = self._ML_buckling
+
+ # In the main app this may be stored as {1.1: {...}, 1.15: {...}}.
+ # Optimization needs the flat dictionary for the selected/current material factor.
+ if isinstance(ml_algo, dict):
+ try:
+ mat_fac = float(self.app._new_material_factor.get())
+ except Exception:
+ try:
+ mat_fac = float(self._initial_calc_obj.Plate.mat_factor)
+ except Exception:
+ mat_fac = None
+
+ if mat_fac in ml_algo and isinstance(ml_algo[mat_fac], dict):
+ return ml_algo[mat_fac]
+
+ # Fallback: if only one nested material-factor dictionary exists, use it.
+ nested_keys = [
+ key for key, value in ml_algo.items()
+ if isinstance(value, dict)
+ ]
+ if len(nested_keys) == 1:
+ return ml_algo[nested_keys[0]]
+
+ return ml_algo
+
+ def _get_selected_material_factor(self):
+ """Return selected material factor from main app or fallback to 1.15."""
+ try:
+ return float(self.app._new_material_factor.get())
+ except Exception:
+ pass
+ try:
+ return float(self._initial_calc_obj.Plate.mat_factor)
+ except Exception:
+ return 1.15
+
+ def _apply_material_factor_to_structure(self, obj, mat_fac):
+ """Apply selected material factor to Plate/Stiffener/Girder before optimization."""
+ try:
+ mat_fac = float(mat_fac)
+ except Exception:
+ return obj
+
+ for attr_name in ('Plate', 'Stiffener', 'Girder'):
+ try:
+ part = getattr(obj, attr_name)
+ except Exception:
+ part = None
+ if part is not None:
+ try:
+ part.mat_factor = mat_fac
+ except Exception:
+ pass
+ return obj
+
+ def _load_pickle_first_existing(self, file_bases):
+ """Load the first existing pickle from one or more base filenames."""
+ if isinstance(file_bases, str):
+ file_bases = (file_bases,)
+
+ for file_base in file_bases:
+ file_name = file_base if file_base.endswith('.pickle') else file_base + '.pickle'
+ if os.path.isfile(file_name):
+ with open(file_name, 'rb') as file:
+ return pickle.load(file)
+ return None
+
def __init__(self, master, app=None):
super(CreateOptGeoWindow, self).__init__()
if __name__ == '__main__':
@@ -40,11 +116,10 @@ def __init__(self, master, app=None):
self._point_dict = test.get_point_dict()
self._canvas_scale = 20
-
self._line_to_struc = test.get_line_to_struc()
self._opt_frames = {}
- self._active_points = ['point1','point4','point8','point5']
+ self._active_points = ['point1', 'point4', 'point8', 'point5']
self._ML_buckling = dict() # Buckling machine learning algorithm
for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
'cl SP ult int predictor', 'cl SP ult int scaler',
@@ -56,31 +131,121 @@ def __init__(self, master, app=None):
'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler',
'CSR predictor', 'CSR scaler'
],
- ["ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
- "CL_CSR-Tank_req_cl_predictor",
- "CL_CSR-Tank_req_cl_UP_scaler",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
- self._ML_buckling[name] = None
- if os.path.isfile(file_base + '.pickle'):
- file = open(file_base + '.pickle', 'rb')
- self._ML_buckling[name] = pickle.load(file)
- file.close()
+ ["ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
+ "CLPIPE_CL_CSR-Tank_req_cl_predictor",
+ "CLPIPE_CL_CSR-Tank_req_cl_UP_scaler",
+ "CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
+ "CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
+ self._ML_buckling[name] = None
+ if os.path.isfile(file_base + '.pickle'):
+ file = open(file_base + '.pickle', 'rb')
+ self._ML_buckling[name] = pickle.load(file)
+ file.close()
+
+ # Numeric UF pipeline models. These are optional in standalone mode.
+ numeric_model_specs = (
+ ('num SP int validity predictor', (
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num SP int validity xscaler', (
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num SP int UF reg predictor', (
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num SP int UF reg xscaler', (
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num SP int UF reg yscaler', (
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+
+ ('num SP GLGT validity predictor', (
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num SP GLGT validity xscaler', (
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num SP GLGT UF reg predictor', (
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num SP GLGT UF reg xscaler', (
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num SP GLGT UF reg yscaler', (
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+
+ ('num UP int validity predictor', (
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num UP int validity xscaler', (
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num UP int UF reg predictor', (
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num UP int UF reg xscaler', (
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+ ('num UP int UF reg yscaler', (
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1',
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1',
+ )),
+
+ ('num UP GLGT validity predictor', (
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num UP GLGT validity xscaler', (
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num UP GLGT UF reg predictor', (
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num UP GLGT UF reg xscaler', (
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ ('num UP GLGT UF reg yscaler', (
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3',
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3',
+ )),
+ )
+
+ for name, file_bases in numeric_model_specs:
+ self._ML_buckling[name] = self._load_pickle_first_existing(file_bases)
else:
self.app = app
self._load_objects = app._load_dict
@@ -95,7 +260,6 @@ def __init__(self, master, app=None):
self._root_dir = app._root_dir
self._ML_buckling = app._ML_buckling
-
self._opt_structure = {}
self._opt_frames_obj = []
self._frame = master
@@ -103,7 +267,7 @@ def __init__(self, master, app=None):
self._frame.geometry('1800x950')
self._frame.grab_set()
self._canvas_origo = (50, 720 - 50)
-
+
self._canvas_base_origo = self._canvas_origo
self._canvas_draw_origo = list(self._canvas_base_origo)
self._previous_drag_mouse = list(self._canvas_draw_origo)
@@ -123,12 +287,11 @@ def __init__(self, master, app=None):
tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=95)
tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=135)
- algorithms = ('anysmart',' ')
+ algorithms = ('anysmart', ' ')
tk.Label(self._frame, text='-- Plate field span optimizer for plate fields separated by frames. --',
font='Verdana 15 bold').place(x=10, y=10)
-
# upper and lower bounds for optimization
# [0.6, 0.012, 0.3, 0.01, 0.1, 0.01]
self._new_spacing_upper = tk.DoubleVar()
@@ -173,7 +336,6 @@ def __init__(self, master, app=None):
self._new_option_fraction = tk.IntVar()
self._new_option_panel = tk.IntVar()
-
ent_w = 10
self._ent_spacing_upper = tk.Entry(self._frame, textvariable=self._new_spacing_upper, width=ent_w)
self._ent_spacing_lower = tk.Entry(self._frame, textvariable=self._new_spacing_lower, width=ent_w)
@@ -219,21 +381,20 @@ def __init__(self, master, app=None):
width=ent_w)
self._ent_opt_girder_scale_high = tk.Entry(self._frame, textvariable=self._new_opt_girder_scale_high,
- width=int(ent_w/2))
+ width=int(ent_w / 2))
self._ent_opt_girder_scale_low = tk.Entry(self._frame, textvariable=self._new_opt_girder_scale_low,
- width=int(ent_w/2))
+ width=int(ent_w / 2))
self._ent_opt_max_span = tk.Entry(self._frame, textvariable=self._new_opt_span_max,
- width=int(ent_w/2))
+ width=int(ent_w / 2))
self._ent_opt_min_span = tk.Entry(self._frame, textvariable=self._new_opt_span_min,
- width=int(ent_w/2))
-
+ width=int(ent_w / 2))
start_x, start_y, dx, dy = 20, 70, 100, 40
- tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg = 'silver')\
+ tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg='silver') \
.place(x=start_x + 8 * dx, y=start_y + 0.5 * dy)
- tk.Entry(self._frame, textvariable=self._new_processes, width = 12, bg = 'silver')\
+ tk.Entry(self._frame, textvariable=self._new_processes, width=12, bg='silver') \
.place(x=start_x + 8 * dx, y=start_y + 1.4 * dy)
self._prop_canvas_dim = (500, 450)
@@ -273,8 +434,8 @@ def __init__(self, master, app=None):
y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Estimated running time for algorithm not calculated.',
font='Verdana 9 bold').place(x=start_x, y=start_y + 2.8 * dy)
- tk.Label(self._frame, text='- Harmonize stiffener spacing for section.', font='Verdana 9 bold')\
- .place(x=start_x + 5*dx, y=start_y + 2.8 * dy)
+ tk.Label(self._frame, text='- Harmonize stiffener spacing for section.', font='Verdana 9 bold') \
+ .place(x=start_x + 5 * dx, y=start_y + 2.8 * dy)
# self._runnig_time_label = tk.Label(self._frame, text='', font='Verdana 9 bold')
# self._runnig_time_label.place(x=start_x + 2.7 * dx, y=start_y + 2.8 * dy)
# tk.Label(self._frame, text='seconds ', font='Verdana 9 bold').place(x=start_x + 3.3 * dx, y=start_y + 2.8 * dy)
@@ -363,9 +524,8 @@ def __init__(self, master, app=None):
# self._new_algorithm_random_trials.trace('w', self.update_running_time)
# self._new_algorithm.trace('w', self.update_running_time)
-
self.running_time_per_item = 4e-05
- #self._runnig_time_label.config(text=str(self.get_running_time()))
+ # self._runnig_time_label.config(text=str(self.get_running_time()))
self._ent_algorithm.place(x=start_x + dx * 10, y=start_y + dy)
self.algorithm_random_label = tk.Label(self._frame, text='Number of trials')
tk.Button(self._frame, text='algorith information', command=self.algorithm_info, bg='white') \
@@ -379,10 +539,9 @@ def __init__(self, master, app=None):
# command=self.save_and_close, bg='green', font='Verdana 10 bold', fg='yellow')
# self.close_and_save.place(x=start_x + dx * 10, y=10)
-
tk.Button(self._frame, text='Open predefined stiffeners example',
- command=self.open_example_file, bg='white', font='Verdana 10')\
- .place(x=start_x+dx*10,y=10)
+ command=self.open_example_file, bg='white', font='Verdana 10') \
+ .place(x=start_x + dx * 10, y=10)
# Selection of constraints
self._new_check_sec_mod = tk.BooleanVar()
@@ -393,7 +552,9 @@ def __init__(self, master, app=None):
self._new_check_slamming = tk.BooleanVar()
self._new_check_local_buckling = tk.BooleanVar()
self._new_harmonize_spacing = tk.BooleanVar()
+ self._new_check_buckling_semi_analytical = tk.BooleanVar()
self._new_check_buckling_ml_cl = tk.BooleanVar()
+ self._new_check_buckling_ml_numeric = tk.BooleanVar()
self._new_check_sec_mod.set(True)
self._new_check_min_pl_thk.set(True)
@@ -405,32 +566,40 @@ def __init__(self, master, app=None):
self._new_option_fraction.set(None)
self._new_option_panel.set(None)
self._new_harmonize_spacing.set(False)
+ self._new_check_buckling_semi_analytical.set(False)
self._new_check_buckling_ml_cl.set(False)
+ self._new_check_buckling_ml_numeric.set(False)
+ self._new_check_buckling_semi_analytical.trace('w', self.update_running_time)
self._new_check_buckling_ml_cl.trace('w', self.update_running_time)
+ self._new_check_buckling_ml_numeric.trace('w', self.update_running_time)
- start_y, start_x, dy = 570, 100, 25
- tk.Label(self._frame,text='Check for minimum section modulus').place(x=start_x+dx*9.7,y=start_y+4*dy)
- tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x+dx*9.7,y=start_y+5*dy)
- tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x+dx*9.7,y=start_y+6*dy)
- tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x+dx*9.7,y=start_y+7*dy)
+ start_y, start_x, dy = 570, 100, 25
+ tk.Label(self._frame, text='Check for minimum section modulus').place(x=start_x + dx * 9.7, y=start_y + 4 * dy)
+ tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x + dx * 9.7, y=start_y + 5 * dy)
+ tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x + dx * 9.7, y=start_y + 6 * dy)
+ tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x + dx * 9.7, y=start_y + 7 * dy)
tk.Label(self._frame, text='Check for fatigue (RP-C203)').place(x=start_x + dx * 9.7, y=start_y + 8 * dy)
tk.Label(self._frame, text='Check for bow slamming').place(x=start_x + dx * 9.7, y=start_y + 9 * dy)
tk.Label(self._frame, text='Check for local stf. buckling').place(x=start_x + dx * 9.7, y=start_y + 10 * dy)
- tk.Label(self._frame, text='Check for buckling, ML-CL').place(x=start_x + dx * 9.7, y=start_y + 11 * dy)
+ tk.Label(self._frame, text='Check for buckling, SemiAnalytical S3/U3').place(x=start_x + dx * 9.7,
+ y=start_y + 11 * dy)
+ tk.Label(self._frame, text='Check for buckling, ML-CL deactivated').place(x=start_x + dx * 9.7, y=start_y + 12 * dy)
+ tk.Label(self._frame, text='Check for buckling, ML-Numeric').place(x=start_x + dx * 9.7,
+ y=start_y + 13 * dy)
- tk.Label(self._frame, text='Frame (girder data) for weight calculation:', font = 'Verdana 9 bold')\
+ tk.Label(self._frame, text='Frame (girder data) for weight calculation:', font='Verdana 9 bold') \
.place(x=start_x + dx * 13,
- y=start_y + 4 * dy)
+ y=start_y + 4 * dy)
tk.Label(self._frame, text='Girder thickness').place(x=start_x + dx * 13, y=start_y + 5 * dy)
tk.Label(self._frame, text='Stiffener height').place(x=start_x + dx * 13, y=start_y + 6 * dy)
tk.Label(self._frame, text='Stiffener thickness').place(x=start_x + dx * 13, y=start_y + 7 * dy)
tk.Label(self._frame, text='Stf. flange width').place(x=start_x + dx * 13, y=start_y + 8 * dy)
tk.Label(self._frame, text='Stf. flange thickenss').place(x=start_x + dx * 13, y=start_y + 9 * dy)
- tk.Label(self._frame, text='For weight calculation of girder: Max span mult / Min span mult')\
- .place(x=start_x + dx * 13,y=start_y + 10 * dy)
- tk.Label(self._frame, text='Maximum span / Minimum span ->')\
- .place(x=start_x + dx * 13,y=start_y + 12 * dy)
+ tk.Label(self._frame, text='For weight calculation of girder: Max span mult / Min span mult') \
+ .place(x=start_x + dx * 13, y=start_y + 10 * dy)
+ tk.Label(self._frame, text='Maximum span / Minimum span ->') \
+ .place(x=start_x + dx * 13, y=start_y + 14 * dy)
self._ent_opt_girder_thk.place(x=start_x + dx * 15, y=start_y + 5 * dy)
self._ent_opt_girder_stf_web_h.place(x=start_x + dx * 15, y=start_y + 6 * dy)
@@ -442,19 +611,24 @@ def __init__(self, master, app=None):
self._ent_opt_max_span.place(x=start_x + dx * 15, y=start_y + 12 * dy)
self._ent_opt_min_span.place(x=start_x + dx * 15.5, y=start_y + 12 * dy)
- tk.Checkbutton(self._frame,variable=self._new_check_sec_mod).place(x=start_x+dx*12,y=start_y+4*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x+dx*12,y=start_y+5*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x+dx*12,y=start_y+6*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x+dx*12,y=start_y+7*dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_sec_mod).place(x=start_x + dx * 12, y=start_y + 4 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x + dx * 12, y=start_y + 5 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x + dx * 12, y=start_y + 6 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x + dx * 12, y=start_y + 7 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_fatigue).place(x=start_x + dx * 12, y=start_y + 8 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_slamming).place(x=start_x + dx * 12, y=start_y + 9 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_local_buckling).place(x=start_x + dx * 12,
y=start_y + 10 * dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_cl).place(x=start_x + dx * 12,
- y=start_y + 11 * dy)
-
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_semi_analytical).place(x=start_x + dx * 12,
+ y=start_y + 11 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_cl, state='disabled').place(
+ x=start_x + dx * 12,
+ y=start_y + 12 * dy,
+ )
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_numeric).place(x=start_x + dx * 12,
+ y=start_y + 13 * dy)
- tk.Checkbutton(self._frame, variable=self._new_harmonize_spacing).place(x=start_x + 3.9*dx, y=180)
+ tk.Checkbutton(self._frame, variable=self._new_harmonize_spacing).place(x=start_x + 3.9 * dx, y=180)
# Stress scaling
self._new_fup = tk.DoubleVar()
@@ -462,49 +636,48 @@ def __init__(self, master, app=None):
self._new_fdwn = tk.DoubleVar()
self._new_fdwn.set(1)
- tk.Label(self._frame, text='Factor when scaling stresses up, fup')\
- .place(x=start_x + dx * 9.7, y=start_y + 12 * dy)
- ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width = 5)
- ent_fup.place(x=start_x + dx * 12, y=start_y + 12 * dy)
- tk.Label(self._frame, text='Factor when scaling stresses up, fdown')\
- .place(x=start_x + dx * 9.7, y=start_y + 13 * dy)
- ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width = 5)
- ent_fdwn.place(x=start_x + dx * 12, y=start_y + 13 * dy)
+ tk.Label(self._frame, text='Factor when scaling stresses up, fup') \
+ .place(x=start_x + dx * 9.7, y=start_y + 14 * dy)
+ ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width=5)
+ ent_fup.place(x=start_x + dx * 12, y=start_y + 14 * dy)
+ tk.Label(self._frame, text='Factor when scaling stresses up, fdown') \
+ .place(x=start_x + dx * 9.7, y=start_y + 15 * dy)
+ ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width=5)
+ ent_fdwn.place(x=start_x + dx * 12, y=start_y + 15 * dy)
self._toggle_btn = tk.Button(self._frame, text="Iterate predefiened stiffeners", relief="raised",
- command=self.toggle, bg = 'salmon')
- self._toggle_btn.place(x=start_x+dx*10.5, y=start_y - dy * 16.8)
+ command=self.toggle, bg='salmon')
+ self._toggle_btn.place(x=start_x + dx * 10.5, y=start_y - dy * 16.8)
self._toggle_object, self._filez = None, None
- self._options_fractions = (None, )
- self._options_panels = (None, )
- tk.Label(self._frame, text='Select number of panels:').place(x=start_x+dx*12, y=start_y - dy * 20.5)
- tk.Label(self._frame, text='Select panel to plot: ').place(x=start_x+dx*12, y=start_y - dy * 19.5)
+ self._options_fractions = (None,)
+ self._options_panels = (None,)
+ tk.Label(self._frame, text='Select number of panels:').place(x=start_x + dx * 12, y=start_y - dy * 20.5)
+ tk.Label(self._frame, text='Select panel to plot: ').place(x=start_x + dx * 12, y=start_y - dy * 19.5)
self._ent_option_fractions = tk.OptionMenu(self._frame, self._new_option_fraction, *self._options_fractions,
command=self.get_plate_field_options)
self._ent_option_field = tk.OptionMenu(self._frame, self._new_option_panel, *self._options_panels,
command=self.get_plate_field_options)
- self._option_fractions_place = [start_x+dx*13.5, start_y - dy * 20.5]
- self._options_panels_place = [start_x+dx*13.5, start_y - dy * 19.5]
+ self._option_fractions_place = [start_x + dx * 13.5, start_y - dy * 20.5]
+ self._options_panels_place = [start_x + dx * 13.5, start_y - dy * 19.5]
self._ent_option_fractions.place(x=self._option_fractions_place[0], y=self._option_fractions_place[1])
self._ent_option_field.place(x=self._options_panels_place[0], y=self._options_panels_place[1])
- self.run_results = tk.Button(self._frame,text='show calculated', command=self.plot_results, bg='white',
- font='Verdana 10',fg='black')
- self.run_results.place(x=start_x+dx*13, y=start_y - dy * 18)
-
- self.run_results_prev = tk.Button(self._frame,text='Show previous\n'
- 'results', command=self.show_previous_results, bg='white',
- font='Verdana 10',fg='black')
- self.run_results_prev.place(x=start_x+dx*15, y=start_y - dy * 20)
+ self.run_results = tk.Button(self._frame, text='show calculated', command=self.plot_results, bg='white',
+ font='Verdana 10', fg='black')
+ self.run_results.place(x=start_x + dx * 13, y=start_y - dy * 18)
+ self.run_results_prev = tk.Button(self._frame, text='Show previous\n'
+ 'results', command=self.show_previous_results, bg='white',
+ font='Verdana 10', fg='black')
+ self.run_results_prev.place(x=start_x + dx * 15, y=start_y - dy * 20)
# ----------------------------------END OF OPTIMIZE SINGLE COPY-----------------------------------------------
self.progress_count = tk.IntVar()
self.progress_count.set(0)
- self.progress_bar = Progressbar(self._frame, orient="horizontal",length=200, mode="determinate",
+ self.progress_bar = Progressbar(self._frame, orient="horizontal", length=200, mode="determinate",
variable=self.progress_count)
- #self.progress_bar.place(x=start_x+dx*10.5,y=start_y-dy*16.5)
+ # self.progress_bar.place(x=start_x+dx*10.5,y=start_y-dy*16.5)
self._active_lines = []
self.controls()
@@ -589,7 +762,7 @@ def show_previous_results(self):
# return
self.draw_select_canvas(opt_results=self._geo_results)
- def run_optimizaion(self, load_pre = False, save_results = False, harmonize = False):
+ def run_optimizaion(self, load_pre=False, save_results=False, harmonize=False):
'''
Function when pressing the optimization botton inside this window.
:return:
@@ -602,34 +775,39 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
contraints = (self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
self._new_check_shear_area.get(), self._new_check_buckling.get(),
self._new_check_fatigue.get(), self._new_check_slamming.get(),
- self._new_check_local_buckling.get(), False, self._new_check_buckling_ml_cl.get(), False)
+ self._new_check_local_buckling.get(), self._new_check_buckling_semi_analytical.get(),
+ False,
+ self._new_check_buckling_ml_numeric.get())
+
+ selected_ml_algo = self._get_selected_ml_buckling()
+ selected_mat_fac = self._get_selected_material_factor()
self.pso_parameters = (self._new_swarm_size.get(), self._new_omega.get(), self._new_phip.get(),
- self._new_phig.get(),self._new_maxiter.get(), self._new_minstep.get(),
+ self._new_phig.get(), self._new_maxiter.get(), self._new_minstep.get(),
self._new_minfunc.get())
opt_girder_prop = (self._new_opt_girder_thk.get(), self._new_opt_girder_stf_web_h.get(),
self._new_opt_girder_stf_web_thk.get(), self._new_opt_girder_stf_flange_b.get(),
- self._new_opt_girder_stf_flange_thk.get(),self._new_opt_girder_scale_high.get(),
+ self._new_opt_girder_stf_flange_thk.get(), self._new_opt_girder_scale_high.get(),
self._new_opt_girder_scale_low.get())
min_max_span = (self._new_opt_span_min.get(), self._new_opt_span_max.get())
-
init_objects, fatigue_objects, fat_press_ext_int, slamming_pressures, lateral_press, fatigue_objects, \
- slamming_press = [list() for dummy in range(7)]
+ slamming_press = [list() for dummy in range(7)]
broke = False
pressure_side = 'both sides' # default value
- for line,coord in self._opt_structure.items():
+ for line, coord in self._opt_structure.items():
if self.opt_create_struc_obj(self._opt_structure[line]) is None:
broke = True
break
else:
- init_objects.append(self.opt_create_struc_obj(self._opt_structure[line])[0])
+ init_obj_single = self.opt_create_struc_obj(self._opt_structure[line])[0]
+ self._apply_material_factor_to_structure(init_obj_single, selected_mat_fac)
+ init_objects.append(init_obj_single)
fat_obj_single = self.opt_create_struc_obj(self._opt_structure[line])[2]
fatigue_objects.append(fat_obj_single)
-
if __name__ == '__main__':
import example_data as ex
lateral_press.append(0.2) # for testing
@@ -645,17 +823,18 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
else:
p1, p2 = self._opt_structure[line]
# Check if line is horizontal or vertical
- if p2[0] == p1[0]: # Vertical
- to_find = [p2[0], min(p2[1], p1[1]) + abs((p2[1]-p1[1])*0.5)]
- elif p2[1] == p1[1]: # Horizontal
- to_find = [min(p2[0], p1[0])+ (p2[0]-p1[0])*0.5, p2[1]]
- else: # Other orientations
- to_find = [min(p2[0],p1[0])+abs((p2[0]-p1[0])*0.5), min(p2[1]-p1[1])+abs((p2[1]-p1[1])*0.5)]
+ if p2[0] == p1[0]: # Vertical
+ to_find = [p2[0], min(p2[1], p1[1]) + abs((p2[1] - p1[1]) * 0.5)]
+ elif p2[1] == p1[1]: # Horizontal
+ to_find = [min(p2[0], p1[0]) + (p2[0] - p1[0]) * 0.5, p2[1]]
+ else: # Other orientations
+ to_find = [min(p2[0], p1[0]) + abs((p2[0] - p1[0]) * 0.5),
+ min(p2[1] - p1[1]) + abs((p2[1] - p1[1]) * 0.5)]
# Taking properites from the closest line.
closet_line = self.opt_find_closest_orig_line(to_find)
- pressure_side = self._line_to_struc[closet_line].overpressure_side
- #print('Closest line', closet_line, p1, p2, to_find)
+ pressure_side = self._line_overpressure_side(closet_line)
+ # print('Closest line', closet_line, p1, p2, to_find)
gotten_lat_press = self.app.get_highest_pressure(closet_line)
lateral_press.append(gotten_lat_press['normal'] / 1e6)
slamming_press.append(gotten_lat_press['slamming'])
@@ -667,7 +846,7 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
fat_press_single['p_int']['part']))
fat_press_ext_int.append(fat_press_tuple)
else:
- fat_press_ext_int.append(((0, 0, 0), (0, 0,0)))
+ fat_press_ext_int.append(((0, 0, 0), (0, 0, 0)))
# except AttributeError:
# print('AttributeError')
@@ -685,7 +864,6 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
deltas[0])
resulting_geo = list()
-
if self._new_harmonize_spacing.get():
geo_results = dict()
for spacing in spacings:
@@ -694,29 +872,30 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
this_max_var = copy.deepcopy(max_var)
this_max_var[0] = spacing
- geo_results = op.run_optmizataion(initial_structure_obj=init_objects,min_var=this_min_var,
- max_var=this_max_var,lateral_pressure=lateral_press,
- deltas=self.get_deltas(), algorithm='anysmart',side=pressure_side,
- const_chk = contraints,pso_options = self.pso_parameters,
- is_geometric=True,fatigue_obj= fatigue_objects,
+ geo_results = op.run_optmizataion(initial_structure_obj=init_objects, min_var=this_min_var,
+ max_var=this_max_var, lateral_pressure=lateral_press,
+ deltas=self.get_deltas(), algorithm='anysmart',
+ side=pressure_side,
+ const_chk=contraints, pso_options=self.pso_parameters,
+ is_geometric=True, fatigue_obj=fatigue_objects,
fat_press_ext_int=fat_press_ext_int,
min_max_span=min_max_span, tot_len=self.opt_get_length(),
- frame_height=self.opt_get_distance(), frame_distance = distances,
+ frame_height=self.opt_get_distance(), frame_distance=distances,
predefined_stiffener_iter=self._filez,
- processes = self._new_processes.get(),
+ processes=self._new_processes.get(),
slamming_press=slamming_press, opt_girder_prop=opt_girder_prop,
- fdwn = self._new_fdwn.get(), fup = self._new_fdwn.get(),
- ml_algo=self._ML_buckling)
+ fdwn=self._new_fdwn.get(), fup=self._new_fup.get(),
+ ml_algo=selected_ml_algo,
+ material_factor=selected_mat_fac)
resulting_geo.append(geo_results)
- #need to find the lowest
+ # need to find the lowest
for fraction in resulting_geo[0].keys():
weight = float('inf')
best_idx = None
for idx, geo_res in enumerate(resulting_geo):
this_sub_fraction_weight = geo_res[fraction][0]
if this_sub_fraction_weight < weight:
-
best_idx = idx
weight = this_sub_fraction_weight
@@ -734,18 +913,20 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
predefined_stiffener_iter=self._filez,
processes=self._new_processes.get(),
slamming_press=slamming_press, opt_girder_prop=opt_girder_prop,
- fdwn=self._new_fdwn.get(), fup=self._new_fdwn.get())
+ fdwn=self._new_fdwn.get(), fup=self._new_fup.get(),
+ ml_algo=selected_ml_algo,
+ material_factor=selected_mat_fac)
self._geo_results = geo_results
- if len([val*2 for val in self._geo_results.keys()]) != 0:
+ if len([val * 2 for val in self._geo_results.keys()]) != 0:
self._ent_option_fractions.destroy()
self._ent_option_fractions = tk.OptionMenu(self._frame, self._new_option_fraction,
- *tuple([val*2 for val in self._geo_results.keys()]),
+ *tuple([val * 2 for val in self._geo_results.keys()]),
command=self.get_plate_field_options)
self._ent_option_fractions.place(x=self._option_fractions_place[0], y=self._option_fractions_place[1])
- #SAVING RESULTS
+ # SAVING RESULTS
if save_results:
with open('geo_opt_2.pickle', 'wb') as file:
pickle.dump(geo_results, file)
@@ -755,23 +936,23 @@ def run_optimizaion(self, load_pre = False, save_results = False, harmonize = Fa
self._ent_option_fractions.destroy()
self._ent_option_fractions = tk.OptionMenu(self._frame, self._new_option_fraction,
- *tuple([val*2 for val in self._geo_results.keys()]),
+ *tuple([val * 2 for val in self._geo_results.keys()]),
command=self.get_plate_field_options)
self._ent_option_fractions.place(x=self._option_fractions_place[0], y=self._option_fractions_place[1])
save_file, filename = None, None
if save_results:
- save_file = filedialog.asksaveasfile(mode="w", defaultextension=".txt", title = 'Save results to file')
+ save_file = filedialog.asksaveasfile(mode="w", defaultextension=".txt", title='Save results to file')
if save_file is None: # ask saveasfile return `None` if dialog closed with "cancel".
filename = None
else:
filename = save_file.name
save_file, xplot, yplot = self.draw_result_text(self._geo_results, save_to_file=filename)
- self.draw_select_canvas(opt_results=self._geo_results, save_file = save_file)
+ self.draw_select_canvas(opt_results=self._geo_results, save_file=save_file)
plt.axes(facecolor='lightslategray')
- plt.plot(xplot, yplot,color='yellow', linestyle='solid', marker='o',markerfacecolor='white', markersize=6)
+ plt.plot(xplot, yplot, color='yellow', linestyle='solid', marker='o', markerfacecolor='white', markersize=6)
plt.xlabel('Length of plate fields [m]')
plt.ylabel('Weight / max weight')
plt.title('Length of plate fields vs. total weight')
@@ -788,45 +969,44 @@ def opt_get_fractions(self):
break
to_return = []
for dummy_i in range(init_fractions):
- to_return.append(1/init_fractions)
+ to_return.append(1 / init_fractions)
return to_return
- def opt_create_struc_obj(self,opt_line):
- ''' Creating preliminary stucture object from selected optimized line.
+ def opt_create_struc_obj(self, opt_line):
+ ''' Creating preliminary stucture object from selected optimized line.
The properties of the new line oto be optimized is taken from the closest original line.'''
pt1 = opt_line[0]
pt2 = opt_line[1]
vector = [pt2[0] - pt1[0], pt2[1] - pt1[1]]
- point = [pt1[0]+vector[0]*0.5, pt1[1]+vector[1]*0.5]
+ point = [pt1[0] + vector[0] * 0.5, pt1[1] + vector[1] * 0.5]
if self.opt_find_closest_orig_line(point) == None:
return None
- objects = [copy.deepcopy(x) if x != None else None for x in
- self._line_to_struc[self.opt_find_closest_orig_line(point)]]
- objects[0].Plate.set_span(dist(pt1,pt2))
+ objects = self._copy_line_structure_bundle(self.opt_find_closest_orig_line(point))
+ objects[0].Plate.set_span(dist(pt1, pt2))
objects[0].Stiffener.set_span(dist(pt1, pt2))
return objects
- def opt_find_closest_orig_line(self,coord):
+ def opt_find_closest_orig_line(self, coord):
''' Find the closest original line to the optimized line.
Used to create initial structure objects. '''
- for key,value in self._line_dict.items():
+ for key, value in self._line_dict.items():
- pt1 = list(self._point_dict['point'+str(value[0])])
- pt2 = list(self._point_dict['point'+str(value[1])])
- distance = dist(pt2,pt1)
- vector = [pt2[0]-pt1[0],pt2[1]-pt1[1]]
- current = list(self._point_dict['point'+str(value[0])])
+ pt1 = list(self._point_dict['point' + str(value[0])])
+ pt2 = list(self._point_dict['point' + str(value[1])])
+ distance = dist(pt2, pt1)
+ vector = [pt2[0] - pt1[0], pt2[1] - pt1[1]]
+ current = list(self._point_dict['point' + str(value[0])])
for dummy_i in range(1000):
- delta = distance/1000
- current[0] += (vector[0]/distance) * delta
- current[1] += (vector[1]/distance) * delta
- if dist(coord,current) <= 0.1:
- if self._line_to_struc[key][0].Plate.get_structure_type() not in ('GENERAL_INTERNAL_NONWT', 'FRAME'):
+ delta = distance / 1000
+ current[0] += (vector[0] / distance) * delta
+ current[1] += (vector[1] / distance) * delta
+ if dist(coord, current) <= 0.1:
+ if self._line_structure_type(key) not in ('GENERAL_INTERNAL_NONWT', 'FRAME'):
return key
else:
return None
@@ -834,20 +1014,20 @@ def opt_find_closest_orig_line(self,coord):
def opt_get_distance(self):
''' Getting the largest disctance between the two lines to be optimized. '''
if len(self._active_points) == 4:
- return dist(self._point_dict[self._active_points[0]],self._point_dict[self._active_points[2]])
+ return dist(self._point_dict[self._active_points[0]], self._point_dict[self._active_points[2]])
else:
return None
def opt_get_length(self):
''' Getting the length of the lines to be optimized. '''
- if len(self._active_points)==4:
- return dist(self._point_dict[self._active_points[0]],self._point_dict[self._active_points[1]])
+ if len(self._active_points) == 4:
+ return dist(self._point_dict[self._active_points[0]], self._point_dict[self._active_points[1]])
- def opt_get_fraction_bounds(self, max_len = 6, min_len = 2):
+ def opt_get_fraction_bounds(self, max_len=6, min_len=2):
''' Return the fraction bounds(basis upper/lower) to be considered. '''
- return int(self.opt_get_length()/max_len), int(self.opt_get_length()/min_len)
+ return int(self.opt_get_length() / max_len), int(self.opt_get_length() / min_len)
- def opt_create_frames(self,fractions):
+ def opt_create_frames(self, fractions):
''' Creating frames between the the two lines to be optimized. '''
count = 1
@@ -861,73 +1041,73 @@ def opt_create_frames(self,fractions):
[self._point_dict[self._active_points[3]][0],
self._point_dict[self._active_points[3]][1]]]
- start = 0
+ start = 0
for fraction in fractions:
start += fraction
if start != 1:
- self._opt_frames['opt_frame'+str(count)] = [[self._point_dict[self._active_points[0]][0] +
- round(self.opt_get_length()*start,5),
- self._point_dict[self._active_points[0]][1]],
- [self._point_dict[self._active_points[2]][0] +
- round(self.opt_get_length() * start,5),
- self._point_dict[self._active_points[2]][1]]]
- count+=1
+ self._opt_frames['opt_frame' + str(count)] = [[self._point_dict[self._active_points[0]][0] +
+ round(self.opt_get_length() * start, 5),
+ self._point_dict[self._active_points[0]][1]],
+ [self._point_dict[self._active_points[2]][0] +
+ round(self.opt_get_length() * start, 5),
+ self._point_dict[self._active_points[2]][1]]]
+ count += 1
distances = {'start_dist': dist(self._opt_frames['opt_frame_start'][0], self._opt_frames['opt_frame_start'][1]),
'stop_dist': dist(self._opt_frames['opt_frame_stop'][0], self._opt_frames['opt_frame_stop'][1])}
return self._opt_frames, distances
- def opt_create_main_structure(self,frames,start1,stop1,start2,stop2):
+ def opt_create_main_structure(self, frames, start1, stop1, start2, stop2):
''' This creates line definition for the new structure objects.
The scipt searches the line to find frames.'''
- line1_coord = self._point_dict[start1],self._point_dict[stop1]
- line2_coord = self._point_dict[start2],self._point_dict[stop2]
+ line1_coord = self._point_dict[start1], self._point_dict[stop1]
+ line2_coord = self._point_dict[start2], self._point_dict[stop2]
structure = {}
-
- p1_low,p1_high = list(line1_coord[0]),list(line2_coord[0])
- p2_low,p2_high = list(line1_coord[1]),list(line2_coord[1])
- vector_low,vector_high = [p2_low[0]-p1_low[0],p2_low[1]-p1_low[1]],[p2_high[0]-p1_high[0],p2_high[1]-p1_high[1]]
+ p1_low, p1_high = list(line1_coord[0]), list(line2_coord[0])
+ p2_low, p2_high = list(line1_coord[1]), list(line2_coord[1])
+ vector_low, vector_high = [p2_low[0] - p1_low[0], p2_low[1] - p1_low[1]], [p2_high[0] - p1_high[0],
+ p2_high[1] - p1_high[1]]
# Starting search on the lower or inner line
count = 1
- tmp_struc = [p1_low] # starting point defined.
+ tmp_struc = [p1_low] # starting point defined.
found = None
for frame, coords in frames.items():
current = list(p1_low)
- if frame!='opt_frame_start' and frame!='opt_frame_stop':
+ if frame != 'opt_frame_start' and frame != 'opt_frame_stop':
for jump in range(100):
current[0] += vector_low[0] / 100
current[1] += vector_low[1] / 100
- if dist(current,coords[0]) < 0.1 and frame != found:
+ if dist(current, coords[0]) < 0.1 and frame != found:
found = frame
tmp_struc.append(coords[0])
- self._opt_structure['opt_struc'+str(count)] = tmp_struc # adding found line
+ self._opt_structure['opt_struc' + str(count)] = tmp_struc # adding found line
tmp_struc = [coords[0]]
count += 1
tmp_struc.append(p2_low)
- self._opt_structure['opt_struc'+str(count)] = tmp_struc # adding found line (end)
+ self._opt_structure['opt_struc' + str(count)] = tmp_struc # adding found line (end)
count += 1
# Starting search of upper or outer line.
- tmp_struc = [p1_high] # starting point defined.
+ tmp_struc = [p1_high] # starting point defined.
found = None
for frame, coords in frames.items():
current = list(p1_high)
- if frame!='opt_frame_start' and frame!='opt_frame_stop':
+ if frame != 'opt_frame_start' and frame != 'opt_frame_stop':
for jump in range(100):
current[0] += vector_high[0] / 100
current[1] += vector_high[1] / 100
- if dist(current,coords[1]) < 0.1 and frame != found:
+ if dist(current, coords[1]) < 0.1 and frame != found:
found = frame
tmp_struc.append(coords[1])
- self._opt_structure['opt_struc'+str(count)] = tmp_struc # adding found line
+ self._opt_structure['opt_struc' + str(count)] = tmp_struc # adding found line
tmp_struc = [coords[1]]
count += 1
tmp_struc.append(p2_high)
- self._opt_structure['opt_struc'+str(count)] = tmp_struc # adding found line (end)
+ self._opt_structure['opt_struc' + str(count)] = tmp_struc # adding found line (end)
return self._opt_structure
@@ -978,7 +1158,27 @@ def update_running_time(self, *args):
# except ZeroDivisionError:
# pass # _tkinter.TclError: pass
- if self._new_check_buckling_ml_cl.get():
+ selected_buckling_checks = [
+ self._new_check_buckling.get(),
+ self._new_check_buckling_semi_analytical.get(),
+ False,
+ self._new_check_buckling_ml_numeric.get(),
+ ]
+
+ if selected_buckling_checks.count(True) > 1:
+ messagebox.showerror('You can only select one buckling type. Reselect.')
+
+ if self._new_check_buckling.get():
+ self._new_check_buckling.set(False)
+ self._new_check_local_buckling.set(False)
+
+ if self._new_check_buckling_semi_analytical.get():
+ self._new_check_buckling_semi_analytical.set(False)
+
+ if self._new_check_buckling_ml_numeric.get():
+ self._new_check_buckling_ml_numeric.set(False)
+
+ elif (self._new_check_buckling_semi_analytical.get() or self._new_check_buckling_ml_numeric.get()):
self._new_check_buckling.set(False)
self._new_check_local_buckling.set(False)
@@ -1062,14 +1262,14 @@ def draw_properties(self, init_obj=None, opt_obj=None, line=None):
ctr_y - m * (init_obj.get_pl_thk() + init_obj.get_web_h()),
ctr_x + m * init_obj.get_fl_w() / 2,
ctr_y - m * (
- init_obj.get_pl_thk() + init_obj.get_web_h() + init_obj.get_fl_thk()),
+ init_obj.get_pl_thk() + init_obj.get_web_h() + init_obj.get_fl_thk()),
fill=init_color, stipple=init_stipple)
else:
self._canvas_opt.create_rectangle(ctr_x - m * init_obj.get_web_thk() / 2,
ctr_y - m * (init_obj.get_pl_thk() + init_obj.get_web_h()),
ctr_x + m * init_obj.get_fl_w(),
ctr_y - m * (
- init_obj.get_pl_thk() + init_obj.get_web_h() + init_obj.get_fl_thk()),
+ init_obj.get_pl_thk() + init_obj.get_web_h() + init_obj.get_fl_thk()),
fill=init_color, stipple=init_stipple)
if opt_obj != None:
@@ -1082,35 +1282,35 @@ def draw_properties(self, init_obj=None, opt_obj=None, line=None):
m * opt_obj.get_pl_thk(),
ctr_x + m * opt_obj.get_web_thk() / 2,
ctr_y - m * (
- opt_obj.get_web_h() + opt_obj.get_pl_thk())
+ opt_obj.get_web_h() + opt_obj.get_pl_thk())
, fill=opt_color, stipple=opt_stippe)
if init_obj.get_stiffener_type() not in ['L', 'L-bulb']:
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.get_fl_w() / 2, ctr_y
- m * (
- opt_obj.get_pl_thk() + opt_obj.get_web_h()),
+ opt_obj.get_pl_thk() + opt_obj.get_web_h()),
ctr_x + m * opt_obj.get_fl_w() / 2, ctr_y -
m * (
- opt_obj.get_pl_thk() + opt_obj.get_web_h() +
- opt_obj.get_fl_thk()),
+ opt_obj.get_pl_thk() + opt_obj.get_web_h() +
+ opt_obj.get_fl_thk()),
fill=opt_color, stipple=opt_stippe)
else:
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.get_web_thk() / 2, ctr_y
- m * (
- opt_obj.get_pl_thk() + opt_obj.get_web_h()),
+ opt_obj.get_pl_thk() + opt_obj.get_web_h()),
ctr_x + m * opt_obj.get_fl_w(), ctr_y -
m * (
- opt_obj.get_pl_thk() + opt_obj.get_web_h() +
- opt_obj.get_fl_thk()),
+ opt_obj.get_pl_thk() + opt_obj.get_web_h() +
+ opt_obj.get_fl_thk()),
fill=opt_color, stipple=opt_stippe)
self._canvas_opt.create_line(10, 50, 30, 50, fill=opt_color, width=5)
self._canvas_opt.create_text(270, 50,
- text='Optimized - Pl.: ' + str(round(opt_obj.get_s() * 1000,1)) + 'x' +
- str(round(opt_obj.get_pl_thk() * 1000,1)) + ' Stf.: '
- + str(round(opt_obj.get_web_h() * 1000,1)) +
- 'x' + str(round(opt_obj.get_web_thk() * 1000,1)) + '+' +
- str(round(opt_obj.get_fl_w() * 1000,1)) +
- 'x' + str(round(opt_obj.get_fl_thk() * 1000,1)),
+ text='Optimized - Pl.: ' + str(round(opt_obj.get_s() * 1000, 1)) + 'x' +
+ str(round(opt_obj.get_pl_thk() * 1000, 1)) + ' Stf.: '
+ + str(round(opt_obj.get_web_h() * 1000, 1)) +
+ 'x' + str(round(opt_obj.get_web_thk() * 1000, 1)) + '+' +
+ str(round(opt_obj.get_fl_w() * 1000, 1)) +
+ 'x' + str(round(opt_obj.get_fl_thk() * 1000, 1)),
font='Verdana 8', fill=opt_color)
self._canvas_opt.create_text(120, 70, text='Weight (per Lg width): '
+ str(int(op.calc_weight([opt_obj.get_s(),
@@ -1129,12 +1329,12 @@ def draw_properties(self, init_obj=None, opt_obj=None, line=None):
if __name__ == '__main__':
lateral_press = 0.2 # for testing
else:
- lateral_press = self.app.get_highest_pressure(line)['normal'] /1e6
+ lateral_press = self.app.get_highest_pressure(line)['normal'] / 1e6
self._canvas_opt.create_text(250, self._prop_canvas_dim[1] - 10,
text='Lateral pressure: ' + str(lateral_press) + ' kPa',
font='Verdana 10 bold', fill='red')
- def draw_select_canvas(self, opt_results = None, save_file = None):
+ def draw_select_canvas(self, opt_results=None, save_file=None):
'''
Making the lines canvas.
:return:
@@ -1145,9 +1345,11 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
if opt_results is None:
# stippled lines and text.
- self._canvas_select.create_line(self._canvas_draw_origo[0], 0, self._canvas_draw_origo[0], self._select_canvas_dim[1],
+ self._canvas_select.create_line(self._canvas_draw_origo[0], 0, self._canvas_draw_origo[0],
+ self._select_canvas_dim[1],
stipple='gray50')
- self._canvas_select.create_line(0, self._canvas_draw_origo[1], self._select_canvas_dim[0], self._canvas_draw_origo[1],
+ self._canvas_select.create_line(0, self._canvas_draw_origo[1], self._select_canvas_dim[0],
+ self._canvas_draw_origo[1],
stipple='gray50')
self._canvas_select.create_text(self._canvas_draw_origo[0] - 30,
self._canvas_draw_origo[1] + 20, text='(0,0)',
@@ -1168,21 +1370,24 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
coord2 = self.get_point_canvas_coord('point' + str(value[1]))
vector = [coord2[0] - coord1[0], coord2[1] - coord1[1]]
# drawing a bold line if it is selected
- if self._line_to_struc[line][0].Plate.get_structure_type() not in ('GENERAL_INTERNAL_NONWT','FRAME'):
+ if self._line_structure_type(line) not in ('GENERAL_INTERNAL_NONWT', 'FRAME'):
if line in self._active_lines:
- self._canvas_select.create_line(coord1, coord2, width=6, fill=color,stipple='gray50')
- self._canvas_select.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 + 10,
+ self._canvas_select.create_line(coord1, coord2, width=6, fill=color, stipple='gray50')
+ self._canvas_select.create_text(coord1[0] + vector[0] / 2 + 5,
+ coord1[1] + vector[1] / 2 + 10,
text='Line ' + str(get_num(line)), font='Verdand 10 bold',
fill='red')
else:
- self._canvas_select.create_line(coord1, coord2, width=3, fill=color,stipple='gray25')
- self._canvas_select.create_text(coord1[0] - 20 + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 +
+ self._canvas_select.create_line(coord1, coord2, width=3, fill=color, stipple='gray25')
+ self._canvas_select.create_text(coord1[0] - 20 + vector[0] / 2 + 5,
+ coord1[1] + vector[1] / 2 +
- 10,text='line' + str(get_num(line)),font="Text 8", fill='black')
+ 10, text='line' + str(get_num(line)), font="Text 8",
+ fill='black')
if len(self._opt_frames) != 0:
- for key,value in self._opt_frames.items():
+ for key, value in self._opt_frames.items():
coord1 = self.get_canvas_coord(value[0])
coord2 = self.get_canvas_coord(value[1])
vector = [coord2[0] - coord1[0], coord2[1] - coord1[1]]
@@ -1190,7 +1395,7 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
else:
pass
- if len(self._active_points)>1:
+ if len(self._active_points) > 1:
color = 'blue'
coord1 = self.get_point_canvas_coord(self._active_points[0])
coord2 = self.get_point_canvas_coord(self._active_points[1])
@@ -1208,46 +1413,47 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
# drawing the point dictionary
- for key,value in self._point_dict.items():
+ for key, value in self._point_dict.items():
pt_size = 6
if key in self._active_points:
self._canvas_select.create_oval(self.get_point_canvas_coord(key)[0] - pt_size + 2,
- self.get_point_canvas_coord(key)[1] - pt_size + 2,
- self.get_point_canvas_coord(key)[0] + pt_size + 2,
- self.get_point_canvas_coord(key)[1] + pt_size + 2, fill='blue')
+ self.get_point_canvas_coord(key)[1] - pt_size + 2,
+ self.get_point_canvas_coord(key)[0] + pt_size + 2,
+ self.get_point_canvas_coord(key)[1] + pt_size + 2, fill='blue')
if self._active_points.index(key) == 0:
self._canvas_select.create_text(self.get_point_canvas_coord(key)[0] - 5,
self.get_point_canvas_coord(key)[1] - 14, text='START 1',
- font=text_type, fill = 'blue')
+ font=text_type, fill='blue')
elif self._active_points.index(key) == 1:
self._canvas_select.create_text(self.get_point_canvas_coord(key)[0] - 5,
self.get_point_canvas_coord(key)[1] - 14,
- text='STOP 1',font=text_type, fill='blue')
+ text='STOP 1', font=text_type, fill='blue')
elif self._active_points.index(key) == 2:
self._canvas_select.create_text(self.get_point_canvas_coord(key)[0] - 5,
self.get_point_canvas_coord(key)[1] - 14,
- text='START 2',font=text_type, fill='blue')
+ text='START 2', font=text_type, fill='blue')
elif self._active_points.index(key) == 3:
self._canvas_select.create_text(self.get_point_canvas_coord(key)[0] - 5,
self.get_point_canvas_coord(key)[1] - 14,
- text='STOP 2',font=text_type, fill='blue')
+ text='STOP 2', font=text_type, fill='blue')
else:
pass
else:
self._canvas_select.create_oval(self.get_point_canvas_coord(key)[0] - pt_size,
- self.get_point_canvas_coord(key)[1] - pt_size,
- self.get_point_canvas_coord(key)[0] + pt_size,
- self.get_point_canvas_coord(key)[1] + pt_size, fill='red')
+ self.get_point_canvas_coord(key)[1] - pt_size,
+ self.get_point_canvas_coord(key)[0] + pt_size,
+ self.get_point_canvas_coord(key)[1] + pt_size, fill='red')
self._canvas_select.create_text(self.get_point_canvas_coord(key)[0] - 5,
- self.get_point_canvas_coord(key)[1] - 14, text='pt.'+str(get_num(key)),
+ self.get_point_canvas_coord(key)[1] - 14,
+ text='pt.' + str(get_num(key)),
font='Verdana 8', fill='blue')
else:
self._canvas_select.create_text([20, 20], text='Results are presented here. '
'All results may not fit the screen. '
'All results are seen in your saved result file.',
font='Verdana 12 bold',
- fill='red', anchor = 'w')
+ fill='red', anchor='w')
delta, start_x, y_loc = 20, 10, 40
@@ -1262,40 +1468,47 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
if save_file is not None:
save_file.write('\n')
save_file.write('--------------------------------------------------------------------------' + '\n')
- save_file.write('Plate fields: '+str(len(values[2]['objects']))+ ' Frames: '+
+ save_file.write('Plate fields: ' + str(len(values[2]['objects'])) + ' Frames: ' +
str(len(values[2]['frames'])) + '\n')
self._canvas_select.create_text([start_x + delta, y_loc],
- text=str(len(check_ok))+' panels with weight '+ str(round(values[0],1)),
+ text=str(len(check_ok)) + ' panels with weight ' + str(
+ round(values[0], 1)),
anchor='w', font=text_type)
y_loc += delta
item_count, endstring = 0, ''
for data_idx, data in enumerate(values[1]):
for idx, stuc_info in enumerate(data):
- if type(stuc_info) == calc_structure.AllStructure:
+ if isinstance(stuc_info, AllStructure):
if y_loc > 700:
y_loc = 120
start_x += 350
if item_count == 0:
- endstring = ' START 1'+' OK!\n' if values[1][data_idx][3] else ' START 1'+' NOT OK!\n'
- elif item_count > 0 and item_count < len(values[1]) / 2-1 and len(values[1]) != 4:
- endstring = ' -------'+' OK!\n' if values[1][data_idx][3] else ' -------'+' NOT OK!\n'
- elif item_count == len(values[1])/2-1:
- endstring = ' -END 1-'+' OK!\n' if values[1][data_idx][3] else ' -END 1-'+' NOT OK!\n'
- elif item_count == len(values[1])/2:
- endstring = ' START 2'+' OK!\n' if values[1][data_idx][3] else ' START 2'+' NOT OK!\n'
- elif item_count > len(values[1])/2 and item_count < len(values[1])-1:
- endstring = ' -------'+' OK!\n' if values[1][data_idx][3] else ' -------'+' NOT OK!\n'
- elif item_count == len(values[1])-1:
- endstring = ' -END 2-'+' OK!\n' if values[1][data_idx][3] else ' -END 2-'+' NOT OK!\n'
+ endstring = ' START 1' + ' OK!\n' if values[1][data_idx][
+ 3] else ' START 1' + ' NOT OK!\n'
+ elif item_count > 0 and item_count < len(values[1]) / 2 - 1 and len(values[1]) != 4:
+ endstring = ' -------' + ' OK!\n' if values[1][data_idx][
+ 3] else ' -------' + ' NOT OK!\n'
+ elif item_count == len(values[1]) / 2 - 1:
+ endstring = ' -END 1-' + ' OK!\n' if values[1][data_idx][
+ 3] else ' -END 1-' + ' NOT OK!\n'
+ elif item_count == len(values[1]) / 2:
+ endstring = ' START 2' + ' OK!\n' if values[1][data_idx][
+ 3] else ' START 2' + ' NOT OK!\n'
+ elif item_count > len(values[1]) / 2 and item_count < len(values[1]) - 1:
+ endstring = ' -------' + ' OK!\n' if values[1][data_idx][
+ 3] else ' -------' + ' NOT OK!\n'
+ elif item_count == len(values[1]) - 1:
+ endstring = ' -END 2-' + ' OK!\n' if values[1][data_idx][
+ 3] else ' -END 2-' + ' NOT OK!\n'
self._canvas_select.create_text([start_x + delta, y_loc],
- text=stuc_info.get_one_line_string_mixed()+endstring,
+ text=stuc_info.get_one_line_string_mixed() + endstring,
anchor='w', font=text_type)
y_loc += 15
if save_file is not None:
- save_file.write(stuc_info.get_one_line_string_mixed()+' ' +
+ save_file.write(stuc_info.get_one_line_string_mixed() + ' ' +
stuc_info.get_extended_string_mixed() +
' | ' + stuc_info.Plate.get_report_stresses() +
endstring)
@@ -1309,13 +1522,14 @@ def draw_select_canvas(self, opt_results = None, save_file = None):
for val in values[2]['frames']]) + '\n')
save_file.write('Scales used on frames: ' +
str([str(round(val, 3)) for val in values[2]['scales']]) + '\n')
- save_file.write('----------------------------------------------------------------------------'+'\n')
+ save_file.write(
+ '----------------------------------------------------------------------------' + '\n')
if save_file is not None:
save_file.write('\n ------------- END ---------------')
save_file.close()
- def draw_result_text(self, geo_opt_obj, save_to_file = None):
+ def draw_result_text(self, geo_opt_obj, save_to_file=None):
''' Textual version of the results. '''
self._canvas_opt.delete('all')
@@ -1334,7 +1548,7 @@ def draw_result_text(self, geo_opt_obj, save_to_file = None):
self._canvas_opt.create_text([start_x, y_loc],
text='| Plate fields | Fields length | Weight index | All OK? |',
- font='Verdana 10 bold', fill='red', anchor = 'w')
+ font='Verdana 10 bold', fill='red', anchor='w')
y_loc += delta / 2
self._canvas_opt.create_text([start_x, y_loc],
text='************************************************', anchor='w',
@@ -1347,7 +1561,6 @@ def draw_result_text(self, geo_opt_obj, save_to_file = None):
if weight != float('inf'):
max_weight = weight if weight > max_weight else max_weight
-
if save_to_file is not None:
save_file = open(save_to_file, 'w')
save_file.write('| Plate fields | Fields length | Weight index | All OK? |\n')
@@ -1358,31 +1571,31 @@ def draw_result_text(self, geo_opt_obj, save_to_file = None):
check_ok = [val[2] is True for val in value[1]]
- self._canvas_opt.create_text([start_x + 20, y_loc ], text=str(len(check_ok)),
+ self._canvas_opt.create_text([start_x + 20, y_loc], text=str(len(check_ok)),
anchor='w', font=text_type)
- self._canvas_opt.create_text([start_x + 120, y_loc ], text=str('No results\n' if
- self._geo_results[key][1][0][0] is None else
- round(self._geo_results[key][1][0][0].
- Plate.span,4)),
+ self._canvas_opt.create_text([start_x + 120, y_loc], text=str('No results\n' if
+ self._geo_results[key][1][0][0] is None else
+ round(self._geo_results[key][1][0][0].
+ Plate.span, 4)),
anchor='w', font=text_type)
- self._canvas_opt.create_text([start_x + 220, y_loc ],
+ self._canvas_opt.create_text([start_x + 220, y_loc],
text=str(round(self._geo_results[key][0] / max_weight, 3))
if max_weight != 0 else '',
anchor='w', font=text_type)
- self._canvas_opt.create_text([start_x + 330, y_loc ], text=str(all(check_ok)),
+ self._canvas_opt.create_text([start_x + 330, y_loc], text=str(all(check_ok)),
anchor='w', font=text_type)
if save_to_file is not None:
- save_file.write(str(len(check_ok))+ ' ' + 'No results\n' if self._geo_results[key][1][0][0] is None
- else str(round(self._geo_results[key][1][0][0].Plate.span,
- 4)) + ' ' +
- str(round(self._geo_results[key][0] / max_weight, 3))
- + '\n' if max_weight != 0 else
- '' + ' ' + str(all(check_ok))+'\n')
+ save_file.write(str(len(check_ok)) + ' ' + 'No results\n' if self._geo_results[key][1][0][0] is None
+ else str(round(self._geo_results[key][1][0][0].Plate.span,
+ 4)) + ' ' +
+ str(round(self._geo_results[key][0] / max_weight, 3))
+ + '\n' if max_weight != 0 else
+ '' + ' ' + str(all(check_ok)) + '\n')
if self._geo_results[key][1][0][0] is not None:
- xplot.append(round(self._geo_results[key][1][0][0].Plate.span,4))
+ xplot.append(round(self._geo_results[key][1][0][0].Plate.span, 4))
yplot.append(round(self._geo_results[key][0] / max_weight, 4))
if save_to_file is not None:
@@ -1390,6 +1603,21 @@ def draw_result_text(self, geo_opt_obj, save_to_file = None):
else:
return None, xplot, yplot
+ def _line_structure_bundle(self, line):
+ return self._line_to_struc[line]
+
+ def _line_structure(self, line):
+ return line_structure.structure(self._line_structure_bundle(line))
+
+ def _line_structure_type(self, line):
+ return line_structure.plate(self._line_structure_bundle(line)).get_structure_type()
+
+ def _line_overpressure_side(self, line):
+ return self._line_structure(line).overpressure_side
+
+ def _copy_line_structure_bundle(self, line):
+ return line_structure.copy_bundle(self._line_structure_bundle(line))
+
def algorithm_info(self):
''' When button is clicked, info is displayed.'''
@@ -1459,8 +1687,8 @@ def get_point_canvas_coord(self, point_no):
:param point_no:
:return:
'''
- point_coord_x = self._canvas_draw_origo[0] + self._point_dict[point_no][0]* self._canvas_scale
- point_coord_y = self._canvas_draw_origo[1] - self._point_dict[point_no][1]* self._canvas_scale
+ point_coord_x = self._canvas_draw_origo[0] + self._point_dict[point_no][0] * self._canvas_scale
+ point_coord_y = self._canvas_draw_origo[1] - self._point_dict[point_no][1] * self._canvas_scale
return [point_coord_x, point_coord_y]
@@ -1527,14 +1755,14 @@ def button_1_click(self, event):
self._active_point = ''
for point, coords in self._point_dict.items():
point_coord = self.get_point_canvas_coord(point)
- if point_coord[0]-margin < click_x < point_coord[0]+margin and\
- point_coord[1]-margin < click_y < point_coord[1]+margin:
+ if point_coord[0] - margin < click_x < point_coord[0] + margin and \
+ point_coord[1] - margin < click_y < point_coord[1] + margin:
self._active_point = point
self._point_is_active = True
- if len(self._active_points)<4:
+ if len(self._active_points) < 4:
self._active_points.append(self._active_point)
- if len(self._active_points)==4:
+ if len(self._active_points) == 4:
self.opt_create_frames(self.opt_get_fractions())
self.draw_select_canvas()
@@ -1558,7 +1786,7 @@ def button_2_click(self, event):
'''
self._previous_drag_mouse = [event.x, event.y]
- if self._opt_resutls =={}:
+ if self._opt_resutls == {}:
return
click_x = self._canvas_select.winfo_pointerx() - self._canvas_select.winfo_rootx()
click_y = self._canvas_select.winfo_pointery() - self._canvas_select.winfo_rooty()
@@ -1586,19 +1814,19 @@ def button_2_click(self, event):
self._active_lines = []
self._active_lines.append(key)
if key in self._opt_resutls.keys() and self._opt_resutls[key] != None:
- self.draw_properties(init_obj=self._line_to_struc[key][0],
- opt_obj=self._opt_resutls[key][0],
+ self.draw_properties(init_obj=self._line_structure(key),
+ opt_obj=self._opt_resutls[key][0],
line=key)
else:
- self.draw_properties(init_obj=self._line_to_struc[key][0], line=key)
+ self.draw_properties(init_obj=self._line_structure(key), line=key)
break
self.draw_select_canvas()
self.draw_select_canvas()
self.update_running_time()
#############################
- self.opt_create_main_structure(self.opt_create_frames(self.opt_get_fractions())[0],self._active_points[0],
- self._active_points[1],self._active_points[2],self._active_points[3])
+ self.opt_create_main_structure(self.opt_create_frames(self.opt_get_fractions())[0], self._active_points[0],
+ self._active_points[1], self._active_points[2], self._active_points[3])
def save_and_close(self):
'''
@@ -1619,7 +1847,7 @@ def save_and_close(self):
return
self._frame.destroy()
- def toggle(self, found_files = None, obj = None, iterating = False, given_path: str = None):
+ def toggle(self, found_files=None, obj=None, iterating=False, given_path: str = None):
'''
On off button.
:param found_files:
@@ -1633,20 +1861,20 @@ def toggle(self, found_files = None, obj = None, iterating = False, given_path:
predefined_structure = None
if self._toggle_btn.config('relief')[-1] == 'sunken':
self._toggle_btn.config(relief="raised")
- self._toggle_btn.config(bg = 'salmon')
- self._ent_spacing_upper.config(bg = 'white')
- self._ent_spacing_lower.config(bg = 'white')
- self._ent_delta_spacing.config(bg = 'white')
+ self._toggle_btn.config(bg='salmon')
+ self._ent_spacing_upper.config(bg='white')
+ self._ent_spacing_lower.config(bg='white')
+ self._ent_delta_spacing.config(bg='white')
self._filez = None
else:
self._toggle_btn.config(relief="sunken")
self._toggle_btn.config(bg='lightgreen')
- self._ent_spacing_upper.config(bg = 'lightgreen')
- self._ent_spacing_lower.config(bg = 'lightgreen')
- self._ent_delta_spacing.config(bg = 'lightgreen')
- self._ent_pl_thk_upper.config(bg = 'lightgreen')
- self._ent_pl_thk_lower.config(bg = 'lightgreen')
- self._ent_delta_pl_thk.config(bg = 'lightgreen')
+ self._ent_spacing_upper.config(bg='lightgreen')
+ self._ent_spacing_lower.config(bg='lightgreen')
+ self._ent_delta_spacing.config(bg='lightgreen')
+ self._ent_pl_thk_upper.config(bg='lightgreen')
+ self._ent_pl_thk_lower.config(bg='lightgreen')
+ self._ent_delta_pl_thk.config(bg='lightgreen')
if given_path is None:
self._filez = list(askopenfilenames(parent=self._frame, title='Choose files to open',
initialdir=self._root_dir))
@@ -1675,32 +1903,33 @@ def plot_results(self):
'Plotting a selected panel'
if self._geo_results is not None \
and type(self._new_option_fraction.get()) == int and type(self._new_option_panel.get()) == int:
- op.plot_optimization_results(self._geo_results[int(self._new_option_fraction.get()/2)][1]
+ op.plot_optimization_results(self._geo_results[int(self._new_option_fraction.get() / 2)][1]
[self._new_option_panel.get()])
def get_plate_field_options(self, event):
if self._geo_results is not None:
self._ent_option_field.destroy()
- to_add = tuple([val for val in range(len(self._geo_results[int(self._new_option_fraction.get()/2)][1]))])
+ to_add = tuple([val for val in range(len(self._geo_results[int(self._new_option_fraction.get() / 2)][1]))])
self._ent_option_field = tk.OptionMenu(self._frame, self._new_option_panel, *to_add)
self._ent_option_field.place(x=self._options_panels_place[0], y=self._options_panels_place[1])
- def mouse_scroll(self,event):
- self._canvas_scale += event.delta/50
+ def mouse_scroll(self, event):
+ self._canvas_scale += event.delta / 50
self._canvas_scale = 0 if self._canvas_scale < 0 else self._canvas_scale
self.draw_select_canvas()
- def button_2_click_and_drag(self,event):
+ def button_2_click_and_drag(self, event):
- self._canvas_draw_origo = (self._canvas_draw_origo[0]-(self._previous_drag_mouse[0]-event.x),
- self._canvas_draw_origo[1]-(self._previous_drag_mouse[1]-event.y))
+ self._canvas_draw_origo = (self._canvas_draw_origo[0] - (self._previous_drag_mouse[0] - event.x),
+ self._canvas_draw_origo[1] - (self._previous_drag_mouse[1] - event.y))
- self._previous_drag_mouse = (event.x,event.y)
+ self._previous_drag_mouse = (event.x, event.y)
self.draw_select_canvas()
+
if __name__ == '__main__':
root = tk.Tk()
my_app = CreateOptGeoWindow(master=root)
diff --git a/anystruct/optimize_multiple_window.py b/anystruct/optimize_multiple_window.py
index d301ce36..53277e8c 100644
--- a/anystruct/optimize_multiple_window.py
+++ b/anystruct/optimize_multiple_window.py
@@ -10,6 +10,7 @@
import anystruct.main_application
import anystruct.optimize as op
import anystruct.example_data as test
+ import anystruct.line_structure as line_structure
from anystruct.calc_structure import *
import anystruct.calc_structure as calc
from anystruct.helper import *
@@ -18,6 +19,7 @@
import ANYstructure.anystruct.main_application
import ANYstructure.anystruct.optimize as op
import ANYstructure.anystruct.example_data as test
+ import ANYstructure.anystruct.line_structure as line_structure
from ANYstructure.anystruct.calc_structure import *
import ANYstructure.anystruct.calc_structure as calc
from ANYstructure.anystruct.helper import *
@@ -25,12 +27,19 @@
def helper_harmonizer_multi(iterator):
- '''
- :param :
- :return:
- '''
+ """
+ Multiprocessing helper for harmonized optimization.
+
+ The ML result payload is selected from the active constraint:
+ chk[8] -> ML-CL, deactivated
+ chk[9] -> ML-Numeric result, expected [buckling_uf, ultimate_uf, valid_prediction]
+ """
this_check, master_x = list(), None
+ use_semi_analytical = len(iterator['info']['checks']) > 7 and iterator['info']['checks'][7]
+ use_ml_numeric = len(iterator['info']['checks']) > 9 and iterator['info']['checks'][9]
+ use_ml_cl = len(iterator['info']['checks']) > 8 and iterator['info']['checks'][8]
+
for slave_line in iterator['info']['lines']:
lateral_press = iterator['info'][slave_line]['lateral pressure']
fat_press = iterator['info'][slave_line]['fatigue pressure']
@@ -38,19 +47,40 @@ def helper_harmonizer_multi(iterator):
slamming_pressure = iterator['info'][slave_line]['slamming pressure']
chk_calc_obj = iterator['info'][slave_line]['chk_calc_obj']
master_x = list(iterator['x'])
- ml_cl = iterator['info'][slave_line]['ML-CL']
+
+ if use_semi_analytical:
+ ml_result = iterator['info'][slave_line].get('SemiAnalytical', [float('inf'), float('inf'), 0, 0.87])
+ elif use_ml_numeric:
+ ml_result = iterator['info'][slave_line].get('ML-Numeric', [float('inf'), float('inf'), 0])
+ elif use_ml_cl:
+ ml_result = [0, 0]
+ else:
+ ml_result = None
+
fup = iterator['info'][slave_line]['fup']
fdwn = iterator['info'][slave_line]['fdwn']
+
if iterator['info']['keep spacing']:
x = [chk_calc_obj.get_s()] + master_x[1:] + [chk_calc_obj.span, chk_calc_obj.girder_lg]
else:
x = master_x + [chk_calc_obj.span, chk_calc_obj.girder_lg]
- chk_any = op.any_constraints_all(x=x, obj=chk_calc_obj, lat_press=lateral_press,
- init_weight=float('inf'), side='p', chk=iterator['info']['checks'],
- fat_dict=None if fat_obj == None else fat_obj.get_fatigue_properties(),
- fat_press=fat_press, slamming_press=slamming_pressure,PULSrun=None,
- print_result=False,fdwn=fdwn, fup=fup, ml_results=ml_cl)
+ chk_any = op.any_constraints_all(
+ x=x,
+ obj=chk_calc_obj,
+ lat_press=lateral_press,
+ init_weight=float('inf'),
+ side='p',
+ chk=iterator['info']['checks'],
+ fat_dict=None if fat_obj is None else fat_obj.get_fatigue_properties(),
+ fat_press=fat_press,
+ slamming_press=slamming_pressure,
+ PULSrun=None,
+ print_result=False,
+ fdwn=fdwn,
+ fup=fup,
+ ml_results=ml_result,
+ )
this_check.append(chk_any[0])
if all(this_check) and master_x is not None:
@@ -58,10 +88,12 @@ def helper_harmonizer_multi(iterator):
else:
return None
+
class CreateOptimizeMultipleWindow():
'''
This class initiates the MultiOpt window.
'''
+
def __init__(self, master, app=None):
super(CreateOptimizeMultipleWindow, self).__init__()
if __name__ == '__main__':
@@ -77,7 +109,7 @@ def __init__(self, master, app=None):
self._fatigue_pressure = test.get_fatigue_pressures()
self._fatigue_object = test.get_fatigue_object()
self._normal_pressure = test.get_random_pressure()
- image_dir = os.path.dirname(__file__)+'\\images\\'
+ image_dir = os.path.dirname(__file__) + '\\images\\'
self._active_lines = []
self._ML_buckling = dict() # Buckling machine learning algorithm
for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
@@ -89,33 +121,33 @@ def __init__(self, master, app=None):
'cl UP buc GLGT predictor', 'cl UP buc GLGT scaler',
'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler'
],
- ["ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
- "CL_CSR-Tank_req_cl_predictor",
- "CL_CSR-Tank_req_cl_UP_scaler",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
- self._ML_buckling[name] = None
- if os.path.isfile(file_base + '.pickle'):
- file = open(file_base + '.pickle', 'rb')
- from sklearn.neural_network import MLPClassifier
- from sklearn.preprocessing import StandardScaler
- self._ML_buckling[name] = pickle.load(file)
- file.close()
+ ["ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
+ "ml_files\\CLPIPE_CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
+ "CLPIPE_CL_CSR-Tank_req_cl_predictor",
+ "CLPIPE_CL_CSR-Tank_req_cl_UP_scaler",
+ "CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
+ "CLPIPE_CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
+ self._ML_buckling[name] = None
+ if os.path.isfile(file_base + '.pickle'):
+ file = open(file_base + '.pickle', 'rb')
+ from sklearn.neural_network import MLPClassifier
+ from sklearn.preprocessing import StandardScaler
+ self._ML_buckling[name] = pickle.load(file)
+ file.close()
else:
self.app = app
self._load_objects = app._load_dict
@@ -155,13 +187,11 @@ def __init__(self, master, app=None):
tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=95)
tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=135)
-
algorithms = ('anysmart', 'random', 'random_no_delta')
tk.Label(self._frame, text='-- Structural optimizer for multiple selections --',
font='Verdana 15 bold').place(x=10, y=10)
-
# upper and lower bounds for optimization
# [0.6, 0.012, 0.3, 0.01, 0.1, 0.01]
self._new_spacing_upper = tk.DoubleVar()
@@ -187,12 +217,12 @@ def __init__(self, master, app=None):
self._new_delta_fl_w = tk.DoubleVar()
self._new_delta_fl_thk = tk.DoubleVar()
self._new_swarm_size = tk.IntVar()
- self._new_omega = tk.DoubleVar()
- self._new_phip = tk.DoubleVar()
- self._new_phig = tk.DoubleVar()
+ self._new_omega = tk.DoubleVar()
+ self._new_phip = tk.DoubleVar()
+ self._new_phig = tk.DoubleVar()
self._new_maxiter = tk.IntVar()
- self._new_minstep = tk.DoubleVar()
- self._new_minfunc = tk.DoubleVar()
+ self._new_minstep = tk.DoubleVar()
+ self._new_minfunc = tk.DoubleVar()
ent_w = 10
self._ent_spacing_upper = tk.Entry(self._frame, textvariable=self._new_spacing_upper, width=ent_w)
@@ -209,7 +239,8 @@ def __init__(self, master, app=None):
self._ent_fl_thk_lower = tk.Entry(self._frame, textvariable=self._new_fl_thk_lower, width=ent_w)
self._ent_span = tk.Entry(self._frame, textvariable=self._new_span, width=ent_w)
self._ent_width_lg = tk.Entry(self._frame, textvariable=self._new_width_lg, width=ent_w)
- self._ent_algorithm = tk.OptionMenu(self._frame, self._new_algorithm, command=self.selected_algorithm, *algorithms)
+ self._ent_algorithm = tk.OptionMenu(self._frame, self._new_algorithm, command=self.selected_algorithm,
+ *algorithms)
self._ent_random_trials = tk.Entry(self._frame, textvariable=self._new_algorithm_random_trials)
self._ent_delta_spacing = tk.Entry(self._frame, textvariable=self._new_delta_spacing, width=ent_w)
self._ent_delta_pl_thk = tk.Entry(self._frame, textvariable=self._new_delta_pl_thk, width=ent_w)
@@ -218,36 +249,34 @@ def __init__(self, master, app=None):
self._ent_delta_fl_w = tk.Entry(self._frame, textvariable=self._new_delta_fl_w, width=ent_w)
self._ent_delta_fl_thk = tk.Entry(self._frame, textvariable=self._new_delta_fl_thk, width=ent_w)
-
pso_width = 10
- self._ent_swarm_size = tk.Entry(self._frame,textvariable=self._new_swarm_size, width = pso_width)
- self._ent_omega = tk.Entry(self._frame,textvariable=self._new_omega, width = pso_width)
- self._ent_phip = tk.Entry(self._frame,textvariable=self._new_phip, width = pso_width)
- self._ent_phig = tk.Entry(self._frame,textvariable=self._new_phig, width = pso_width)
- self._ent_maxiter = tk.Entry(self._frame,textvariable=self._new_maxiter, width = pso_width)
- self._ent_minstep = tk.Entry(self._frame,textvariable=self._new_minstep, width = pso_width)
- self._ent_minfunc = tk.Entry(self._frame,textvariable=self._new_minfunc, width = pso_width)
+ self._ent_swarm_size = tk.Entry(self._frame, textvariable=self._new_swarm_size, width=pso_width)
+ self._ent_omega = tk.Entry(self._frame, textvariable=self._new_omega, width=pso_width)
+ self._ent_phip = tk.Entry(self._frame, textvariable=self._new_phip, width=pso_width)
+ self._ent_phig = tk.Entry(self._frame, textvariable=self._new_phig, width=pso_width)
+ self._ent_maxiter = tk.Entry(self._frame, textvariable=self._new_maxiter, width=pso_width)
+ self._ent_minstep = tk.Entry(self._frame, textvariable=self._new_minstep, width=pso_width)
+ self._ent_minfunc = tk.Entry(self._frame, textvariable=self._new_minfunc, width=pso_width)
start_x, start_y, dx, dy = 20, 70, 100, 40
self._new_processes = tk.IntVar()
self._new_processes.set(max(cpu_count() - 1, 1))
- tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg = 'silver')\
+ tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg='silver') \
.place(x=start_x + 12.3 * dx, y=start_y - 0.2 * dy)
- tk.Entry(self._frame, textvariable=self._new_processes, width = 12, bg = 'silver')\
- .place(x=start_x + 12.3 * dx, y=start_y + 0.7* dy)
+ tk.Entry(self._frame, textvariable=self._new_processes, width=12, bg='silver') \
+ .place(x=start_x + 12.3 * dx, y=start_y + 0.7 * dy)
self._prop_canvas_dim = (500, 450)
self._draw_scale = 500
self._canvas_opt = tk.Canvas(self._frame, width=self._prop_canvas_dim[0], height=self._prop_canvas_dim[1],
- background='azure',relief='groove', borderwidth=2)
- self._canvas_opt.place(x=start_x+10.5*dx, y=start_y+3.5*dy)
+ background='azure', relief='groove', borderwidth=2)
+ self._canvas_opt.place(x=start_x + 10.5 * dx, y=start_y + 3.5 * dy)
self._select_canvas_dim = (1000, 720)
- self._canvas_select = tk.Canvas(self._frame, width=self._select_canvas_dim[0], height=self._select_canvas_dim[1],
- background='azure',relief='groove', borderwidth=2)
- self._canvas_select.place(x=start_x+0*dx, y=start_y+3.5*dy)
-
-
+ self._canvas_select = tk.Canvas(self._frame, width=self._select_canvas_dim[0],
+ height=self._select_canvas_dim[1],
+ background='azure', relief='groove', borderwidth=2)
+ self._canvas_select.place(x=start_x + 0 * dx, y=start_y + 3.5 * dy)
# Labels for the pso
self._lb_swarm_size = tk.Label(self._frame, text='swarm size')
@@ -262,17 +291,17 @@ def __init__(self, master, app=None):
tk.Label(self._frame, text='Iteration delta [mm]', font='Verdana 9').place(x=start_x, y=start_y + dy)
tk.Label(self._frame, text='Lower bounds [mm]', font='Verdana 9').place(x=start_x, y=start_y + 2 * dy)
tk.Label(self._frame, text='Spacing [mm]', font='Verdana 7 bold').place(x=start_x + 1.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Plate thk. [mm]', font='Verdana 7 bold').place(x=start_x + 2.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Web height [mm]', font='Verdana 7 bold').place(x=start_x + 3.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Web thk. [mm]', font='Verdana 7 bold').place(x=start_x + 4.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Flange width [mm]', font='Verdana 7 bold').place(x=start_x + 5.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Flange thk. [mm]', font='Verdana 7 bold').place(x=start_x + 6.97 * dx,
- y=start_y - 0.6 * dy)
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='Estimated running time for algorithm: ',
font='Verdana 9 bold').place(x=start_x, y=start_y + 2.8 * dy)
self._runnig_time_label = tk.Label(self._frame, text='', font='Verdana 9 bold')
@@ -331,11 +360,12 @@ def __init__(self, master, app=None):
self._new_check_fatigue = tk.BooleanVar()
self._new_check_slamming = tk.BooleanVar()
self._new_check_local_buckling = tk.BooleanVar()
+ self._new_check_semi_analytical_buckling = tk.BooleanVar()
self._new_check_ml_buckling = tk.BooleanVar()
+ self._new_check_ml_numeric_buckling = tk.BooleanVar()
self._new_harmonizer = tk.BooleanVar()
self._keep_spacing = tk.BooleanVar()
-
self._new_check_sec_mod.set(True)
self._new_check_min_pl_thk.set(True)
self._new_check_shear_area.set(True)
@@ -345,7 +375,9 @@ def __init__(self, master, app=None):
self._new_check_local_buckling.set(True)
self._new_harmonizer.set(False)
self._keep_spacing.set(False)
+ self._new_check_semi_analytical_buckling.set(False)
self._new_check_ml_buckling.set(False)
+ self._new_check_ml_numeric_buckling.set(False)
self._new_swarm_size.set(100)
self._new_omega.set(0.5)
@@ -375,69 +407,77 @@ def __init__(self, master, app=None):
self._new_fl_thk_lower.trace('w', self.update_running_time)
self._new_algorithm_random_trials.trace('w', self.update_running_time)
self._new_algorithm.trace('w', self.update_running_time)
- self._keep_spacing.trace('w',self.trace_keep_spacing_check)
+ self._keep_spacing.trace('w', self.trace_keep_spacing_check)
+ self._new_check_semi_analytical_buckling.trace('w', self.update_running_time)
self._new_check_ml_buckling.trace('w', self.update_running_time)
+ self._new_check_ml_numeric_buckling.trace('w', self.update_running_time)
self.running_time_per_item = 1.009943181818182e-5
self._runnig_time_label.config(text=str(self.get_running_time()))
tk.Label(self._frame, text='Select algorithm type --->', font='Verdana 8 bold').place(x=start_x + dx * 8,
- y=start_y + 1 * dy)
+ y=start_y + 1 * dy)
self._ent_algorithm.place(x=start_x + dx * 10, y=start_y + dy)
self.algorithm_random_label = tk.Label(self._frame, text='Number of trials')
tk.Button(self._frame, text='algorithm information', command=self.algorithm_info, bg='white') \
- .place(x=start_x + dx * 15, y=start_y + dy *-0.5)
+ .place(x=start_x + dx * 15, y=start_y + dy * -0.5)
self.run_button = tk.Button(self._frame, text='RUN OPTIMIZATION!', command=self.run_optimizaion, bg='red',
font='Verdana 10', fg='Yellow')
self.run_button.place(x=start_x + dx * 8, y=start_y)
- self.run_results = tk.Button(self._frame,text='show calculated', command=self.plot_results, bg='white',
- font='Verdana 10',fg='black')
- self.run_results.place(x=start_x+dx*8, y=start_y+dy*1.5)
+ self.run_results = tk.Button(self._frame, text='show calculated', command=self.plot_results, bg='white',
+ font='Verdana 10', fg='black')
+ self.run_results.place(x=start_x + dx * 8, y=start_y + dy * 1.5)
self._opt_actual_running_time.place(x=start_x + dx * 8, y=start_y - dy * 1.5)
self.close_and_save = tk.Button(self._frame, text='Return and replace with selected optimized structure',
command=self.save_and_close, bg='green', font='Verdana 10 bold', fg='yellow')
self.close_and_save.place(x=start_x + dx * 10, y=10)
tk.Button(self._frame, text='Open predefined stiffeners example',
- command=self.open_example_file, bg='white', font='Verdana 10')\
- .place(x=start_x+dx*15,y=10)
-
-
-
-
+ command=self.open_example_file, bg='white', font='Verdana 10') \
+ .place(x=start_x + dx * 15, y=10)
start_y, start_x, dy = 530, 100, 35
- tk.Label(self._frame,text='Check for minimum section modulus').place(x=start_x+dx*9.7,y=start_y+4*dy)
- tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x+dx*9.7,y=start_y+5*dy)
- tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x+dx*9.7,y=start_y+6*dy)
- tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x+dx*9.7,y=start_y+7*dy)
+ tk.Label(self._frame, text='Check for minimum section modulus').place(x=start_x + dx * 9.7, y=start_y + 4 * dy)
+ tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x + dx * 9.7, y=start_y + 5 * dy)
+ tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x + dx * 9.7, y=start_y + 6 * dy)
+ tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x + dx * 9.7, y=start_y + 7 * dy)
tk.Label(self._frame, text='Check for fatigue (RP-C203)').place(x=start_x + dx * 9.7, y=start_y + 8 * dy)
tk.Label(self._frame, text='Check for bow slamming').place(x=start_x + dx * 9.7, y=start_y + 9 * dy)
tk.Label(self._frame, text='Check for local stf. buckling').place(x=start_x + dx * 9.7, y=start_y + 10 * dy)
- tk.Label(self._frame, text='Check for buckling (ML-CL)').place(x=start_x + dx * 9.7, y=start_y + 11 * dy)
+ tk.Label(self._frame, text='Check for buckling (SemiAnalytical S3/U3)').place(x=start_x + dx * 9.7,
+ y=start_y + 11 * dy)
+ tk.Label(self._frame, text='Check for buckling (ML-CL deactivated)').place(x=start_x + dx * 9.7, y=start_y + 12 * dy)
+ tk.Label(self._frame, text='Check for buckling (ML-Numeric)').place(x=start_x + dx * 9.7,
+ y=start_y + 13 * dy)
tk.Label(self._frame, text='Check to harmonize results. Same stiffener and plate dimensions '
- '(defined by largest in opt).', font='Verdana 10 bold')\
+ '(defined by largest in opt).', font='Verdana 10 bold') \
.place(x=start_x + dx * +8.5, y=start_y - 10.5 * dy)
tk.Label(self._frame, text='Check to skip iterating over spacing (respective line spacing used).',
- font='Verdana 10 bold')\
+ font='Verdana 10 bold') \
.place(x=start_x + dx * +8.5, y=start_y - 9.8 * dy)
- tk.Checkbutton(self._frame,variable=self._new_check_sec_mod).place(x=start_x+dx*12,y=start_y+4*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x+dx*12,y=start_y+5*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x+dx*12,y=start_y+6*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x+dx*12,y=start_y+7*dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_sec_mod).place(x=start_x + dx * 12, y=start_y + 4 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x + dx * 12, y=start_y + 5 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x + dx * 12, y=start_y + 6 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x + dx * 12, y=start_y + 7 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_fatigue).place(x=start_x + dx * 12, y=start_y + 8 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_slamming).place(x=start_x + dx * 12, y=start_y + 9 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_local_buckling).place(x=start_x + dx * 12,
y=start_y + 10 * dy)
- tk.Checkbutton(self._frame, variable=self._new_check_ml_buckling).place(x=start_x + dx * 12,
- y=start_y + 11 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_semi_analytical_buckling).place(x=start_x + dx * 12,
+ y=start_y + 11 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_ml_buckling, state='disabled').place(
+ x=start_x + dx * 12,
+ y=start_y + 12 * dy,
+ )
+ tk.Checkbutton(self._frame, variable=self._new_check_ml_numeric_buckling).place(x=start_x + dx * 12,
+ y=start_y + 13 * dy)
tk.Checkbutton(self._frame, variable=self._new_harmonizer).place(x=start_x + dx * 8, y=start_y - 10.5 * dy)
tk.Checkbutton(self._frame, variable=self._keep_spacing).place(x=start_x + dx * +8, y=start_y - 9.8 * dy)
self._toggle_btn = tk.Button(self._frame, text="Iterate predefiened stiffeners", relief="raised",
- command=self.toggle, bg = 'salmon')
+ command=self.toggle, bg='salmon')
- self._toggle_btn.place(x=start_x+dx*9, y=start_y - dy * 13)
+ self._toggle_btn.place(x=start_x + dx * 9, y=start_y - dy * 13)
self._toggle_object, self._filez = None, None
# Stress scaling
@@ -446,13 +486,13 @@ def __init__(self, master, app=None):
self._new_fdwn = tk.DoubleVar()
self._new_fdwn.set(1)
- tk.Label(self._frame, text='Factor when scaling stresses up, fup')\
+ tk.Label(self._frame, text='Factor when scaling stresses up, fup') \
.place(x=start_x + dx * 13, y=start_y + 5 * dy)
- ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width = 10)
+ ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width=10)
ent_fup.place(x=start_x + dx * 15.5, y=start_y + 5 * dy)
- tk.Label(self._frame, text='Factor when scaling stresses up, fdown')\
+ tk.Label(self._frame, text='Factor when scaling stresses up, fdown') \
.place(x=start_x + dx * 13, y=start_y + 6 * dy)
- ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width = 10)
+ ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width=10)
ent_fdwn.place(x=start_x + dx * 15.5, y=start_y + 6 * dy)
self.draw_properties()
@@ -460,10 +500,9 @@ def __init__(self, master, app=None):
# ----------------------------------END OF OPTIMIZE SINGLE COPY-----------------------------------------------
self.progress_count = tk.IntVar()
self.progress_count.set(0)
- self.progress_bar = Progressbar(self._frame, orient="horizontal",length=200, mode="determinate",
+ self.progress_bar = Progressbar(self._frame, orient="horizontal", length=200, mode="determinate",
variable=self.progress_count)
- self.progress_bar.place(x=start_x+dx*10.5,y=start_y-dy*11.5)
-
+ self.progress_bar.place(x=start_x + dx * 10.5, y=start_y - dy * 11.5)
self.controls()
self.draw_select_canvas()
@@ -475,7 +514,6 @@ def trace_keep_spacing_check(self, *args):
self._ent_delta_spacing.configure({"background": "red"})
self._ent_spacing_upper.configure({"background": "red"})
-
def selected_algorithm(self, event):
'''
Action when selecting an algorithm in the optionm menu.
@@ -526,23 +564,23 @@ def selected_algorithm(self, event):
self._ent_random_trials.place_forget()
start_x = 150
- self._lb_swarm_size.place(x=start_x + dx*11 , y=start_y - 1 * dy)
- self._lb_omega.place(x=start_x + dx*11 , y=start_y - 0 * dy)
- self._lb_phip.place(x=start_x + dx*11 , y=start_y + 1 * dy)
- self._lb_phig.place(x=start_x + dx*11 , y=start_y + 2 * dy)
+ self._lb_swarm_size.place(x=start_x + dx * 11, y=start_y - 1 * dy)
+ self._lb_omega.place(x=start_x + dx * 11, y=start_y - 0 * dy)
+ self._lb_phip.place(x=start_x + dx * 11, y=start_y + 1 * dy)
+ self._lb_phig.place(x=start_x + dx * 11, y=start_y + 2 * dy)
- self._lb_maxiter.place(x=start_x + dx*14 , y=start_y - 1 * dy)
- self._lb_minstep.place(x=start_x + dx*14, y=start_y + 0 * dy)
- self._lb_minfunc.place(x=start_x + dx*14, y=start_y + 1 * dy)
+ self._lb_maxiter.place(x=start_x + dx * 14, y=start_y - 1 * dy)
+ self._lb_minstep.place(x=start_x + dx * 14, y=start_y + 0 * dy)
+ self._lb_minfunc.place(x=start_x + dx * 14, y=start_y + 1 * dy)
- self._ent_swarm_size.place(x=start_x + dx*12 , y=start_y - 1 * dy)
- self._ent_omega.place(x=start_x + dx*12 , y=start_y - 0 * dy)
- self._ent_phip.place(x=start_x + dx*12 , y=start_y + 1 * dy)
- self._ent_phig.place(x=start_x + dx*12 , y=start_y + 2 * dy)
+ self._ent_swarm_size.place(x=start_x + dx * 12, y=start_y - 1 * dy)
+ self._ent_omega.place(x=start_x + dx * 12, y=start_y - 0 * dy)
+ self._ent_phip.place(x=start_x + dx * 12, y=start_y + 1 * dy)
+ self._ent_phig.place(x=start_x + dx * 12, y=start_y + 2 * dy)
- self._ent_maxiter.place(x=start_x + dx*15 , y=start_y - 1 * dy)
- self._ent_minstep.place(x=start_x + dx*15, y=start_y + 0 * dy)
- self._ent_minfunc.place(x=start_x + dx*15, y=start_y + 1 * dy)
+ self._ent_maxiter.place(x=start_x + dx * 15, y=start_y - 1 * dy)
+ self._ent_minstep.place(x=start_x + dx * 15, y=start_y + 0 * dy)
+ self._ent_minfunc.place(x=start_x + dx * 15, y=start_y + 1 * dy)
def get_pressure_input(self, line):
if __name__ == '__main__':
@@ -553,7 +591,7 @@ def get_pressure_input(self, line):
else:
lateral_press = self.app.get_highest_pressure(line)['normal'] / 1e6
- fat_obj = self.app._line_to_struc[line][2]
+ fat_obj = line_structure.fatigue(self.app._line_to_struc[line])
if fat_obj is not None:
try:
fat_press = self.app.get_fatigue_pressures(line, fat_obj.get_accelerations())
@@ -583,32 +621,37 @@ def run_optimizaion(self):
Function when pressing the optimization botton inside this window.
:return:
'''
- self.run_button.config(bg = 'white')
+ self.run_button.config(bg='white')
self._opt_results = {}
t_start = time.time()
self.progress_bar.config(maximum=len(self._active_lines))
self._opt_actual_running_time.config(text='')
-
+
contraints = (self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
self._new_check_shear_area.get(), self._new_check_buckling.get(),
self._new_check_fatigue.get(), self._new_check_slamming.get(),
- self._new_check_local_buckling.get(), False, self._new_check_ml_buckling.get(), False)
-
- self.pso_parameters = (self._new_swarm_size.get(),self._new_omega.get(),self._new_phip.get(),
- self._new_phig.get(),self._new_maxiter.get(),self._new_minstep.get(),
+ self._new_check_local_buckling.get(), self._new_check_semi_analytical_buckling.get(),
+ self._new_check_ml_buckling.get(),
+ self._new_check_ml_numeric_buckling.get())
+
+ self.pso_parameters = (self._new_swarm_size.get(), self._new_omega.get(), self._new_phip.get(),
+ self._new_phig.get(), self._new_maxiter.get(), self._new_minstep.get(),
self._new_minfunc.get())
max_min_span = None
-
+
self.progress_count.set(0)
counter = 0
found_files = self._filez
+ selected_mat_fac = self._get_selected_material_factor()
+
for line in self._active_lines:
- init_obj = self._line_to_struc[line][0]
+ init_obj = self._line_structure(line)
+ self._apply_material_factor_to_structure(init_obj, selected_mat_fac)
if __name__ == '__main__':
- lateral_press = 200 #for testing
+ lateral_press = 200 # for testing
fat_obj = test.get_fatigue_object()
fat_press = test.get_fatigue_pressures()
slamming_pressure = test.get_slamming_pressure()
@@ -625,27 +668,28 @@ def run_optimizaion(self):
fat_obj = input_pressures['fatigue object']
if self._toggle_btn.config('relief')[-1] == 'sunken':
- found_files, predefined_stiffener_iter = self.toggle(found_files=found_files, obj = init_obj,
+ found_files, predefined_stiffener_iter = self.toggle(found_files=found_files, obj=init_obj,
iterating=True)
else:
predefined_stiffener_iter = None
self._opt_results[line] = list(op.run_optmizataion(init_obj, self.get_lower_bounds(init_obj),
- self.get_upper_bounds(init_obj),
- lateral_press,self.get_deltas(),
- algorithm=self._new_algorithm.get(),
- trials=self._new_algorithm_random_trials.get(),
- side=init_obj.Plate.get_side(),
- const_chk=contraints,
- pso_options=self.pso_parameters,
- fatigue_obj=fat_obj,
- fat_press_ext_int=fat_press,
- slamming_press=slamming_pressure,
- predefined_stiffener_iter = predefined_stiffener_iter,
- processes=self._new_processes.get(),
- min_max_span=max_min_span, use_weight_filter=False,
- fdwn = self._new_fdwn.get(), fup = self._new_fdwn.get(),
- ml_algo=self._ML_buckling))
+ self.get_upper_bounds(init_obj),
+ lateral_press, self.get_deltas(),
+ algorithm=self._new_algorithm.get(),
+ trials=self._new_algorithm_random_trials.get(),
+ side=init_obj.Plate.get_side(),
+ const_chk=contraints,
+ pso_options=self.pso_parameters,
+ fatigue_obj=fat_obj,
+ fat_press_ext_int=fat_press,
+ slamming_press=slamming_pressure,
+ predefined_stiffener_iter=predefined_stiffener_iter,
+ processes=self._new_processes.get(),
+ min_max_span=max_min_span, use_weight_filter=False,
+ fdwn=self._new_fdwn.get(), fup=self._new_fup.get(),
+ ml_algo=self._get_selected_ml_algo(),
+ material_factor=selected_mat_fac))
self._harmonizer_data[line] = {}
counter += 1
self.progress_count.set(counter)
@@ -653,7 +697,7 @@ def run_optimizaion(self):
if self._opt_results[line] != None:
self._opt_actual_running_time.config(text='Accumulated running time: \n'
- + str(time.time() - t_start) + ' sec')
+ + str(time.time() - t_start) + ' sec')
# print('Runned', line, 'OK')
# else:
# print('Runned', line, 'NOT OK - no results')
@@ -683,23 +727,29 @@ def opt_harmonizer_historic(self):
# except TypeError:
# [print(val) for val in fail_ok[2]]
- all_ok_checks.append(tuple([round(val,10) for val in fail_ok[2][0:6]]))
+ all_ok_checks.append(tuple([round(val, 10) for val in fail_ok[2][0:6]]))
all_ok_checks = set(all_ok_checks)
# make iterator for multiprocessing
iterator = list()
- to_check = (self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
+ to_check = [self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
self._new_check_shear_area.get(), self._new_check_buckling.get(),
self._new_check_fatigue.get(), self._new_check_slamming.get(),
- self._new_check_local_buckling.get(), False, self._new_check_ml_buckling.get(), False)
+ self._new_check_local_buckling.get(), self._new_check_semi_analytical_buckling.get(),
+ False,
+ self._new_check_ml_numeric_buckling.get()]
iter_run_info = dict()
+ selected_mat_fac = self._get_selected_material_factor()
+
for slave_line in self._opt_results.keys():
input_pressures = self.get_pressure_input(slave_line)
iter_run_info[slave_line] = {'lateral pressure': input_pressures['lateral pressure'],
'fatigue pressure': input_pressures['fatigue pressure'],
- 'fatigue object':input_pressures['fatigue object'],
- 'slamming pressure':input_pressures['slamming pressure'],
- 'chk_calc_obj': self._opt_results[slave_line][0], 'ML-CL': [0,0],
+ 'fatigue object': input_pressures['fatigue object'],
+ 'slamming pressure': input_pressures['slamming pressure'],
+ 'chk_calc_obj': self._opt_results[slave_line][0],
+ 'SemiAnalytical': [float('inf'), float('inf'), 0, 0.87],
+ 'ML-Numeric': [float('inf'), float('inf'), 0],
'fup': self._new_fup.get(), 'fdwn': self._new_fdwn.get()}
iter_run_info['lines'] = list(self._opt_results.keys())
iter_run_info['checks'] = to_check
@@ -708,100 +758,69 @@ def opt_harmonizer_historic(self):
for x_check in all_ok_checks:
iterator.append({'x': x_check, 'info': iter_run_info})
- if to_check[8]:
- # Do ML-CL checks
+ if to_check[7] or to_check[9]:
+ # Do SemiAnalytical or ML-Numeric checks for harmonized candidates
to_run = list()
+ to_run_owner = list()
+
for x_and_info in iterator:
for slave_line in x_and_info['info']['lines']:
iter_run_info = x_and_info['info']
lateral_press = iter_run_info[slave_line]['lateral pressure']
- fat_press = iter_run_info[slave_line]['fatigue pressure']
fat_obj = iter_run_info[slave_line]['fatigue object']
- slamming_pressure = iter_run_info[slave_line]['slamming pressure']
chk_calc_obj = iter_run_info[slave_line]['chk_calc_obj']
master_x = list(x_and_info['x'])
+
if iter_run_info['keep spacing']:
- x = [chk_calc_obj.Plate.get_s()] + master_x[1:] + [chk_calc_obj.Plate.span, chk_calc_obj.Plate.girder_lg]
+ x = [chk_calc_obj.Plate.get_s()] + master_x[1:] + [
+ chk_calc_obj.Plate.span,
+ chk_calc_obj.Plate.girder_lg,
+ ]
else:
x = master_x + [chk_calc_obj.Plate.span, chk_calc_obj.Plate.girder_lg]
+
fdwn = self._new_fdwn.get()
- fup = self._new_fdwn.get()
- calc_object = op.create_new_calc_obj(chk_calc_obj, x, fat_obj.get_fatigue_properties(), fdwn=fdwn,
- fup=fup)
-
- calc_object_stf = op.create_new_calc_obj(chk_calc_obj.Stiffener, x,
- fat_obj.get_fatigue_properties(), fdwn=fdwn, fup=fup)
- calc_object_pl = op.create_new_calc_obj(chk_calc_obj.Plate, x, fat_obj.get_fatigue_properties(),
- fdwn=fdwn, fup=fup)
- calc_object = [calc.AllStructure(Plate=calc_object_pl[0], Stiffener=calc_object_stf[0], Girder=None,
- main_dict=chk_calc_obj.get_main_properties()['main dict']),
- calc_object_pl[1]]
+ fup = self._new_fup.get()
+
+ calc_object_stf = op.create_new_calc_obj(
+ chk_calc_obj.Stiffener,
+ x,
+ None if fat_obj is None else fat_obj.get_fatigue_properties(),
+ fdwn=fdwn,
+ fup=fup,
+ )
+ calc_object_pl = op.create_new_calc_obj(
+ chk_calc_obj.Plate,
+ x,
+ None if fat_obj is None else fat_obj.get_fatigue_properties(),
+ fdwn=fdwn,
+ fup=fup,
+ )
+ calc_object = [
+ calc.AllStructure(
+ Plate=calc_object_pl[0],
+ Stiffener=None if chk_calc_obj.Stiffener is None else calc_object_stf[0],
+ Girder=None,
+ main_dict=chk_calc_obj.get_main_properties()['main dict'],
+ ),
+ calc_object_pl[1],
+ ]
calc_object[0].lat_press = lateral_press
+ to_run_owner.append((x_and_info, slave_line))
to_run.append((calc_object, x, lateral_press))
- # ML-CL to be used.
- sp_int, sp_gl_gt, up_int, up_gl_gt, \
- sp_int_idx, sp_gl_gt_idx, up_int_idx, up_gl_gt_idx = \
- list(), list(), list(), list(), list(), list(), list(), list()
-
- # Create iterator
- idx_count = 0
- for calc_object, x, lat_press in to_run:
- idx_count += 1
+ if to_check[7]:
+ sort_again = self._predict_semi_analytical_for_to_run(to_run)
+ result_key = 'SemiAnalytical'
+ else:
+ sort_again = self._predict_ml_numeric_for_to_run(to_run)
+ result_key = 'ML-Numeric'
- if calc_object[0].Plate.get_puls_sp_or_up() == 'UP':
- if calc_object[0].Plate.get_puls_boundary() == 'Int':
- up_int.append(calc_object[0].Stiffener.get_buckling_ml_input(lat_press, alone=False))
- up_int_idx.append(idx_count-1)
- else:
- up_gl_gt_idx.append(idx_count-1)
- else:
- if calc_object[0].Stiffener.get_puls_boundary() == 'Int':
- sp_int.append(calc_object[0].Stiffener.get_buckling_ml_input(lat_press, alone=False))
- sp_int_idx.append(idx_count-1)
- else:
- sp_gl_gt.append(calc_object[0].Stiffener.get_buckling_ml_input(lat_press, alone=False))
- sp_gl_gt_idx.append(idx_count-1)
-
- # Predict
- sort_again = np.zeros([len(to_run), 2])
-
- if len(sp_int) != 0:
- sp_int_res = [self._ML_buckling['cl SP buc int predictor'].predict(self._ML_buckling['cl SP buc int scaler']
- .transform(sp_int)),
- self._ML_buckling['cl SP ult int predictor'].predict(self._ML_buckling['cl SP buc int scaler']
- .transform(sp_int))]
- for idx, res_buc, res_ult in zip(sp_int_idx, sp_int_res[0], sp_int_res[1]):
- sort_again[idx] = [res_buc, res_ult]
-
- if len(sp_gl_gt) != 0:
- sp_gl_gt_res = [self._ML_buckling['cl SP buc GLGT predictor'].predict(self._ML_buckling['cl SP buc GLGT scaler']
- .transform(sp_gl_gt)),
- self._ML_buckling['cl SP buc GLGT predictor'].predict(self._ML_buckling['cl SP buc GLGT scaler']
- .transform(sp_gl_gt))]
- for idx, res_buc, res_ult in zip(sp_gl_gt_idx, sp_gl_gt_res[0], sp_gl_gt_res[1]):
- sort_again[idx] = [res_buc, res_ult]
- if len(up_int) != 0:
- up_int_res = [self._ML_buckling['cl UP buc int predictor'].predict(self._ML_buckling['cl UP buc int scaler']
- .transform(up_int)),
- self._ML_buckling['cl UP ult int predictor'].predict(self._ML_buckling['cl UP buc int scaler']
- .transform(up_int))]
- for idx, res_buc, res_ult in zip(up_int_idx, up_int_res[0], up_int_res[1]):
- sort_again[idx] = [res_buc, res_ult]
- if len(up_gl_gt) != 0:
- up_gl_gt_res = [self._ML_buckling['cl UP buc GLGT predictor'].predict(self._ML_buckling['cl UP buc GLGT scaler']
- .transform(up_gl_gt)),
- self._ML_buckling['cl UP buc GLGT predictor'].predict(self._ML_buckling['cl UP buc GLGT scaler']
- .transform(up_gl_gt))]
- for idx, res_buc, res_ult in zip(up_gl_gt_idx, up_gl_gt_res[0], up_gl_gt_res[1]):
- sort_again[idx] = [res_buc, res_ult]
-
- for idx, x_and_info in enumerate(iterator):
- for slave_line in x_and_info['info']['lines']:
- iterator[idx]['info'][slave_line]['ML-CL'] = sort_again[idx]
+ for idx, (x_and_info, slave_line) in enumerate(to_run_owner):
+ x_and_info['info'][slave_line][result_key] = sort_again[idx]
- # END ML-CL calc
+ # END ML calc
processes = max(cpu_count() - 1, 1)
with Pool(processes) as my_process:
@@ -812,36 +831,32 @@ def opt_harmonizer_historic(self):
if res is not None:
after_multirun_check_ok.append(res)
- lowest_area, lowest_x = float('inf'), None
+ lowest_area, lowest_x = float('inf'), None
for ok_chkd in set(after_multirun_check_ok):
- if sum(op.get_field_tot_area(ok_chkd )) < lowest_area:
+ if sum(op.get_field_tot_area(ok_chkd)) < lowest_area:
lowest_area = sum(op.get_field_tot_area(ok_chkd))
lowest_x = ok_chkd
if lowest_area != float('inf'):
for line in self._opt_results.keys():
+ line_structure_obj = self._line_structure(line)
if self._keep_spacing:
- this_x = [self._line_to_struc[line][0].Plate.get_s()] + list(lowest_x)[1:] + \
- [self._line_to_struc[line][0].Plate.span, self._line_to_struc[line][0].Plate.girder_lg]
+ this_x = [line_structure_obj.Plate.get_s()] + list(lowest_x)[1:] + \
+ [line_structure_obj.Plate.span, line_structure_obj.Plate.girder_lg]
else:
- this_x = list(lowest_x) + [self._line_to_struc[line][0].Plate.span,
- self._line_to_struc[line][0].Plate.girder_lg]
+ this_x = list(lowest_x) + [line_structure_obj.Plate.span,
+ line_structure_obj.Plate.girder_lg]
- calc_object_stf = op.create_new_calc_obj(self._line_to_struc[line][0].Plate, this_x,
- fat_obj.get_fatigue_properties(), fdwn=fdwn, fup=fup)
- calc_object_pl = op.create_new_calc_obj(self._line_to_struc[line][0].Stiffener, this_x,
+ calc_object_stf = op.create_new_calc_obj(line_structure_obj.Plate, this_x,
fat_obj.get_fatigue_properties(), fdwn=fdwn, fup=fup)
- self._opt_results[line][0] = [calc.AllStructure(Plate=calc_object_pl[0], Stiffener=calc_object_stf[0], Girder=None,
- main_dict=chk_calc_obj.get_main_properties()['main dict']),
- calc_object_pl[1]]
-
- if self._line_to_struc[line][2] != None:
- self._opt_results[line][2] = opt.create_new_calc_obj(init_obj= self._line_to_struc[line][1],
- x = this_x,
- fat_dict=self._line_to_struc[line]
- [2].get_fatigue_properties())[1]
- else:
- self._line_to_struc[line][2] = None
+ calc_object_pl = op.create_new_calc_obj(line_structure_obj.Stiffener, this_x,
+ fat_obj.get_fatigue_properties(), fdwn=fdwn, fup=fup)
+ self._opt_results[line][0] = [
+ calc.AllStructure(Plate=calc_object_pl[0], Stiffener=calc_object_stf[0], Girder=None,
+ main_dict=chk_calc_obj.get_main_properties()['main dict']),
+ calc_object_pl[1]]
+
+ self._update_harmonized_fatigue_result(line, this_x)
return True
else:
for line in self._opt_results.keys():
@@ -856,17 +871,17 @@ def opt_harmonizer(self):
'''
# Find highest section modulus.
- harm_res= {}
+ harm_res = {}
chk = (self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
- self._new_check_shear_area.get(), self._new_check_buckling.get(),
- self._new_check_fatigue.get(), self._new_check_slamming.get(),
- self._new_check_local_buckling.get())
+ self._new_check_shear_area.get(), self._new_check_buckling.get(),
+ self._new_check_fatigue.get(), self._new_check_slamming.get(),
+ self._new_check_local_buckling.get())
for master_line in self._opt_results.keys():
master_obj = self._opt_results[master_line][0]
master_x = [master_obj.Plate.get_s(), master_obj.Plate.get_pl_thk(), master_obj.Stiffener.get_web_h(),
master_obj.Stiffener.get_web_thk(), master_obj.Stiffener.get_fl_w(),
master_obj.Stiffener.get_fl_thk(),
- master_obj.Plate.span,master_obj.Plate.girder_lg]
+ master_obj.Plate.span, master_obj.Plate.girder_lg]
harm_res[master_line] = []
for slave_line in self._opt_results.keys():
input_pressures = self.get_pressure_input(slave_line)
@@ -877,31 +892,33 @@ def opt_harmonizer(self):
chk_calc_obj = self._opt_results[slave_line][1]
chk_result = list(op.run_optmizataion(chk_calc_obj,
- master_x[0:6]+[chk_calc_obj.Plate.span,
- chk_calc_obj.Plate.girder_lg],
- master_x[0:6]+[chk_calc_obj.Plate.span,
- chk_calc_obj.Plate.girder_lg],
- lateral_press,self.get_deltas(),
+ master_x[0:6] + [chk_calc_obj.Plate.span,
+ chk_calc_obj.Plate.girder_lg],
+ master_x[0:6] + [chk_calc_obj.Plate.span,
+ chk_calc_obj.Plate.girder_lg],
+ lateral_press, self.get_deltas(),
algorithm=self._new_algorithm.get(),
trials=self._new_algorithm_random_trials.get(),
side=chk_calc_obj.Plate.get_side(), const_chk=chk,
- pso_options=self.pso_parameters,fatigue_obj=fat_obj,
+ pso_options=self.pso_parameters, fatigue_obj=fat_obj,
fat_press_ext_int=fat_press, slamming_press=slamming_pressure,
- predefined_stiffener_iter = None,
+ predefined_stiffener_iter=None,
processes=self._new_processes.get(), min_max_span=None,
use_weight_filter=True,
- fdwn = self._new_fdwn.get(), fup = self._new_fdwn.get()))[0:4]
+ fdwn=self._new_fdwn.get(), fup=self._new_fup.get()))[0:4]
print('Master:', master_line, 'Slave', slave_line, 'Check', chk_result[-1])
harm_res[master_line].append(chk_result)
- harmonized_area, harmonized_line =float('inf'), None
+ harmonized_area, harmonized_line = float('inf'), None
for master_line, all_slave_res in harm_res.items():
if all([slave_line_res[-1] for slave_line_res in all_slave_res]):
master_obj = self._opt_results[master_line][0]
master_area = sum(op.get_field_tot_area([master_obj.Plate.get_s(), master_obj.Plate.get_pl_thk(),
- master_obj.Stiffener.get_web_h(), master_obj.Stiffener.get_web_thk(),
- master_obj.Stiffener.get_fl_w(), master_obj.Stiffener.get_fl_thk(),
- master_obj.Plate.span,master_obj.Plate.girder_lg]))
+ master_obj.Stiffener.get_web_h(),
+ master_obj.Stiffener.get_web_thk(),
+ master_obj.Stiffener.get_fl_w(),
+ master_obj.Stiffener.get_fl_thk(),
+ master_obj.Plate.span, master_obj.Plate.girder_lg]))
if master_area < harmonized_area:
harmonized_area = master_area
harmonized_line = master_line
@@ -911,21 +928,18 @@ def opt_harmonizer(self):
harmonized_x_stf = self._opt_results[harmonized_line][0].Stiffener.get_tuple()
harmonized_x = harmonized_x_pl[0:2] + harmonized_x_stf[2:]
for line in self._opt_results.keys():
- self._opt_results[line][0] = opt.create_new_structure_obj(self._line_to_struc[line][0], harmonized_x)
+ line_structure_obj = self._line_structure(line)
+ self._opt_results[line][0] = opt.create_new_structure_obj(line_structure_obj, harmonized_x)
- calc_object_stf = op.create_new_calc_obj(self._line_to_struc[line][0].Plate, harmonized_x)
- calc_object_pl = op.create_new_calc_obj(self._line_to_struc[line][0].Stiffener,harmonized_x)
+ calc_object_stf = op.create_new_calc_obj(line_structure_obj.Plate, harmonized_x)
+ calc_object_pl = op.create_new_calc_obj(line_structure_obj.Stiffener, harmonized_x)
self._opt_results[line][0] = [calc.AllStructure(Plate=calc_object_pl[0], Stiffener=calc_object_stf[0],
- Girder=None, main_dict=chk_calc_obj.get_main_properties()['main dict']),
- calc_object_pl[1]]
-
- if self._line_to_struc[line][2] != None:
- self._opt_results[line][2] = opt.create_new_calc_obj(init_obj= self._line_to_struc[line][1],
- x = harmonized_x,
- fat_dict=self._line_to_struc[line]
- [2].get_fatigue_properties())[1]
- else:
- self._line_to_struc[line][2] = None
+ Girder=None,
+ main_dict=chk_calc_obj.get_main_properties()[
+ 'main dict']),
+ calc_object_pl[1]]
+
+ self._update_harmonized_fatigue_result(line, harmonized_x)
return True
else:
for line in self._opt_results.keys():
@@ -943,18 +957,20 @@ def get_running_time(self):
number_of_combinations = \
max((self._new_spacing_upper.get() - self._new_spacing_lower.get()) / self._new_delta_spacing.get(),
1) * \
- max((self._new_pl_thk_upper.get() - self._new_pl_thk_lower.get()) / self._new_delta_pl_thk.get(), 1) * \
+ max((self._new_pl_thk_upper.get() - self._new_pl_thk_lower.get()) / self._new_delta_pl_thk.get(),
+ 1) * \
max((self._new_web_h_upper.get() - self._new_web_h_lower.get()) / self._new_delta_web_h.get(), 1) * \
max((self._new_web_thk_upper.get() - self._new_web_thk_lower.get()) / self._new_delta_web_thk.get(),
1) * \
max((self._new_fl_w_upper.get() - self._new_fl_w_lower.get()) / self._new_delta_fl_w.get(), 1) * \
max((self._new_fl_thk_upper.get() - self._new_fl_thk_lower.get()) / self._new_delta_fl_thk.get(), 1)
- return int(number_of_combinations * self.running_time_per_item)*len(self._active_lines)
+ return int(number_of_combinations * self.running_time_per_item) * len(self._active_lines)
except TclError:
return 0
else:
try:
- return int(self._new_algorithm_random_trials.get() * self.running_time_per_item)*len(self._active_lines)
+ return int(self._new_algorithm_random_trials.get() * self.running_time_per_item) * len(
+ self._active_lines)
except TclError:
return 0
@@ -967,26 +983,318 @@ def get_deltas(self):
float(self._new_delta_web_h.get()) / 1000, float(self._new_delta_web_thk.get()) / 1000,
float(self._new_delta_fl_w.get()) / 1000, float(self._new_delta_fl_thk.get()) / 1000])
+ def _get_selected_material_factor(self):
+ """
+ Return selected material factor from the main application when available.
+ Fallback is the material factor on the first selected line, and then 1.15.
+ """
+ try:
+ return float(self.app._new_material_factor.get())
+ except Exception:
+ pass
+
+ try:
+ if len(self._active_lines) > 0:
+ first_line = self._active_lines[0]
+ return float(self._line_structure(first_line).Plate.mat_factor)
+ except Exception:
+ pass
+
+ return 1.15
+
+ def _apply_material_factor_to_structure(self, obj, mat_fac):
+ """Apply selected material factor to Plate/Stiffener/Girder for optimizer checks."""
+ try:
+ mat_fac = float(mat_fac)
+ except Exception:
+ return obj
+
+ for attr_name in ('Plate', 'Stiffener', 'Girder'):
+ try:
+ part = getattr(obj, attr_name)
+ except Exception:
+ part = None
+ if part is not None:
+ try:
+ part.mat_factor = mat_fac
+ except Exception:
+ pass
+
+ return obj
+
+ def _get_selected_ml_algo(self):
+ """
+ Return ML model dictionary for the selected material factor.
+
+ Main app usually stores:
+ self._ML_buckling[1.10][key]
+ self._ML_buckling[1.15][key]
+
+ Standalone/test mode may store a flat dictionary:
+ self._ML_buckling[key]
+ """
+ mat_fac = self._get_selected_material_factor()
+
+ try:
+ if mat_fac in self._ML_buckling:
+ return self._ML_buckling[mat_fac]
+ except Exception:
+ pass
+
+ return self._ML_buckling
+
+ def _get_ml_input_for_calc_object(self, calc_object, lat_press):
+ """
+ Return the correct ML input row for SP/UP candidate objects.
+ """
+ if calc_object[0].Plate.get_puls_sp_or_up() == 'UP' or calc_object[0].Stiffener is None:
+ return calc_object[0].Plate.get_buckling_ml_input(lat_press, alone=False)
+
+ return calc_object[0].Stiffener.get_buckling_ml_input(lat_press, alone=False)
+
+ def _predict_ml_cl_for_to_run(self, to_run):
+ """
+ Predict ML-CL class results for harmonizer candidate objects.
+
+ ML-CL is deactivated; this method is retained only for compatibility
+ with older internal call sites.
+
+ Returns
+ -------
+ np.ndarray
+ shape = (len(to_run), 2)
+ columns = [buckling_class, ultimate_class]
+ """
+ ml_algo = self._get_selected_ml_algo()
+
+ sp_int, sp_gl_gt, up_int, up_gl_gt = list(), list(), list(), list()
+ sp_int_idx, sp_gl_gt_idx, up_int_idx, up_gl_gt_idx = list(), list(), list(), list()
+
+ for idx, (calc_object, x, lat_press) in enumerate(to_run):
+ ml_input = self._get_ml_input_for_calc_object(calc_object, lat_press)
+
+ if calc_object[0].Plate.get_puls_sp_or_up() == 'UP':
+ if calc_object[0].Plate.get_puls_boundary() == 'Int':
+ up_int.append(ml_input)
+ up_int_idx.append(idx)
+ else:
+ up_gl_gt.append(ml_input)
+ up_gl_gt_idx.append(idx)
+ else:
+ if calc_object[0].Stiffener.get_puls_boundary() == 'Int':
+ sp_int.append(ml_input)
+ sp_int_idx.append(idx)
+ else:
+ sp_gl_gt.append(ml_input)
+ sp_gl_gt_idx.append(idx)
+
+ sort_again = np.zeros([len(to_run), 2])
+
+ if len(sp_int) != 0:
+ x_buc = ml_algo['cl SP buc int scaler'].transform(sp_int)
+ x_ult = ml_algo['cl SP ult int scaler'].transform(sp_int)
+ sp_int_res = [
+ ml_algo['cl SP buc int predictor'].predict(x_buc),
+ ml_algo['cl SP ult int predictor'].predict(x_ult),
+ ]
+ for idx, res_buc, res_ult in zip(sp_int_idx, sp_int_res[0], sp_int_res[1]):
+ sort_again[idx] = [res_buc, res_ult]
+
+ if len(sp_gl_gt) != 0:
+ x_buc = ml_algo['cl SP buc GLGT scaler'].transform(sp_gl_gt)
+ x_ult = ml_algo['cl SP ult GLGT scaler'].transform(sp_gl_gt)
+ sp_gl_gt_res = [
+ ml_algo['cl SP buc GLGT predictor'].predict(x_buc),
+ ml_algo['cl SP ult GLGT predictor'].predict(x_ult),
+ ]
+ for idx, res_buc, res_ult in zip(sp_gl_gt_idx, sp_gl_gt_res[0], sp_gl_gt_res[1]):
+ sort_again[idx] = [res_buc, res_ult]
+
+ if len(up_int) != 0:
+ x_buc = ml_algo['cl UP buc int scaler'].transform(up_int)
+ x_ult = ml_algo['cl UP ult int scaler'].transform(up_int)
+ up_int_res = [
+ ml_algo['cl UP buc int predictor'].predict(x_buc),
+ ml_algo['cl UP ult int predictor'].predict(x_ult),
+ ]
+ for idx, res_buc, res_ult in zip(up_int_idx, up_int_res[0], up_int_res[1]):
+ sort_again[idx] = [res_buc, res_ult]
+
+ if len(up_gl_gt) != 0:
+ x_buc = ml_algo['cl UP buc GLGT scaler'].transform(up_gl_gt)
+ x_ult = ml_algo['cl UP ult GLGT scaler'].transform(up_gl_gt)
+ up_gl_gt_res = [
+ ml_algo['cl UP buc GLGT predictor'].predict(x_buc),
+ ml_algo['cl UP ult GLGT predictor'].predict(x_ult),
+ ]
+ for idx, res_buc, res_ult in zip(up_gl_gt_idx, up_gl_gt_res[0], up_gl_gt_res[1]):
+ sort_again[idx] = [res_buc, res_ult]
+
+ return sort_again
+
+ def _predict_semi_analytical_for_to_run(self, to_run):
+ """
+ Predict built-in SemiAnalytical UF results for harmonizer candidate objects.
+
+ Returns columns [buckling_uf, ultimate_uf, valid_prediction, acceptance].
+ U3 will reuse this GUI slot when the unstiffened solver branch is added.
+ """
+ sort_again = np.full([len(to_run), 4], float('inf'), dtype=float)
+ sort_again[:, 2] = 0
+ sort_again[:, 3] = 0.87
+
+ for idx, (calc_object, x, lat_press) in enumerate(to_run):
+ sort_again[idx, 0:3] = op._predict_semi_analytical_uf(calc_object, lat_press)
+
+ return sort_again
+
+ def _predict_ml_numeric_for_to_run(self, to_run):
+ """
+ Predict ML-Numeric UF results for harmonizer candidate objects.
+
+ The numeric regressor predicts raw UF. The optimization check expects
+ material-factored UF, so this method applies:
+ UF = predicted_UF * material_factor
+
+ Returns
+ -------
+ np.ndarray
+ shape = (len(to_run), 3)
+ columns = [buckling_uf, ultimate_uf, valid_prediction]
+ """
+ ml_algo = self._get_selected_ml_algo()
+ mat_fac = self._get_selected_material_factor()
+
+ groups = {
+ 'sp_int': {'x': [], 'idx': [], 'prefix': 'num SP int'},
+ 'sp_glgt': {'x': [], 'idx': [], 'prefix': 'num SP GLGT'},
+ 'up_int': {'x': [], 'idx': [], 'prefix': 'num UP int'},
+ 'up_glgt': {'x': [], 'idx': [], 'prefix': 'num UP GLGT'},
+ }
+
+ for idx, (calc_object, x, lat_press) in enumerate(to_run):
+ ml_input = self._get_ml_input_for_calc_object(calc_object, lat_press)
+
+ if calc_object[0].Plate.get_puls_sp_or_up() == 'UP':
+ if calc_object[0].Plate.get_puls_boundary() == 'Int':
+ key = 'up_int'
+ else:
+ key = 'up_glgt'
+ else:
+ if calc_object[0].Stiffener.get_puls_boundary() == 'Int':
+ key = 'sp_int'
+ else:
+ key = 'sp_glgt'
+
+ groups[key]['x'].append(ml_input)
+ groups[key]['idx'].append(idx)
+
+ sort_again = np.zeros([len(to_run), 3])
+ sort_again[:, 0] = float('inf')
+ sort_again[:, 1] = float('inf')
+ sort_again[:, 2] = 0
+
+ for group in groups.values():
+ if len(group['x']) == 0:
+ continue
+
+ prefix = group['prefix']
+
+ valid_predictor_key = f'{prefix} validity predictor'
+ valid_xscaler_key = f'{prefix} validity xscaler'
+ reg_predictor_key = f'{prefix} UF reg predictor'
+ reg_xscaler_key = f'{prefix} UF reg xscaler'
+ reg_yscaler_key = f'{prefix} UF reg yscaler'
+
+ required_keys = [
+ valid_predictor_key,
+ valid_xscaler_key,
+ reg_predictor_key,
+ reg_xscaler_key,
+ reg_yscaler_key,
+ ]
+
+ if any(key not in ml_algo or ml_algo[key] is None for key in required_keys):
+ continue
+
+ x_valid = ml_algo[valid_xscaler_key].transform(group['x'])
+ valid_pred = ml_algo[valid_predictor_key].predict(x_valid)
+
+ x_reg = ml_algo[reg_xscaler_key].transform(group['x'])
+ y_scaled = ml_algo[reg_predictor_key].predict(x_reg)
+ y_numeric_raw = ml_algo[reg_yscaler_key].inverse_transform(y_scaled)
+
+ for local_idx, global_idx in enumerate(group['idx']):
+ valid_int = int(valid_pred[local_idx])
+ if valid_int == 1:
+ sort_again[global_idx, 0] = float(y_numeric_raw[local_idx, 0]) * mat_fac
+ sort_again[global_idx, 1] = float(y_numeric_raw[local_idx, 1]) * mat_fac
+ sort_again[global_idx, 2] = 1
+ else:
+ sort_again[global_idx, 0] = float('inf')
+ sort_again[global_idx, 1] = float('inf')
+ sort_again[global_idx, 2] = valid_int
+ return sort_again
def update_running_time(self, *args):
- '''
- Estimate the running time of the algorithm.
- :return:
- '''
+ """
+ Estimate the running time of the algorithm and keep buckling checks mutually exclusive.
+ """
try:
self._runnig_time_label.config(text=str(self.get_running_time()))
except ZeroDivisionError:
pass # _tkinter.TclError: pass
- if self._new_check_ml_buckling.get() == True:
+ selected_buckling_checks = [
+ self._new_check_buckling.get(),
+ self._new_check_semi_analytical_buckling.get(),
+ False,
+ self._new_check_ml_numeric_buckling.get(),
+ ]
+
+ if selected_buckling_checks.count(True) > 1:
+ tk.messagebox.showerror('You can only select one buckling type. Reselect.')
+
+ self._new_check_buckling.set(False)
+ self._new_check_local_buckling.set(False)
+ self._new_check_semi_analytical_buckling.set(False)
+ self._new_check_ml_buckling.set(False)
+ self._new_check_ml_numeric_buckling.set(False)
+
+ elif (self._new_check_semi_analytical_buckling.get() or self._new_check_ml_numeric_buckling.get()):
self._new_check_buckling.set(False)
self._new_check_local_buckling.set(False)
- def get_upper_bounds(self,obj):
+ def _line_bundle(self, line):
+ return self._line_to_struc[line]
+
+ def _line_structure(self, line):
+ return line_structure.structure(self._line_bundle(line))
+
+ def _line_stiffener(self, line):
+ return line_structure.stiffener(self._line_bundle(line))
+
+ def _line_fatigue(self, line):
+ return line_structure.fatigue(self._line_bundle(line))
+
+ def _update_harmonized_fatigue_result(self, line, x):
+ fatigue_obj = self._line_fatigue(line)
+ if fatigue_obj is not None:
+ self._opt_results[line][2] = opt.create_new_calc_obj(
+ init_obj=self._line_stiffener(line),
+ x=x,
+ fat_dict=fatigue_obj.get_fatigue_properties())[1]
+ else:
+ self._clear_line_fatigue(line)
+
+ def _clear_line_fatigue(self, line):
+ self._line_bundle(line)[line_structure.FATIGUE] = None
+
+ def get_upper_bounds(self, obj):
'''
Return an numpy array of upper bounds.
- :return:
+ :return:
'''
if self._keep_spacing:
spacing = obj.Plate.get_s()
@@ -997,10 +1305,10 @@ def get_upper_bounds(self,obj):
self._new_fl_w_upper.get() / 1000, self._new_fl_thk_upper.get() / 1000,
obj.Plate.span, obj.Plate.girder_lg])
- def get_lower_bounds(self,obj):
+ def get_lower_bounds(self, obj):
'''
Return an numpy array of lower bounds.
- :return:
+ :return:
'''
if self._keep_spacing:
spacing = obj.Plate.get_s()
@@ -1014,8 +1322,8 @@ def get_lower_bounds(self,obj):
def checkered(self, line_distance):
'''
Creates a grid in the properties canvas.
- :param line_distance:
- :return:
+ :param line_distance:
+ :return:
'''
# vertical lines at an interval of "line_distance" pixel
for x in range(line_distance, self._prop_canvas_dim[0], line_distance):
@@ -1024,7 +1332,7 @@ def checkered(self, line_distance):
for y in range(line_distance, self._prop_canvas_dim[1], line_distance):
self._canvas_opt.create_line(0, y, self._prop_canvas_dim[0], y, fill="grey", stipple='gray50')
- def draw_properties(self,init_obj = None, opt_obj=None,line=None):
+ def draw_properties(self, init_obj=None, opt_obj=None, line=None):
'''
Drawing properties in the canvas.
:return:
@@ -1041,114 +1349,123 @@ def draw_properties(self,init_obj = None, opt_obj=None,line=None):
self._canvas_opt.create_rectangle(0, 0, self._prop_canvas_dim[0] + 10, 80, fill='white')
self._canvas_opt.create_line(10, 10, 30, 10, fill=init_color, width=5)
- self._canvas_opt.create_text(270, 10, text='Initial - Pl.: ' + str(init_obj.Plate.get_s() * 1000) + 'x' + str(
- init_obj.Plate.get_pl_thk() * 1000) +
- ' Stf.: ' + str(init_obj.Stiffener.get_web_h() * 1000) + 'x' + str(
- init_obj.Stiffener.get_web_thk() * 1000) + '+' +
- str(init_obj.Stiffener.get_fl_w() * 1000) + 'x' +
- str(init_obj.Stiffener.get_fl_thk() * 1000),
- font='Verdana 8',
- fill=init_color)
+ self._canvas_opt.create_text(270, 10,
+ text='Initial - Pl.: ' + str(init_obj.Plate.get_s() * 1000) + 'x' + str(
+ init_obj.Plate.get_pl_thk() * 1000) +
+ ' Stf.: ' + str(init_obj.Stiffener.get_web_h() * 1000) + 'x' + str(
+ init_obj.Stiffener.get_web_thk() * 1000) + '+' +
+ str(init_obj.Stiffener.get_fl_w() * 1000) + 'x' +
+ str(init_obj.Stiffener.get_fl_thk() * 1000),
+ font='Verdana 8',
+ fill=init_color)
self._canvas_opt.create_text(120, 30, text='Weight (per Lg width): ' +
- str(int(op.calc_weight([init_obj.Plate.get_s(),
- init_obj.Plate.get_pl_thk(),
- init_obj.Stiffener.get_web_h(),
- init_obj.Stiffener.get_web_thk(),
- init_obj.Stiffener.get_fl_w(),
- init_obj.Stiffener.get_fl_thk(),
- init_obj.Stiffener.span,
- init_obj.Stiffener.girder_lg]))),
- font='Verdana 8', fill=init_color)
-
- self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Plate.get_s() / 2, ctr_y, ctr_x + m * init_obj.Plate.get_s() / 2,
- ctr_y - m * init_obj.Plate.get_pl_thk(), fill=init_color, stipple=init_stipple)
- self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Stiffener.get_web_thk() / 2, ctr_y - m * init_obj.Stiffener.get_pl_thk(),
- ctr_x + m * init_obj.Stiffener.get_web_thk() / 2, ctr_y - m * (init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_pl_thk())
- , fill=init_color, stipple=init_stipple)
+ str(int(op.calc_weight([init_obj.Plate.get_s(),
+ init_obj.Plate.get_pl_thk(),
+ init_obj.Stiffener.get_web_h(),
+ init_obj.Stiffener.get_web_thk(),
+ init_obj.Stiffener.get_fl_w(),
+ init_obj.Stiffener.get_fl_thk(),
+ init_obj.Stiffener.span,
+ init_obj.Stiffener.girder_lg]))),
+ font='Verdana 8', fill=init_color)
+
+ self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Plate.get_s() / 2, ctr_y,
+ ctr_x + m * init_obj.Plate.get_s() / 2,
+ ctr_y - m * init_obj.Plate.get_pl_thk(), fill=init_color,
+ stipple=init_stipple)
+ self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Stiffener.get_web_thk() / 2,
+ ctr_y - m * init_obj.Stiffener.get_pl_thk(),
+ ctr_x + m * init_obj.Stiffener.get_web_thk() / 2, ctr_y - m * (
+ init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_pl_thk())
+ , fill=init_color, stipple=init_stipple)
if init_obj.Stiffener.get_stiffener_type() not in ['L', 'L-bulb']:
- self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Stiffener.get_fl_w() / 2, ctr_y - m * (init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h()),
- ctr_x + m * init_obj.Stiffener.get_fl_w() / 2,
- ctr_y - m * (init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_fl_thk()),
- fill=init_color, stipple=init_stipple)
+ self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Stiffener.get_fl_w() / 2, ctr_y - m * (
+ init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h()),
+ ctr_x + m * init_obj.Stiffener.get_fl_w() / 2,
+ ctr_y - m * (
+ init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_fl_thk()),
+ fill=init_color, stipple=init_stipple)
else:
self._canvas_opt.create_rectangle(ctr_x - m * init_obj.Stiffener.get_web_thk() / 2,
- ctr_y - m * (init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h()),
- ctr_x + m * init_obj.Stiffener.get_fl_w(),
- ctr_y - m * (init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_fl_thk()),
- fill=init_color, stipple=init_stipple)
-
+ ctr_y - m * (
+ init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h()),
+ ctr_x + m * init_obj.Stiffener.get_fl_w(),
+ ctr_y - m * (
+ init_obj.Plate.get_pl_thk() + init_obj.Stiffener.get_web_h() + init_obj.Stiffener.get_fl_thk()),
+ fill=init_color, stipple=init_stipple)
+
if opt_obj != None:
# [0.6, 0.012, 0.25, 0.01, 0.1, 0.01]
- self._canvas_opt.config(bg = 'palegreen')
+ self._canvas_opt.config(bg='palegreen')
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.Plate.get_s() / 2, ctr_y,
- ctr_x + m * opt_obj.Plate.get_s() / 2,
- ctr_y - m * opt_obj.Plate.get_pl_thk(), fill=opt_color,
- stipple=opt_stippe)
+ ctr_x + m * opt_obj.Plate.get_s() / 2,
+ ctr_y - m * opt_obj.Plate.get_pl_thk(), fill=opt_color,
+ stipple=opt_stippe)
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.Stiffener.get_web_thk() / 2, ctr_y -
- m * opt_obj.Plate.get_pl_thk(),
- ctr_x + m * opt_obj.Stiffener.get_web_thk() / 2,
- ctr_y - m * (
- opt_obj.Stiffener.get_web_h() + opt_obj.Plate.get_pl_thk())
- , fill=opt_color, stipple=opt_stippe)
+ m * opt_obj.Plate.get_pl_thk(),
+ ctr_x + m * opt_obj.Stiffener.get_web_thk() / 2,
+ ctr_y - m * (
+ opt_obj.Stiffener.get_web_h() + opt_obj.Plate.get_pl_thk())
+ , fill=opt_color, stipple=opt_stippe)
if init_obj.Stiffener.get_stiffener_type() not in ['L', 'L-bulb']:
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.Stiffener.get_fl_w() / 2, ctr_y
- - m * (
- opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h()),
- ctr_x + m * opt_obj.Stiffener.get_fl_w() / 2, ctr_y -
- m * (
- opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h() +
- opt_obj.Stiffener.get_fl_thk()),
- fill=opt_color, stipple=opt_stippe)
+ - m * (
+ opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h()),
+ ctr_x + m * opt_obj.Stiffener.get_fl_w() / 2, ctr_y -
+ m * (
+ opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h() +
+ opt_obj.Stiffener.get_fl_thk()),
+ fill=opt_color, stipple=opt_stippe)
else:
self._canvas_opt.create_rectangle(ctr_x - m * opt_obj.Stiffener.get_web_thk() / 2, ctr_y
- - m * (
- opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h()),
- ctr_x + m * opt_obj.Stiffener.get_fl_w(), ctr_y -
- m * (
- opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h() +
- opt_obj.Stiffener.get_fl_thk()),
- fill=opt_color, stipple=opt_stippe)
+ - m * (
+ opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h()),
+ ctr_x + m * opt_obj.Stiffener.get_fl_w(), ctr_y -
+ m * (
+ opt_obj.Plate.get_pl_thk() + opt_obj.Stiffener.get_web_h() +
+ opt_obj.Stiffener.get_fl_thk()),
+ fill=opt_color, stipple=opt_stippe)
self._canvas_opt.create_line(10, 50, 30, 50, fill=opt_color, width=5)
self._canvas_opt.create_text(270, 50,
- text='Optimized - Pl.: ' + str(round(opt_obj.Plate.get_s() * 1000,1)) + 'x' +
- str(round(opt_obj.Plate.get_pl_thk() * 1000,1)) + ' Stf.: '
- + str(round(opt_obj.Stiffener.get_web_h() * 1000,1)) +
- 'x' + str(round(opt_obj.Stiffener.get_web_thk() * 1000,1)) + '+' +
- str(round(opt_obj.Stiffener.get_fl_w() * 1000,1)) +
- 'x' + str(round(opt_obj.Stiffener.get_fl_thk() * 1000,1)),
- font='Verdana 8', fill=opt_color)
+ text='Optimized - Pl.: ' + str(round(opt_obj.Plate.get_s() * 1000, 1)) + 'x' +
+ str(round(opt_obj.Plate.get_pl_thk() * 1000, 1)) + ' Stf.: '
+ + str(round(opt_obj.Stiffener.get_web_h() * 1000, 1)) +
+ 'x' + str(round(opt_obj.Stiffener.get_web_thk() * 1000, 1)) + '+' +
+ str(round(opt_obj.Stiffener.get_fl_w() * 1000, 1)) +
+ 'x' + str(round(opt_obj.Stiffener.get_fl_thk() * 1000, 1)),
+ font='Verdana 8', fill=opt_color)
self._canvas_opt.create_text(120, 70, text='Weight (per Lg width): '
- + str(int(op.calc_weight([opt_obj.Plate.get_s(),
- opt_obj.Plate.get_pl_thk(),
- opt_obj.Stiffener.get_web_h(),
- opt_obj.Stiffener.get_web_thk(),
- opt_obj.Stiffener.get_fl_w(),
- opt_obj.Stiffener.get_fl_thk(),
- opt_obj.Plate.span,
- opt_obj.Plate.girder_lg]))),
- font='Verdana 8', fill=opt_color)
+ + str(int(op.calc_weight([opt_obj.Plate.get_s(),
+ opt_obj.Plate.get_pl_thk(),
+ opt_obj.Stiffener.get_web_h(),
+ opt_obj.Stiffener.get_web_thk(),
+ opt_obj.Stiffener.get_fl_w(),
+ opt_obj.Stiffener.get_fl_thk(),
+ opt_obj.Plate.span,
+ opt_obj.Plate.girder_lg]))),
+ font='Verdana 8', fill=opt_color)
elif self._opt_results != {}:
self._canvas_opt.config(bg='green')
self._canvas_opt.create_text(200, 200, text='Optimization results avaliable.\n\n'
- 'Middle click orange lines to\n view results.',
- font = 'Verdana 14 bold')
+ 'Middle click orange lines to\n view results.',
+ font='Verdana 14 bold')
else:
self._canvas_opt.config(bg='mistyrose')
- self._canvas_opt.create_text(200, 60, text='No optimization results found.', font = 'Verdana 14 bold')
+ self._canvas_opt.create_text(200, 60, text='No optimization results found.', font='Verdana 14 bold')
if line != None:
if __name__ == '__main__':
lateral_press = 200 # for testing
else:
lateral_press = self.app.get_highest_pressure(line)['normal'] / 1000
- self._canvas_opt.create_text(250, self._prop_canvas_dim[1]-10,
- text= line + ' lateral pressure: '+str(lateral_press)+' kPa',
- font='Verdana 10 bold',fill='red')
-
+ self._canvas_opt.create_text(250, self._prop_canvas_dim[1] - 10,
+ text=line + ' lateral pressure: ' + str(lateral_press) + ' kPa',
+ font='Verdana 10 bold', fill='red')
+
def draw_select_canvas(self, load_selected=False):
'''
Making the lines canvas.
@@ -1158,22 +1475,24 @@ def draw_select_canvas(self, load_selected=False):
# grid for the canavs
- self._canvas_select.create_line(self._canvas_draw_origo[0], 0, self._canvas_draw_origo[0], self._select_canvas_dim[1],
- stipple='gray50')
- self._canvas_select.create_line(0, self._canvas_draw_origo[1], self._select_canvas_dim[0], self._canvas_draw_origo[1],
- stipple='gray50')
- self._canvas_select.create_text(self._canvas_draw_origo[0] - 30 ,
- self._canvas_draw_origo[1] + 20 , text='(0,0)',
- font='Text 10')
- self._canvas_select.create_text([800 ,60],
- text='Mouse left click: select lines to loads\n'
- 'Mouse mid click: show properties for one line\n'
- 'Mouse right click: clear all selection\n'
- 'Shift key press: add selected line\n'
- 'Control key press: remove selected line\n\n'
- 'NOTE! Select lines you want to return before\n'
- 'pressing return button.', font='Verdana 8 bold',
- fill='red')
+ self._canvas_select.create_line(self._canvas_draw_origo[0], 0, self._canvas_draw_origo[0],
+ self._select_canvas_dim[1],
+ stipple='gray50')
+ self._canvas_select.create_line(0, self._canvas_draw_origo[1], self._select_canvas_dim[0],
+ self._canvas_draw_origo[1],
+ stipple='gray50')
+ self._canvas_select.create_text(self._canvas_draw_origo[0] - 30,
+ self._canvas_draw_origo[1] + 20, text='(0,0)',
+ font='Text 10')
+ self._canvas_select.create_text([800, 60],
+ text='Mouse left click: select lines to loads\n'
+ 'Mouse mid click: show properties for one line\n'
+ 'Mouse right click: clear all selection\n'
+ 'Shift key press: add selected line\n'
+ 'Control key press: remove selected line\n\n'
+ 'NOTE! Select lines you want to return before\n'
+ 'pressing return button.', font='Verdana 8 bold',
+ fill='red')
# drawing the line dictionary.
if len(self._line_dict) != 0:
for line, value in self._line_dict.items():
@@ -1189,14 +1508,14 @@ def draw_select_canvas(self, load_selected=False):
color, width = 'orange', 8
self._canvas_select.create_line(coord1, coord2, width=width, fill=color)
self._canvas_select.create_text(coord1[0] + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 + 10,
- text='Line ' + str(get_num(line)), font='Verdand 10 bold',
- fill='red')
+ text='Line ' + str(get_num(line)), font='Verdand 10 bold',
+ fill='red')
else:
if line in self._opt_results.keys():
color = 'orange'
self._canvas_select.create_line(coord1, coord2, width=3, fill=color)
self._canvas_select.create_text(coord1[0] - 20 + vector[0] / 2 + 5, coord1[1] + vector[1] / 2 + 10,
- text='line' + str(get_num(line)), font="Text 8", fill='black')
+ text='line' + str(get_num(line)), font="Text 8", fill='black')
def algorithm_info(self):
''' When button is clicked, info is displayed.'''
@@ -1255,7 +1574,7 @@ def on_closing(self):
return
mess = tk.messagebox.showwarning('Closed without saving', 'Closing will not save loads you have created',
- type = 'okcancel')
+ type='okcancel')
if mess == 'ok':
self._frame.grab_release()
self._frame.destroy()
@@ -1264,11 +1583,11 @@ def on_closing(self):
def get_point_canvas_coord(self, point_no):
'''
Returning the canvas coordinates of the point. This value will change with slider.
- :param point_no:
- :return:
+ :param point_no:
+ :return:
'''
- point_coord_x = self._canvas_draw_origo[0] + self._point_dict[point_no][0]* self._canvas_scale
- point_coord_y = self._canvas_draw_origo[1] - self._point_dict[point_no][1]* self._canvas_scale
+ point_coord_x = self._canvas_draw_origo[0] + self._point_dict[point_no][0] * self._canvas_scale
+ point_coord_y = self._canvas_draw_origo[1] - self._point_dict[point_no][1] * self._canvas_scale
return [point_coord_x, point_coord_y]
@@ -1288,14 +1607,14 @@ def controls(self):
self._frame.bind("", self.mouse_scroll)
self._frame.bind("", self.button_2_click_and_drag)
- def shift_pressed(self,event=None):
+ def shift_pressed(self, event=None):
'''
Event is executed when shift key pressed.
:return:
'''
self._add_to_lines = True
- def ctrl_pressed(self,event=None):
+ def ctrl_pressed(self, event=None):
'''
Event when control is pressed.
:param event:
@@ -1336,7 +1655,7 @@ def left_click(self, event):
if self._add_to_lines:
if key not in self._active_lines:
self._active_lines.append(key)
- elif self._add_to_lines== False:
+ elif self._add_to_lines == False:
if key in self._active_lines:
self._active_lines.remove(key)
self._canvas_select.delete('all')
@@ -1344,7 +1663,7 @@ def left_click(self, event):
self.draw_select_canvas()
self.update_running_time()
- def right_click(self,event):
+ def right_click(self, event):
'''
Event when right click.
:param evnet:
@@ -1357,7 +1676,7 @@ def right_click(self,event):
self.draw_select_canvas()
self.update_running_time()
- def mid_click(self,event):
+ def mid_click(self, event):
'''
Event when right click.
:param evnet:
@@ -1392,12 +1711,12 @@ def mid_click(self,event):
self._canvas_select.delete('all')
self._active_lines = []
self._active_lines.append(key)
- if key in self._opt_results.keys() and self._opt_results[key]!=None:
- self.draw_properties(init_obj=self._line_to_struc[key][0],opt_obj=self._opt_results[key][0],
+ if key in self._opt_results.keys() and self._opt_results[key] != None:
+ self.draw_properties(init_obj=self._line_structure(key), opt_obj=self._opt_results[key][0],
line=key)
self._mid_click_line = key
else:
- self.draw_properties(init_obj=self._line_to_struc[key][0],line=key)
+ self.draw_properties(init_obj=self._line_structure(key), line=key)
self._mid_click_line = None
break
self.draw_select_canvas()
@@ -1426,12 +1745,12 @@ def save_and_close(self):
return
self.app.on_close_opt_multiple_window(to_return)
- messagebox.showinfo(title='Return info', message='Returning: '+str(list(to_return.keys())) +
+ messagebox.showinfo(title='Return info', message='Returning: ' + str(list(to_return.keys())) +
'\nLines without results are not returned.')
self._frame.destroy()
- def toggle(self, found_files = None, obj = None, iterating = False):
+ def toggle(self, found_files=None, obj=None, iterating=False):
'''
On off button.
:param found_files:
@@ -1445,16 +1764,16 @@ def toggle(self, found_files = None, obj = None, iterating = False):
predefined_structure = None
if self._toggle_btn.config('relief')[-1] == 'sunken':
self._toggle_btn.config(relief="raised")
- self._toggle_btn.config(bg = 'salmon')
- self._ent_spacing_upper.config(bg = 'white')
- self._ent_spacing_lower.config(bg = 'white')
- self._ent_delta_spacing.config(bg = 'white')
+ self._toggle_btn.config(bg='salmon')
+ self._ent_spacing_upper.config(bg='white')
+ self._ent_spacing_lower.config(bg='white')
+ self._ent_delta_spacing.config(bg='white')
else:
self._toggle_btn.config(relief="sunken")
self._toggle_btn.config(bg='lightgreen')
- self._ent_spacing_upper.config(bg = 'lightgreen')
- self._ent_spacing_lower.config(bg = 'lightgreen')
- self._ent_delta_spacing.config(bg = 'lightgreen')
+ self._ent_spacing_upper.config(bg='lightgreen')
+ self._ent_spacing_lower.config(bg='lightgreen')
+ self._ent_delta_spacing.config(bg='lightgreen')
openfile = list(askopenfilenames(parent=self._frame, title='Choose files to open',
initialdir=self._root_dir))
if openfile == []:
@@ -1476,18 +1795,18 @@ def plot_results(self):
if len(self._opt_results[self._mid_click_line]) != 0:
op.plot_optimization_results(self._opt_results[self._mid_click_line])
- def mouse_scroll(self,event):
- self._canvas_scale += event.delta/50
+ def mouse_scroll(self, event):
+ self._canvas_scale += event.delta / 50
self._canvas_scale = 0 if self._canvas_scale < 0 else self._canvas_scale
self.draw_select_canvas()
- def button_2_click_and_drag(self,event):
+ def button_2_click_and_drag(self, event):
- self._canvas_draw_origo = (self._canvas_draw_origo[0]-(self._previous_drag_mouse[0]-event.x),
- self._canvas_draw_origo[1]-(self._previous_drag_mouse[1]-event.y))
+ self._canvas_draw_origo = (self._canvas_draw_origo[0] - (self._previous_drag_mouse[0] - event.x),
+ self._canvas_draw_origo[1] - (self._previous_drag_mouse[1] - event.y))
- self._previous_drag_mouse = (event.x,event.y)
+ self._previous_drag_mouse = (event.x, event.y)
self.draw_select_canvas()
def open_example_file(self):
diff --git a/anystruct/optimize_window.py b/anystruct/optimize_window.py
index 9c50b90c..12cb009a 100644
--- a/anystruct/optimize_window.py
+++ b/anystruct/optimize_window.py
@@ -12,73 +12,126 @@
import anystruct.example_data as test
import anystruct.example_data as ex
import anystruct.helper as hlp
+ import anystruct.line_structure as line_structure
import anystruct.optimize as op
except ModuleNotFoundError:
from ANYstructure.anystruct.calc_structure import CalcScantlings, AllStructure
import ANYstructure.anystruct.example_data as test
import ANYstructure.anystruct.example_data as ex
import ANYstructure.anystruct.helper as hlp
+ import ANYstructure.anystruct.line_structure as line_structure
import ANYstructure.anystruct.optimize as op
+
+
class CreateOptimizeWindow():
'''
This class initiates the single optimization window.
'''
- def __init__(self,master,app=None):
- super(CreateOptimizeWindow,self).__init__()
+ def __init__(self, master, app=None):
+ super(CreateOptimizeWindow, self).__init__()
if __name__ == '__main__':
import pickle
Plate = CalcScantlings(ex.obj_dict)
- Stiffener = None#CalcScantlings(ex.obj_dict)
+ Stiffener = None # CalcScantlings(ex.obj_dict)
Girder = None # CalcScantlings(ex.obj_dict_heavy)
- self._initial_calc_obj = AllStructure(Plate=Plate, Stiffener=Stiffener, Girder=Girder,
- main_dict=ex.prescriptive_main_dict)
+ self._initial_calc_obj = AllStructure(Plate=Plate, Stiffener=Stiffener, Girder=Girder,
+ main_dict=ex.prescriptive_main_dict)
- #self._initial_calc_obj = test.get_structure_calc_object(heavy=True)
+ # self._initial_calc_obj = test.get_structure_calc_object(heavy=True)
self._lateral_pressure = 0.2
self._fatigue_object = test.get_fatigue_object()
self._fatigue_pressure = test.get_fatigue_pressures()
self._slamming_pressure = test.get_slamming_pressure()
- image_dir = os.path.dirname(__file__)+'\\images\\'
- self._PULS_object = None
- self._puls_acceptance = 0.87
- self._initial_calc_obj.lat_press = self._lateral_pressure/1000
+ image_dir = os.path.dirname(__file__) + '\\images\\'
+ self._initial_calc_obj.lat_press = self._lateral_pressure / 1000
self._ML_buckling = dict() # Buckling machine learning algorithm
self._root_dir = '/\\'
- for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
- 'cl SP ult int predictor', 'cl SP ult int scaler',
- 'cl SP buc GLGT predictor', 'cl SP buc GLGT scaler',
- 'cl SP ult GLGT predictor', 'cl SP ult GLGT scaler',
- 'cl UP buc int predictor', 'cl UP buc int scaler',
- 'cl UP ult int predictor', 'cl UP ult int scaler',
- 'cl UP buc GLGT predictor', 'cl UP buc GLGT scaler',
- 'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler'
- ],
- ["ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_buc_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_buc_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_ult_scaler_In-plane_support_cl_2,_3_UP",
- "CL_CSR-Tank_req_cl_predictor",
- "CL_CSR-Tank_req_cl_UP_scaler",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_SP_scaler"]):
+ ml_model_files = [
+ # Classification pipeline
+ ('cl SP buc int predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_SP'),
+ ('cl SP buc int scaler', 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_SP'),
+ ('cl SP ult int predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_SP'),
+ ('cl SP ult int scaler', 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_SP'),
+
+ ('cl SP buc GLGT predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_SP'),
+ ('cl SP buc GLGT scaler',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_SP'),
+ ('cl SP ult GLGT predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_SP'),
+ ('cl SP ult GLGT scaler',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_SP'),
+
+ ('cl UP buc int predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_UP'),
+ ('cl UP buc int scaler', 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_UP'),
+ ('cl UP ult int predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_UP'),
+ ('cl UP ult int scaler', 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_UP'),
+
+ ('cl UP buc GLGT predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_UP'),
+ ('cl UP buc GLGT scaler',
+ 'ml_files\\CLPIPE_CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_UP'),
+ ('cl UP ult GLGT predictor',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_UP'),
+ ('cl UP ult GLGT scaler',
+ 'ml_files\\CLPIPE_CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_UP'),
+
+ # Numeric UF pipeline
+ ('num SP int validity predictor',
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num SP int validity xscaler',
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num SP int UF reg predictor',
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num SP int UF reg xscaler',
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num SP int UF reg yscaler',
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+
+ ('num SP GLGT validity predictor',
+ 'ml_files\\NUMPIPE_VALID_predictor_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num SP GLGT validity xscaler',
+ 'ml_files\\NUMPIPE_VALID_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num SP GLGT UF reg predictor',
+ 'ml_files\\NUMPIPE_REG_predictor_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num SP GLGT UF reg xscaler',
+ 'ml_files\\NUMPIPE_REG_xscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num SP GLGT UF reg yscaler',
+ 'ml_files\\NUMPIPE_REG_yscaler_SP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+
+ ('num UP int validity predictor',
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num UP int validity xscaler',
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num UP int UF reg predictor',
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num UP int UF reg xscaler',
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+ ('num UP int UF reg yscaler',
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_1_In-plane_support_cl_1'),
+
+ ('num UP GLGT validity predictor',
+ 'ml_files\\NUMPIPE_VALID_predictor_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num UP GLGT validity xscaler',
+ 'ml_files\\NUMPIPE_VALID_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num UP GLGT UF reg predictor',
+ 'ml_files\\NUMPIPE_REG_predictor_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num UP GLGT UF reg xscaler',
+ 'ml_files\\NUMPIPE_REG_xscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ('num UP GLGT UF reg yscaler',
+ 'ml_files\\NUMPIPE_REG_yscaler_UP_UF_numeric_In-plane_support_cl_2,_3_In-plane_support_cl_2,_3'),
+ ]
+
+ for name, file_base in ml_model_files:
self._ML_buckling[name] = None
if os.path.isfile(file_base + '.pickle'):
file = open(file_base + '.pickle', 'rb')
- from sklearn.neural_network import MLPClassifier
- from sklearn.preprocessing import StandardScaler
+ import pickle
self._ML_buckling[name] = pickle.load(file)
file.close()
@@ -94,8 +147,9 @@ def __init__(self,master,app=None):
else:
self.app = app
- self._initial_calc_obj = app._line_to_struc[app._active_line][0]
- self._fatigue_object = app._line_to_struc[app._active_line][2]
+ active_bundle = app._line_to_struc[app._active_line]
+ self._initial_calc_obj = line_structure.structure(active_bundle)
+ self._fatigue_object = line_structure.fatigue(active_bundle)
try:
self._fatigue_pressure = app.get_fatigue_pressures(app._active_line,
self._fatigue_object.get_accelerations())
@@ -113,11 +167,8 @@ def __init__(self,master,app=None):
self._slamming_pressure = self.app.get_highest_pressure(self.app._active_line)['slamming']
except KeyError:
self._slamming_pressure = 0
- image_dir = app._root_dir +'\\images\\'
+ image_dir = app._root_dir + '\\images\\'
self._root_dir = app._root_dir
- self._PULS_object = app._PULS_results
- self._puls_acceptance = self.app._new_puls_uf.get()
-
self._ML_buckling = app._ML_buckling
self._predefined_stiffener_iter = None
@@ -129,40 +180,39 @@ def __init__(self,master,app=None):
self._opt_runned = False
self._opt_results = ()
- self._opt_actual_running_time = tk.Label(self._frame,text='',font='Verdana 12 bold')
-
+ self._opt_actual_running_time = tk.Label(self._frame, text='', font='Verdana 12 bold')
self._draw_scale = 500
self._canvas_dim = (500, 450)
- self._canvas_opt = tk.Canvas(self._frame,width=self._canvas_dim[0], height=self._canvas_dim[1],
- background='azure',relief = 'groove', borderwidth=2)
+ self._canvas_opt = tk.Canvas(self._frame, width=self._canvas_dim[0], height=self._canvas_dim[1],
+ background='azure', relief='groove', borderwidth=2)
- tk.Frame(self._frame,width=770,height=5, bg="grey", colormap="new").place(x=20,y=127)
+ tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=127)
tk.Frame(self._frame, width=770, height=5, bg="grey", colormap="new").place(x=20, y=167)
- self._canvas_opt.place(x=10,y=300)
+ self._canvas_opt.place(x=10, y=300)
- algorithms = ('anysmart','random','random_no_delta', 'anydetail')
+ algorithms = ('anysmart', 'random', 'random_no_delta', 'anydetail')
- tk.Label(self._frame,text='-- Structural optimizer --',font='Verdana 15 bold').place(x=10,y=10)
+ tk.Label(self._frame, text='-- Structural optimizer --', font='Verdana 15 bold').place(x=10, y=10)
if self._initial_calc_obj.Stiffener is not None:
self._spacing = self._initial_calc_obj.Plate.get_s()
self._pl_thk = self._initial_calc_obj.Plate.get_pl_thk()
self._stf_web_h = self._initial_calc_obj.Stiffener.get_web_h()
- self._stf_web_thk =self._initial_calc_obj.Stiffener.get_web_thk()
+ self._stf_web_thk = self._initial_calc_obj.Stiffener.get_web_thk()
self._fl_w = self._initial_calc_obj.Stiffener.get_fl_w()
- self._fl_thk =self._initial_calc_obj.Stiffener.get_fl_thk()
+ self._fl_thk = self._initial_calc_obj.Stiffener.get_fl_thk()
else:
self._spacing = self._initial_calc_obj.Plate.get_s()
self._pl_thk = self._initial_calc_obj.Plate.get_pl_thk()
self._stf_web_h = 0
- self._stf_web_thk =0
+ self._stf_web_thk = 0
self._fl_w = 0
- self._fl_thk =0
+ self._fl_thk = 0
# upper and lower bounds for optimization
- #[0.6, 0.012, 0.3, 0.01, 0.1, 0.01]
+ # [0.6, 0.012, 0.3, 0.01, 0.1, 0.01]
self._new_spacing_upper = tk.DoubleVar()
self._new_spacing_lower = tk.DoubleVar()
self._new_pl_thk_upper = tk.DoubleVar()
@@ -180,23 +230,22 @@ def __init__(self,master,app=None):
self._new_algorithm = tk.StringVar()
self._new_algorithm_random_trials = tk.IntVar()
self._new_swarm_size = tk.IntVar()
- self._new_omega = tk.DoubleVar()
- self._new_phip = tk.DoubleVar()
- self._new_phig = tk.DoubleVar()
+ self._new_omega = tk.DoubleVar()
+ self._new_phip = tk.DoubleVar()
+ self._new_phig = tk.DoubleVar()
self._new_maxiter = tk.IntVar()
- self._new_minstep = tk.DoubleVar()
- self._new_minfunc = tk.DoubleVar()
+ self._new_minstep = tk.DoubleVar()
+ self._new_minfunc = tk.DoubleVar()
self._new_slamming_pressure = tk.DoubleVar()
self._new_fatigue_int_press = tk.DoubleVar()
self._new_fatigue_ext_press = tk.DoubleVar()
-
ent_w = 10
- self._ent_spacing_upper = tk.Entry(self._frame, textvariable = self._new_spacing_upper, width = ent_w)
+ self._ent_spacing_upper = tk.Entry(self._frame, textvariable=self._new_spacing_upper, width=ent_w)
self._ent_spacing_lower = tk.Entry(self._frame, textvariable=self._new_spacing_lower, width=ent_w)
- self._ent_pl_thk_upper= tk.Entry(self._frame, textvariable=self._new_pl_thk_upper, width=ent_w)
- self._ent_pl_thk_lower= tk.Entry(self._frame, textvariable=self._new_pl_thk_lower, width=ent_w)
+ self._ent_pl_thk_upper = tk.Entry(self._frame, textvariable=self._new_pl_thk_upper, width=ent_w)
+ self._ent_pl_thk_lower = tk.Entry(self._frame, textvariable=self._new_pl_thk_lower, width=ent_w)
self._ent_web_h_upper = tk.Entry(self._frame, textvariable=self._new_web_h_upper, width=ent_w)
self._ent_web_h_lower = tk.Entry(self._frame, textvariable=self._new_web_h_lower, width=ent_w)
@@ -213,19 +262,20 @@ def __init__(self,master,app=None):
self._ent_span = tk.Entry(self._frame, textvariable=self._new_span, width=ent_w)
self._ent_width_lg = tk.Entry(self._frame, textvariable=self._new_width_lg, width=ent_w)
self._ent_slamming_pressure = tk.Entry(self._frame, textvariable=self._new_slamming_pressure, width=ent_w)
-
- #additional choices for the random and pso algorithm
- self._ent_algorithm = tk.OptionMenu(self._frame,self._new_algorithm,command=self.selected_algorithm,*algorithms)
- self._ent_random_trials = tk.Entry(self._frame,textvariable=self._new_algorithm_random_trials)
+
+ # additional choices for the random and pso algorithm
+ self._ent_algorithm = tk.OptionMenu(self._frame, self._new_algorithm, command=self.selected_algorithm,
+ *algorithms)
+ self._ent_random_trials = tk.Entry(self._frame, textvariable=self._new_algorithm_random_trials)
pso_width = 10
- self._ent_swarm_size = tk.Entry(self._frame,textvariable=self._new_swarm_size, width = pso_width)
- self._ent_omega = tk.Entry(self._frame,textvariable=self._new_omega, width = pso_width)
- self._ent_phip = tk.Entry(self._frame,textvariable=self._new_phip, width = pso_width)
- self._ent_phig = tk.Entry(self._frame,textvariable=self._new_phig, width = pso_width)
- self._ent_maxiter = tk.Entry(self._frame,textvariable=self._new_maxiter, width = pso_width)
- self._ent_minstep = tk.Entry(self._frame,textvariable=self._new_minstep, width = pso_width)
- self._ent_minfunc = tk.Entry(self._frame,textvariable=self._new_minfunc, width = pso_width)
+ self._ent_swarm_size = tk.Entry(self._frame, textvariable=self._new_swarm_size, width=pso_width)
+ self._ent_omega = tk.Entry(self._frame, textvariable=self._new_omega, width=pso_width)
+ self._ent_phip = tk.Entry(self._frame, textvariable=self._new_phip, width=pso_width)
+ self._ent_phig = tk.Entry(self._frame, textvariable=self._new_phig, width=pso_width)
+ self._ent_maxiter = tk.Entry(self._frame, textvariable=self._new_maxiter, width=pso_width)
+ self._ent_minstep = tk.Entry(self._frame, textvariable=self._new_minstep, width=pso_width)
+ self._ent_minfunc = tk.Entry(self._frame, textvariable=self._new_minfunc, width=pso_width)
self._new_delta_spacing = tk.DoubleVar()
self._new_delta_pl_thk = tk.DoubleVar()
@@ -233,7 +283,7 @@ def __init__(self,master,app=None):
self._new_delta_web_thk = tk.DoubleVar()
self._new_delta_fl_w = tk.DoubleVar()
self._new_delta_fl_thk = tk.DoubleVar()
-
+
self._new_opt_spacing = tk.DoubleVar()
self._new_opt_pl_thk = tk.DoubleVar()
self._new_opt_web_h = tk.DoubleVar()
@@ -241,22 +291,20 @@ def __init__(self,master,app=None):
self._new_opt_fl_w = tk.DoubleVar()
self._new_opt_fl_thk = tk.DoubleVar()
- self._ent_delta_spacing = tk.Entry(self._frame, textvariable = self._new_delta_spacing, width = ent_w)
- self._ent_delta_pl_thk = tk.Entry(self._frame, textvariable = self._new_delta_pl_thk, width = ent_w)
- self._ent_delta_web_h = tk.Entry(self._frame, textvariable = self._new_delta_web_h, width = ent_w)
- self._ent_delta_web_thk = tk.Entry(self._frame, textvariable = self._new_delta_web_thk, width = ent_w)
- self._ent_delta_fl_w = tk.Entry(self._frame, textvariable = self._new_delta_fl_w, width = ent_w)
- self._ent_delta_fl_thk = tk.Entry(self._frame, textvariable = self._new_delta_fl_thk, width = ent_w)
+ self._ent_delta_spacing = tk.Entry(self._frame, textvariable=self._new_delta_spacing, width=ent_w)
+ self._ent_delta_pl_thk = tk.Entry(self._frame, textvariable=self._new_delta_pl_thk, width=ent_w)
+ self._ent_delta_web_h = tk.Entry(self._frame, textvariable=self._new_delta_web_h, width=ent_w)
+ self._ent_delta_web_thk = tk.Entry(self._frame, textvariable=self._new_delta_web_thk, width=ent_w)
+ self._ent_delta_fl_w = tk.Entry(self._frame, textvariable=self._new_delta_fl_w, width=ent_w)
+ self._ent_delta_fl_thk = tk.Entry(self._frame, textvariable=self._new_delta_fl_thk, width=ent_w)
bg_col = 'pink'
- self._ent_opt_spacing = tk.Entry(self._frame, textvariable=self._new_opt_spacing, width=ent_w,bg=bg_col)
- self._ent_opt_pl_thk = tk.Entry(self._frame, textvariable=self._new_opt_pl_thk, width=ent_w,bg=bg_col)
- self._ent_opt_web_h = tk.Entry(self._frame, textvariable=self._new_opt_web_h, width=ent_w,bg=bg_col)
- self._ent_opt_web_thk = tk.Entry(self._frame, textvariable=self._new_opt_web_thk, width=ent_w,bg=bg_col)
- self._ent_opt_fl_w = tk.Entry(self._frame, textvariable=self._new_opt_fl_w, width=ent_w,bg=bg_col)
- self._ent_opt_fl_thk = tk.Entry(self._frame, textvariable=self._new_opt_fl_thk, width=ent_w,bg=bg_col)
-
-
+ self._ent_opt_spacing = tk.Entry(self._frame, textvariable=self._new_opt_spacing, width=ent_w, bg=bg_col)
+ self._ent_opt_pl_thk = tk.Entry(self._frame, textvariable=self._new_opt_pl_thk, width=ent_w, bg=bg_col)
+ self._ent_opt_web_h = tk.Entry(self._frame, textvariable=self._new_opt_web_h, width=ent_w, bg=bg_col)
+ self._ent_opt_web_thk = tk.Entry(self._frame, textvariable=self._new_opt_web_thk, width=ent_w, bg=bg_col)
+ self._ent_opt_fl_w = tk.Entry(self._frame, textvariable=self._new_opt_fl_w, width=ent_w, bg=bg_col)
+ self._ent_opt_fl_thk = tk.Entry(self._frame, textvariable=self._new_opt_fl_thk, width=ent_w, bg=bg_col)
# stresses in plate and stiffener
@@ -267,158 +315,161 @@ def __init__(self,master,app=None):
self._new_design_pressure = tk.DoubleVar()
self._new_pressure_side = tk.StringVar()
-
self._ent_trans_stress_high = tk.Entry(self._frame, textvariable=self._new_trans_stress_high, width=ent_w)
self._ent_trans_stress_low = tk.Entry(self._frame, textvariable=self._new_trans_stress_low, width=ent_w)
self._ent_axial_stress = tk.Entry(self._frame, textvariable=self._new_axial_stress, width=ent_w)
self._ent_design_pressure = tk.Entry(self._frame, textvariable=self._new_design_pressure, width=ent_w)
- self._ent_design_pressure_side = tk.OptionMenu(self._frame,self._new_pressure_side,*('p','s'))
+ self._ent_design_pressure_side = tk.OptionMenu(self._frame, self._new_pressure_side, *('p', 's'))
self._ent_shear_stress = tk.Entry(self._frame, textvariable=self._new_shear_stress, width=ent_w)
- start_x,start_y,dx,dy = 20,100,100,40
+ start_x, start_y, dx, dy = 20, 100, 100, 40
self._new_processes = tk.IntVar()
self._new_processes.set(max(cpu_count() - 1, 1))
- tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg = 'silver')\
+ tk.Label(self._frame, text='Processes\n (CPUs)', font='Verdana 9 bold', bg='silver') \
.place(x=start_x + 8.3 * dx, y=start_y - 1.1 * dy)
- tk.Entry(self._frame, textvariable=self._new_processes, width = 12, bg = 'silver')\
+ tk.Entry(self._frame, textvariable=self._new_processes, width=12, bg='silver') \
.place(x=start_x + 8.3 * dx, y=start_y - 0.3 * dy)
-
- tk.Label(self._frame,text='Upper bounds [mm]',font='Verdana 9').place(x=start_x,y=start_y)
- tk.Label(self._frame, text='Iteration delta [mm]',font='Verdana 9').place(x=start_x, y=start_y+dy)
- tk.Label(self._frame, text='Lower bounds [mm]',font='Verdana 9').place(x=start_x, y=start_y+2*dy)
- tk.Label(self._frame, text='Spacing [mm]', font='Verdana 7 bold').place(x=start_x + 1.97 * dx, y=start_y-0.6*dy)
- tk.Label(self._frame, text='Plate thk. [mm]', font='Verdana 7 bold').place(x=start_x + 2.97 * dx, y=start_y-0.6*dy)
- tk.Label(self._frame, text='Web height [mm]', font='Verdana 7 bold').place(x=start_x + 3.97 * dx, y=start_y-0.6*dy)
- tk.Label(self._frame, text='Web thk. [mm]', font='Verdana 7 bold').place(x=start_x + 4.97 * dx, y=start_y-0.6*dy)
- tk.Label(self._frame, text='Flange width [mm]', font='Verdana 7 bold').place(x=start_x + 5.97 * dx, y=start_y-0.6*dy)
- tk.Label(self._frame, text='Flange thk. [mm]', font='Verdana 7 bold').place(x=start_x + 6.97 * dx, y=start_y-0.6*dy)
+ tk.Label(self._frame, text='Upper bounds [mm]', font='Verdana 9').place(x=start_x, y=start_y)
+ tk.Label(self._frame, text='Iteration delta [mm]', font='Verdana 9').place(x=start_x, y=start_y + dy)
+ tk.Label(self._frame, text='Lower bounds [mm]', font='Verdana 9').place(x=start_x, y=start_y + 2 * dy)
+ tk.Label(self._frame, text='Spacing [mm]', font='Verdana 7 bold').place(x=start_x + 1.97 * dx,
+ y=start_y - 0.6 * dy)
+ tk.Label(self._frame, text='Plate thk. [mm]', font='Verdana 7 bold').place(x=start_x + 2.97 * dx,
+ y=start_y - 0.6 * dy)
+ tk.Label(self._frame, text='Web height [mm]', font='Verdana 7 bold').place(x=start_x + 3.97 * dx,
+ y=start_y - 0.6 * dy)
+ tk.Label(self._frame, text='Web thk. [mm]', font='Verdana 7 bold').place(x=start_x + 4.97 * dx,
+ y=start_y - 0.6 * dy)
+ tk.Label(self._frame, text='Flange width [mm]', font='Verdana 7 bold').place(x=start_x + 5.97 * dx,
+ y=start_y - 0.6 * dy)
+ tk.Label(self._frame, text='Flange thk. [mm]', font='Verdana 7 bold').place(x=start_x + 6.97 * dx,
+ y=start_y - 0.6 * dy)
tk.Label(self._frame, text='--------- Number of combinations to run --------->\n'
- 'PULS buckling is time consuming, about 0.2 sec. per comb.\n'
- 'RP-C203 is much faster and can run many more combinations, 1M+.\n'
- 'ML-CL is about as fast as RP-C203.',
- font='Verdana 9 bold').place(x=start_x+0.1*dx, y=start_y + 2.8 * dy, anchor = tk.NW)
-
- self._runnig_time_label = tk.Label(self._frame, text='',font='Verdana 12 bold', fg = 'red')
- self._runnig_time_label.place(x=start_x+4.3*dx, y=start_y + 2.8 * dy)
- #tk.Label(self._frame, text='seconds ',font='Verdana 9 bold').place(x=start_x+6*dx, y=start_y + 2.8 * dy)
- self._result_label = tk.Label(self._frame, text = '',font = 'Verdana 9 bold' )
+ 'RP-C203 can run many combinations, 1M+.\n'
+ 'ML-Numeric is about as fast as RP-C203.',
+ font='Verdana 9 bold').place(x=start_x + 0.1 * dx, y=start_y + 2.8 * dy, anchor=tk.NW)
+
+ self._runnig_time_label = tk.Label(self._frame, text='', font='Verdana 12 bold', fg='red')
+ self._runnig_time_label.place(x=start_x + 4.3 * dx, y=start_y + 2.8 * dy)
+ # tk.Label(self._frame, text='seconds ',font='Verdana 9 bold').place(x=start_x+6*dx, y=start_y + 2.8 * dy)
+ self._result_label = tk.Label(self._frame, text='', font='Verdana 9 bold')
self._result_label.place(x=start_x, y=start_y + 4.2 * dy)
- self._ent_spacing_upper.place(x=start_x+dx*2,y=start_y)
- self._ent_delta_spacing.place(x=start_x+dx*2,y=start_y+dy)
- self._ent_spacing_lower.place(x=start_x+dx*2,y=start_y+2*dy)
+ self._ent_spacing_upper.place(x=start_x + dx * 2, y=start_y)
+ self._ent_delta_spacing.place(x=start_x + dx * 2, y=start_y + dy)
+ self._ent_spacing_lower.place(x=start_x + dx * 2, y=start_y + 2 * dy)
+
+ self._ent_pl_thk_upper.place(x=start_x + dx * 3, y=start_y)
+ self._ent_delta_pl_thk.place(x=start_x + dx * 3, y=start_y + dy)
+ self._ent_pl_thk_lower.place(x=start_x + dx * 3, y=start_y + 2 * dy)
- self._ent_pl_thk_upper.place(x=start_x+dx*3,y=start_y)
- self._ent_delta_pl_thk.place(x=start_x+dx*3,y=start_y+dy)
- self._ent_pl_thk_lower.place(x=start_x+dx*3,y=start_y+2*dy)
+ self._ent_web_h_upper.place(x=start_x + dx * 4, y=start_y)
+ self._ent_delta_web_h.place(x=start_x + dx * 4, y=start_y + dy)
+ self._ent_web_h_lower.place(x=start_x + dx * 4, y=start_y + 2 * dy)
- self._ent_web_h_upper.place(x=start_x+dx*4,y=start_y)
- self._ent_delta_web_h.place(x=start_x+dx*4,y=start_y+dy)
- self._ent_web_h_lower.place(x=start_x+dx*4,y=start_y+2*dy)
+ self._ent_web_thk_upper.place(x=start_x + dx * 5, y=start_y)
+ self._ent_delta_web_thk.place(x=start_x + dx * 5, y=start_y + dy)
+ self._ent_web_thk_lower.place(x=start_x + dx * 5, y=start_y + 2 * dy)
- self._ent_web_thk_upper.place(x=start_x+dx*5,y=start_y)
- self._ent_delta_web_thk.place(x=start_x+dx*5,y=start_y+dy)
- self._ent_web_thk_lower.place(x=start_x+dx*5,y=start_y+2*dy)
+ self._ent_fl_w_upper.place(x=start_x + dx * 6, y=start_y)
+ self._ent_delta_fl_w.place(x=start_x + dx * 6, y=start_y + dy)
+ self._ent_fl_w_lower.place(x=start_x + dx * 6, y=start_y + 2 * dy)
- self._ent_fl_w_upper.place(x=start_x+dx*6,y=start_y)
- self._ent_delta_fl_w.place(x=start_x+dx*6,y=start_y+dy)
- self._ent_fl_w_lower.place(x=start_x+dx*6,y=start_y+2*dy)
+ self._ent_fl_thk_upper.place(x=start_x + dx * 7, y=start_y)
+ self._ent_delta_fl_thk.place(x=start_x + dx * 7, y=start_y + dy)
+ self._ent_fl_thk_lower.place(x=start_x + dx * 7, y=start_y + 2 * dy)
- self._ent_fl_thk_upper.place(x=start_x+dx*7,y=start_y)
- self._ent_delta_fl_thk.place(x=start_x+dx*7,y=start_y+dy)
- self._ent_fl_thk_lower.place(x=start_x+dx*7,y=start_y+2*dy)
-
###
# tk.Label(self._frame,text='Optimized result:\n')\
# .place(x=start_x,y=start_y+ver_mult*dy*0.9)
dx_mult = 0.7
- tk.Label(self._frame,text='Optimized values').place(x=start_x,y=start_y+17*dy)
+ tk.Label(self._frame, text='Optimized values').place(x=start_x, y=start_y + 17 * dy)
tk.Label(self._frame, text='s').place(x=start_x, y=start_y + 18 * dy)
tk.Label(self._frame, text='pl_thk').place(x=start_x, y=start_y + 19 * dy)
- self._ent_opt_spacing.place(x=start_x+dx_mult*dx,y=start_y+18*dy)
- self._ent_opt_pl_thk.place(x=start_x+dx_mult*dx,y=start_y+19*dy)
-
- tk.Label(self._frame, text='web_h').place(x=start_x+2*dx_mult*dx, y=start_y + 18 * dy)
- tk.Label(self._frame, text='web_htk').place(x=start_x+2*dx_mult*dx, y=start_y + 19 * dy)
- self._ent_opt_web_h.place(x=start_x+3*dx_mult*dx,y=start_y+18*dy)
- self._ent_opt_web_thk.place(x=start_x+3*dx_mult*dx,y=start_y+19*dy)
-
- tk.Label(self._frame, text='fl_thk').place(x=start_x+4*dx_mult*dx, y=start_y + 18 * dy)
- tk.Label(self._frame, text='fl_ttk.').place(x=start_x+4*dx_mult*dx, y=start_y + 19 * dy)
- self._ent_opt_fl_w.place(x=start_x+5*dx_mult*dx,y=start_y+18*dy)
- self._ent_opt_fl_thk.place(x=start_x+5*dx_mult*dx,y=start_y+19*dy)
-
- #Labels for the pso
-
- self._lb_swarm_size = tk.Label(self._frame,text='swarm size')
- self._lb_omega = tk.Label(self._frame,text='omega')
- self._lb_phip = tk.Label(self._frame,text='phip')
- self._lb_phig = tk.Label(self._frame,text='phig')
- self._lb_maxiter = tk.Label(self._frame,text='maxiter')
- self._lb_minstep = tk.Label(self._frame,text='minstep')
- self._lb_minfunc = tk.Label(self._frame,text='minfunc')
+ self._ent_opt_spacing.place(x=start_x + dx_mult * dx, y=start_y + 18 * dy)
+ self._ent_opt_pl_thk.place(x=start_x + dx_mult * dx, y=start_y + 19 * dy)
+
+ tk.Label(self._frame, text='web_h').place(x=start_x + 2 * dx_mult * dx, y=start_y + 18 * dy)
+ tk.Label(self._frame, text='web_htk').place(x=start_x + 2 * dx_mult * dx, y=start_y + 19 * dy)
+ self._ent_opt_web_h.place(x=start_x + 3 * dx_mult * dx, y=start_y + 18 * dy)
+ self._ent_opt_web_thk.place(x=start_x + 3 * dx_mult * dx, y=start_y + 19 * dy)
+
+ tk.Label(self._frame, text='fl_thk').place(x=start_x + 4 * dx_mult * dx, y=start_y + 18 * dy)
+ tk.Label(self._frame, text='fl_ttk.').place(x=start_x + 4 * dx_mult * dx, y=start_y + 19 * dy)
+ self._ent_opt_fl_w.place(x=start_x + 5 * dx_mult * dx, y=start_y + 18 * dy)
+ self._ent_opt_fl_thk.place(x=start_x + 5 * dx_mult * dx, y=start_y + 19 * dy)
+
+ # Labels for the pso
+
+ self._lb_swarm_size = tk.Label(self._frame, text='swarm size')
+ self._lb_omega = tk.Label(self._frame, text='omega')
+ self._lb_phip = tk.Label(self._frame, text='phip')
+ self._lb_phig = tk.Label(self._frame, text='phig')
+ self._lb_maxiter = tk.Label(self._frame, text='maxiter')
+ self._lb_minstep = tk.Label(self._frame, text='minstep')
+ self._lb_minfunc = tk.Label(self._frame, text='minfunc')
###
- tk.Label(self._frame, text='Sigma,y1_Sd - large transversal stress', font='Verdana 9')\
- .place(x=start_x+dx*5,y=start_y+11.5*dy)
- tk.Label(self._frame, text='MPa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+11.5*dy)
-
- tk.Label(self._frame, text='Sigma,y2_Sd - small transversal stress', font='Verdana 9')\
- .place(x=start_x+dx*5,y=start_y+12.5*dy)
- tk.Label(self._frame, text='MPa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+12.5*dy)
-
- tk.Label(self._frame, text='Sigma,x_Sd - axial stress', font='Verdana 9')\
- .place(x=start_x+dx*5,y=start_y+13.5*dy)
- tk.Label(self._frame, text='MPa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+13.5*dy)
-
- tk.Label(self._frame, text='Tau,xy - shear stress', font='Verdana 9')\
- .place(x=start_x+dx*5,y=start_y+14.5*dy)
- tk.Label(self._frame, text='MPa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+14.5*dy)
-
- tk.Label(self._frame, text='Applied pressure ', font='Verdana 9 bold')\
- .place(x=start_x+dx*5,y=start_y+15.5*dy)
- tk.Label(self._frame, text='kPa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+15.5*dy)
- tk.Label(self._frame, text='Plate or stiffener side (p/s): ', font='Verdana 9 bold')\
- .place(x=start_x+dx*9.5,y=start_y+15.5*dy)
+ tk.Label(self._frame, text='Sigma,y1_Sd - large transversal stress', font='Verdana 9') \
+ .place(x=start_x + dx * 5, y=start_y + 11.5 * dy)
+ tk.Label(self._frame, text='MPa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 11.5 * dy)
+
+ tk.Label(self._frame, text='Sigma,y2_Sd - small transversal stress', font='Verdana 9') \
+ .place(x=start_x + dx * 5, y=start_y + 12.5 * dy)
+ tk.Label(self._frame, text='MPa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 12.5 * dy)
+
+ tk.Label(self._frame, text='Sigma,x_Sd - axial stress', font='Verdana 9') \
+ .place(x=start_x + dx * 5, y=start_y + 13.5 * dy)
+ tk.Label(self._frame, text='MPa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 13.5 * dy)
+
+ tk.Label(self._frame, text='Tau,xy - shear stress', font='Verdana 9') \
+ .place(x=start_x + dx * 5, y=start_y + 14.5 * dy)
+ tk.Label(self._frame, text='MPa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 14.5 * dy)
+
+ tk.Label(self._frame, text='Applied pressure ', font='Verdana 9 bold') \
+ .place(x=start_x + dx * 5, y=start_y + 15.5 * dy)
+ tk.Label(self._frame, text='kPa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 15.5 * dy)
+ tk.Label(self._frame, text='Plate or stiffener side (p/s): ', font='Verdana 9 bold') \
+ .place(x=start_x + dx * 9.5, y=start_y + 15.5 * dy)
tk.Label(self._frame, text='Span: ', font='Verdana 9') \
.place(x=start_x + dx * 5, y=start_y + 16.5 * dy)
- tk.Label(self._frame, text='m', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+16.5*dy)
+ tk.Label(self._frame, text='m', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 16.5 * dy)
tk.Label(self._frame, text='Girder length,Lg: ', font='Verdana 9') \
.place(x=start_x + dx * 5, y=start_y + 17.5 * dy)
- tk.Label(self._frame, text='m', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+17.5*dy)
+ tk.Label(self._frame, text='m', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 17.5 * dy)
tk.Label(self._frame, text='Slamming pressure ', font='Verdana 9') \
.place(x=start_x + dx * 5, y=start_y + 18.5 * dy)
- tk.Label(self._frame, text='Pa', font='Verdana 9')\
- .place(x=start_x+dx*9,y=start_y+18.5*dy)
+ tk.Label(self._frame, text='Pa', font='Verdana 9') \
+ .place(x=start_x + dx * 9, y=start_y + 18.5 * dy)
if self._fatigue_pressure is not None:
- tk.Label(self._frame, text='Fatigue pressure: internal= '+str(self._fatigue_pressure['p_int'])+ ' external= '
- +str(self._fatigue_pressure['p_ext']), font='Verdana 7') \
+ tk.Label(self._frame,
+ text='Fatigue pressure: internal= ' + str(self._fatigue_pressure['p_int']) + ' external= '
+ + str(self._fatigue_pressure['p_ext']), font='Verdana 7') \
.place(x=start_x + dx * 5, y=start_y + 19.3 * dy)
else:
- tk.Label(self._frame, text='Fatigue pressure: internal= '+str(0)+ ' external= '
- +str(0), font='Verdana 7') \
+ tk.Label(self._frame, text='Fatigue pressure: internal= ' + str(0) + ' external= '
+ + str(0), font='Verdana 7') \
.place(x=start_x + dx * 5, y=start_y + 19.3 * dy)
-
- self._ent_trans_stress_high.place(x=start_x+dx*8,y=start_y+11.5*dy)
- self._ent_trans_stress_low.place(x=start_x+dx*8,y=start_y+12.5*dy)
- self._ent_axial_stress.place(x=start_x+dx*8,y=start_y+13.5*dy)
+ self._ent_trans_stress_high.place(x=start_x + dx * 8, y=start_y + 11.5 * dy)
+ self._ent_trans_stress_low.place(x=start_x + dx * 8, y=start_y + 12.5 * dy)
+ self._ent_axial_stress.place(x=start_x + dx * 8, y=start_y + 13.5 * dy)
self._ent_shear_stress.place(x=start_x + dx * 8, y=start_y + 14.5 * dy)
self._ent_design_pressure.place(x=start_x + dx * 8, y=start_y + 15.5 * dy)
self._ent_design_pressure_side.place(x=start_x + dx * 12, y=start_y + 15.5 * dy)
@@ -426,9 +477,9 @@ def __init__(self,master,app=None):
self._ent_width_lg.place(x=start_x + dx * 8, y=start_y + 17.5 * dy)
self._ent_slamming_pressure.place(x=start_x + dx * 8, y=start_y + 18.5 * dy)
- #setting default values
- init_dim = float(10) #mm
- init_thk = float(1) #mm
+ # setting default values
+ init_dim = float(10) # mm
+ init_thk = float(1) # mm
self._new_delta_spacing.set(init_dim)
self._new_delta_pl_thk.set(init_thk)
self._new_delta_web_h.set(init_dim)
@@ -447,22 +498,22 @@ def __init__(self,master,app=None):
self._new_fatigue_ext_press.set(0), self._new_fatigue_int_press.set(0)
else:
self._new_fatigue_int_press.set(self._fatigue_pressure['p_int']), \
- self._new_fatigue_ext_press.set(self._fatigue_pressure['p_ext'])
-
- self._new_spacing_upper.set(round(self._spacing*1000,5))
- self._new_spacing_lower.set(round(max(self._spacing*1000,0),5))
- self._new_pl_thk_upper.set(round(self._pl_thk*1000+10,5))
- self._new_pl_thk_lower.set(round(max(self._pl_thk*1000-10,float(10)),5))
- self._new_web_h_upper.set(round(self._stf_web_h*1000+100,5))
- self._new_web_h_lower.set(round(max(self._stf_web_h*1000-100,100),5))
- self._new_web_thk_upper.set(round(self._stf_web_thk*1000+10,5))
- self._new_web_thk_lower.set(round(max(self._stf_web_thk*1000-10,float(10)),5))
+ self._new_fatigue_ext_press.set(self._fatigue_pressure['p_ext'])
+
+ self._new_spacing_upper.set(round(self._spacing * 1000, 5))
+ self._new_spacing_lower.set(round(max(self._spacing * 1000, 0), 5))
+ self._new_pl_thk_upper.set(round(self._pl_thk * 1000 + 10, 5))
+ self._new_pl_thk_lower.set(round(max(self._pl_thk * 1000 - 10, float(10)), 5))
+ self._new_web_h_upper.set(round(self._stf_web_h * 1000 + 100, 5))
+ self._new_web_h_lower.set(round(max(self._stf_web_h * 1000 - 100, 100), 5))
+ self._new_web_thk_upper.set(round(self._stf_web_thk * 1000 + 10, 5))
+ self._new_web_thk_lower.set(round(max(self._stf_web_thk * 1000 - 10, float(10)), 5))
if self._initial_calc_obj.Stiffener is not None:
if self._initial_calc_obj.Stiffener.get_stiffener_type() != 'FB':
- self._new_fl_w_upper.set(min(round(self._fl_w*1000+100,5), 200))
- self._new_fl_w_lower.set(round(max(self._fl_w*1000-100,100),5))
- self._new_fl_thk_upper.set(round(self._fl_thk*1000+10,15))
- self._new_fl_thk_lower.set(round(max(self._fl_thk*1000-10,10),15))
+ self._new_fl_w_upper.set(min(round(self._fl_w * 1000 + 100, 5), 200))
+ self._new_fl_w_lower.set(round(max(self._fl_w * 1000 - 100, 100), 5))
+ self._new_fl_thk_upper.set(round(self._fl_thk * 1000 + 10, 15))
+ self._new_fl_thk_lower.set(round(max(self._fl_thk * 1000 - 10, 10), 15))
else:
self._new_fl_w_upper.set(0)
@@ -472,7 +523,7 @@ def __init__(self,master,app=None):
self._new_pressure_side.set('p')
self._new_width_lg.set(10)
- self._new_span.set(round(self._initial_calc_obj.Plate.span,5))
+ self._new_span.set(round(self._initial_calc_obj.Plate.span, 5))
self._new_algorithm.set('anysmart')
self._new_algorithm_random_trials.set(100000)
self._new_swarm_size.set(100)
@@ -483,32 +534,30 @@ def __init__(self,master,app=None):
self._new_minstep.set(1e-8)
self._new_minfunc.set(1e-8)
-
- self._new_delta_spacing.trace('w',self.update_running_time)
- self._new_delta_pl_thk.trace('w',self.update_running_time)
- self._new_delta_web_h.trace('w',self.update_running_time)
- self._new_delta_web_thk.trace('w',self.update_running_time)
- self._new_delta_fl_w.trace('w',self.update_running_time)
- self._new_delta_fl_thk.trace('w',self.update_running_time)
- self._new_spacing_upper.trace('w',self.update_running_time)
- self._new_spacing_lower.trace('w',self.update_running_time)
- self._new_pl_thk_upper.trace('w',self.update_running_time)
- self._new_pl_thk_lower.trace('w',self.update_running_time)
- self._new_web_h_upper.trace('w',self.update_running_time)
- self._new_web_h_lower.trace('w',self.update_running_time)
- self._new_web_thk_upper.trace('w',self.update_running_time)
- self._new_web_thk_lower.trace('w',self.update_running_time)
- self._new_fl_w_upper.trace('w',self.update_running_time)
- self._new_fl_w_lower.trace('w',self.update_running_time)
- self._new_fl_thk_upper.trace('w',self.update_running_time)
- self._new_fl_thk_lower.trace('w',self.update_running_time)
- self._new_algorithm_random_trials.trace('w',self.update_running_time)
- self._new_algorithm.trace('w',self.update_running_time)
-
-
- self.running_time_per_item = {'PULS':0.2489626556016598, 'RP': 1.009943181818182e-5}
- self.initial_weight = op.calc_weight([self._spacing,self._pl_thk,self._stf_web_h,self._stf_web_thk,
- self._fl_w,self._fl_thk,self._new_span.get(),self._new_width_lg.get()])
+ self._new_delta_spacing.trace('w', self.update_running_time)
+ self._new_delta_pl_thk.trace('w', self.update_running_time)
+ self._new_delta_web_h.trace('w', self.update_running_time)
+ self._new_delta_web_thk.trace('w', self.update_running_time)
+ self._new_delta_fl_w.trace('w', self.update_running_time)
+ self._new_delta_fl_thk.trace('w', self.update_running_time)
+ self._new_spacing_upper.trace('w', self.update_running_time)
+ self._new_spacing_lower.trace('w', self.update_running_time)
+ self._new_pl_thk_upper.trace('w', self.update_running_time)
+ self._new_pl_thk_lower.trace('w', self.update_running_time)
+ self._new_web_h_upper.trace('w', self.update_running_time)
+ self._new_web_h_lower.trace('w', self.update_running_time)
+ self._new_web_thk_upper.trace('w', self.update_running_time)
+ self._new_web_thk_lower.trace('w', self.update_running_time)
+ self._new_fl_w_upper.trace('w', self.update_running_time)
+ self._new_fl_w_lower.trace('w', self.update_running_time)
+ self._new_fl_thk_upper.trace('w', self.update_running_time)
+ self._new_fl_thk_lower.trace('w', self.update_running_time)
+ self._new_algorithm_random_trials.trace('w', self.update_running_time)
+ self._new_algorithm.trace('w', self.update_running_time)
+
+ self.running_time_per_item = {'RP': 1.009943181818182e-5}
+ self.initial_weight = op.calc_weight([self._spacing, self._pl_thk, self._stf_web_h, self._stf_web_thk,
+ self._fl_w, self._fl_thk, self._new_span.get(), self._new_width_lg.get()])
img_file_name = 'img_plate_and_stiffener.gif'
if os.path.isfile('images/' + img_file_name):
@@ -516,40 +565,41 @@ def __init__(self,master,app=None):
else:
file_path = self._root_dir + '/images/' + img_file_name
photo = tk.PhotoImage(file=file_path)
- label = tk.Label(self._frame,image=photo)
+ label = tk.Label(self._frame, image=photo)
label.image = photo # keep a reference!
label.place(x=550, y=300)
-
- tk.Label(self._frame,text='Select algorithm', font = 'Verdana 8 bold').place(x=start_x+dx*11, y=start_y+0.5*dy)
- self._ent_algorithm.place(x=start_x+dx*11, y=start_y+dy)
+ tk.Label(self._frame, text='Select algorithm', font='Verdana 8 bold').place(x=start_x + dx * 11,
+ y=start_y + 0.5 * dy)
+ self._ent_algorithm.place(x=start_x + dx * 11, y=start_y + dy)
self.algorithm_random_label = tk.Label(self._frame, text='Number of trials')
- tk.Button(self._frame,text='algorith information',command=self.algorithm_info,bg='white')\
- .place(x=start_x+dx*11, y=start_y+dy*2)
- self.run_button = tk.Button(self._frame,text='RUN OPTIMIZATION!', command=self.run_optimizaion, bg='red',
- font='Verdana 10 bold',fg='Yellow', relief="raised")
- self.run_button.place(x=start_x+dx*8, y=start_y+dy*0.5, relwidth = 0.15)
- self.run_results = tk.Button(self._frame,text='show calculated', command=self.plot_results, bg='white',
- font='Verdana 10',fg='black')
- self.run_results.place(x=start_x+dx*8, y=start_y+dy*1.5)
- self._opt_actual_running_time.place(x=start_x+dx*9.5, y=start_y-dy)
+ tk.Button(self._frame, text='algorith information', command=self.algorithm_info, bg='white') \
+ .place(x=start_x + dx * 11, y=start_y + dy * 2)
+ self.run_button = tk.Button(self._frame, text='RUN OPTIMIZATION!', command=self.run_optimizaion, bg='red',
+ font='Verdana 10 bold', fg='Yellow', relief="raised")
+ self.run_button.place(x=start_x + dx * 8, y=start_y + dy * 0.5, relwidth=0.15)
+ self.run_results = tk.Button(self._frame, text='show calculated', command=self.plot_results, bg='white',
+ font='Verdana 10', fg='black')
+ self.run_results.place(x=start_x + dx * 8, y=start_y + dy * 1.5)
+ self._opt_actual_running_time.place(x=start_x + dx * 9.5, y=start_y - dy)
- self.close_and_save =tk.Button(self._frame,text='Return and replace initial structure with optimized',
- command=self.save_and_close,bg='green',font='Verdana 10',fg='yellow')
- self.close_and_save.place(x=start_x+dx*5,y=10)
+ self.close_and_save = tk.Button(self._frame, text='Return and replace initial structure with optimized',
+ command=self.save_and_close, bg='green', font='Verdana 10', fg='yellow')
+ self.close_and_save.place(x=start_x + dx * 5, y=10)
tk.Button(self._frame, text='Open predefined stiffeners example',
- command=self.open_example_file, bg='white', font='Verdana 10')\
- .place(x=start_x+dx*10,y=10)
+ command=self.open_example_file, bg='white', font='Verdana 10') \
+ .place(x=start_x + dx * 10, y=10)
# Selection of constraints
self._new_check_sec_mod = tk.BooleanVar()
self._new_check_min_pl_thk = tk.BooleanVar()
self._new_check_shear_area = tk.BooleanVar()
self._new_check_buckling = tk.BooleanVar()
- self._new_check_buckling_puls = tk.BooleanVar()
+ self._new_check_buckling_semi_analytical = tk.BooleanVar()
self._new_check_buckling_ml_cl = tk.BooleanVar()
+ self._new_check_buckling_ml_numeric = tk.BooleanVar()
self._new_check_fatigue = tk.BooleanVar()
self._new_check_slamming = tk.BooleanVar()
self._new_check_local_buckling = tk.BooleanVar()
@@ -562,38 +612,45 @@ def __init__(self,master,app=None):
self._new_check_slamming.set(False)
self._new_check_local_buckling.set(True)
self._new_use_weight_filter.set(True)
- self._new_check_buckling_puls.set(False)
+ self._new_check_buckling_semi_analytical.set(False)
self._new_check_buckling_ml_cl.set(False)
- self._new_check_buckling_puls.trace('w', self.update_running_time)
+ self._new_check_buckling_ml_numeric.set(False)
+ self._new_check_buckling_semi_analytical.trace('w', self.update_running_time)
self._new_check_buckling_ml_cl.trace('w', self.update_running_time)
-
+ self._new_check_buckling_ml_numeric.trace('w', self.update_running_time)
start_y = 140
- tk.Label(self._frame,text='Check for minimum section mofdulus').place(x=start_x+dx*9.7,y=start_y+4*dy)
- tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x+dx*9.7,y=start_y+5*dy)
- tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x+dx*9.7,y=start_y+6*dy)
- tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x+dx*9.7,y=start_y+7*dy)
+ tk.Label(self._frame, text='Check for minimum section mofdulus').place(x=start_x + dx * 9.7, y=start_y + 4 * dy)
+ tk.Label(self._frame, text='Check for minimum plate thk.').place(x=start_x + dx * 9.7, y=start_y + 5 * dy)
+ tk.Label(self._frame, text='Check for minimum shear area').place(x=start_x + dx * 9.7, y=start_y + 6 * dy)
+ tk.Label(self._frame, text='Check for buckling (RP-C201)').place(x=start_x + dx * 9.7, y=start_y + 7 * dy)
tk.Label(self._frame, text='Check for fatigue (RP-C203)').place(x=start_x + dx * 9.7, y=start_y + 8 * dy)
tk.Label(self._frame, text='Check for bow slamming').place(x=start_x + dx * 9.7, y=start_y + 9 * dy)
tk.Label(self._frame, text='Check for local stf. buckling').place(x=start_x + dx * 9.7, y=start_y + 10 * dy)
tk.Label(self._frame, text='Use weight filter (for speed)').place(x=start_x + dx * 9.7, y=start_y + 11 * dy)
- tk.Label(self._frame, text='Check for buckling (PULS)').place(x=start_x + dx * 9.7, y=start_y + 12 * dy)
- tk.Label(self._frame, text='Check for buckling (ML-CL)').place(x=start_x + dx * 9.7, y=start_y + 13 * dy)
-
- tk.Checkbutton(self._frame,variable=self._new_check_sec_mod).place(x=start_x+dx*12,y=start_y+4*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x+dx*12,y=start_y+5*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x+dx*12,y=start_y+6*dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x+dx*12,y=start_y+7*dy)
+ tk.Label(self._frame, text='Check for buckling (SemiAnalytical S3/U3)').place(x=start_x + dx * 9.7,
+ y=start_y + 12 * dy)
+ tk.Label(self._frame, text='Check for buckling (ML-CL deactivated)').place(x=start_x + dx * 9.7, y=start_y + 13 * dy)
+ tk.Label(self._frame, text='Check for buckling (ML-Numeric)').place(x=start_x + dx * 9.7, y=start_y + 14 * dy)
+
+ tk.Checkbutton(self._frame, variable=self._new_check_sec_mod).place(x=start_x + dx * 12, y=start_y + 4 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_min_pl_thk).place(x=start_x + dx * 12, y=start_y + 5 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_shear_area).place(x=start_x + dx * 12, y=start_y + 6 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling).place(x=start_x + dx * 12, y=start_y + 7 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_fatigue).place(x=start_x + dx * 12, y=start_y + 8 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_slamming).place(x=start_x + dx * 12, y=start_y + 9 * dy)
tk.Checkbutton(self._frame, variable=self._new_check_local_buckling).place(x=start_x + dx * 12,
y=start_y + 10 * dy)
tk.Checkbutton(self._frame, variable=self._new_use_weight_filter).place(x=start_x + dx * 12,
- y=start_y + 11 * dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling_puls).place(x=start_x + dx * 12,
- y=start_y + 12 * dy)
- tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_cl).place(x=start_x + dx * 12,
- y=start_y + 13 * dy)
+ y=start_y + 11 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_semi_analytical).place(x=start_x + dx * 12,
+ y=start_y + 12 * dy)
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_cl, state='disabled').place(
+ x=start_x + dx * 12,
+ y=start_y + 13 * dy,
+ )
+ tk.Checkbutton(self._frame, variable=self._new_check_buckling_ml_numeric).place(x=start_x + dx * 12,
+ y=start_y + 14 * dy)
# Stress scaling
self._new_fup = tk.DoubleVar()
@@ -601,31 +658,31 @@ def __init__(self,master,app=None):
self._new_fdwn = tk.DoubleVar()
self._new_fdwn.set(1)
- tk.Label(self._frame, text='Factor when scaling stresses up, fup')\
+ tk.Label(self._frame, text='Factor when scaling stresses up, fup') \
.place(x=start_x + dx * 9.7, y=start_y + 16 * dy)
- ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width = 10)
+ ent_fup = tk.Entry(self._frame, textvariable=self._new_fup, width=10)
ent_fup.place(x=start_x + dx * 12, y=start_y + 16 * dy)
- tk.Label(self._frame, text='Factor when scaling stresses up, fdown')\
+ tk.Label(self._frame, text='Factor when scaling stresses up, fdown') \
.place(x=start_x + dx * 9.7, y=start_y + 17 * dy)
- ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width = 10)
+ ent_fdwn = tk.Entry(self._frame, textvariable=self._new_fdwn, width=10)
ent_fdwn.place(x=start_x + dx * 12, y=start_y + 17 * dy)
# tk.Button(self._frame,text='Iterate predefiened stiffeners',command=self.open_multiple_files ,bg='yellow')\
# .place(x=start_x, y=start_y - dy * 2)
self._toggle_btn = tk.Button(self._frame, text="Iterate predefiened stiffeners", relief="raised",
- command=self.toggle, bg = 'salmon')
+ command=self.toggle, bg='salmon')
self._toggle_btn.place(x=start_x, y=start_y - dy * 2)
self._toggle_object, self._filez = self._initial_calc_obj, None
self.draw_properties()
self.update_running_time()
- def selected_algorithm(self,event):
+ def selected_algorithm(self, event):
'''
Action when selecting an algorithm.
:return:
'''
start_x, start_y, dx, dy = 20, 100, 100, 40
- if self._new_algorithm.get()=='random' or self._new_algorithm.get()=='random_no_delta':
+ if self._new_algorithm.get() == 'random' or self._new_algorithm.get() == 'random_no_delta':
self._ent_random_trials.place_forget()
self.algorithm_random_label.place_forget()
self._lb_swarm_size.place_forget()
@@ -642,9 +699,9 @@ def selected_algorithm(self,event):
self._ent_maxiter.place_forget()
self._ent_minstep.place_forget()
self._ent_minfunc.place_forget()
- self._ent_random_trials.place(x=start_x+dx*11.3, y=start_y+1.2*dy)
- self.algorithm_random_label.place(x=start_x+dx*11.3, y=start_y+0.5*dy)
- elif self._new_algorithm.get()=='anysmart' or self._new_algorithm.get()=='anydetail':
+ self._ent_random_trials.place(x=start_x + dx * 11.3, y=start_y + 1.2 * dy)
+ self.algorithm_random_label.place(x=start_x + dx * 11.3, y=start_y + 0.5 * dy)
+ elif self._new_algorithm.get() == 'anysmart' or self._new_algorithm.get() == 'anydetail':
self._ent_random_trials.place_forget()
self.algorithm_random_label.place_forget()
self._lb_swarm_size.place_forget()
@@ -662,38 +719,107 @@ def selected_algorithm(self,event):
self._ent_minstep.place_forget()
self._ent_minfunc.place_forget()
- elif self._new_algorithm.get()=='pso':
- y_place_label =11.2
+ elif self._new_algorithm.get() == 'pso':
+ y_place_label = 11.2
y_place = 12.2
self._ent_random_trials.place_forget()
-
- self._lb_swarm_size.place(x=start_x+dx*y_place_label, y=start_y-2*dy)
- self._lb_omega.place(x=start_x+dx*y_place_label, y=start_y-1*dy)
- self._lb_phip.place(x=start_x+dx*y_place_label, y=start_y-0*dy)
- self._lb_phig.place(x=start_x+dx*y_place_label, y=start_y+1*dy)
- self._lb_maxiter.place(x=start_x+dx*y_place_label, y=start_y+2*dy)
- self._lb_minstep.place(x=start_x+dx*y_place_label, y=start_y+3*dy)
- self._lb_minfunc.place(x=start_x+dx*y_place_label, y=start_y+4*dy)
-
- self._ent_swarm_size.place(x=start_x+dx*y_place, y=start_y-2*dy)
- self._ent_omega.place(x=start_x+dx*y_place, y=start_y-1*dy)
- self._ent_phip.place(x=start_x+dx*y_place, y=start_y+0*dy)
- self._ent_phig.place(x=start_x+dx*y_place, y=start_y+1*dy)
- self._ent_maxiter.place(x=start_x+dx*y_place, y=start_y+2*dy)
- self._ent_minstep.place(x=start_x+dx*y_place, y=start_y+3*dy)
- self._ent_minfunc.place(x=start_x+dx*y_place, y=start_y+4*dy)
+
+ self._lb_swarm_size.place(x=start_x + dx * y_place_label, y=start_y - 2 * dy)
+ self._lb_omega.place(x=start_x + dx * y_place_label, y=start_y - 1 * dy)
+ self._lb_phip.place(x=start_x + dx * y_place_label, y=start_y - 0 * dy)
+ self._lb_phig.place(x=start_x + dx * y_place_label, y=start_y + 1 * dy)
+ self._lb_maxiter.place(x=start_x + dx * y_place_label, y=start_y + 2 * dy)
+ self._lb_minstep.place(x=start_x + dx * y_place_label, y=start_y + 3 * dy)
+ self._lb_minfunc.place(x=start_x + dx * y_place_label, y=start_y + 4 * dy)
+
+ self._ent_swarm_size.place(x=start_x + dx * y_place, y=start_y - 2 * dy)
+ self._ent_omega.place(x=start_x + dx * y_place, y=start_y - 1 * dy)
+ self._ent_phip.place(x=start_x + dx * y_place, y=start_y + 0 * dy)
+ self._ent_phig.place(x=start_x + dx * y_place, y=start_y + 1 * dy)
+ self._ent_maxiter.place(x=start_x + dx * y_place, y=start_y + 2 * dy)
+ self._ent_minstep.place(x=start_x + dx * y_place, y=start_y + 3 * dy)
+ self._ent_minfunc.place(x=start_x + dx * y_place, y=start_y + 4 * dy)
def modify_structure_object(self):
''' Chaning parameters in the structure object before running. '''
pass
+ def _get_material_factor_for_optimization(self):
+ '''
+ Returns the material factor used to select ML models and scale numeric UF.
+
+ In normal GUI usage this follows the main application's material-factor selector.
+ In standalone/test usage it falls back to the current calculation object's
+ material factor.
+ '''
+ try:
+ return float(self.app._new_material_factor.get())
+ except Exception:
+ try:
+ return float(self._initial_calc_obj.Plate.mat_factor)
+ except Exception:
+ return 1.15
+
+ def _get_ml_algo_for_optimization(self):
+ '''
+ The main application may store ML models either as a flat dictionary:
+ self._ML_buckling['cl SP buc int predictor']
+
+ or as a material-factor dictionary:
+ self._ML_buckling[1.15]['cl SP buc int predictor']
+
+ The optimization code expects the flat dictionary for the selected material
+ factor, so this helper returns the correct form.
+ '''
+ mat_fac = self._get_material_factor_for_optimization()
+
+ try:
+ if mat_fac in self._ML_buckling and isinstance(self._ML_buckling[mat_fac], dict):
+ return self._ML_buckling[mat_fac]
+ except Exception:
+ pass
+
+ try:
+ mat_fac_key = str(mat_fac)
+ if mat_fac_key in self._ML_buckling and isinstance(self._ML_buckling[mat_fac_key], dict):
+ return self._ML_buckling[mat_fac_key]
+ except Exception:
+ pass
+
+ return self._ML_buckling
+
+ def _ensure_single_buckling_check(self):
+ '''
+ Only one buckling formulation should be active at a time:
+ RP-C201, SemiAnalytical S3/U3, or ML-Numeric.
+ '''
+ selected = [
+ self._new_check_buckling.get(),
+ self._new_check_buckling_semi_analytical.get(),
+ False,
+ self._new_check_buckling_ml_numeric.get(),
+ ]
+
+ if selected.count(True) > 1:
+ tk.messagebox.showerror('You can only select one buckling type. Reselect.')
+
+ if self._new_check_buckling.get():
+ self._new_check_buckling.set(False)
+ self._new_check_local_buckling.set(False)
+
+ if self._new_check_buckling_semi_analytical.get():
+ self._new_check_buckling_semi_analytical.set(False)
+
+ if self._new_check_buckling_ml_numeric.get():
+ self._new_check_buckling_ml_numeric.set(False)
+
def run_optimizaion(self):
'''
function for button
:return:
'''
- self.run_button.config(bg = 'white')
+ self.run_button.config(bg='white')
self.run_button.config(fg='red')
self.run_button.config(text='RUNNING OPTIMIZATION')
self.run_button.config(relief="sunken")
@@ -701,86 +827,73 @@ def run_optimizaion(self):
self._opt_actual_running_time.update()
t_start = time.time()
self._opt_results, self._opt_runned = (), False
- if self._PULS_object is not None:
- puls_sheet_location = self._PULS_object.puls_sheet_location
- puls_acceptance = self._puls_acceptance
- if self._new_check_buckling_puls.get() == True:
- if puls_sheet_location is None or not os.path.isfile(
- puls_sheet_location):
- tk.messagebox.showerror('No PULS excel sheet located', 'Set location of PULS excel sheet.\n'
- 'Note that PULS excel may require 32 bit '
- 'office.\n\n'
- 'A sheet may be provided but does not exist'
- ' in :\n'
- + self._PULS_results.puls_sheet_location +
- '\n\n Return to main window an run one or more lines in PULS.')
- else:
- puls_sheet_location = None
- puls_acceptance =0.87
-
-
- self.pso_parameters = (self._new_swarm_size.get(),self._new_omega.get(),self._new_phip.get(),
+ self.pso_parameters = (self._new_swarm_size.get(), self._new_omega.get(), self._new_phip.get(),
self._new_phig.get(),
- self._new_maxiter.get(),self._new_minstep.get(),self._new_minfunc.get())
+ self._new_maxiter.get(), self._new_minstep.get(), self._new_minfunc.get())
- contraints = (self._new_check_sec_mod.get(),self._new_check_min_pl_thk.get(),
+ contraints = (self._new_check_sec_mod.get(), self._new_check_min_pl_thk.get(),
self._new_check_shear_area.get(), self._new_check_buckling.get(),
self._new_check_fatigue.get(), self._new_check_slamming.get(),
- self._new_check_local_buckling.get(), self._new_check_buckling_puls.get(),
- self._new_check_buckling_ml_cl.get(), False)
+ self._new_check_local_buckling.get(), self._new_check_buckling_semi_analytical.get(),
+ False, self._new_check_buckling_ml_numeric.get())
self._initial_calc_obj.Plate.set_span(self._new_span.get())
+ selected_mat_fac = self._get_material_factor_for_optimization()
+ self._initial_calc_obj.Plate.mat_factor = selected_mat_fac
+ if self._initial_calc_obj.Stiffener is not None:
+ self._initial_calc_obj.Stiffener.mat_factor = selected_mat_fac
+ if self._initial_calc_obj.Girder is not None:
+ self._initial_calc_obj.Girder.mat_factor = selected_mat_fac
if self._fatigue_pressure is not None:
- fat_press = ((self._fatigue_pressure['p_ext']['loaded'],self._fatigue_pressure['p_ext']['ballast'],
+ fat_press = ((self._fatigue_pressure['p_ext']['loaded'], self._fatigue_pressure['p_ext']['ballast'],
self._fatigue_pressure['p_ext']['part']),
- (self._fatigue_pressure['p_int']['loaded'],self._fatigue_pressure['p_int']['ballast'],
+ (self._fatigue_pressure['p_int']['loaded'], self._fatigue_pressure['p_int']['ballast'],
self._fatigue_pressure['p_int']['part']))
else:
fat_press = None
- self._opt_results= op.run_optmizataion(self._initial_calc_obj,self.get_lower_bounds(),
- self.get_upper_bounds(),self._new_design_pressure.get(),
- self.get_deltas(),algorithm=self._new_algorithm.get(),
- trials=self._new_algorithm_random_trials.get(),
- side=self._new_pressure_side.get(),
- const_chk=contraints,pso_options = self.pso_parameters,
- fatigue_obj=self._fatigue_object,
- fat_press_ext_int=fat_press,
- slamming_press = self._new_slamming_pressure.get(),
- predefined_stiffener_iter=self._predefined_stiffener_iter,
- processes=self._new_processes.get(),
- use_weight_filter = False if self._new_check_buckling_puls.get()
- else self._new_use_weight_filter.get(),
- puls_sheet = puls_sheet_location, puls_acceptance = puls_acceptance,
- fdwn = self._new_fdwn.get(), fup = self._new_fdwn.get(),
- ml_algo= self._ML_buckling)
+ self._opt_results = op.run_optmizataion(self._initial_calc_obj, self.get_lower_bounds(),
+ self.get_upper_bounds(), self._new_design_pressure.get(),
+ self.get_deltas(), algorithm=self._new_algorithm.get(),
+ trials=self._new_algorithm_random_trials.get(),
+ side=self._new_pressure_side.get(),
+ const_chk=contraints, pso_options=self.pso_parameters,
+ fatigue_obj=self._fatigue_object,
+ fat_press_ext_int=fat_press,
+ slamming_press=self._new_slamming_pressure.get(),
+ predefined_stiffener_iter=self._predefined_stiffener_iter,
+ processes=self._new_processes.get(),
+ use_weight_filter=self._new_use_weight_filter.get(),
+ fdwn=self._new_fdwn.get(), fup=self._new_fup.get(),
+ ml_algo=self._get_ml_algo_for_optimization(),
+ material_factor=selected_mat_fac)
if self._opt_results is not None and self._opt_results[0] is not None:
self._opt_actual_running_time.config(text='Actual running time: \n'
- +str(round((time.time()-t_start)/60,4))+' min')
+ + str(round((time.time() - t_start) / 60, 4)) + ' min')
self._opt_actual_running_time.update()
self._opt_runned = True
if self._opt_results[0].Stiffener is not None:
- text = 'Optimization result | Spacing: ' + str(round(self._opt_results[0].Plate.get_s(), 10) * 1000) +\
- ' Plate thickness: ' + str(round(self._opt_results[0].Plate.get_pl_thk() * 1000, 10)) +\
- ' Stiffener - T' + str(round(self._opt_results[0].Stiffener.get_web_h() * 1000, 10)) + 'x'\
- +str(round(self._opt_results[0].Stiffener.get_web_thk() * 1000, 10)) +\
- '+' + str(round(self._opt_results[0].Stiffener.get_fl_w() * 1000, 10)) + 'x'\
- +str(round(self._opt_results[0].Stiffener.get_fl_thk() * 1000, 10))
+ text = 'Optimization result | Spacing: ' + str(round(self._opt_results[0].Plate.get_s(), 10) * 1000) + \
+ ' Plate thickness: ' + str(round(self._opt_results[0].Plate.get_pl_thk() * 1000, 10)) + \
+ ' Stiffener - T' + str(round(self._opt_results[0].Stiffener.get_web_h() * 1000, 10)) + 'x' \
+ + str(round(self._opt_results[0].Stiffener.get_web_thk() * 1000, 10)) + \
+ '+' + str(round(self._opt_results[0].Stiffener.get_fl_w() * 1000, 10)) + 'x' \
+ + str(round(self._opt_results[0].Stiffener.get_fl_thk() * 1000, 10))
else:
- text = 'Optimization result | Spacing: ' + str(round(self._opt_results[0].Plate.get_s(), 10) * 1000) +\
- ' Plate thickness: ' + str(round(self._opt_results[0].Plate.get_pl_thk() * 1000, 10))
+ text = 'Optimization result | Spacing: ' + str(round(self._opt_results[0].Plate.get_s(), 10) * 1000) + \
+ ' Plate thickness: ' + str(round(self._opt_results[0].Plate.get_pl_thk() * 1000, 10))
- self._result_label.config(text= text)
+ self._result_label.config(text=text)
- self._new_opt_spacing.set(round(self._opt_results[0].Plate.get_s(),5))
- self._new_opt_pl_thk.set(round(self._opt_results[0].Plate.get_pl_thk(),5))
+ self._new_opt_spacing.set(round(self._opt_results[0].Plate.get_s(), 5))
+ self._new_opt_pl_thk.set(round(self._opt_results[0].Plate.get_pl_thk(), 5))
if self._opt_results[0].Stiffener is not None:
- self._new_opt_web_h.set(round(self._opt_results[0].Stiffener.get_web_h(),5))
- self._new_opt_web_thk.set(round(self._opt_results[0].Stiffener.get_web_thk(),5))
- self._new_opt_fl_w.set(round(self._opt_results[0].Stiffener.get_fl_w(),5))
- self._new_opt_fl_thk.set(round(self._opt_results[0].Stiffener.get_fl_thk(),5))
+ self._new_opt_web_h.set(round(self._opt_results[0].Stiffener.get_web_h(), 5))
+ self._new_opt_web_thk.set(round(self._opt_results[0].Stiffener.get_web_thk(), 5))
+ self._new_opt_fl_w.set(round(self._opt_results[0].Stiffener.get_fl_w(), 5))
+ self._new_opt_fl_thk.set(round(self._opt_results[0].Stiffener.get_fl_thk(), 5))
self.draw_properties()
else:
messagebox.showinfo(title='Nothing found', message='No better alternatives found. Modify input.\n'
@@ -797,104 +910,88 @@ def get_running_time(self):
:return:
'''
- if self._new_algorithm.get() in ['anysmart','anydetail']:
+ if self._new_algorithm.get() in ['anysmart', 'anydetail']:
all_combs = op.any_get_all_combs(self.get_lower_bounds(), self.get_upper_bounds(), self.get_deltas(),
predef_stiffeners=None if self._predefined_stiffener_iter is None else
[item.get_tuple() for item in self._predefined_stiffener_iter])
number_of_combinations = len([val for val in all_combs])
- return int(number_of_combinations * self.running_time_per_item['PULS' if self._new_check_buckling_puls.get()
- else 'RP']), number_of_combinations
+ return int(number_of_combinations * self.running_time_per_item['RP']), number_of_combinations
- elif self._new_algorithm.get() in ['pso','random','random_no_delta']:
+ elif self._new_algorithm.get() in ['pso', 'random', 'random_no_delta']:
try:
number_of_combinations = \
- max((self._new_spacing_upper.get()-self._new_spacing_lower.get())/self._new_delta_spacing.get(),1)* \
- max((self._new_pl_thk_upper.get()-self._new_pl_thk_lower.get())/self._new_delta_pl_thk.get(),1)*\
- max((self._new_web_h_upper.get()-self._new_web_h_lower.get())/self._new_delta_web_h.get(),1)*\
- max((self._new_web_thk_upper.get()-self._new_web_thk_lower.get())/self._new_delta_web_thk.get(),1)*\
- max((self._new_fl_w_upper.get()-self._new_fl_w_lower.get())/self._new_delta_fl_w.get(),1)*\
- max((self._new_fl_thk_upper.get()-self._new_fl_thk_lower.get())/self._new_delta_fl_thk.get(),1)
- return int(number_of_combinations*self.running_time_per_item['PULS' if self._new_check_buckling_puls.get()
- else 'RP']),number_of_combinations
+ max((self._new_spacing_upper.get() - self._new_spacing_lower.get()) / self._new_delta_spacing.get(),
+ 1) * \
+ max((self._new_pl_thk_upper.get() - self._new_pl_thk_lower.get()) / self._new_delta_pl_thk.get(),
+ 1) * \
+ max((self._new_web_h_upper.get() - self._new_web_h_lower.get()) / self._new_delta_web_h.get(), 1) * \
+ max((self._new_web_thk_upper.get() - self._new_web_thk_lower.get()) / self._new_delta_web_thk.get(),
+ 1) * \
+ max((self._new_fl_w_upper.get() - self._new_fl_w_lower.get()) / self._new_delta_fl_w.get(), 1) * \
+ max((self._new_fl_thk_upper.get() - self._new_fl_thk_lower.get()) / self._new_delta_fl_thk.get(), 1)
+ return int(number_of_combinations * self.running_time_per_item['RP']), number_of_combinations
except TclError:
- return 0,0
+ return 0, 0
else:
try:
- return int(self._new_algorithm_random_trials.get() * self.running_time_per_item['PULS' if self._new_check_buckling_puls.get()
- else 'RP']),\
- self._new_algorithm_random_trials.get()
+ return int(self._new_algorithm_random_trials.get() * self.running_time_per_item['RP']), \
+ self._new_algorithm_random_trials.get()
except TclError:
- return 0,0
+ return 0, 0
def get_deltas(self):
'''
Return a numpy array of the deltas.
:return:
'''
- return np.array([float(self._new_delta_spacing.get())/1000,float(self._new_delta_pl_thk.get())/1000,
- float(self._new_delta_web_h.get())/1000,float(self._new_delta_web_thk.get())/1000,
- float(self._new_delta_fl_w.get())/1000,float(self._new_delta_fl_thk.get())/1000])
+ return np.array([float(self._new_delta_spacing.get()) / 1000, float(self._new_delta_pl_thk.get()) / 1000,
+ float(self._new_delta_web_h.get()) / 1000, float(self._new_delta_web_thk.get()) / 1000,
+ float(self._new_delta_fl_w.get()) / 1000, float(self._new_delta_fl_thk.get()) / 1000])
- def update_running_time(self,*args):
+ def update_running_time(self, *args):
'''
Estimate the running time of the algorithm.
:return:
'''
try:
- self._runnig_time_label.config(text=str(int(self.get_running_time()[1])) + ' (about '+
- str(max(round(self.get_running_time()[1]*self.running_time_per_item['PULS'
- if self._new_check_buckling_puls.get()
- else 'RP']/60,2), 0.1))+ ' min.)')
+ self._runnig_time_label.config(text=str(int(self.get_running_time()[1])) + ' (about ' +
+ str(max(round(self.get_running_time()[1] *
+ self.running_time_per_item['RP'] / 60, 2), 0.1)) +
+ ' min.)')
except (ZeroDivisionError, TclError):
- pass# _tkinter.TclError: pass
+ pass # _tkinter.TclError: pass
- if [self._new_check_buckling_ml_cl.get(),self._new_check_buckling_puls.get(),
- self._new_check_buckling.get()].count(True) > 1:
- tk.messagebox.showerror('You can only select one buckling type. Reselect.')
- if self._new_check_buckling_puls.get():
- self._new_check_buckling_puls.set(False)
- if self._new_check_buckling_ml_cl.get():
- self._new_check_buckling_ml_cl.set(False)
- if self._new_check_buckling.get():
- self._new_check_buckling.set(False)
- self._new_check_local_buckling.set(False)
+ self._ensure_single_buckling_check()
-
- if self._new_check_buckling_puls.get():
- if self._PULS_object is None or self._PULS_object.puls_sheet_location is None:
- tk.messagebox.showerror('Missing PULS sheet', 'Go back to main window and set a PULS sheet location\n'
- 'by running one or more lines.')
- self._new_check_buckling_puls.set(False)
-
def get_upper_bounds(self):
'''
Return an numpy array of upper bounds.
- :return:
+ :return:
'''
- return np.array([self._new_spacing_upper.get()/1000,self._new_pl_thk_upper.get()/1000,
- self._new_web_h_upper.get()/1000,self._new_web_thk_upper.get()/1000,
- self._new_fl_w_upper.get()/1000,self._new_fl_thk_upper.get()/1000,
- self._new_span.get(),self._new_width_lg.get()])
-
+ return np.array([self._new_spacing_upper.get() / 1000, self._new_pl_thk_upper.get() / 1000,
+ self._new_web_h_upper.get() / 1000, self._new_web_thk_upper.get() / 1000,
+ self._new_fl_w_upper.get() / 1000, self._new_fl_thk_upper.get() / 1000,
+ self._new_span.get(), self._new_width_lg.get()])
+
def get_lower_bounds(self):
'''
Return an numpy array of lower bounds.
- :return:
+ :return:
'''
- return np.array([self._new_spacing_lower.get()/1000,self._new_pl_thk_lower.get()/1000,
- self._new_web_h_lower.get()/1000,self._new_web_thk_lower.get()/1000,
- self._new_fl_w_lower.get()/1000,self._new_fl_thk_lower.get()/1000,
+ return np.array([self._new_spacing_lower.get() / 1000, self._new_pl_thk_lower.get() / 1000,
+ self._new_web_h_lower.get() / 1000, self._new_web_thk_lower.get() / 1000,
+ self._new_fl_w_lower.get() / 1000, self._new_fl_thk_lower.get() / 1000,
self._new_span.get(), self._new_width_lg.get()])
- def checkered(self,line_distance):
+ def checkered(self, line_distance):
# vertical lines at an interval of "line_distance" pixel
for x in range(line_distance, self._canvas_dim[0], line_distance):
- self._canvas_opt.create_line(x, 0, x, self._canvas_dim[0], fill="grey",stipple='gray50')
+ self._canvas_opt.create_line(x, 0, x, self._canvas_dim[0], fill="grey", stipple='gray50')
# horizontal lines at an interval of "line_distance" pixel
for y in range(line_distance, self._canvas_dim[1], line_distance):
- self._canvas_opt.create_line(0, y, self._canvas_dim[0], y, fill="grey",stipple='gray50')
+ self._canvas_opt.create_line(0, y, self._canvas_dim[0], y, fill="grey", stipple='gray50')
def draw_properties(self):
'''
@@ -903,82 +1000,104 @@ def draw_properties(self):
'''
self._canvas_opt.delete('all')
self.checkered(10)
- ctr_x = self._canvas_dim[0]/2
- ctr_y = self._canvas_dim[1]/2+200
+ ctr_x = self._canvas_dim[0] / 2
+ ctr_y = self._canvas_dim[1] / 2 + 200
m = self._draw_scale
- init_color,init_stipple = 'blue','gray12'
- opt_color,opt_stippe = 'red','gray12'
- self._canvas_opt.create_rectangle(0,0,self._canvas_dim[0]+10,80,fill='white')
- self._canvas_opt.create_line(10,10,30,10,fill = init_color,width=5)
- self._canvas_opt.create_text(270,10,text='Initial - Pl.: '+str(self._spacing*1000) +'x'+str(self._pl_thk*1000)+
- ' Stf.: '+str(self._stf_web_h*1000)+'x'+str(self._stf_web_thk*1000)+'+'+
- str(self._fl_w*1000)+'x'+str(self._fl_thk*1000), font = 'Verdana 8',
- fill = init_color)
- self._canvas_opt.create_text(120,30,text='Weight (per Lg width): '+str(int(self.initial_weight)),
- font = 'Verdana 8',fill = init_color)
-
- self._canvas_opt.create_rectangle(ctr_x-m*self._spacing/2, ctr_y,ctr_x+m*self._spacing/2,
- ctr_y-m*self._pl_thk, fill=init_color, stipple=init_stipple )
- self._canvas_opt.create_rectangle(ctr_x - m * self._stf_web_thk / 2, ctr_y-m* self._pl_thk,
- ctr_x + m * self._stf_web_thk / 2, ctr_y - m *(self._stf_web_h+self._pl_thk)
- , fill=init_color, stipple=init_stipple )
+ init_color, init_stipple = 'blue', 'gray12'
+ opt_color, opt_stippe = 'red', 'gray12'
+ self._canvas_opt.create_rectangle(0, 0, self._canvas_dim[0] + 10, 80, fill='white')
+ self._canvas_opt.create_line(10, 10, 30, 10, fill=init_color, width=5)
+ self._canvas_opt.create_text(270, 10, text='Initial - Pl.: ' + str(self._spacing * 1000) + 'x' + str(
+ self._pl_thk * 1000) +
+ ' Stf.: ' + str(self._stf_web_h * 1000) + 'x' + str(
+ self._stf_web_thk * 1000) + '+' +
+ str(self._fl_w * 1000) + 'x' + str(self._fl_thk * 1000),
+ font='Verdana 8',
+ fill=init_color)
+ self._canvas_opt.create_text(120, 30, text='Weight (per Lg width): ' + str(int(self.initial_weight)),
+ font='Verdana 8', fill=init_color)
+
+ self._canvas_opt.create_rectangle(ctr_x - m * self._spacing / 2, ctr_y, ctr_x + m * self._spacing / 2,
+ ctr_y - m * self._pl_thk, fill=init_color, stipple=init_stipple)
+ self._canvas_opt.create_rectangle(ctr_x - m * self._stf_web_thk / 2, ctr_y - m * self._pl_thk,
+ ctr_x + m * self._stf_web_thk / 2,
+ ctr_y - m * (self._stf_web_h + self._pl_thk)
+ , fill=init_color, stipple=init_stipple)
if self._initial_calc_obj.Stiffener is None:
return
if self._initial_calc_obj.Stiffener.get_stiffener_type() not in ['L', 'L-bulb']:
- self._canvas_opt.create_rectangle(ctr_x-m*self._fl_w/2, ctr_y-m*(self._pl_thk+self._stf_web_h),
- ctr_x+m*self._fl_w/2, ctr_y-m*(self._pl_thk+self._stf_web_h+self._fl_thk),
- fill=init_color, stipple=init_stipple)
+ self._canvas_opt.create_rectangle(ctr_x - m * self._fl_w / 2, ctr_y - m * (self._pl_thk + self._stf_web_h),
+ ctr_x + m * self._fl_w / 2,
+ ctr_y - m * (self._pl_thk + self._stf_web_h + self._fl_thk),
+ fill=init_color, stipple=init_stipple)
else:
- self._canvas_opt.create_rectangle(ctr_x-m*self._stf_web_thk/2, ctr_y-m*(self._pl_thk+self._stf_web_h),
- ctr_x+m*self._fl_w, ctr_y-m*(self._pl_thk+self._stf_web_h+self._fl_thk),
- fill=init_color, stipple=init_stipple)
+ self._canvas_opt.create_rectangle(ctr_x - m * self._stf_web_thk / 2,
+ ctr_y - m * (self._pl_thk + self._stf_web_h),
+ ctr_x + m * self._fl_w,
+ ctr_y - m * (self._pl_thk + self._stf_web_h + self._fl_thk),
+ fill=init_color, stipple=init_stipple)
if self._opt_runned:
self._canvas_opt.create_rectangle(ctr_x - m * self._opt_results[0].Stiffener.get_s() / 2, ctr_y,
- ctr_x + m * self._opt_results[0].Stiffener.get_s() / 2,
- ctr_y - m * self._opt_results[0].Stiffener.get_pl_thk(), fill=opt_color,
- stipple=opt_stippe)
+ ctr_x + m * self._opt_results[0].Stiffener.get_s() / 2,
+ ctr_y - m * self._opt_results[0].Stiffener.get_pl_thk(), fill=opt_color,
+ stipple=opt_stippe)
self._canvas_opt.create_rectangle(ctr_x - m * self._opt_results[0].Stiffener.get_web_thk() / 2, ctr_y -
- m * self._opt_results[0].Stiffener.get_pl_thk(),
- ctr_x + m * self._opt_results[0].Stiffener.get_web_thk() / 2,
- ctr_y - m * (self._opt_results[0].Stiffener.get_web_h() + self._opt_results[0].Stiffener.get_pl_thk())
- , fill=opt_color, stipple=opt_stippe)
+ m * self._opt_results[0].Stiffener.get_pl_thk(),
+ ctr_x + m * self._opt_results[0].Stiffener.get_web_thk() / 2,
+ ctr_y - m * (self._opt_results[0].Stiffener.get_web_h() +
+ self._opt_results[0].Stiffener.get_pl_thk())
+ , fill=opt_color, stipple=opt_stippe)
if self._opt_results[0].Stiffener.get_stiffener_type() not in ['L', 'L-bulb']:
self._canvas_opt.create_rectangle(ctr_x - m * self._opt_results[0].Stiffener.get_fl_w() / 2, ctr_y
- - m * (self._opt_results[0].Stiffener.get_pl_thk()+ self._opt_results[0].Stiffener.get_web_h()),
- ctr_x + m * self._opt_results[0].Stiffener.get_fl_w() / 2,ctr_y -
- m * (self._opt_results[0].Stiffener.get_pl_thk() + self._opt_results[0].Stiffener.get_web_h() +
- self._opt_results[0].Stiffener.get_fl_thk()),
- fill=opt_color, stipple=opt_stippe)
+ - m * (self._opt_results[0].Stiffener.get_pl_thk() +
+ self._opt_results[0].Stiffener.get_web_h()),
+ ctr_x + m * self._opt_results[0].Stiffener.get_fl_w() / 2, ctr_y -
+ m * (self._opt_results[0].Stiffener.get_pl_thk() + self._opt_results[
+ 0].Stiffener.get_web_h() +
+ self._opt_results[0].Stiffener.get_fl_thk()),
+ fill=opt_color, stipple=opt_stippe)
else:
self._canvas_opt.create_rectangle(ctr_x - m * self._opt_results[0].Stiffener.get_web_thk() / 2, ctr_y
- - m * (self._opt_results[0].Stiffener.get_pl_thk()+ self._opt_results[0].Stiffener.get_web_h()),
- ctr_x + m * self._opt_results[0].Stiffener.get_fl_w() ,ctr_y -
- m * (self._opt_results[0].Stiffener.get_pl_thk() + self._opt_results[0].Stiffener.get_web_h() +
- self._opt_results[0].Stiffener.get_fl_thk()),
- fill=opt_color, stipple=opt_stippe)
+ - m * (self._opt_results[0].Stiffener.get_pl_thk() +
+ self._opt_results[0].Stiffener.get_web_h()),
+ ctr_x + m * self._opt_results[0].Stiffener.get_fl_w(), ctr_y -
+ m * (self._opt_results[0].Stiffener.get_pl_thk() + self._opt_results[
+ 0].Stiffener.get_web_h() +
+ self._opt_results[0].Stiffener.get_fl_thk()),
+ fill=opt_color, stipple=opt_stippe)
self._canvas_opt.create_line(10, 50, 30, 50, fill=opt_color, width=5)
- self._canvas_opt.create_text(270,50,text='Optimized - Pl.: '+str(round(self._opt_results[0].Stiffener.get_s()*1000,1))
- +'x'+ str(round(self._opt_results[0].Stiffener.get_pl_thk()*1000,1))+
- ' Stf.: '+str(round(self._opt_results[0].Stiffener.get_web_h()*1000,1))+
- 'x'+str(round(self._opt_results[0].Stiffener.get_web_thk()*1000,1))+'+'+
- str(round(self._opt_results[0].Stiffener.get_fl_w()*1000,1))+
- 'x'+str(round(self._opt_results[0].Stiffener.get_fl_thk()*1000,1)),
- font = 'Verdana 8',fill = opt_color)
+ self._canvas_opt.create_text(270, 50, text='Optimized - Pl.: ' + str(
+ round(self._opt_results[0].Stiffener.get_s() * 1000, 1))
+ + 'x' + str(
+ round(self._opt_results[0].Stiffener.get_pl_thk() * 1000, 1)) +
+ ' Stf.: ' + str(
+ round(self._opt_results[0].Stiffener.get_web_h() * 1000, 1)) +
+ 'x' + str(
+ round(self._opt_results[0].Stiffener.get_web_thk() * 1000, 1)) + '+' +
+ str(round(self._opt_results[0].Stiffener.get_fl_w() * 1000, 1)) +
+ 'x' + str(
+ round(self._opt_results[0].Stiffener.get_fl_thk() * 1000, 1)),
+ font='Verdana 8', fill=opt_color)
self._canvas_opt.create_text(120, 70, text='Weight (per Lg width): '
- + str(int(op.calc_weight([self._opt_results[0].Stiffener.get_s(),
- self._opt_results[0].Stiffener.get_pl_thk(),
- self._opt_results[0].Stiffener.get_web_h(),
- self._opt_results[0].Stiffener.get_web_thk(),
- self._opt_results[0].Stiffener.get_fl_w(),
- self._opt_results[0].Stiffener.get_fl_thk(),
- self._new_span.get(),
- self._new_width_lg.get()]))),
- font='Verdana 8', fill=opt_color)
+ + str(int(op.calc_weight([self._opt_results[0].Stiffener.get_s(),
+ self._opt_results[
+ 0].Stiffener.get_pl_thk(),
+ self._opt_results[
+ 0].Stiffener.get_web_h(),
+ self._opt_results[
+ 0].Stiffener.get_web_thk(),
+ self._opt_results[
+ 0].Stiffener.get_fl_w(),
+ self._opt_results[
+ 0].Stiffener.get_fl_thk(),
+ self._new_span.get(),
+ self._new_width_lg.get()]))),
+ font='Verdana 8', fill=opt_color)
def save_and_close(self):
'''
@@ -993,7 +1112,7 @@ def save_and_close(self):
try:
self.app.on_close_opt_window(self._opt_results)
except (IndexError, TypeError):
- messagebox.showinfo(title='Nothing to return',message='No results to return.')
+ messagebox.showinfo(title='Nothing to return', message='No results to return.')
return
self._frame.destroy()
@@ -1033,28 +1152,28 @@ def algorithm_info(self):
' before the search terminates (Default: 1e-8)\n'
' minfunc : The minimum change of swarm’s best objective value\n'
' before the search terminates (Default: 1e-8)\n\n'
-
+
'\n'
'All algorithms calculates local scantling and buckling requirements')
def toggle(self):
if self._toggle_btn.config('relief')[-1] == 'sunken':
self._toggle_btn.config(relief="raised")
- self._toggle_btn.config(bg = 'salmon')
- self._ent_spacing_upper.config(bg = 'white')
- self._ent_spacing_lower.config(bg = 'white')
- self._ent_delta_spacing.config(bg = 'white')
- predefined_stiffener_iter = []
+ self._toggle_btn.config(bg='salmon')
+ self._ent_spacing_upper.config(bg='white')
+ self._ent_spacing_lower.config(bg='white')
+ self._ent_delta_spacing.config(bg='white')
+ predefined_stiffener_iter = []
else:
self._toggle_btn.config(relief="sunken")
- self._toggle_btn.config(bg = 'salmon')
+ self._toggle_btn.config(bg='salmon')
self._toggle_btn.config(bg='lightgreen')
- self._ent_spacing_upper.config(bg = 'lightgreen')
- self._ent_spacing_lower.config(bg = 'lightgreen')
- self._ent_delta_spacing.config(bg = 'lightgreen')
- self._ent_pl_thk_upper.config(bg = 'lightgreen')
- self._ent_pl_thk_lower.config(bg = 'lightgreen')
- self._ent_delta_pl_thk.config(bg = 'lightgreen')
+ self._ent_spacing_upper.config(bg='lightgreen')
+ self._ent_spacing_lower.config(bg='lightgreen')
+ self._ent_delta_spacing.config(bg='lightgreen')
+ self._ent_pl_thk_upper.config(bg='lightgreen')
+ self._ent_pl_thk_lower.config(bg='lightgreen')
+ self._ent_delta_pl_thk.config(bg='lightgreen')
open_files = askopenfilenames(parent=self._frame, title='Choose files to open',
initialdir=self._root_dir)
@@ -1066,14 +1185,14 @@ def toggle(self):
obj=self._initial_calc_obj.Stiffener)
if predefined_stiffener_iter == []:
self._toggle_btn.config(relief="raised")
- self._toggle_btn.config(bg = 'salmon')
- self._ent_spacing_upper.config(bg = 'white')
- self._ent_spacing_lower.config(bg = 'white')
- self._ent_delta_spacing.config(bg = 'white')
- self._ent_pl_thk_upper.config(bg = 'white')
- self._ent_pl_thk_lower.config(bg = 'white')
- self._ent_delta_pl_thk.config(bg = 'white')
- self._predefined_stiffener_iter = None
+ self._toggle_btn.config(bg='salmon')
+ self._ent_spacing_upper.config(bg='white')
+ self._ent_spacing_lower.config(bg='white')
+ self._ent_delta_spacing.config(bg='white')
+ self._ent_pl_thk_upper.config(bg='white')
+ self._ent_pl_thk_lower.config(bg='white')
+ self._ent_delta_pl_thk.config(bg='white')
+ self._predefined_stiffener_iter = None
else:
self._predefined_stiffener_iter = predefined_stiffener_iter
self.update_running_time()
@@ -1106,7 +1225,6 @@ def receive_progress_info():
print('hi')
-
if __name__ == '__main__':
root = tk.Tk()
my_app = CreateOptimizeWindow(master=root)
diff --git a/anystruct/project_application.py b/anystruct/project_application.py
new file mode 100644
index 00000000..eed21d61
--- /dev/null
+++ b/anystruct/project_application.py
@@ -0,0 +1,619 @@
+"""Project snapshot, hydration, open-transfer, and persistence services."""
+
+import copy
+import json
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, MutableMapping
+
+from . import example_data, line_structure, project_io
+from .calc_loads import Loads
+from .calc_structure import (
+ AllStructure,
+ CalcFatigue,
+ CalcScantlings,
+ CylinderAndCurvedPlate,
+ Shell,
+ Structure,
+)
+from .project_state import ProjectState
+
+
+ProjectFileCodec = project_io.ProjectFileCodec
+
+
+@dataclass(frozen=True)
+class LoadCombinationRecord:
+ """Plain load-combination value captured at the UI boundary."""
+
+ name: Any
+ static_factor: Any
+ dynamic_factor: Any
+ include: Any
+
+ def to_legacy(self) -> list[Any]:
+ return [self.name, self.static_factor, self.dynamic_factor, self.include]
+
+
+class ProjectPersistenceError(RuntimeError):
+ """Failure raised while a project file is loaded or saved."""
+
+ def __init__(self, action: str, location: Any, error: Exception):
+ self.action = action
+ self.location = str(location)
+ self.error = error
+ super().__init__(f"Could not {action} project file {self.location!r}: {error}")
+
+
+class ProjectPersistenceService:
+ """Load and save project state without depending on Tk file objects."""
+
+ BACKUP_FILE_NAME = "backup.txt"
+ codec = ProjectFileCodec
+
+ @classmethod
+ def save_state_to_path(cls, state: ProjectState, path: str | Path) -> Path:
+ project_path = Path(path)
+ try:
+ with project_path.open("w", encoding="utf-8") as project_file:
+ cls.codec.dump(state, project_file)
+ except (OSError, TypeError, ValueError) as error:
+ raise ProjectPersistenceError("save", project_path, error) from error
+ return project_path
+
+ @classmethod
+ def load_state_from_path(cls, path: str | Path) -> ProjectState:
+ project_path = Path(path)
+ try:
+ with project_path.open(encoding="utf-8") as project_file:
+ return cls.codec.load(project_file)
+ except (OSError, json.JSONDecodeError, TypeError, ValueError) as error:
+ raise ProjectPersistenceError("load", project_path, error) from error
+
+ @classmethod
+ def backup_path(cls, root_dir: str | Path) -> Path:
+ return Path(root_dir).parent / cls.BACKUP_FILE_NAME
+
+ @classmethod
+ def backup_exists(cls, root_dir: str | Path) -> bool:
+ return cls.backup_path(root_dir).is_file()
+
+
+@dataclass(frozen=True)
+class ProjectFileTarget:
+ """Resolved project file path plus whether it should become the remembered save path."""
+
+ path: Path
+ remember_as_last_save: bool = False
+
+
+class ProjectFileDialogService:
+ """Resolve project file targets without depending on Tk dialogs."""
+
+ @staticmethod
+ def selected_save_target(filename: str | Path, *, backup: bool = False) -> ProjectFileTarget:
+ return ProjectFileTarget(Path(filename), remember_as_last_save=not backup)
+
+ @staticmethod
+ def selected_output_target(filename: str | Path | None) -> ProjectFileTarget | None:
+ if filename in (None, ""):
+ return None
+ return ProjectFileTarget(Path(filename), remember_as_last_save=False)
+
+ @staticmethod
+ def selected_open_target(filename: str | Path | None) -> ProjectFileTarget | None:
+ if filename in (None, ""):
+ return None
+ return ProjectFileTarget(Path(filename), remember_as_last_save=False)
+
+ @staticmethod
+ def backup_save_target(root_dir: str | Path) -> ProjectFileTarget:
+ return ProjectFileTarget(ProjectPersistenceService.backup_path(root_dir), remember_as_last_save=False)
+
+ @staticmethod
+ def remembered_save_target(last_save_file: str | Path | None) -> ProjectFileTarget | None:
+ if last_save_file is None:
+ return None
+ return ProjectFileTarget(Path(last_save_file), remember_as_last_save=False)
+
+ @staticmethod
+ def restore_target(root_dir: str | Path) -> ProjectFileTarget | None:
+ backup_path = ProjectPersistenceService.backup_path(root_dir)
+ if not backup_path.is_file():
+ return None
+ return ProjectFileTarget(backup_path, remember_as_last_save=False)
+
+ @staticmethod
+ def example_open_target(file_name: str | Path, root_dir: str | Path) -> ProjectFileTarget:
+ project_path = Path(file_name)
+ if not project_path.is_file():
+ project_path = Path(root_dir) / project_path
+ return ProjectFileTarget(project_path, remember_as_last_save=False)
+
+
+@dataclass(frozen=True)
+class ProjectSaveInput:
+ """Plain project data captured at the save boundary."""
+
+ project_information: str
+ theme: str
+ points: MutableMapping[str, Any]
+ lines: MutableMapping[str, Any]
+ line_bundles: MutableMapping[str, list[Any]]
+ load_assignments: MutableMapping[str, list[Any]]
+ accelerations: MutableMapping[str, Any]
+ load_combinations: tuple[LoadCombinationRecord, ...]
+ tanks: MutableMapping[str, Any]
+ tank_grid: Any
+ tank_search_data: Any
+ buckling_method: str
+ shifting: MutableMapping[str, Any]
+ weight_and_cog: MutableMapping[str, Any]
+
+
+@dataclass(frozen=True)
+class ProjectSaveResult:
+ """Persisted save state and its resolved project path."""
+
+ path: Path
+ state: ProjectState
+
+
+class ProjectSnapshotService:
+ """Create persistence snapshots from domain objects and plain UI values."""
+
+ @classmethod
+ def create_state(
+ cls,
+ *,
+ project_information: str,
+ theme: str,
+ points: MutableMapping[str, Any],
+ lines: MutableMapping[str, Any],
+ line_bundles: MutableMapping[str, list[Any]],
+ load_assignments: MutableMapping[str, list[Any]],
+ accelerations: MutableMapping[str, Any],
+ load_combinations: list[LoadCombinationRecord],
+ tanks: MutableMapping[str, Any],
+ tank_grid: Any,
+ tank_search_data: Any,
+ buckling_method: str,
+ shifting: MutableMapping[str, Any],
+ weight_and_cog: MutableMapping[str, Any],
+ ) -> ProjectState:
+ structures, shell_structures, fatigue = cls._serialize_line_bundles(line_bundles)
+ return ProjectState(
+ project_information=project_information,
+ theme=theme,
+ points=points,
+ lines=lines,
+ structures=structures,
+ shell_structures=shell_structures,
+ loads=cls._serialize_loads(load_assignments),
+ accelerations=accelerations,
+ load_combinations=cls._serialize_load_combinations(load_combinations),
+ tanks=cls._serialize_tanks(tanks, tank_grid, tank_search_data),
+ fatigue=fatigue,
+ buckling_method=buckling_method,
+ shifting=shifting,
+ weight_and_cog=weight_and_cog,
+ )
+
+ @staticmethod
+ def _serialize_line_bundles(line_bundles):
+ structures = {}
+ shell_structures = {}
+ fatigue = {}
+
+ for line_name, legacy_bundle in line_bundles.items():
+ bundle = line_structure.LineStructureBundle.from_legacy_bundle(legacy_bundle)
+ structures[line_name] = bundle.line_structure.get_main_properties()
+ shell_structures[line_name] = (
+ None if bundle.cylinder is None else bundle.cylinder.get_all_properties()
+ )
+ if bundle.fatigue is None:
+ fatigue[line_name] = None
+ continue
+
+ try:
+ fatigue[line_name] = bundle.fatigue.get_fatigue_properties()
+ except AttributeError:
+ fatigue[line_name] = None
+
+ return structures, shell_structures, fatigue
+
+ @staticmethod
+ def _serialize_loads(load_assignments):
+ return {
+ load_name: [load_data[0].get_load_parmeters(), load_data[1]]
+ for load_name, load_data in load_assignments.items()
+ }
+
+ @staticmethod
+ def _serialize_load_combinations(load_combinations):
+ return {
+ combination_number: load_combination.to_legacy()
+ for combination_number, load_combination in enumerate(load_combinations)
+ }
+
+ @staticmethod
+ def _serialize_tanks(tanks, tank_grid, tank_search_data):
+ tank_properties = {"grid": tank_grid, "search_data": tank_search_data}
+ for tank_name, tank in tanks.items():
+ tank_properties[tank_name] = tank.get_parameters()
+ return tank_properties
+
+
+class ProjectSaveService:
+ """Create and persist project snapshots from plain save input."""
+
+ @staticmethod
+ def create_state(save_input: ProjectSaveInput) -> ProjectState:
+ return ProjectSnapshotService.create_state(
+ project_information=save_input.project_information,
+ theme=save_input.theme,
+ points=save_input.points,
+ lines=save_input.lines,
+ line_bundles=save_input.line_bundles,
+ load_assignments=save_input.load_assignments,
+ accelerations=save_input.accelerations,
+ load_combinations=list(save_input.load_combinations),
+ tanks=save_input.tanks,
+ tank_grid=save_input.tank_grid,
+ tank_search_data=save_input.tank_search_data,
+ buckling_method=save_input.buckling_method,
+ shifting=save_input.shifting,
+ weight_and_cog=save_input.weight_and_cog,
+ )
+
+ @classmethod
+ def save_path(cls, path: str | Path, save_input: ProjectSaveInput) -> ProjectSaveResult:
+ state = cls.create_state(save_input)
+ return ProjectSaveResult(
+ path=ProjectPersistenceService.save_state_to_path(state, path),
+ state=state,
+ )
+
+
+@dataclass(frozen=True)
+class ProjectHydrationDefaults:
+ """Legacy structure defaults that the UI used to inject while opening files."""
+
+ structure_types: dict[str, Any]
+ zstar_optimization: Any = True
+ puls_buckling_method: Any = "ultimate"
+ puls_boundary: Any = "Int"
+ puls_stiffener_end: Any = "Continuous"
+ puls_sp_or_up: Any = "SP"
+ puls_up_boundary: Any = "SSSS"
+ material_factor: Any = 1.15
+
+
+@dataclass(frozen=True)
+class ProjectHydrationResult:
+ """Domain objects rebuilt from a saved project before Tk widgets are touched."""
+
+ line_bundles: dict[str, list[Any]]
+ load_assignments: dict[str, list[Any]]
+ section_properties: tuple[dict[str, Any], ...]
+
+
+class ProjectHydrationService:
+ """Rebuild supported domain objects from saved project state."""
+
+ LOAD_VARIABLES = (
+ "poly_third",
+ "poly_second",
+ "poly_first",
+ "poly_const",
+ "load_condition",
+ "structure_type",
+ "man_press",
+ "static_draft",
+ "name_of_load",
+ "limit_state",
+ "slamming mult pl",
+ "slamming mult stf",
+ )
+
+ @classmethod
+ def hydrate_objects(cls, state: ProjectState, defaults: ProjectHydrationDefaults):
+ line_bundles, section_properties = cls._hydrate_line_bundles(state, defaults)
+ load_assignments = cls._hydrate_loads(state.loads, state.lines, line_bundles)
+ return ProjectHydrationResult(
+ line_bundles=line_bundles,
+ load_assignments=load_assignments,
+ section_properties=tuple(section_properties),
+ )
+
+ @classmethod
+ def _hydrate_line_bundles(cls, state: ProjectState, defaults: ProjectHydrationDefaults):
+ line_bundles = {}
+ section_properties = []
+ old_save_file = False
+
+ for line_name, structure_properties in copy.deepcopy(state.structures).items():
+ if len(structure_properties) > 10:
+ old_save_file = True
+
+ line_bundle = line_structure.LineStructureBundle(
+ loads=[],
+ load_combinations={},
+ )
+
+ if old_save_file:
+ structure, fatigue, section_properties_for_line = cls._hydrate_legacy_flat_structure(
+ structure_properties,
+ state.fatigue.get(line_name),
+ defaults,
+ )
+ else:
+ structure, fatigue, section_properties_for_line = cls._hydrate_structure(
+ structure_properties,
+ state.fatigue.get(line_name),
+ )
+
+ line_bundle.line_structure = structure
+ line_bundle.fatigue = fatigue
+ line_bundle.cylinder = cls._hydrate_cylinder(state.shell_structures.get(line_name))
+ line_bundles[line_name] = line_bundle.to_legacy_bundle()
+ if section_properties_for_line is not None:
+ section_properties.append(section_properties_for_line)
+
+ return line_bundles, section_properties
+
+ @classmethod
+ def _hydrate_legacy_flat_structure(cls, structure_properties, fatigue_properties, defaults):
+ cls._add_legacy_structure_defaults(structure_properties, defaults)
+ cls._split_legacy_sigma_x(structure_properties)
+
+ main_dict = copy.deepcopy(example_data.prescriptive_main_dict)
+ end_support_map = {"C": "Continuous", "S": "Sniped"}
+ stiffener_end = structure_properties["puls stiffener end"][0]
+ structure_properties["puls stiffener end"] = [
+ end_support_map.get(stiffener_end, stiffener_end),
+ structure_properties["puls stiffener end"][1],
+ ]
+ main_dict["material yield"] = [355e6, "Pa"]
+ main_dict["load factor on stresses"] = [1, ""]
+ main_dict["load factor on pressure"] = [1, ""]
+ main_dict["buckling method"] = [structure_properties["puls buckling method"], ""]
+ main_dict["stiffener end support"] = structure_properties["puls stiffener end"]
+ main_dict["girder end support"] = ["Continuous", ""]
+
+ domain = (
+ "Flat plate, stiffened"
+ if structure_properties["puls sp or up"][0] == "SP"
+ else "Flat plate, unstiffened"
+ )
+ main_dict["calculation domain"] = [domain, ""]
+
+ pressure_side_map = {"p": "plate side", "s": "stiffener side"}
+ if "press_side" in structure_properties:
+ pressure_side = structure_properties["press_side"][0]
+ structure_properties["press_side"] = [pressure_side_map[pressure_side], ""]
+ else:
+ structure_properties["press_side"] = "both sides"
+ structure_properties["panel or shell"] = "panel"
+
+ structure = AllStructure(
+ Plate=CalcScantlings(structure_properties),
+ Stiffener=None if domain == "Flat plate, unstiffened" else CalcScantlings(structure_properties),
+ Girder=None,
+ main_dict=main_dict,
+ )
+ fatigue = (
+ None
+ if fatigue_properties is None
+ else CalcFatigue(structure_properties, fatigue_properties)
+ )
+ return structure, fatigue, structure_properties
+
+ @staticmethod
+ def _hydrate_structure(structure_properties, fatigue_properties):
+ structure = AllStructure(
+ Plate=(
+ None
+ if structure_properties["Plate"] is None
+ else CalcScantlings(structure_properties["Plate"])
+ ),
+ Stiffener=(
+ None
+ if structure_properties["Stiffener"] is None
+ else CalcScantlings(structure_properties["Stiffener"])
+ ),
+ Girder=(
+ None
+ if structure_properties["Girder"] is None
+ else CalcScantlings(structure_properties["Girder"])
+ ),
+ main_dict=structure_properties["main dict"],
+ )
+ fatigue = (
+ None
+ if fatigue_properties is None
+ else CalcFatigue(structure_properties["Stiffener"], fatigue_properties)
+ )
+ return structure, fatigue, structure_properties["Stiffener"]
+
+ @classmethod
+ def _hydrate_cylinder(cls, cylinder_properties):
+ if cylinder_properties is None:
+ return None
+
+ cylinder_properties = copy.deepcopy(cylinder_properties)
+ for structure_type in ("Long. stf.", "Ring stf.", "Ring frame"):
+ if cylinder_properties[structure_type] is not None:
+ cls._split_legacy_sigma_x(cylinder_properties[structure_type])
+
+ return CylinderAndCurvedPlate(
+ cylinder_properties["Main class"],
+ shell=(
+ None
+ if cylinder_properties["Shell"] is None
+ else Shell(cylinder_properties["Shell"])
+ ),
+ long_stf=(
+ None
+ if cylinder_properties["Long. stf."] is None
+ else Structure(cylinder_properties["Long. stf."])
+ ),
+ ring_stf=(
+ None
+ if cylinder_properties["Ring stf."] is None
+ else Structure(cylinder_properties["Ring stf."])
+ ),
+ ring_frame=(
+ None
+ if cylinder_properties["Ring frame"] is None
+ else Structure(cylinder_properties["Ring frame"])
+ ),
+ )
+
+ @classmethod
+ def _hydrate_loads(cls, load_properties, lines, line_bundles):
+ load_assignments = {}
+ for load_name, data in load_properties.items():
+ values = list(data[0])
+ if len(values) != len(cls.LOAD_VARIABLES):
+ values.extend([1, 1])
+ properties = dict(zip(cls.LOAD_VARIABLES, values))
+ load_assignments[load_name] = [Loads(properties), data[1]]
+
+ for line_name in lines:
+ if line_name in data[1] and line_name in line_bundles:
+ line_structure.LineStructureBundle.from_legacy_bundle(
+ line_bundles[line_name]
+ ).loads.append(load_assignments[load_name][0])
+
+ return load_assignments
+
+ @staticmethod
+ def _add_legacy_structure_defaults(structure_properties, defaults):
+ default_values = {
+ "structure_types": defaults.structure_types,
+ "zstar_optimization": defaults.zstar_optimization,
+ "puls buckling method": defaults.puls_buckling_method,
+ "puls boundary": defaults.puls_boundary,
+ "puls stiffener end": defaults.puls_stiffener_end,
+ "puls sp or up": defaults.puls_sp_or_up,
+ "puls up boundary": defaults.puls_up_boundary,
+ "mat_factor": defaults.material_factor,
+ }
+ for property_name, property_value in default_values.items():
+ if property_name not in structure_properties:
+ unit = " " if property_name == "structure_types" else ""
+ structure_properties[property_name] = [property_value, unit]
+
+ @staticmethod
+ def _split_legacy_sigma_x(properties):
+ if "sigma_x" in properties:
+ properties["sigma_x1"] = properties["sigma_x"]
+ properties["sigma_x2"] = properties["sigma_x"]
+ properties.pop("sigma_x")
+
+
+@dataclass(frozen=True)
+class ProjectOpenResult:
+ """Assembled project payload ready for the UI open boundary."""
+
+ state: ProjectState
+ transfer: "ProjectOpenTransfer"
+ hydration: ProjectHydrationResult
+
+
+class ProjectOpenService:
+ """Load and assemble project data before the UI applies it."""
+
+ @classmethod
+ def open_path(cls, path: str | Path, defaults: ProjectHydrationDefaults) -> ProjectOpenResult:
+ return cls.assemble(
+ ProjectPersistenceService.load_state_from_path(path),
+ defaults,
+ )
+
+ @staticmethod
+ def assemble(state: ProjectState, defaults: ProjectHydrationDefaults) -> ProjectOpenResult:
+ return ProjectOpenResult(
+ state=state,
+ transfer=ProjectOpenTransferService.create_transfer(state),
+ hydration=ProjectHydrationService.hydrate_objects(state, defaults),
+ )
+
+
+@dataclass(frozen=True)
+class OpenLoadCombinationRecord:
+ """Saved load-combination values ready for Tk variable creation."""
+
+ name: tuple[Any, ...]
+ static_factor: Any
+ dynamic_factor: Any
+ include: Any = None
+
+ @property
+ def has_include(self) -> bool:
+ return self.include is not None
+
+ @classmethod
+ def from_legacy(cls, data: list[Any]) -> "OpenLoadCombinationRecord":
+ return cls(
+ name=tuple(data[0]),
+ static_factor=data[1],
+ dynamic_factor=data[2],
+ include=None if len(data) < 4 else data[3],
+ )
+
+
+@dataclass(frozen=True)
+class ProjectOpenTransfer:
+ """Plain project values that `openfile` can apply at the Tk boundary."""
+
+ project_information: str
+ theme: str
+ points: dict[str, Any]
+ lines: dict[str, Any]
+ shifting: dict[str, Any]
+ accelerations: dict[str, Any]
+ load_combinations: tuple[OpenLoadCombinationRecord, ...]
+ tank_grid: Any
+ tank_search_data: dict[int, Any] | None
+ tank_properties: dict[str, Any]
+ buckling_method: str
+ weight_and_cog: dict[str, Any]
+
+
+class ProjectOpenTransferService:
+ """Normalize saved project values before Tk view state is populated."""
+
+ DEFAULT_ACCELERATIONS = {"static": 9.81, "dyn_loaded": 0, "dyn_ballast": 0}
+
+ @classmethod
+ def create_transfer(cls, state: ProjectState) -> ProjectOpenTransfer:
+ return ProjectOpenTransfer(
+ project_information=state.project_information,
+ theme=state.theme,
+ points=state.points,
+ lines=state.lines,
+ shifting=state.shifting,
+ accelerations={**cls.DEFAULT_ACCELERATIONS, **state.accelerations},
+ load_combinations=tuple(
+ OpenLoadCombinationRecord.from_legacy(data)
+ for data in state.load_combinations.values()
+ ),
+ tank_grid=state.tanks.get("grid"),
+ tank_search_data=cls._normalize_tank_search_data(state.tanks.get("search_data")),
+ tank_properties={
+ key: value
+ for key, value in state.tanks.items()
+ if key not in {"grid", "search_data"}
+ },
+ buckling_method=state.buckling_method,
+ weight_and_cog=state.weight_and_cog,
+ )
+
+ @staticmethod
+ def _normalize_tank_search_data(search_data):
+ try:
+ return {int(key): value for key, value in search_data.items()}
+ except (AttributeError, TypeError, ValueError):
+ return None
diff --git a/anystruct/project_io.py b/anystruct/project_io.py
new file mode 100644
index 00000000..b7d13efe
--- /dev/null
+++ b/anystruct/project_io.py
@@ -0,0 +1,99 @@
+"""Versioned JSON project persistence helpers."""
+
+import json
+from typing import Any, IO
+
+from anystruct.project_state import PROJECT_FORMAT_VERSION, ProjectState
+
+
+PROJECT_FORMAT_KEY = "format version"
+REMOVED_EXTERNAL_PULS_METHOD = "DNV PULS"
+DEACTIVATED_ML_CL_METHODS = (
+ "ML-CL (" + "PU" + "LS based)",
+ "ML-CL (SemiAnalytical based)",
+)
+ML_NUMERIC_METHOD = "ML-Numeric (PULS based)"
+DEFAULT_BUCKLING_METHOD = "DNV-RP-C201 - prescriptive"
+
+
+class ProjectFileCodec:
+ """Versioned project-file codec and migration entry point."""
+
+ current_version = PROJECT_FORMAT_VERSION
+ format_key = PROJECT_FORMAT_KEY
+ removed_external_puls_method = REMOVED_EXTERNAL_PULS_METHOD
+ deactivated_ml_cl_methods = DEACTIVATED_ML_CL_METHODS
+ ml_numeric_method = ML_NUMERIC_METHOD
+ default_buckling_method = DEFAULT_BUCKLING_METHOD
+ obsolete_external_puls_keys = ("PULS results",)
+
+ @classmethod
+ def migrate_mapping(cls, project_data: dict[str, Any]) -> dict[str, Any]:
+ """Normalize a loaded project mapping into the current supported shape."""
+ source_version = cls._source_version(project_data)
+ if source_version > cls.current_version:
+ raise ValueError(
+ f"Project format version {source_version} is newer than supported version {cls.current_version}"
+ )
+ migrated = dict(project_data)
+ for obsolete_key in cls.obsolete_external_puls_keys:
+ migrated.pop(obsolete_key, None)
+ if migrated.get("buckling method") == cls.removed_external_puls_method:
+ migrated["buckling method"] = cls.default_buckling_method
+ if migrated.get("buckling method") in cls.deactivated_ml_cl_methods:
+ migrated["buckling method"] = cls.ml_numeric_method
+ migrated[cls.format_key] = cls.current_version
+ return migrated
+
+ @classmethod
+ def _source_version(cls, project_data: dict[str, Any]) -> int:
+ if cls.format_key not in project_data:
+ return 0
+ return int(project_data[cls.format_key])
+
+ @classmethod
+ def decode_mapping(cls, project_data: dict[str, Any]) -> ProjectState:
+ """Decode current or legacy project payloads into canonical project state."""
+ return ProjectState.from_legacy_mapping(cls.migrate_mapping(project_data))
+
+ @classmethod
+ def encode_mapping(cls, project_state: ProjectState) -> dict[str, Any]:
+ """Encode canonical project state using the compatible JSON project shape."""
+ payload = cls.migrate_mapping(project_state.to_legacy_mapping())
+ payload[cls.format_key] = cls.current_version
+ return payload
+
+ @classmethod
+ def load(cls, project_file: IO[str]) -> ProjectState:
+ """Load current or legacy project JSON and drop obsolete external PULS state."""
+ return cls.decode_mapping(json.load(project_file))
+
+ @classmethod
+ def dump(cls, project_state: ProjectState, project_file: IO[str]) -> None:
+ """Write the canonical state using the compatible JSON project shape."""
+ json.dump(cls.encode_mapping(project_state), project_file)
+
+
+def migrate_project_mapping(project_data: dict[str, Any]) -> dict[str, Any]:
+ """Compatibility wrapper for the versioned project-file codec."""
+ return ProjectFileCodec.migrate_mapping(project_data)
+
+
+def decode_project_mapping(project_data: dict[str, Any]) -> ProjectState:
+ """Compatibility wrapper for the versioned project-file codec."""
+ return ProjectFileCodec.decode_mapping(project_data)
+
+
+def encode_project_mapping(project_state: ProjectState) -> dict[str, Any]:
+ """Compatibility wrapper for the versioned project-file codec."""
+ return ProjectFileCodec.encode_mapping(project_state)
+
+
+def load_project_state(project_file: IO[str]) -> ProjectState:
+ """Compatibility wrapper for the versioned project-file codec."""
+ return ProjectFileCodec.load(project_file)
+
+
+def dump_project_state(project_state: ProjectState, project_file: IO[str]) -> None:
+ """Compatibility wrapper for the versioned project-file codec."""
+ ProjectFileCodec.dump(project_state, project_file)
diff --git a/anystruct/project_services.py b/anystruct/project_services.py
new file mode 100644
index 00000000..b6a1c615
--- /dev/null
+++ b/anystruct/project_services.py
@@ -0,0 +1,1220 @@
+"""Application services for project edits that do not depend on Tkinter."""
+
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, Callable, MutableMapping
+
+from . import api_helpers, helper as hlp, line_structure
+from .helper import one_load_combination
+from .project_application import (
+ LoadCombinationRecord,
+ OpenLoadCombinationRecord,
+ ProjectHydrationDefaults,
+ ProjectHydrationResult,
+ ProjectHydrationService,
+ ProjectFileDialogService,
+ ProjectFileTarget,
+ ProjectFileCodec,
+ ProjectOpenResult,
+ ProjectOpenService,
+ ProjectOpenTransfer,
+ ProjectOpenTransferService,
+ ProjectPersistenceError,
+ ProjectPersistenceService,
+ ProjectSaveInput,
+ ProjectSaveResult,
+ ProjectSaveService,
+ ProjectSnapshotService,
+)
+
+
+POINT_PREFIX = "point"
+LINE_PREFIX = "line"
+
+
+def _numbered_name(prefix: str, number: int) -> str:
+ return f"{prefix}{number}"
+
+
+def _name_number(name: str, prefix: str) -> int:
+ if not name.startswith(prefix):
+ raise ValueError(f"{name!r} is not a {prefix} name")
+ return int(name[len(prefix):])
+
+
+def _next_available_name(records: MutableMapping[str, Any], prefix: str) -> str:
+ number = 1
+ while _numbered_name(prefix, number) in records:
+ number += 1
+ return _numbered_name(prefix, number)
+
+
+@dataclass(frozen=True)
+class PointRecord:
+ """Named point used at the project editing boundary."""
+
+ name: str
+ x: float
+ y: float
+
+ @classmethod
+ def from_legacy(cls, name: str, coordinates: list[float]) -> "PointRecord":
+ return cls(name=name, x=coordinates[0], y=coordinates[1])
+
+ def to_legacy(self) -> list[float]:
+ return [self.x, self.y]
+
+
+@dataclass(frozen=True)
+class LineRecord:
+ """Named line whose endpoints use project point names."""
+
+ name: str
+ first_point: str
+ second_point: str
+
+ @classmethod
+ def from_legacy(cls, name: str, point_numbers: list[int]) -> "LineRecord":
+ return cls(
+ name=name,
+ first_point=_numbered_name(POINT_PREFIX, point_numbers[0]),
+ second_point=_numbered_name(POINT_PREFIX, point_numbers[1]),
+ )
+
+ @property
+ def point_numbers(self) -> list[int]:
+ return [
+ _name_number(self.first_point, POINT_PREFIX),
+ _name_number(self.second_point, POINT_PREFIX),
+ ]
+
+ @property
+ def endpoint_keys(self) -> tuple[str, str]:
+ first, second = self.point_numbers
+ return f"p{first}p{second}", f"p{second}p{first}"
+
+ def to_legacy(self) -> list[int]:
+ return self.point_numbers
+
+
+class ProjectEditService:
+ """Mutate legacy geometry dictionaries through typed project records."""
+
+ def __init__(self, points: MutableMapping[str, list[float]], lines: MutableMapping[str, list[int]]):
+ self._points = points
+ self._lines = lines
+
+ def next_point_name(self) -> str:
+ return _next_available_name(self._points, POINT_PREFIX)
+
+ def next_line_name(self) -> str:
+ return _next_available_name(self._lines, LINE_PREFIX)
+
+ def add_point(self, name: str, coordinates: tuple[float, float]) -> PointRecord | None:
+ if list(coordinates) in self._points.values():
+ return None
+
+ point = PointRecord(name=name, x=coordinates[0], y=coordinates[1])
+ self._points[point.name] = point.to_legacy()
+ return point
+
+ def move_point(self, name: str, coordinates: tuple[float, float]) -> PointRecord | None:
+ if name not in self._points or list(coordinates) in self._points.values():
+ return None
+
+ point = PointRecord(name=name, x=coordinates[0], y=coordinates[1])
+ self._points[name] = point.to_legacy()
+ return point
+
+ def add_line(self, first_point: str, second_point: str) -> LineRecord | None:
+ if first_point == second_point or first_point not in self._points or second_point not in self._points:
+ return None
+ if self._line_exists(first_point, second_point):
+ return None
+
+ line = LineRecord(name=self.next_line_name(), first_point=first_point, second_point=second_point)
+ self._lines[line.name] = line.to_legacy()
+ return line
+
+ def line(self, name: str) -> LineRecord:
+ return LineRecord.from_legacy(name, self._lines[name])
+
+ def connected_line_names(self, point: str) -> list[str]:
+ point_number = _name_number(point, POINT_PREFIX)
+ return [name for name, endpoints in self._lines.items() if point_number in endpoints]
+
+ def remove_line(self, name: str) -> LineRecord:
+ line = self.line(name)
+ self._lines.pop(name)
+ return line
+
+ def remove_point(self, name: str) -> PointRecord:
+ point = PointRecord.from_legacy(name, self._points[name])
+ self._points.pop(name)
+ return point
+
+ def _line_exists(self, first_point: str, second_point: str) -> bool:
+ requested = {_name_number(first_point, POINT_PREFIX), _name_number(second_point, POINT_PREFIX)}
+ return any(set(endpoints) == requested for endpoints in self._lines.values())
+
+
+class LineStructureService:
+ """Assign and update the legacy line structure bundles behind typed access."""
+
+ def __init__(self, line_bundles: MutableMapping[str, list[Any]]):
+ self._line_bundles = line_bundles
+
+ def has_assignment(self, line_name: str) -> bool:
+ return line_name in self._line_bundles
+
+ def assign_structure(self, line_name: str, structure: Any, cylinder: Any = None):
+ bundle = line_structure.LineStructureBundle(
+ line_structure=structure,
+ loads=[None],
+ cylinder=cylinder,
+ )
+ self._line_bundles[line_name] = bundle.to_legacy_bundle()
+ return bundle
+
+ def structure(self, line_name: str):
+ return self.bundle(line_name).line_structure
+
+ def fatigue(self, line_name: str):
+ return self.bundle(line_name).fatigue
+
+ def cylinder(self, line_name: str):
+ return self.bundle(line_name).cylinder
+
+ def replace_structure(self, line_name: str, structure: Any):
+ bundle = self.bundle(line_name)
+ bundle.line_structure = structure
+ self._write_bundle(line_name, bundle)
+ return structure
+
+ def update_structure_properties(self, line_name: str, properties: dict[str, Any]):
+ structure = self.structure(line_name)
+ structure.set_main_properties(properties)
+ structure.need_recalc = True
+ return structure
+
+ def set_cylinder(self, line_name: str, cylinder: Any):
+ bundle = self.bundle(line_name)
+ bundle.cylinder = cylinder
+ self._write_bundle(line_name, bundle)
+ return cylinder
+
+ def sync_fatigue_after_structure_update(self, line_name: str, properties: dict[str, Any]):
+ bundle = self.bundle(line_name)
+ if bundle.fatigue is None:
+ return None
+
+ if bundle.line_structure.calculation_domain == "Flat plate, unstiffened":
+ bundle.fatigue = None
+ self._write_bundle(line_name, bundle)
+ return None
+
+ bundle.fatigue.set_main_properties(properties["Stiffener"])
+ return bundle.fatigue
+
+ def replace_loads(self, line_name: str, loads: list[Any]):
+ bundle = self.bundle(line_name)
+ bundle.loads = loads
+ self._write_bundle(line_name, bundle)
+ return bundle.loads
+
+ def append_load(self, line_name: str, load: Any):
+ bundle = self.bundle(line_name)
+ bundle.loads.append(load)
+ return bundle.loads
+
+ def bundle(self, line_name: str):
+ return line_structure.LineStructureBundle.from_legacy_bundle(self._line_bundles[line_name])
+
+ def _write_bundle(self, line_name: str, bundle: line_structure.LineStructureBundle):
+ self._line_bundles[line_name] = bundle.to_legacy_bundle()
+
+
+@dataclass(frozen=True)
+class FlatStructurePropertyRequest:
+ """Plain values needed to build a flat-plate structure property bundle."""
+
+ calculation_domain: str
+ base_values: dict[str, Any]
+ girder_values: dict[str, Any]
+ buckling_values: dict[str, Any]
+ structure_types: dict[str, Any]
+
+
+class FlatStructurePropertyService:
+ """Build legacy flat-plate property bundles from plain boundary values."""
+
+ @classmethod
+ def build(cls, request: FlatStructurePropertyRequest):
+ values = request.base_values
+ obj_dict = {
+ 'mat_yield': [api_helpers.mpa_to_pa(values['material']), 'Pa'],
+ 'mat_factor': [values['material_factor'], ''],
+ 'span': [api_helpers.mm_to_m(values['span']), 'm'],
+ 'spacing': [api_helpers.mm_to_m(values['spacing']), 'm'],
+ 'plate_thk': [api_helpers.mm_to_m(values['plate_thk']), 'm'],
+ 'stf_web_height': [api_helpers.mm_to_m(values['stf_web_h']), 'm'],
+ 'stf_web_thk': [api_helpers.mm_to_m(values['stf_web_t']), 'm'],
+ 'stf_flange_width': [api_helpers.mm_to_m(values['stf_fl_w']), 'm'],
+ 'stf_flange_thk': [api_helpers.mm_to_m(values['stf_fl_t']), 'm'],
+ 'structure_type': [values['structure_type'], ''],
+ 'stf_type': [values['stf_type'], ''],
+ 'sigma_y1': [values['sigma_y1'], 'MPa'],
+ 'sigma_y2': [values['sigma_y2'], 'MPa'],
+ 'sigma_x1': [values['sigma_x1'], 'MPa'],
+ 'sigma_x2': [values['sigma_x2'], 'MPa'],
+ 'tau_xy': [values['tau_xy'], 'MPa'],
+ 'plate_kpp': [values['plate_kpp'], ''],
+ 'stf_kps': [values['stf_kps'], ''],
+ 'stf_km1': [values['stf_km1'], ''],
+ 'stf_km2': [values['stf_km2'], ''],
+ 'stf_km3': [values['stf_km3'], ''],
+ 'press_side': [values['pressure_side'], ''],
+ 'structure_types': [request.structure_types, ''],
+ 'zstar_optimization': [values['zstar_optimization'], ''],
+ 'puls buckling method': [values['puls_method'], ''],
+ 'puls boundary': [values['puls_boundary'], ''],
+ 'puls stiffener end': [values['puls_stiffener_end'], ''],
+ 'puls sp or up': [values['puls_sp_or_up'], ''],
+ 'puls up boundary': [values['puls_up_boundary'], ''],
+ 'panel or shell': [values['panel_or_shell'], ''],
+ 'girder_lg': [api_helpers.mm_to_m(values['girder_lg']), ''],
+ }
+
+ obj_dict_pl = dict(obj_dict)
+ obj_dict_stf = dict(obj_dict)
+ obj_dict_girder = dict(obj_dict)
+ girder_values = request.girder_values
+ obj_dict_girder['stf_web_height'] = [api_helpers.mm_to_m(girder_values['web_h']), 'm']
+ obj_dict_girder['stf_web_thk'] = [api_helpers.mm_to_m(girder_values['web_t']), 'm']
+ obj_dict_girder['stf_flange_width'] = [api_helpers.mm_to_m(girder_values['fl_w']), 'm']
+ obj_dict_girder['stf_flange_thk'] = [api_helpers.mm_to_m(girder_values['fl_t']), 'm']
+ obj_dict_girder['stf_type'] = [girder_values['type'], '']
+
+ buckling_values = request.buckling_values
+ main_dict = {
+ 'minimum pressure in adjacent spans': [buckling_values['min_pressure_adjacent_spans'], ''],
+ 'material yield': [api_helpers.mpa_to_pa(values['material']), 'Pa'],
+ 'load factor on stresses': [buckling_values['load_factor_stresses'], ''],
+ 'load factor on pressure': [1, ''],
+ 'buckling method': [values['puls_method'], ''],
+ 'stiffener end support': [buckling_values['stiffener_end_support'], ''],
+ 'girder end support': [buckling_values['girder_end_support'], ''],
+ 'tension field': [buckling_values['tension_field'], ''],
+ 'plate effective agains sigy': [buckling_values['plate_effective_against_sigy'], ''],
+ 'buckling length factor stf': [buckling_values['buckling_length_factor_stf'], ''],
+ 'buckling length factor girder': [buckling_values['buckling_length_factor_girder'], ''],
+ 'km3': [buckling_values['km3'], ''],
+ 'km2': [buckling_values['km2'], ''],
+ 'girder distance between lateral support': [buckling_values['girder_dist_lateral_support'], ''],
+ 'stiffener distance between lateral support': [buckling_values['stiffener_dist_lateral_support'], ''],
+ 'panel length, Lp': [buckling_values['panel_length'], ''],
+ 'pressure side': [values['pressure_side'], ''],
+ 'fabrication method stiffener': [buckling_values['fabrication_method_stiffener'], ''],
+ 'fabrication method girder': [buckling_values['fabrication_method_girder'], ''],
+ 'calculation domain': [request.calculation_domain, ''],
+ }
+
+ prop_dict = {
+ 'main dict': main_dict,
+ 'Plate': obj_dict_pl,
+ 'Stiffener': None if request.calculation_domain == 'Flat plate, unstiffened' else obj_dict_stf,
+ 'Girder': None if request.calculation_domain in ['Flat plate, unstiffened', 'Flat plate, stiffened']
+ else obj_dict_girder,
+ }
+ return prop_dict, obj_dict_stf
+
+
+@dataclass(frozen=True)
+class CylinderStructurePropertyRequest:
+ """Plain values needed to build cylinder property dictionaries."""
+
+ calculation_domain: str
+ dummy_values: dict[str, Any]
+ shell_values: dict[str, Any]
+ longitudinal_values: dict[str, Any]
+ ring_stiffener_values: dict[str, Any]
+ ring_frame_values: dict[str, Any]
+ load_input: dict[str, Any]
+ main_values: dict[str, Any]
+ structure_types: dict[str, Any]
+
+
+@dataclass(frozen=True)
+class CylinderStructurePropertyResult:
+ """Cylinder property dictionaries plus derived stress/force values."""
+
+ main_dict: dict[str, Any]
+ shell_dict: dict[str, Any]
+ longitudinal_dict: dict[str, Any]
+ ring_stiffener_dict: dict[str, Any]
+ ring_frame_dict: dict[str, Any]
+ geometry: int
+ derived_stresses: tuple[Any, Any, Any, Any, Any]
+ derived_forces: tuple[Any, Any, Any, Any]
+
+
+class CylinderStructurePropertyService:
+ """Build legacy cylinder property dictionaries from plain boundary values."""
+
+ @classmethod
+ def build(cls, request: CylinderStructurePropertyRequest) -> CylinderStructurePropertyResult:
+ from .calc_structure import CylinderAndCurvedPlate
+
+ geometry = api_helpers.geometry_id_for_domain(request.calculation_domain)
+ dummy_data = cls._dummy_data(request)
+ shell_dict = cls._shell_dict(request)
+ long_dict = cls._longitudinal_dict(request)
+ ring_stf_dict = cls._ring_stiffener_dict(request)
+ ring_frame_dict = cls._ring_frame_dict(request)
+ derived_stresses, derived_forces = cls._convert_load_input(
+ request,
+ geometry,
+ cylinder_class=CylinderAndCurvedPlate,
+ )
+ sasd, smsd, tTsd, tQsd, shsd = derived_stresses
+ main_values = request.main_values
+ main_dict_cyl = {
+ 'sasd': [api_helpers.mpa_to_pa(sasd), 'Pa'],
+ 'smsd': [api_helpers.mpa_to_pa(smsd), 'Pa'],
+ 'tTsd': [api_helpers.mpa_to_pa(tTsd), 'Pa'],
+ 'tQsd': [api_helpers.mpa_to_pa(tQsd), 'Pa'],
+ 'psd': [api_helpers.mpa_to_pa(request.load_input['psd']), 'Pa'],
+ 'shsd': [api_helpers.mpa_to_pa(shsd), 'Pa'],
+ 'geometry': [geometry, ''],
+ 'material factor': [main_values['material_factor'], ''],
+ 'delta0': [0.005, ''],
+ 'fab method ring stf': [main_values['fab_method_ring_stiffener'], ''],
+ 'fab method ring girder': [main_values['fab_method_ring_frame'], ''],
+ 'E-module': [main_values['e_module'], 'Pa'],
+ 'poisson': [main_values['poisson'], ''],
+ 'mat_yield': [api_helpers.mpa_to_pa(main_values['yield']), 'Pa'],
+ 'length between girders': [api_helpers.mm_to_m(main_values['length_between_girders']), 'm'],
+ 'panel spacing, s': [api_helpers.mm_to_m(main_values['panel_spacing']), 'm'],
+ 'ring stf excluded': [main_values['ring_stiffener_excluded'], ''],
+ 'ring frame excluded': [main_values['ring_frame_excluded'], ''],
+ 'ULS or ALS': [main_values['uls_or_als'], ''],
+ 'end cap pressure': [main_values['end_cap_pressure'], ''],
+ }
+
+ for key, value in dummy_data.items():
+ long_dict.setdefault(key, value)
+ ring_stf_dict.setdefault(key, value)
+ ring_frame_dict.setdefault(key, value)
+
+ return CylinderStructurePropertyResult(
+ main_dict=main_dict_cyl,
+ shell_dict=shell_dict,
+ longitudinal_dict=long_dict,
+ ring_stiffener_dict=ring_stf_dict,
+ ring_frame_dict=ring_frame_dict,
+ geometry=geometry,
+ derived_stresses=derived_stresses,
+ derived_forces=derived_forces,
+ )
+
+ @staticmethod
+ def _dummy_data(request):
+ values = request.dummy_values
+ return {
+ 'span': [api_helpers.mm_to_m(values['span']), 'm'],
+ 'plate_thk': [api_helpers.mm_to_m(values['plate_thk']), 'm'],
+ 'structure_type': [values['structure_type'], ''],
+ 'sigma_y1': [values['sigma_y1'], 'MPa'],
+ 'sigma_y2': [values['sigma_y2'], 'MPa'],
+ 'sigma_x1': [values['sigma_x1'], 'MPa'],
+ 'sigma_x2': [values['sigma_x2'], 'MPa'],
+ 'tau_xy': [values['tau_xy'], 'MPa'],
+ 'plate_kpp': [values['plate_kpp'], ''],
+ 'stf_kps': [values['stf_kps'], ''],
+ 'stf_km1': [values['stf_km1'], ''],
+ 'stf_km2': [values['stf_km2'], ''],
+ 'stf_km3': [values['stf_km3'], ''],
+ 'press_side': [values['pressure_side'], ''],
+ 'structure_types': [request.structure_types, ''],
+ 'zstar_optimization': [values['zstar_optimization'], ''],
+ 'puls buckling method': [values['puls_method'], ''],
+ 'puls boundary': [values['puls_boundary'], ''],
+ 'puls stiffener end': [values['puls_stiffener_end'], ''],
+ 'puls sp or up': [values['puls_sp_or_up'], ''],
+ 'puls up boundary': [values['puls_up_boundary'], ''],
+ 'panel or shell': [values['panel_or_shell'], ''],
+ 'mat_factor': [values['material_factor'], ''],
+ 'spacing': [api_helpers.mm_to_m(values['spacing']), 'm'],
+ }
+
+ @staticmethod
+ def _shell_dict(request):
+ values = request.shell_values
+ return {
+ 'plate_thk': [api_helpers.mm_to_m(values['thickness']), 'm'],
+ 'radius': [api_helpers.mm_to_m(values['radius']), 'm'],
+ 'distance between rings, l': [api_helpers.mm_to_m(values['distance_between_rings']), 'm'],
+ 'length of shell, L': [api_helpers.mm_to_m(values['length']), 'm'],
+ 'tot cyl length, Lc': [api_helpers.mm_to_m(values['total_length']), 'm'],
+ 'eff. buckling lenght factor': [values['k_factor'], ''],
+ 'mat_yield': [api_helpers.mpa_to_pa(request.main_values['yield']), 'Pa'],
+ }
+
+ @staticmethod
+ def _longitudinal_dict(request):
+ values = request.longitudinal_values
+ return {
+ 'spacing': [api_helpers.mm_to_m(values['spacing']), 'm'],
+ 'stf_web_height': [api_helpers.mm_to_m(values['web_h']), 'm'],
+ 'stf_web_thk': [api_helpers.mm_to_m(values['web_t']), 'm'],
+ 'stf_flange_width': [api_helpers.mm_to_m(values['fl_w']), 'm'],
+ 'stf_flange_thk': [api_helpers.mm_to_m(values['fl_t']), 'm'],
+ 'stf_type': [values['type'], ''],
+ 'span': [api_helpers.mm_to_m(request.dummy_values['span']), 'm'],
+ 'mat_yield': [api_helpers.mpa_to_pa(request.main_values['yield']), 'Pa'],
+ 'panel or shell': ['shell', ''],
+ }
+
+ @staticmethod
+ def _ring_stiffener_dict(request):
+ values = request.ring_stiffener_values
+ return {
+ 'stf_web_height': [api_helpers.mm_to_m(values['web_h']), 'm'],
+ 'stf_web_thk': [api_helpers.mm_to_m(values['web_t']), 'm'],
+ 'stf_flange_width': [api_helpers.mm_to_m(values['fl_w']), 'm'],
+ 'stf_flange_thk': [api_helpers.mm_to_m(values['fl_t']), 'm'],
+ 'stf_type': [values['type'], ''],
+ 'mat_yield': [api_helpers.mpa_to_pa(request.main_values['yield']), 'Pa'],
+ 'panel or shell': ['shell', ''],
+ }
+
+ @staticmethod
+ def _ring_frame_dict(request):
+ values = request.ring_frame_values
+ return {
+ 'stf_web_height': [api_helpers.mm_to_m(values['web_h']), 'm'],
+ 'stf_web_thk': [api_helpers.mm_to_m(values['web_t']), 'm'],
+ 'stf_flange_width': [api_helpers.mm_to_m(values['fl_w']), 'm'],
+ 'stf_flange_thk': [api_helpers.mm_to_m(values['fl_t']), 'm'],
+ 'stf_type': [values['type'], ''],
+ 'span': [api_helpers.mm_to_m(request.dummy_values['span']), 'm'],
+ 'mat_yield': [api_helpers.mpa_to_pa(request.main_values['yield']), 'Pa'],
+ 'panel or shell': ['shell', ''],
+ }
+
+ @staticmethod
+ def _convert_load_input(request, geometry, cylinder_class):
+ load_input = request.load_input
+ shell = request.shell_values
+ longitudinal = request.longitudinal_values
+ converter_kwargs = {
+ 'geometry': geometry,
+ 'shell_t': shell['thickness'],
+ 'shell_radius': shell['radius'],
+ 'shell_spacing': longitudinal['spacing'],
+ 'hw': longitudinal['web_h'],
+ 'tw': longitudinal['web_t'],
+ 'b': longitudinal['fl_w'],
+ 'tf': longitudinal['fl_t'],
+ 'CylinderAndCurvedPlate': cylinder_class,
+ }
+ if load_input['mode'] == 1:
+ forces = (
+ load_input['Nsd'],
+ load_input['Msd'],
+ load_input['Tsd'],
+ load_input['Qsd'],
+ )
+ stresses = hlp.helper_cylinder_stress_to_force_to_stress(
+ stresses=None,
+ forces=forces,
+ **converter_kwargs,
+ )
+ return stresses, forces
+
+ stresses = (
+ load_input['sasd'],
+ load_input['smsd'],
+ abs(load_input['tTsd']),
+ load_input['tQsd'],
+ load_input['shsd'],
+ )
+ forces_with_shsd = hlp.helper_cylinder_stress_to_force_to_stress(
+ stresses=stresses,
+ **converter_kwargs,
+ )
+ return stresses, tuple(forces_with_shsd[:4])
+
+
+@dataclass(frozen=True)
+class LineLoadSyncResult:
+ """Outcome of rebuilding per-line load assignments."""
+
+ invalidated_lines: tuple[str, ...]
+ changed_lines: tuple[str, ...]
+
+
+class LineLoadService:
+ """Rebuild per-line load references independently of the load window."""
+
+ def __init__(self, line_bundles: MutableMapping[str, list[Any]]):
+ self._line_bundles = line_bundles
+ self._structures = LineStructureService(line_bundles)
+
+ def rebuild_line_loads(
+ self,
+ line_names,
+ load_assignments: MutableMapping[str, list[Any]],
+ previous_load_assignments: MutableMapping[str, list[Any]] | None,
+ ) -> LineLoadSyncResult:
+ for line_name in self._line_bundles:
+ self._structures.replace_loads(line_name, [])
+
+ invalidated = tuple(mark_lines_for_recalculation(self._line_bundles))
+ changed = []
+ previous_load_assignments = previous_load_assignments or {}
+
+ for line_name in line_names:
+ if not self._structures.has_assignment(line_name):
+ continue
+
+ for load, load_lines in load_assignments.values():
+ if self._line_load_changed(line_name, load, load_lines, previous_load_assignments):
+ if line_name not in changed:
+ changed.append(line_name)
+ if line_name in load_lines:
+ self._structures.append_load(line_name, load)
+
+ return LineLoadSyncResult(invalidated_lines=invalidated, changed_lines=tuple(changed))
+
+ @staticmethod
+ def _line_load_changed(line_name, load, load_lines, previous_load_assignments):
+ previous = previous_load_assignments.get(load.get_name())
+ if previous is None:
+ return line_name in load_lines
+
+ previous_load, previous_lines = previous
+ load_changed_on_line = str(load) != str(previous_load) and line_name in load_lines + previous_lines
+ line_assignment_changed = line_name in set(previous_lines).symmetric_difference(load_lines)
+ return load_changed_on_line or line_assignment_changed
+
+
+def _read_boundary_value(value):
+ return value.get() if hasattr(value, "get") else value
+
+
+@dataclass(frozen=True)
+class LoadFactorRecord:
+ """Plain load-factor values captured from the load-combination boundary."""
+
+ name: tuple[Any, ...]
+ static_factor: Any
+ dynamic_factor: Any
+ include: Any
+
+ @classmethod
+ def from_boundary_values(cls, name, values):
+ return cls(
+ name=tuple(name),
+ static_factor=_read_boundary_value(values[0]),
+ dynamic_factor=_read_boundary_value(values[1]),
+ include=_read_boundary_value(values[2]),
+ )
+
+
+@dataclass(frozen=True)
+class _LegacyLoadFactorValue:
+ """Small get()-adapter for the legacy helper pressure calculation API."""
+
+ value: Any
+
+ def get(self):
+ return self.value
+
+
+def load_factor_records(load_factors) -> dict[tuple[Any, ...], LoadFactorRecord]:
+ """Capture load factors from either Tk variables or plain boundary values."""
+ return {
+ tuple(name): LoadFactorRecord.from_boundary_values(name, values)
+ for name, values in load_factors.items()
+ }
+
+
+@dataclass(frozen=True)
+class LinePressureInput:
+ """Plain application input for one line pressure/load-combination run."""
+
+ line_name: str
+ line_bundle: list[Any]
+ coordinate: tuple[Any, Any]
+ defined_tanks: tuple[tuple[str, Any], ...]
+ accelerations: MutableMapping[str, Any]
+ load_factors: MutableMapping[tuple[Any, ...], LoadFactorRecord]
+
+
+class LinePressureService:
+ """Calculate line load combinations without depending on Tk variables."""
+
+ ZERO_PRESSURE_STRUCTURE_TYPES = {"", "FRAME", "GENERAL_INTERNAL_NONWT"}
+
+ @classmethod
+ def calculate_combinations(cls, line_input: LinePressureInput, limit_state="ULS", get_load_info=False):
+ if limit_state == "FLS":
+ return None
+
+ results = {}
+ load_info = []
+ for combination_name in ("dnva", "dnvb"):
+ results[combination_name] = []
+ for load_condition in ("loaded", "ballast"):
+ returned = cls.calculate_one(line_input, combination_name, load_condition)
+ if returned is not None:
+ results[combination_name].append(returned[0])
+ load_info.extend(returned[1])
+
+ for combination_name, load_condition in (
+ ("tanktest", "tanktest"),
+ ("manual", "manual"),
+ ("slamming", "slamming"),
+ ):
+ returned = cls.calculate_one(line_input, combination_name, load_condition)
+ results[combination_name] = [returned[0]]
+ load_info.extend(returned[1])
+
+ return load_info if get_load_info else results
+
+ @classmethod
+ def calculate_one(cls, line_input: LinePressureInput, combination_name, load_condition):
+ bundle = line_structure.LineStructureBundle.from_legacy_bundle(line_input.line_bundle)
+ defined_loads = [
+ load
+ for load in bundle.loads
+ if load is not None and load.get_limit_state() != "FLS"
+ ]
+
+ acceleration_key = f"dyn_{load_condition}"
+ acceleration = (
+ line_input.accelerations["static"],
+ 0 if load_condition in {"tanktest", "manual", "slamming"}
+ else line_input.accelerations[acceleration_key],
+ )
+ structure = bundle.line_structure
+ current_line_object = [line_input.line_name, structure.Plate]
+ if structure.Plate.get_structure_type() in cls.ZERO_PRESSURE_STRUCTURE_TYPES:
+ return [0, ""]
+
+ return one_load_combination(
+ current_line_object,
+ line_input.coordinate,
+ defined_loads,
+ load_condition,
+ list(line_input.defined_tanks),
+ combination_name,
+ acceleration,
+ cls._legacy_load_factor_values(line_input.load_factors),
+ )
+
+ @classmethod
+ def highest_pressure(cls, line_input: LinePressureInput, limit_state="ULS"):
+ if limit_state != "ULS":
+ return {"normal": 0, "slamming": 0}
+
+ results = cls.calculate_combinations(line_input)
+ normal_pressures = []
+ slamming_pressure = 0
+ slamming_plate_factor = 1
+ slamming_stiffener_factor = 1
+
+ for combination_name, values in results.items():
+ if combination_name != "slamming":
+ normal_pressures.append(max(values))
+ continue
+ if values is not None:
+ for load in line_structure.loads(line_input.line_bundle):
+ if load is not None and load.get_load_condition() == "slamming":
+ slamming_plate_factor = load.get_slamming_reduction_plate()
+ slamming_stiffener_factor = load.get_slamming_reduction_stf()
+ slamming_pressure = max(values)
+
+ return {
+ "normal": max(normal_pressures),
+ "slamming": slamming_pressure,
+ "slamming plate reduction factor": slamming_plate_factor,
+ "slamming stf reduction factor": slamming_stiffener_factor,
+ }
+
+ @staticmethod
+ def _legacy_load_factor_values(load_factors):
+ return {
+ name: (
+ _LegacyLoadFactorValue(record.static_factor),
+ _LegacyLoadFactorValue(record.dynamic_factor),
+ _LegacyLoadFactorValue(record.include),
+ )
+ for name, record in load_factors.items()
+ }
+
+
+@dataclass(frozen=True)
+class SesamExportRequest:
+ """Plain project payload for the supported SESAM JavaScript export."""
+
+ points: MutableMapping[str, Any]
+ lines: MutableMapping[str, Any]
+ sections: Any
+ line_bundles: MutableMapping[str, list[Any]]
+
+
+class SesamExportService:
+ """Build the supported SESAM JavaScript export outside GUI callbacks."""
+
+ @staticmethod
+ def build_js_lines(request: SesamExportRequest, export_factory=None) -> tuple[str, ...]:
+ if export_factory is None:
+ from . import sesam_interface
+
+ export_factory = sesam_interface.JSfile
+
+ export = export_factory(
+ request.points,
+ request.lines,
+ request.sections,
+ line_to_struc=request.line_bundles,
+ )
+ export.write_points()
+ export.write_lines()
+ export.write_sections()
+ export.write_beams()
+ return tuple(export.output_lines)
+
+ @classmethod
+ def write_js_path(cls, request: SesamExportRequest, path, export_factory=None) -> Path:
+ export_path = Path(path)
+ export_path.write_text("".join(cls.build_js_lines(request, export_factory)), encoding="utf-8")
+ return export_path
+
+
+@dataclass(frozen=True)
+class ExcelProjectImportData:
+ """Supported workbook rows read before the Tk import callback applies them."""
+
+ flat_plate_rows: tuple[Any, ...]
+ cylinder_rows: tuple[Any, ...]
+ flat_plate_records: tuple["FlatPlateExcelImportRecord", ...]
+ cylinder_records: tuple["CylinderExcelImportRecord", ...]
+
+
+@dataclass(frozen=True)
+class FlatPlateExcelImportRecord:
+ """Flat-plate line values decoded from the supported import workbook."""
+
+ calculation_domain: Any
+ first_point: tuple[Any, Any]
+ second_point: tuple[Any, Any]
+ plate_values: tuple[Any, ...]
+ stress_values: tuple[Any, ...]
+ manual_pressure: Any
+ girder_values: tuple[Any, ...]
+ buckling_values: tuple[Any, ...]
+
+ @classmethod
+ def from_row(cls, row: list[Any] | tuple[Any, ...]):
+ return cls(
+ calculation_domain=row[0],
+ first_point=(row[1], row[2]),
+ second_point=(row[3], row[4]),
+ plate_values=tuple(row[5:11]),
+ stress_values=tuple(row[12:17]),
+ manual_pressure=row[16],
+ girder_values=tuple(row[18:24]),
+ buckling_values=tuple(row[24:36]),
+ )
+
+
+@dataclass(frozen=True)
+class CylinderExcelImportRecord:
+ """Cylinder line values decoded from the supported import workbook."""
+
+ calculation_domain: Any
+ first_point: tuple[Any, Any]
+ second_point: tuple[Any, Any]
+ shell_values: tuple[Any, ...]
+ longitudinal_values: tuple[Any, ...]
+ ring_stiffener_values: tuple[Any, ...]
+ ring_frame_values: tuple[Any, ...]
+ stress_values: tuple[Any, ...]
+ force_values: tuple[Any, ...]
+ end_values: tuple[Any, ...]
+
+ @classmethod
+ def from_row(cls, row: list[Any] | tuple[Any, ...]):
+ return cls(
+ calculation_domain=row[0],
+ first_point=(row[1], row[2]),
+ second_point=(row[3], row[4]),
+ shell_values=tuple(row[5:12]),
+ longitudinal_values=tuple(row[12:18]),
+ ring_stiffener_values=tuple(row[18:23]),
+ ring_frame_values=tuple(row[23:29]),
+ stress_values=tuple(row[29:35]),
+ force_values=tuple(row[35:39]),
+ end_values=tuple(row[39:43]),
+ )
+
+
+@dataclass(frozen=True)
+class CylinderExcelImportDefaults:
+ """Current project defaults needed when a workbook omits cylinder UI-only values."""
+
+ plate_thk: Any
+ structure_type: Any
+ sigma_y1: Any
+ sigma_y2: Any
+ sigma_x1: Any
+ sigma_x2: Any
+ tau_xy: Any
+ plate_kpp: Any
+ stf_kps: Any
+ stf_km1: Any
+ stf_km2: Any
+ stf_km3: Any
+ pressure_side: Any
+ zstar_optimization: Any
+ puls_method: Any
+ puls_boundary: Any
+ puls_stiffener_end: Any
+ puls_sp_or_up: Any
+ puls_up_boundary: Any
+ panel_or_shell: Any
+ material_factor: Any
+ design_pressure: Any
+ shear_stress: Any
+ e_module: Any
+ poisson: Any
+ length_between_girders: Any
+ fab_method_ring_stiffener: Any
+ fab_method_ring_frame: Any
+ end_cap_pressure: Any
+ structure_types: dict[str, Any]
+ ring_stiffener_type: Any = "T"
+ ring_frame_type: Any = "T"
+
+
+class CylinderExcelImportPropertyService:
+ """Convert supported cylinder workbook records into plain property requests."""
+
+ @classmethod
+ def build_request(
+ cls,
+ record: CylinderExcelImportRecord,
+ defaults: CylinderExcelImportDefaults,
+ ) -> CylinderStructurePropertyRequest:
+ span = hlp.dist(record.first_point, record.second_point)
+ shell_thk, shell_radius, dist_rings, shell_length, shell_total_length, shell_k, shell_mat = \
+ record.shell_values
+ long_web_h, long_web_t, long_fl_w, long_fl_t, panel_spacing, long_type = \
+ record.longitudinal_values
+ ring_stf_values, ring_stf_excluded = cls._component_values(
+ record.ring_stiffener_values,
+ default_type=defaults.ring_stiffener_type,
+ has_length=False,
+ )
+ ring_frame_values, ring_frame_excluded = cls._component_values(
+ record.ring_frame_values,
+ default_type=defaults.ring_frame_type,
+ has_length=True,
+ )
+ load_input = cls._load_input(record, defaults)
+ uls_or_als, shell_yield, _ring_stf_fab, _ring_frame_fab = record.end_values
+
+ return CylinderStructurePropertyRequest(
+ calculation_domain=record.calculation_domain,
+ dummy_values={
+ "span": span,
+ "plate_thk": defaults.plate_thk,
+ "structure_type": defaults.structure_type,
+ "sigma_y1": defaults.sigma_y1,
+ "sigma_y2": defaults.sigma_y2,
+ "sigma_x1": defaults.sigma_x1,
+ "sigma_x2": defaults.sigma_x2,
+ "tau_xy": defaults.tau_xy,
+ "plate_kpp": defaults.plate_kpp,
+ "stf_kps": defaults.stf_kps,
+ "stf_km1": defaults.stf_km1,
+ "stf_km2": defaults.stf_km2,
+ "stf_km3": defaults.stf_km3,
+ "pressure_side": defaults.pressure_side,
+ "zstar_optimization": defaults.zstar_optimization,
+ "puls_method": defaults.puls_method,
+ "puls_boundary": defaults.puls_boundary,
+ "puls_stiffener_end": defaults.puls_stiffener_end,
+ "puls_sp_or_up": defaults.puls_sp_or_up,
+ "puls_up_boundary": defaults.puls_up_boundary,
+ "panel_or_shell": defaults.panel_or_shell,
+ "material_factor": defaults.material_factor,
+ "spacing": panel_spacing,
+ },
+ shell_values={
+ "thickness": shell_thk,
+ "radius": shell_radius,
+ "distance_between_rings": dist_rings,
+ "length": shell_length,
+ "total_length": shell_total_length,
+ "k_factor": shell_k,
+ },
+ longitudinal_values={
+ "spacing": panel_spacing,
+ "web_h": long_web_h,
+ "web_t": long_web_t,
+ "fl_w": long_fl_w,
+ "fl_t": long_fl_t,
+ "type": long_type,
+ },
+ ring_stiffener_values={
+ "web_h": ring_stf_values[0],
+ "web_t": ring_stf_values[1],
+ "fl_w": ring_stf_values[2],
+ "fl_t": ring_stf_values[3],
+ "type": ring_stf_values[4],
+ },
+ ring_frame_values={
+ "web_h": ring_frame_values[0],
+ "web_t": ring_frame_values[1],
+ "fl_w": ring_frame_values[2],
+ "fl_t": ring_frame_values[3],
+ "type": ring_frame_values[5],
+ },
+ load_input=load_input,
+ main_values={
+ "material_factor": shell_mat,
+ "fab_method_ring_stiffener": defaults.fab_method_ring_stiffener,
+ "fab_method_ring_frame": defaults.fab_method_ring_frame,
+ "e_module": defaults.e_module,
+ "poisson": defaults.poisson,
+ "yield": shell_yield,
+ "length_between_girders": (
+ defaults.length_between_girders if ring_frame_excluded else ring_frame_values[4]
+ ),
+ "panel_spacing": panel_spacing,
+ "ring_stiffener_excluded": ring_stf_excluded,
+ "ring_frame_excluded": ring_frame_excluded,
+ "uls_or_als": uls_or_als,
+ "end_cap_pressure": defaults.end_cap_pressure,
+ },
+ structure_types=defaults.structure_types,
+ )
+
+ @staticmethod
+ def _component_values(values, *, default_type, has_length):
+ if not values or values[0] is None:
+ if has_length:
+ return (0, 0, 0, 0, 0, default_type), True
+ return (0, 0, 0, 0, default_type), True
+ return values, False
+
+ @staticmethod
+ def _load_input(record, defaults):
+ stresses = record.stress_values or ()
+ forces = record.force_values or ()
+ has_stresses = bool(stresses) and stresses[0] is not None
+ has_forces = bool(forces) and forces[0] is not None
+
+ sasd, smsd, tTsd, tQsd, psd, shsd = (
+ stresses if has_stresses else (0, 0, 0, 0, defaults.design_pressure, defaults.shear_stress)
+ )
+ psd = defaults.design_pressure if psd is None else psd
+ shsd = defaults.shear_stress if shsd is None else shsd
+ nsd, msd, tsd, qsd = forces if has_forces else (0, 0, 0, 0)
+ return {
+ "mode": 1 if has_forces else 2,
+ "Nsd": nsd,
+ "Msd": msd,
+ "Tsd": tsd,
+ "Qsd": qsd,
+ "sasd": sasd,
+ "smsd": smsd,
+ "tTsd": tTsd,
+ "tQsd": tQsd,
+ "psd": psd,
+ "shsd": shsd,
+ }
+
+
+@dataclass(frozen=True)
+class ExcelImportedLine:
+ """One workbook record matched to the project line it created."""
+
+ record: Any
+ line: LineRecord
+
+
+@dataclass(frozen=True)
+class ExcelGeometryImportResult:
+ """Geometry changes made from workbook records before structures are applied."""
+
+ created_points: tuple[PointRecord, ...]
+ imported_lines: tuple[ExcelImportedLine, ...]
+
+
+class ExcelProjectGeometryImportService:
+ """Apply supported import geometry to the legacy project geometry stores."""
+
+ @classmethod
+ def add_records(cls, points, lines, records) -> ExcelGeometryImportResult:
+ editor = ProjectEditService(points, lines)
+ created_points = []
+ imported_lines = []
+
+ for record in records:
+ for workbook_point in (record.first_point, record.second_point):
+ point = editor.add_point(editor.next_point_name(), cls._point_in_meters(workbook_point))
+ if point is not None:
+ created_points.append(point)
+
+ point_names_by_coordinate = {tuple(coordinates): name for name, coordinates in points.items()}
+ for record in records:
+ first_point = point_names_by_coordinate.get(cls._point_in_meters(record.first_point))
+ second_point = point_names_by_coordinate.get(cls._point_in_meters(record.second_point))
+ if first_point is None or second_point is None:
+ continue
+
+ line = editor.add_line(first_point, second_point)
+ if line is not None:
+ imported_lines.append(ExcelImportedLine(record=record, line=line))
+
+ return ExcelGeometryImportResult(
+ created_points=tuple(created_points),
+ imported_lines=tuple(imported_lines),
+ )
+
+ @staticmethod
+ def _point_in_meters(workbook_point):
+ return workbook_point[0] / 1000, workbook_point[1] / 1000
+
+
+class ExcelProjectImportService:
+ """Read the supported Excel project import workbook through an adapter."""
+
+ @classmethod
+ def read_path(cls, path: str | Path, workbook_factory: Callable[..., Any] | None = None):
+ workbook = cls._open_workbook(
+ path,
+ visible=False,
+ read_only=True,
+ workbook_factory=workbook_factory,
+ )
+ try:
+ flat_plate_rows = tuple(workbook.get_sheet_data("flat_plate") or ())
+ cylinder_rows = tuple(workbook.get_sheet_data("cylinder") or ())
+ return ExcelProjectImportData(
+ flat_plate_rows=flat_plate_rows,
+ cylinder_rows=cylinder_rows,
+ flat_plate_records=tuple(
+ FlatPlateExcelImportRecord.from_row(row) for row in flat_plate_rows[1:]
+ ),
+ cylinder_records=tuple(
+ CylinderExcelImportRecord.from_row(row) for row in cylinder_rows[1:]
+ ),
+ )
+ finally:
+ workbook.close_book()
+
+ @classmethod
+ def open_example_path(cls, path: str | Path, workbook_factory: Callable[..., Any] | None = None):
+ """Open the bundled input workbook for inspection in Excel."""
+ return cls._open_workbook(
+ path,
+ visible=True,
+ read_only=True,
+ workbook_factory=workbook_factory,
+ )
+
+ @staticmethod
+ def _open_workbook(path, *, visible, read_only, workbook_factory=None):
+ if workbook_factory is None:
+ from .excel_inteface import ExcelInterface
+
+ workbook_factory = ExcelInterface
+ return workbook_factory(str(path), visible=visible, read_only=read_only)
+
+
+@dataclass(frozen=True)
+class ReportRequest:
+ """Report command values captured before the reporter adapter is called."""
+
+ filename: str
+ title: str
+ seconds: int
+ source_data: Any
+
+
+@dataclass(frozen=True)
+class ReportDataSnapshot:
+ """Plain report input captured before the Tk application calls a renderer."""
+
+ project_information: str
+ buckling_method: str
+ points: MutableMapping[str, Any]
+ lines: MutableMapping[str, Any]
+ line_bundles: MutableMapping[str, list[Any]]
+ tanks: MutableMapping[str, Any]
+ loads: MutableMapping[str, Any]
+ result_state: MutableMapping[str, Any]
+ highest_pressures: MutableMapping[str, Any]
+ ml_classes: Any
+
+
+class ReportRequestService:
+ """Orchestrate supported report requests through an injected adapter."""
+
+ @staticmethod
+ def create_pdf(request: ReportRequest, report_factory: Callable[..., Any] | None = None):
+ if report_factory is None:
+ from .report_generator import LetterMaker
+
+ report_factory = LetterMaker
+
+ report = report_factory(request.filename, request.title, request.seconds, request.source_data)
+ report.createDocument()
+ report.savePDF()
+ return report
+
+ @staticmethod
+ def create_table(
+ request: ReportRequest,
+ report_factory: Callable[..., Any] | None = None,
+ document_factory: Callable[[str], Any] | None = None,
+ ):
+ if report_factory is None:
+ from .report_generator import LetterMaker
+
+ report_factory = LetterMaker
+ if document_factory is None:
+ from reportlab.lib.pagesizes import landscape, letter
+ from reportlab.platypus import SimpleDocTemplate
+
+ document_factory = lambda filename: SimpleDocTemplate(filename, pagesize=landscape(letter))
+
+ report = report_factory(request.filename, request.title, request.seconds, request.source_data)
+ document = document_factory(request.filename)
+ document.build(report.createTable())
+ return document
+
+
+def mark_line_for_recalculation(line_bundles: MutableMapping[str, list[Any]], line_name: str) -> bool:
+ """Mark one assigned line dirty and report whether a structure was available."""
+ structure = line_structure.structure(line_bundles[line_name]) if line_name in line_bundles else None
+ if structure is None:
+ return False
+
+ structure.need_recalc = True
+ return True
+
+
+def mark_lines_for_recalculation(line_bundles: MutableMapping[str, list[Any]]) -> list[str]:
+ """Mark legacy line structure bundles dirty through the named bundle adapter."""
+ invalidated = []
+ for line_name in line_bundles:
+ if mark_line_for_recalculation(line_bundles, line_name):
+ invalidated.append(line_name)
+ return invalidated
diff --git a/anystruct/project_state.py b/anystruct/project_state.py
new file mode 100644
index 00000000..2032d265
--- /dev/null
+++ b/anystruct/project_state.py
@@ -0,0 +1,92 @@
+"""Canonical project data used at persistence and application boundaries."""
+
+from dataclasses import dataclass, field
+from typing import Any
+
+
+PROJECT_FORMAT_VERSION = 1
+
+
+@dataclass
+class ProjectState:
+ """JSON-safe project snapshot independent of Tkinter widget state."""
+
+ project_information: str = ""
+ theme: str = "default"
+ points: dict[str, Any] = field(default_factory=dict)
+ lines: dict[str, Any] = field(default_factory=dict)
+ structures: dict[str, Any] = field(default_factory=dict)
+ shell_structures: dict[str, Any] = field(default_factory=dict)
+ loads: dict[str, Any] = field(default_factory=dict)
+ accelerations: dict[str, Any] = field(default_factory=dict)
+ load_combinations: dict[str, Any] = field(default_factory=dict)
+ tanks: dict[str, Any] = field(default_factory=dict)
+ fatigue: dict[str, Any] = field(default_factory=dict)
+ buckling_method: str = "DNV-RP-C201 - prescriptive"
+ shifting: dict[str, Any] = field(default_factory=dict)
+ weight_and_cog: dict[str, Any] = field(default_factory=dict)
+ extras: dict[str, Any] = field(default_factory=dict)
+ format_version: int = PROJECT_FORMAT_VERSION
+
+ @classmethod
+ def from_legacy_mapping(cls, data: dict[str, Any]) -> "ProjectState":
+ known_keys = {
+ "format version",
+ "project information",
+ "theme",
+ "point_dict",
+ "line_dict",
+ "structure_properties",
+ "shell structure properties",
+ "load_properties",
+ "accelerations_dict",
+ "load_combinations",
+ "tank_properties",
+ "fatigue_properties",
+ "buckling method",
+ "shifting",
+ "Weight and COG",
+ }
+ buckling_method = data.get("buckling method", "DNV-RP-C201 - prescriptive")
+
+ return cls(
+ project_information=data.get("project information", ""),
+ theme=data.get("theme", "default"),
+ points=data.get("point_dict", {}),
+ lines=data.get("line_dict", {}),
+ structures=data.get("structure_properties", {}),
+ shell_structures=data.get("shell structure properties", {}),
+ loads=data.get("load_properties", {}),
+ accelerations=data.get("accelerations_dict", {}),
+ load_combinations=data.get("load_combinations", {}),
+ tanks=data.get("tank_properties", {}),
+ fatigue=data.get("fatigue_properties", {}),
+ buckling_method=buckling_method,
+ shifting=data.get("shifting", {}),
+ weight_and_cog=data.get("Weight and COG", {}),
+ extras={key: value for key, value in data.items() if key not in known_keys},
+ format_version=data.get("format version", PROJECT_FORMAT_VERSION),
+ )
+
+ def to_legacy_mapping(self) -> dict[str, Any]:
+ data = dict(self.extras)
+ data.update(
+ {
+ "format version": self.format_version,
+ "project information": self.project_information,
+ "theme": self.theme,
+ "point_dict": self.points,
+ "line_dict": self.lines,
+ "structure_properties": self.structures,
+ "shell structure properties": self.shell_structures,
+ "load_properties": self.loads,
+ "accelerations_dict": self.accelerations,
+ "load_combinations": self.load_combinations,
+ "tank_properties": self.tanks,
+ "fatigue_properties": self.fatigue,
+ "buckling method": self.buckling_method,
+ "shifting": self.shifting,
+ "Weight and COG": self.weight_and_cog,
+ }
+ )
+ return data
diff --git a/anystruct/report_generator.py b/anystruct/report_generator.py
index fced760d..3f4fa4a5 100644
--- a/anystruct/report_generator.py
+++ b/anystruct/report_generator.py
@@ -18,15 +18,15 @@
from reportlab.platypus import SimpleDocTemplate, TableStyle
from reportlab.lib import colors
from matplotlib import colors as matplotlib_colors
-import tkinter as tk
try:
import anystruct.example_data as test
import anystruct.helper as hlp
+ import anystruct.line_structure as line_structure
except ModuleNotFoundError:
import ANYstructure.anystruct.example_data as test
import ANYstructure.anystruct.helper as hlp
-
+ import ANYstructure.anystruct.line_structure as line_structure
def create_report(input_data):
@@ -87,9 +87,7 @@ def create_report(input_data):
Story.append(Spacer(2, 3))
ptext = 'END OF RESULTS'
Story.append(Paragraph(ptext, styles["Justify"]))
- my_canvas = canvas.Canvas(file_name)
- my_canvas.line(0,0,200,200)
- doc.build(Story, canvasmaker=my_canvas)
+ doc.build(Story)
class LetterMaker(object):
""""""
@@ -114,7 +112,7 @@ def createDocument(self):
address = """ ANYstructure report generator
""" + '
' + \
""" User: """ + '' + user + '' + '
' + '
' + \
""" Time : """ + '' + time_now + '' + '
'+ \
- '
'+'' + self.data._project_information.get('1.0', tk.END) + ''
+ '
'+'' + self.data.project_information + ''
p = Paragraph(address, self.styles["Normal"])
# add a logo and size it
img_file_name = 'ANYstructure_logo.jpg'
@@ -137,8 +135,8 @@ def createDocument(self):
self.createParagraph(ptext, 10, voffset + 85)
delta = 0
h_start = 130
- if self.data._tank_dict != {}:
- for name, obj in self.data._tank_dict.items():
+ if self.data.tanks != {}:
+ for name, obj in self.data.tanks.items():
ptext = '' + 'Name: '+ name + ', content: ' \
+ obj.get_content() + ''
@@ -170,17 +168,19 @@ def createDocument(self):
ptext = '' + "Results for defined structure: " + ''
self.createParagraph(ptext, 10, 0)
- delta = 140 if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive' else 180
+ result_state = self.data.result_state
+ delta = 140 if self.data.buckling_method == 'DNV-RP-C201 - prescriptive' else 180
vpos = 950
- for line in sorted(self.data._line_dict.keys()):
+ for line in sorted(self.data.lines.keys()):
vpos -= delta
- if line in self.data._line_to_struc.keys():
- if self.data._line_to_struc[line][5] is None:
- struc_obj = self.data._line_to_struc[line][0]
- fo = self.data._line_to_struc[line][2]
- pressure = self.data.get_highest_pressure(line)['normal']/1000
+ if line in self.data.line_bundles.keys():
+ line_bundle = self.data.line_bundles[line]
+ if not line_structure.has_cylinder(line_bundle):
+ struc_obj = line_structure.structure(line_bundle)
+ fo = line_structure.fatigue(line_bundle)
+ pressure = self.data.highest_pressures[line]['normal']/1000
textobject = self.c.beginText()
textobject.setTextOrigin(30,vpos)
textobject.setFont("Helvetica-Oblique", 10)
@@ -212,57 +212,57 @@ def createDocument(self):
if fo is not None:
textobject.textLine('Fatigue pressure [Pa]: '+' p_int:'+' loaded/ballast/part = '
- + str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_int']['loaded'],0))
- +'/'+str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_int']['ballast'],0))
- +'/'+str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_int']['part'],0))
+ + str(round(result_state['pressure_fls'][line]['p_int']['loaded'],0))
+ +'/'+str(round(result_state['pressure_fls'][line]['p_int']['ballast'],0))
+ +'/'+str(round(result_state['pressure_fls'][line]['p_int']['part'],0))
+ ' p_ext:'+' loaded/ballast/part = '+
- str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_ext']['loaded'],0))
- +'/'+str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_ext']['ballast'],0))
- +'/'+str(round(self.data.get_color_and_calc_state()['pressure_fls'][line]['p_ext']['part'],0)))
+ str(round(result_state['pressure_fls'][line]['p_ext']['loaded'],0))
+ +'/'+str(round(result_state['pressure_fls'][line]['p_ext']['ballast'],0))
+ +'/'+str(round(result_state['pressure_fls'][line]['p_ext']['part'],0)))
else:
textobject.textLine(' Fatigue pressure: No pressures defined')
- textobject.setFillColor('red') if self.data.get_color_and_calc_state()['colors'][line]['section'] == 'red' \
+ textobject.setFillColor('red') if result_state['colors'][line]['section'] == 'red' \
else textobject.setFillColor('black')
- textobject.textLine('Section modulus: '+str(int(min(self.data.get_color_and_calc_state()['section_modulus'][line]['sec_mod'])
+ textobject.textLine('Section modulus: '+str(int(min(result_state['section_modulus'][line]['sec_mod'])
*1000**3))+ ' [mm3]'+' Min. section modulus: '+
- str(int(self.data.get_color_and_calc_state()['section_modulus'][line]['min_sec_mod']*1000**3))+' [mm3]'+
- ' -> ' + 'OK' if int(min(self.data.get_color_and_calc_state()['section_modulus'][line]['sec_mod'])*1000**3) >=
- int(self.data.get_color_and_calc_state()['section_modulus'][line]['min_sec_mod']*1000**3)
- else 'Section modulus: '+str(int(min(self.data.get_color_and_calc_state()['section_modulus'][line]['sec_mod'])
+ str(int(result_state['section_modulus'][line]['min_sec_mod']*1000**3))+' [mm3]'+
+ ' -> ' + 'OK' if int(min(result_state['section_modulus'][line]['sec_mod'])*1000**3) >=
+ int(result_state['section_modulus'][line]['min_sec_mod']*1000**3)
+ else 'Section modulus: '+str(int(min(result_state['section_modulus'][line]['sec_mod'])
*1000**3))+ ' [mm3]'+ ' Min. section modulus: '+
- str(int(self.data.get_color_and_calc_state()['section_modulus'][line]['min_sec_mod']*1000**3))+' [mm3]'+
+ str(int(result_state['section_modulus'][line]['min_sec_mod']*1000**3))+' [mm3]'+
' -> ' + 'NOT OK')
textobject.setFillColor('black')
- textobject.setFillColor('red') if self.data.get_color_and_calc_state()['colors'][line]['thickness'] == 'red' \
+ textobject.setFillColor('red') if result_state['colors'][line]['thickness'] == 'red' \
else textobject.setFillColor('black')
textobject.textLine('Min plate thickness: '+
- str(round(self.data.get_color_and_calc_state()['thickness'][line]['min_thk'],2)) + ' [mm] '
+ str(round(result_state['thickness'][line]['min_thk'],2)) + ' [mm] '
' -> ' +
'OK' if struc_obj.Plate.get_pl_thk()*1000 >=
- self.data.get_color_and_calc_state()['thickness'][line]['min_thk'] else
+ result_state['thickness'][line]['min_thk'] else
'Min plate thickness: '+ str(round(
- self.data.get_color_and_calc_state()['thickness'][line]['min_thk'],2)) + ' [mm] '
+ result_state['thickness'][line]['min_thk'],2)) + ' [mm] '
' -> '+'NOT OK')
textobject.setFillColor('black')
- textobject.setFillColor('red') if self.data.get_color_and_calc_state()['colors'][line]['shear'] == 'red' \
+ textobject.setFillColor('red') if result_state['colors'][line]['shear'] == 'red' \
else textobject.setFillColor('black')
- textobject.textLine('Shear area: '+str(int(self.data.get_color_and_calc_state()['shear_area'][line]['shear_area']*1000**2))+' [mm2] '+
- ' Min shear area: '+str(int(self.data.get_color_and_calc_state()['shear_area'][line]['min_shear_area']*1000**2))
+ textobject.textLine('Shear area: '+str(int(result_state['shear_area'][line]['shear_area']*1000**2))+' [mm2] '+
+ ' Min shear area: '+str(int(result_state['shear_area'][line]['min_shear_area']*1000**2))
+ ' [mm2] ' +
- ' -> ' + 'OK' if self.data.get_color_and_calc_state()['shear_area'][line]['shear_area'] >=
- self.data.get_color_and_calc_state()['shear_area'][line]['min_shear_area']
- else 'Shear area: '+str(int(self.data.get_color_and_calc_state()['shear_area'][line]['shear_area']*1000**2))+
+ ' -> ' + 'OK' if result_state['shear_area'][line]['shear_area'] >=
+ result_state['shear_area'][line]['min_shear_area']
+ else 'Shear area: '+str(int(result_state['shear_area'][line]['shear_area']*1000**2))+
' [mm2] ' +
- ' Min shear area: '+str(int(self.data.get_color_and_calc_state()['shear_area'][line]['min_shear_area']*1000**2))
+ ' Min shear area: '+str(int(result_state['shear_area'][line]['min_shear_area']*1000**2))
+ ' [mm2] ' + ' -> ' + 'NOT OK')
textobject.setFillColor('black')
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
- textobject.setFillColor('red') if self.data.get_color_and_calc_state()['colors'][line]['buckling'] == 'red' \
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
+ textobject.setFillColor('red') if result_state['colors'][line]['buckling'] == 'red' \
else textobject.setFillColor('black')
buc_util = list()
- for key, val in self.data.get_color_and_calc_state()['buckling'][line].items():
+ for key, val in result_state['buckling'][line].items():
for uf in val.values():
if type(uf) == list:
buc_util.append(uf[0])
@@ -275,69 +275,106 @@ def createDocument(self):
' -> '+'OK' if max(buc_util) < 1 else
'Highest buckling utilization DNV-RP-C203: '+
str(round(max(buc_util),2))+' -> '+'NOT OK')
- elif self.data._new_buckling_method.get() == 'DNV PULS':
- if self.data._PULS_results is not None:
- puls_method = self.data._line_to_struc[line][0].Plate.get_puls_method()
- textobject.textLine('PULS results using '+str(puls_method) + 'utilization with acceptance '+
- str(self.data._PULS_results.puls_acceptance))
- if line in self.data._PULS_results.get_run_results().keys():
- puls_buckling = self.data._PULS_results.get_run_results()[line]['Buckling strength']['Actual usage Factor'][0]
- puls_ultimate = self.data._PULS_results.get_run_results()[line]['Ultimate capacity']['Actual usage Factor'][0]
- if puls_buckling is not None:
- if puls_method == 'buckling' and puls_buckling/self.data._PULS_results.puls_acceptance > 1:
- textobject.setFillColor('red')
- textobject.textLine('PULS buckling utilization = ' + str(puls_buckling))
- textobject.setFillColor('black')
- if puls_ultimate is not None:
- if puls_method == 'ultimate' and puls_ultimate/self.data._PULS_results.puls_acceptance > 1:
- textobject.setFillColor('red')
- textobject.textLine('PULS ultimate utilization = ' + str(puls_ultimate))
- textobject.setFillColor('black')
-
- else:
- puls_method = self.data._line_to_struc[line][0].Plate.get_puls_method()
- textobject.textLine('ML-CL results using '+str(puls_method) + 'utilization with acceptance 0.87')
- if line in self.data._PULS_results.get_run_results().keys():
- ml_buckling = self.data.get_color_and_calc_state()['ML buckling class'][line]['buckling']
- ml_ultimate = self.data.get_color_and_calc_state()['ML buckling class'][line]['ultimate']
- color_ml_buc = self.data.get_color_and_calc_state()['ML buckling colors'][line]['buckling']
- color_ml_ult = self.data.get_color_and_calc_state()['ML buckling colors'][line]['ultimate']
- color_csr = self.data.get_color_and_calc_state()['ML buckling colors'][line]['CSR requirement']
-
- if puls_method == 'buckling':
- textobject.setFillColor('red' if color_ml_buc == 'red' else 'black')
- textobject.textLine('Buckling ML-CL results: ' + self.data._ML_classes[ml_buckling])
+ elif self.data.buckling_method == 'SemiAnalytical S3/U3':
+ puls_method = line_structure.plate(line_bundle).get_puls_method()
+ puls_method = 'ultimate' if str(puls_method).lower() in ['2', 'ultimate'] else 'buckling'
+ semi_analytical = result_state.get('SemiAnalytical', {}).get(line, {})
+ semi_analytical_valid = result_state.get('SemiAnalytical valid', {}).get(line, {})
+ semi_analytical_colors = result_state.get('SemiAnalytical colors', {}).get(line, {})
+ acceptance = semi_analytical.get('acceptance', 0.87)
+
+ textobject.textLine(
+ 'SemiAnalytical S3/U3 results using ' + str(puls_method) +
+ ' material-factored utilization with acceptance <= ' + str(round(acceptance, 3))
+ )
+ if semi_analytical_valid.get('valid prediction', None) == 1:
+ mat_fac = semi_analytical.get('material factor', 1.0)
+ buc_raw = semi_analytical.get('buckling UF raw', None)
+ ult_raw = semi_analytical.get('ultimate UF raw', None)
+ buc_text = str(round(semi_analytical.get('buckling UF', float('inf')), 3))
+ ult_text = str(round(semi_analytical.get('ultimate UF', float('inf')), 3))
+ if buc_raw is not None:
+ buc_text += ' (' + str(round(buc_raw, 3)) + ' x ' + str(round(mat_fac, 3)) + ')'
+ if ult_raw is not None:
+ ult_text += ' (' + str(round(ult_raw, 3)) + ' x ' + str(round(mat_fac, 3)) + ')'
+ textobject.setFillColor(
+ 'red' if semi_analytical_colors.get('buckling', 'red') == 'red' else 'black'
+ )
+ textobject.textLine(
+ 'Buckling SemiAnalytical S3/U3 UF: ' + buc_text
+ )
+ textobject.setFillColor(
+ 'red' if semi_analytical_colors.get('ultimate', 'red') == 'red' else 'black'
+ )
+ textobject.textLine(
+ 'Ultimate SemiAnalytical S3/U3 UF: ' + ult_text
+ )
textobject.setFillColor('black')
- if puls_method == 'ultimate':
- textobject.setFillColor('red' if color_ml_ult == 'red' else 'black')
- textobject.textLine('Ultimate ML-CL result: ' + self.data._ML_classes[ml_ultimate])
- textobject.setFillColor('red' if color_csr == 'red' else 'black')
- textobject.textLine('CSR tank requirement (stiffener): ' + 'OK' if color_csr == 'green'
- else 'red')
+ textobject.textLine('SemiAnalytical UF acceptance limit: <= 1.00')
+ else:
+ textobject.setFillColor('red')
+ textobject.textLine(
+ 'SemiAnalytical S3/U3 status: ' +
+ semi_analytical_valid.get('valid label', 'invalid/unsupported')
+ )
+ textobject.setFillColor('black')
+ elif self.data.buckling_method == 'ML-Numeric (PULS based)':
+ puls_method = line_structure.plate(line_bundle).get_puls_method()
+ puls_method = 'ultimate' if str(puls_method).lower() in ['2', 'ultimate'] else 'buckling'
+ numeric = result_state.get('ML buckling numeric', {}).get(line, {})
+ numeric_valid = result_state.get('ML buckling numeric valid', {}).get(line, {})
+ numeric_colors = result_state.get('ML buckling numeric colors', {}).get(line, {})
+
+ textobject.textLine('ML-Numeric results using ' + str(puls_method) + ' utilization')
+ if numeric_valid.get('valid prediction', None) == 1:
+ mat_fac = numeric.get('material factor', 1.0)
+ buc_raw = numeric.get('buckling UF raw', None)
+ ult_raw = numeric.get('ultimate UF raw', None)
+ buc_text = str(round(numeric.get('buckling UF', float('inf')), 3))
+ ult_text = str(round(numeric.get('ultimate UF', float('inf')), 3))
+ if buc_raw is not None:
+ buc_text += ' (' + str(round(buc_raw, 3)) + ' x ' + str(round(mat_fac, 3)) + ')'
+ if ult_raw is not None:
+ ult_text += ' (' + str(round(ult_raw, 3)) + ' x ' + str(round(mat_fac, 3)) + ')'
+ textobject.setFillColor(
+ 'red' if numeric_colors.get('buckling', 'red') == 'red' else 'black'
+ )
+ textobject.textLine('Buckling ML-Numeric UF: ' + buc_text)
+ textobject.setFillColor(
+ 'red' if numeric_colors.get('ultimate', 'red') == 'red' else 'black'
+ )
+ textobject.textLine('Ultimate ML-Numeric UF: ' + ult_text)
textobject.setFillColor('black')
+ textobject.textLine('ML-Numeric UF acceptance limit: <= 1.00')
+ else:
+ textobject.setFillColor('red')
+ textobject.textLine(
+ 'ML-Numeric status: ' +
+ numeric_valid.get('valid label', 'invalid/NaN')
+ )
+ textobject.setFillColor('black')
+ else:
+ textobject.textLine('ML-CL is deactivated. Use ML-Numeric or SemiAnalytical.')
textobject.setFillColor('black')
- textobject.setFillColor('red') if self.data.get_color_and_calc_state()['colors'][line]['fatigue'] == 'red' \
+ textobject.setFillColor('red') if result_state['colors'][line]['fatigue'] == 'red' \
else textobject.setFillColor('black')
- if self.data.get_color_and_calc_state()['fatigue'][line]['damage'] is not None:
+ if result_state['fatigue'][line]['damage'] is not None:
textobject.textLine('Fatigue (plate/stiffeners) utilization: '+
- str(round(self.data.get_color_and_calc_state()['fatigue'][line]['damage'],2))+ ' * DFF('+
- str(self.data.get_color_and_calc_state()['fatigue'][line]['dff']) + ') = ' +
- str(round(self.data.get_color_and_calc_state()['fatigue'][line]['damage']*
- self.data.get_color_and_calc_state()['fatigue'][line]['dff'],2)) + ' (SN-curve = '+
- self.data.get_color_and_calc_state()['fatigue'][line]['curve']+')')
+ str(round(result_state['fatigue'][line]['damage'],2))+ ' * DFF('+
+ str(result_state['fatigue'][line]['dff']) + ') = ' +
+ str(round(result_state['fatigue'][line]['damage']*
+ result_state['fatigue'][line]['dff'],2)) + ' (SN-curve = '+
+ result_state['fatigue'][line]['curve']+')')
else:
textobject.textLine('No fatigue results')
- # textobject.textLine('Utilization percentage (highest calculated): '+
- # str(int(max(self.data.get_color_and_calc_state()['utilization'][line].values())*100))+ '%')
-
textobject.setFillColor('black')
self.c.drawText(textobject)
vpos -= 10
else:
- cyl_obj = self.data._line_to_struc[line][5]
+ cyl_obj = line_structure.cylinder(line_bundle)
textobject = self.c.beginText()
textobject.setTextOrigin(30, vpos)
textobject.setFont("Helvetica-Oblique", 10)
@@ -421,7 +458,7 @@ def createDocument(self):
self.c.showPage()
idx, new = 0, False
- for load_name in self.data._load_dict.keys():
+ for load_name in self.data.loads.keys():
self.draw_lines(draw_type=None, load_idx_name = [idx % 3, load_name])
if idx % 3 == 2:
self.c.showPage()
@@ -434,14 +471,16 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
:return:
'''
- points = self.data._point_dict
- lines = self.data._line_dict
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
- colors = self.data.get_color_and_calc_state()['colors']
- elif self.data._new_buckling_method.get() == 'DNV PULS':
- colors = self.data.get_color_and_calc_state()['PULS colors']
+ points = self.data.points
+ lines = self.data.lines
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
+ colors = self.data.result_state['colors']
+ elif self.data.buckling_method == 'SemiAnalytical S3/U3':
+ colors = self.data.result_state.get('SemiAnalytical colors', {})
+ elif self.data.buckling_method == 'ML-Numeric (PULS based)':
+ colors = self.data.result_state.get('ML buckling numeric colors', {})
else:
- colors = self.data.get_color_and_calc_state()['ML buckling colors']
+ colors = self.data.result_state['ML buckling colors']
highest_y = max([coord[1] for coord in points.values()])
highest_x = max([coord[0] for coord in points.values()])
@@ -461,33 +500,31 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
self.c.setLineWidth(2)
self.c.setStrokeColor('red')
idx, drawed_data = 0, list()
- all_line_data = self.data.get_color_and_calc_state()
+ all_line_data = self.data.result_state
for line, pt in lines.items():
- if line not in list(self.data._line_to_struc.keys()):
+ if line not in list(self.data.line_bundles.keys()):
continue
+ line_bundle = self.data.line_bundles[line]
+ line_plate = line_structure.plate(line_bundle)
+ line_stiffener = line_structure.stiffener(line_bundle)
if draw_type == 'UF':
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
try:
self.c.setStrokeColor('red' if 'red' in colors[line].values() else 'green')
except KeyError:
self.c.setStrokeColor('black')
- elif self.data._new_buckling_method.get() == 'DNV PULS':
+ else:
+
+ method = line_plate.get_puls_method()
+ method = 'ultimate' if str(method).lower() in ['2', 'ultimate'] else 'buckling'
try:
- method = self.data._line_to_struc[line][0].Plate.get_puls_method()
- if self.data._PULS_results is not None:
- util = self.data._PULS_results.get_utilization(line, method, self.data._new_puls_uf.get())
- if util is not None:
- self.c.setStrokeColor('red' if util > 1 else 'green')
+ self.c.setStrokeColor(colors[line][method])
except KeyError:
self.c.setStrokeColor('black')
- else:
-
- method = self.data._line_to_struc[line][0].Plate.get_puls_method()
- self.c.setStrokeColor(colors[line][method])
- elif draw_type == 'section' and self.data._line_to_struc[line][0].Stiffener is not None:
+ elif draw_type == 'section' and line_stiffener is not None:
self.c.setStrokeColor(all_line_data['color code']['lines'][line]['section'])
- if self.data._line_to_struc[line][0].Stiffener.get_beam_string() not in drawed_data:
+ if line_stiffener.get_beam_string() not in drawed_data:
textobject = self.c.beginText()
if 400 - 20 * idx > 20:
textobject.setTextOrigin(50, 400 - 20 * idx)
@@ -495,21 +532,30 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
textobject.setTextOrigin(300, 400 - 20 * idx)
textobject.setFillColor(all_line_data['color code']['lines'][line]['section'])
textobject.setFont("Helvetica-Oblique", 10)
- textobject.textLine(self.data._line_to_struc[line][0].Stiffener.get_beam_string())
+ textobject.textLine(line_stiffener.get_beam_string())
self.c.drawText(textobject)
- drawed_data.append(self.data._line_to_struc[line][0].Stiffener.get_beam_string())
+ drawed_data.append(line_stiffener.get_beam_string())
idx += 1
elif draw_type == 'plate':
self.c.setStrokeColor(all_line_data['color code']['lines'][line]['plate'])
elif draw_type == 'pressure':
self.c.setStrokeColor(all_line_data['color code']['lines'][line]['pressure color'])
elif draw_type == 'utilization':
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
self.c.setStrokeColor(all_line_data['color code']['lines'][line]['rp uf color'])
- elif self.data._new_buckling_method.get() == 'DNV PULS':
- self.c.setStrokeColor(all_line_data['color code']['lines'][line]['PULS uf color'])
+ elif self.data.buckling_method == 'SemiAnalytical S3/U3':
+ puls_method = line_plate.get_puls_method()
+ puls_method = 'ultimate' if str(puls_method).lower() in ['2', 'ultimate'] else 'buckling'
+ self.c.setStrokeColor(all_line_data.get('SemiAnalytical colors', {}).get(line, {}).get(puls_method, 'black'))
+ elif self.data.buckling_method == 'ML-Numeric (PULS based)':
+ puls_method = line_plate.get_puls_method()
+ puls_method = 'ultimate' if str(puls_method).lower() in ['2', 'ultimate'] else 'buckling'
+ self.c.setStrokeColor(
+ all_line_data.get('ML buckling numeric colors', {}).get(line, {}).get(puls_method, 'black')
+ )
else:
- puls_method = self.data._line_to_struc[line][0].Plate.get_puls_method()
+ puls_method = line_plate.get_puls_method()
+ puls_method = 'ultimate' if str(puls_method).lower() in ['2', 'ultimate'] else 'buckling'
self.c.setStrokeColor(matplotlib_colors.rgb2hex(all_line_data['ML buckling colors'][line][puls_method]))
elif draw_type == 'sigma x':
@@ -523,7 +569,7 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
elif draw_type == 'structure type':
self.c.setStrokeColor(all_line_data['color code']['lines'][line]['structure type'])
elif load_idx_name is not None:
- points = self.data._point_dict
+ points = self.data.points
highest_y = max([coord[1] for coord in points.values()])
highest_x = max([coord[0] for coord in points.values()])
@@ -531,7 +577,7 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
scale = 10
else:
scale = min(180 / highest_y, 300 / highest_x, 10)
- if line in self.data._load_dict[load_idx_name[1]][1]:
+ if line in self.data.loads[load_idx_name[1]][1]:
self.c.setStrokeColor('orange')
else:
self.c.setStrokeColor('black')
@@ -564,7 +610,7 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
textobject.setFont("Helvetica-Oblique", 15)
textobject.textLine('Model plate thicknesses')
self.c.drawText(textobject)
- all_thicknesses =self.data.get_color_and_calc_state()['color code']['all thicknesses']
+ all_thicknesses = self.data.result_state['color code']['all thicknesses']
for idx, thk in enumerate(all_thicknesses):
textobject = self.c.beginText()
if 400 - 20 * idx > 20:
@@ -586,7 +632,7 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
self.c.drawText(textobject)
idx = 0
- pressure_map =self.data.get_color_and_calc_state()['color code']['pressure map']
+ pressure_map = self.data.result_state['color code']['pressure map']
for press in pressure_map:
textobject = self.c.beginText()
if 400 - 20 * idx > 20:
@@ -606,20 +652,24 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
textobject.setTextOrigin(50, 800)
textobject.setFillColor('black')
textobject.setFont("Helvetica-Oblique", 12)
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
this_text = 'DNV-RP-C201 Buckling Strength of Plated Structures'
- elif self.data._new_buckling_method.get() == 'DNV PULS':
- this_text = 'Utilization factors (max of all checks) - PULS (Panel Ultimate Limit State)'
+ elif self.data.buckling_method == 'SemiAnalytical S3/U3':
+ this_text = 'SemiAnalytical S3/U3 utilization factors'
+ elif self.data.buckling_method == 'ML-Numeric (PULS based)':
+ this_text = 'ML-Numeric utilization factors'
else:
- this_text = 'ML-CL utilization factors not avaliable. ML-CLassifier only shows ok or not ok.'
+ this_text = 'ML-CL is deactivated. Use ML-Numeric or SemiAnalytical.'
textobject.textLine(this_text)
self.c.drawText(textobject)
- if self.data._new_buckling_method.get() == 'DNV-RP-C201 - prescriptive':
+ if self.data.buckling_method == 'DNV-RP-C201 - prescriptive':
+ all_utils = all_line_data['color code']['utilization map']
+ elif self.data.buckling_method == 'SemiAnalytical S3/U3':
+ all_utils = all_line_data['color code']['utilization map']
+ elif self.data.buckling_method == 'ML-Numeric (PULS based)':
all_utils = all_line_data['color code']['utilization map']
- elif self.data._new_buckling_method.get() == 'DNV PULS':
- all_utils = all_line_data['color code']['PULS utilization map']
else:
all_utils = list()
@@ -739,7 +789,7 @@ def draw_lines(self, draw_type = 'UF', load_idx_name = None):
self.c.drawText(textobject)
drawed_data.append(value)
elif load_idx_name is not None:
- for lidx, loadtext in enumerate(reversed(self.data._load_dict[load_idx_name[1]][0].get_report_string())):
+ for lidx, loadtext in enumerate(reversed(self.data.loads[load_idx_name[1]][0].get_report_string())):
textobject = self.c.beginText()
textobject.setTextOrigin(370 , origo[1]+50+ 11*lidx)
textobject.setFont("Helvetica-Oblique", 11)
@@ -778,120 +828,136 @@ def createTable(self):
'''
table_all = []
- cylindersy, flat_plates, idx = False, False,-1
- for line in sorted(self.data._line_dict.keys()):
+ result_state = self.data.result_state
+
+ cylinders = False
+ flat_plates = False
+ idx = -1
+
+ for line in sorted(self.data.lines.keys()):
+ if line not in self.data.line_bundles:
+ continue
+
idx += 1
- if self.data._line_to_struc[line][5] is None and cylinders is False:
+ line_bundle = self.data.line_bundles[line]
+
+ if not line_structure.has_cylinder(line_bundle) and cylinders is False:
flat_plates = True
- if idx == 0:
- headers = ['Line', 'pl thk', 's', 'web h', 'web thk', 'fl. w', 'fl. thk', 'sig x1', 'sig x2', 'sig y1',
- 'sig y2', 'tau xy', 'max press.', 'sec. mod', 'min sec.', 'min plt',
- 'shr area', 'min shr A', 'fat uf', 'buc uf']
+
+ if len(table_all) == 0:
+ headers = [
+ 'Line', 'pl thk', 's', 'web h', 'web thk', 'fl. w', 'fl. thk',
+ 'sig x1', 'sig x2', 'sig y1', 'sig y2', 'tau xy',
+ 'max press.', 'sec. mod', 'min sec.', 'min plt',
+ 'shr area', 'min shr A', 'fat uf', 'buc uf'
+ ]
table_all.append(headers)
- if line not in list(self.data._line_to_struc.keys()):
- continue
- struc_obj = self.data._line_to_struc[line][0]
- pressure = round(self.data.get_highest_pressure(line)['normal'] / 1000,0)
-
- if self.data._PULS_results is not None:
- puls_method = self.data._line_to_struc[line][0].Plate.get_puls_method()
- if line in self.data._PULS_results.get_run_results().keys():
- if puls_method == 'buckling':
- buckling_uf = \
- self.data._PULS_results.get_run_results()[line]['Buckling strength']['Actual usage Factor'][0]
- else:
- buckling_uf = \
- self.data._PULS_results.get_run_results()[line]['Ultimate capacity']['Actual usage Factor'][0]
- else:
- try:
- buckling_uf = str(round(max(self.data.get_color_and_calc_state()['buckling'][line]), 2))
- except TypeError:
- buckling_uf = None
- if self.data.get_color_and_calc_state()['fatigue'][line]['damage'] is not None:
- fat_uf = self.data.get_color_and_calc_state()['fatigue'][line]['damage']
- fat_uf = round(fat_uf, 3)
+ struc_obj = line_structure.structure(line_bundle)
+ pressure = round(self.data.highest_pressures[line]['normal'] / 1000, 0)
+
+ try:
+ buckling_uf = str(round(max(result_state['buckling'][line]), 2))
+ except (TypeError, KeyError, ValueError):
+ buckling_uf = None
+
+ if result_state['fatigue'][line]['damage'] is not None:
+ fat_uf = round(result_state['fatigue'][line]['damage'], 3)
else:
- fat_uf = self.data.get_color_and_calc_state()['fatigue'][line]['damage']
-
-
- data = [line,str(struc_obj.Plate.get_pl_thk() * 1000), str(struc_obj.Plate.get_s() * 1000),
- str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_web_h() * 1000),
- str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_web_thk() * 1000),
- str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_fl_w() * 1000),
- str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_fl_thk() * 1000),
- str(round(struc_obj.Plate.sigma_x1, 0)), str(round(struc_obj.Plate.sigma_x2, 0)),
- str(round(struc_obj.Plate.sigma_y1, 0)),
- str(round(struc_obj.Plate.sigma_y2, 0)),
- str(round(struc_obj.Plate.tau_xy, 0)), str(round(pressure, 2) * 1000),
- str(int(min(self.data.get_color_and_calc_state()['section_modulus'][line]['sec_mod']) * 1000 ** 3)),
- str(int(self.data.get_color_and_calc_state()['section_modulus'][line]['min_sec_mod'] * 1000 ** 3)),
- str(round(self.data.get_color_and_calc_state()['thickness'][line]['min_thk'], 2)),
- str(int(self.data.get_color_and_calc_state()['shear_area'][line]['shear_area'] * 1000 ** 2)),
- str(int(self.data.get_color_and_calc_state()['shear_area'][line]['min_shear_area'] * 1000 ** 2)),
- fat_uf, buckling_uf]
+ fat_uf = result_state['fatigue'][line]['damage']
+
+ data = [
+ line,
+ str(struc_obj.Plate.get_pl_thk() * 1000),
+ str(struc_obj.Plate.get_s() * 1000),
+ str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_web_h() * 1000),
+ str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_web_thk() * 1000),
+ str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_fl_w() * 1000),
+ str('' if struc_obj.Stiffener is None else struc_obj.Stiffener.get_fl_thk() * 1000),
+ str(round(struc_obj.Plate.sigma_x1, 0)),
+ str(round(struc_obj.Plate.sigma_x2, 0)),
+ str(round(struc_obj.Plate.sigma_y1, 0)),
+ str(round(struc_obj.Plate.sigma_y2, 0)),
+ str(round(struc_obj.Plate.tau_xy, 0)),
+ str(round(pressure, 2) * 1000),
+ str(int(min(result_state['section_modulus'][line]['sec_mod']) * 1000 ** 3)),
+ str(int(result_state['section_modulus'][line]['min_sec_mod'] * 1000 ** 3)),
+ str(round(result_state['thickness'][line]['min_thk'], 2)),
+ str(int(result_state['shear_area'][line]['shear_area'] * 1000 ** 2)),
+ str(int(result_state['shear_area'][line]['min_shear_area'] * 1000 ** 2)),
+ fat_uf,
+ buckling_uf
+ ]
table_all.append(data)
elif not flat_plates:
cylinders = True
- if idx == 0:
- headers = ['Line', 'Radius', 'Thickness', 'Span', 'Tot. length',
- 'Axial stress', 'Bend stress', 'Tors. stress', 'Shear stress', 'Lat. press.',
- 'Hoop stress',
- 'UF shell', 'UF long. stf.', 'UF ring. stf.', 'UF girder']
+
+ if len(table_all) == 0:
+ headers = [
+ 'Line', 'Radius', 'Thickness', 'Span', 'Tot. length',
+ 'Axial stress', 'Bend stress', 'Tors. stress', 'Shear stress',
+ 'Lat. press.', 'Hoop stress',
+ 'UF shell', 'UF long. stf.', 'UF ring. stf.', 'UF girder'
+ ]
table_all.append(headers)
- cyl_obj = self.data._line_to_struc[line][5]
+
+ cyl_obj = line_structure.cylinder(line_bundle)
+
radius = round(cyl_obj.ShellObj.radius * 1000, 2)
thickness = round(cyl_obj.ShellObj.thk * 1000, 2)
- long_str = cyl_obj.LongStfObj.get_beam_string()
- ring_stf = cyl_obj.LongStfObj.get_beam_string()
- heavy_ring = cyl_obj.LongStfObj.get_beam_string()
span = round(cyl_obj.ShellObj.dist_between_rings, 1)
tot_length = round(cyl_obj.ShellObj.length_of_shell, 1)
- tot_cyl = round(cyl_obj.ShellObj.tot_cyl_length, 1)
+
sigma_axial = cyl_obj.sasd / 1e6
sigma_bend = cyl_obj.smsd / 1e6
sigma_tors = cyl_obj.tTsd / 1e6
tau_xy = cyl_obj.tQsd / 1e6
lat_press = cyl_obj.psd / 1e6
sigma_hoop = cyl_obj.shsd / 1e6
+
results = cyl_obj.get_utilization_factors()
- data = [line, radius, thickness, span, tot_length,
- sigma_axial,
- sigma_bend,
- sigma_tors,
- tau_xy,
- lat_press,
- sigma_hoop,
- round(0 if results['Unstiffened shell'] is None else results['Unstiffened shell'],2),
- round(0 if results['Longitudinal stiffened shell'] is None else results['Longitudinal stiffened shell'],2),
- round(0 if results['Ring stiffened shell'] is None else results['Ring stiffened shell'],2),
- round(0 if results['Heavy ring frame'] is None else results['Heavy ring frame'],2)
- ]
+
+ data = [
+ line,
+ radius,
+ thickness,
+ span,
+ tot_length,
+ sigma_axial,
+ sigma_bend,
+ sigma_tors,
+ tau_xy,
+ lat_press,
+ sigma_hoop,
+ round(0 if results['Unstiffened shell'] is None else results['Unstiffened shell'], 2),
+ round(0 if results['Longitudinal stiffened shell'] is None else results[
+ 'Longitudinal stiffened shell'], 2),
+ round(0 if results['Ring stiffened shell'] is None else results['Ring stiffened shell'], 2),
+ round(0 if results['Heavy ring frame'] is None else results['Heavy ring frame'], 2),
+ ]
+
table_all.append([str(data_item) for data_item in data])
+
+ if not table_all:
+ table_all = [['No reportable structural results found']]
+
if cylinders:
- t = Table(table_all,colWidths=[0.7*inch])
- t.setStyle(TableStyle([
- ('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
- ('BACKGROUND', (0, 0), (-1, -1), colors.lightblue),
- ('FONTSIZE', (0, 0), (-1, -1), 8),
- ('FONTSIZE', (0, 4), (-1, 4), 8),
- ('TEXTFONT', (0, 1), (-1, 1), 'Times-Bold'),
- ('TEXTFONT', (0, 4), (-1, 4), 'Times-Bold'),
- ]))
+ t = Table(table_all, colWidths=[0.7 * inch])
else:
- t = Table(table_all,colWidths=[0.55*inch])
- t.setStyle(TableStyle([
- ('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
- ('BACKGROUND', (0, 0), (-1, -1), colors.lightblue),
- ('FONTSIZE', (0, 0), (-1, -1), 8),
- ('FONTSIZE', (0, 4), (-1, 4), 8),
- ('TEXTFONT', (0, 1), (-1, 1), 'Times-Bold'),
- ('TEXTFONT', (0, 4), (-1, 4), 'Times-Bold'),
- ]))
-
- return [t,]
+ t = Table(table_all, colWidths=[0.55 * inch])
+
+ t.setStyle(TableStyle([
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
+ ('BACKGROUND', (0, 0), (-1, -1), colors.lightblue),
+ ('FONTSIZE', (0, 0), (-1, -1), 8),
+ ('FONTSIZE', (0, 4), (-1, 4), 8),
+ ('TEXTFONT', (0, 1), (-1, 1), 'Times-Bold'),
+ ('TEXTFONT', (0, 4), (-1, 4), 'Times-Bold'),
+ ]))
+
+ return [t]
if __name__ == '__main__':
diff --git a/current_comps.png b/current_comps.png
deleted file mode 100644
index 115631b0..00000000
Binary files a/current_comps.png and /dev/null differ
diff --git a/docs/conf.py b/docs/conf.py
index f824dce2..7195c770 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,7 +18,7 @@
# -- Project information -----------------------------------------------------
project = 'ANYstructure'
-copyright = '2023, Audun Arnesen Nyhus'
+copyright = '2026, Audun Arnesen Nyhus'
author = 'Audun Arnesen Nyhus'
diff --git a/docs/index.rst b/docs/index.rst
index fa626194..35d4626f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,7 +8,7 @@ This page mainly document the API.
For GUI documentation, see the following link:
-`ANYstructure GUI documentation `_
+`ANYstructure GUI documentation `_
Python
------
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..d768116c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,8 @@
+[build-system]
+requires = ["setuptools>=68", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py"]
+addopts = ["-ra", "--basetemp=.pytest_tmp"]
diff --git a/requirements-core.txt b/requirements-core.txt
new file mode 100644
index 00000000..d21312e6
--- /dev/null
+++ b/requirements-core.txt
@@ -0,0 +1,5 @@
+matplotlib
+numpy
+Pillow
+reportlab
+scipy
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 00000000..5fa5d05d
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,4 @@
+-r requirements.txt
+
+build
+pytest
diff --git a/requirements-excel.txt b/requirements-excel.txt
new file mode 100644
index 00000000..1b28e2e0
--- /dev/null
+++ b/requirements-excel.txt
@@ -0,0 +1 @@
+xlwings
diff --git a/requirements-ml.txt b/requirements-ml.txt
new file mode 100644
index 00000000..d5e06028
--- /dev/null
+++ b/requirements-ml.txt
@@ -0,0 +1 @@
+scikit-learn
diff --git a/requirements.txt b/requirements.txt
index 64041db6..ddb8a5f1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
matplotlib
numpy
+Pillow
reportlab
+scikit-learn
scipy
xlwings
-sklearn
diff --git a/setup.py b/setup.py
index 9ff1a24a..c5388937 100644
--- a/setup.py
+++ b/setup.py
@@ -23,11 +23,16 @@ def readme():
with open('README.rst') as file:
return file.read()
+core_requires = ['matplotlib', 'numpy', 'Pillow', 'reportlab', 'scipy']
+excel_requires = ['xlwings']
+ml_requires = ['scikit-learn']
+dev_requires = ['build', 'pytest']
+
setup(
name='ANYstructure', # Required
url = 'https://github.com/audunarn/ANYstructure',
entry_points={"console_scripts": ['ANYstructure = anystruct.__main__:main']},
- version='5.3.3', # Required
+ version='6.0.0', # Required
license='MIT',
description='A plate field optimization tool for offshore structures calculated according to DNV standards',
long_description = readme(),
@@ -41,7 +46,14 @@ def readme():
'Topic :: Scientific/Engineering'],
keywords='dnv-gl-os-c101 dnv-rp-c202 dnv-rp-c201 naval_architecture structural_engineering steel buckling fatigue local_scantlings optimization weight',
include_package_data=True,
- install_requires=['scipy', 'numpy', 'matplotlib', 'reportlab', 'xlwings', 'scikit-learn'],
+ install_requires=core_requires + excel_requires + ml_requires,
+ extras_require={
+ 'core': core_requires,
+ 'excel': excel_requires,
+ 'ml': ml_requires,
+ 'dev': dev_requires,
+ 'all': core_requires + excel_requires + ml_requires,
+ },
packages=['anystruct'],
py_modules = [],
-)
\ No newline at end of file
+)
diff --git a/tests/gui_automatic_run.py b/tests/gui_automatic_run.py
index 3a0a2244..f6fac5b3 100644
--- a/tests/gui_automatic_run.py
+++ b/tests/gui_automatic_run.py
@@ -1,12 +1,94 @@
-from anystruct import main_application
-import multiprocessing, ctypes, os, pickle
+from anystruct import calc_structure, example_data, main_application, ml_models
+import multiprocessing, ctypes, os
+from pathlib import Path
import tkinter as tk
+from tkinter import messagebox
+from matplotlib import pyplot as plt
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_EXTERNAL_ML_FILES = Path(r"C:\python_projects\ANYstructure\anystruct\ml_files")
+
+
+def get_ml_file_directories():
+ directories = []
+ env_dir = os.environ.get("ANYSTRUCTURE_ML_FILES")
+ if env_dir:
+ directories.append(Path(env_dir))
+ directories.extend([REPO_ROOT / "anystruct" / "ml_files", DEFAULT_EXTERNAL_ML_FILES])
+ return directories
+
+
+def configure_noninteractive_dialogs():
+ tk.messagebox = messagebox
+ messagebox.askquestion = lambda *args, **kwargs: "no"
+ messagebox.showerror = lambda *args, **kwargs: "ok"
+ messagebox.showinfo = lambda *args, **kwargs: "ok"
+ messagebox.showwarning = lambda *args, **kwargs: "ok"
+
+
+def configure_noninteractive_plots():
+ plt.show = lambda *args, **kwargs: None
+
+
+def checkpoint(name):
+ print(f"gui_automatic_run: {name}", flush=True)
+
+
+def close_child_windows():
+ root.update_idletasks()
+ for child in list(root.winfo_children()):
+ if isinstance(child, tk.Toplevel):
+ child.destroy()
+ root.update_idletasks()
+ plt.close("all")
+
+
+def assert_window_opens(action, name):
+ before = len([child for child in root.winfo_children() if isinstance(child, tk.Toplevel)])
+ action()
+ root.update_idletasks()
+ after = len([child for child in root.winfo_children() if isinstance(child, tk.Toplevel)])
+ if after <= before:
+ raise RuntimeError(f"{name} did not open a Toplevel window")
+ checkpoint(f"{name} opened")
+ close_child_windows()
+
+
+def make_smoke_cylinder():
+ return calc_structure.CylinderAndCurvedPlate(
+ main_dict=example_data.shell_main_dict,
+ shell=calc_structure.Shell(example_data.shell_dict),
+ long_stf=calc_structure.Structure(example_data.obj_dict_cyl_long2),
+ ring_stf=None,
+ ring_frame=None)
+
+
+def exercise_optimizer_windows():
+ my_dict["_active_line"] = "line1"
+ my_dict["_line_is_active"] = True
+ assert_window_opens(my_app.on_optimize, "single optimizer")
+
+ my_dict["_active_line"] = "line2"
+ my_dict["_line_is_active"] = True
+ my_dict["_line_to_struc"]["line2"][5] = make_smoke_cylinder()
+ assert_window_opens(my_app.on_optimize_cylinder, "cylinder optimizer")
+ my_dict["_line_to_struc"]["line2"][5] = None
+
+ my_dict["_multiselect_lines"] = ["line1", "line2"]
+ assert_window_opens(my_app.on_optimize_multiple, "multiple optimizer")
+ assert_window_opens(my_app.on_geometry_optimize, "span optimizer")
multiprocessing.freeze_support()
-errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
+configure_noninteractive_dialogs()
+configure_noninteractive_plots()
+if os.name == "nt":
+ errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
root = tk.Tk()
+checkpoint("root created")
my_app = main_application.Application(root)
+checkpoint("application created")
my_dict = my_app.__dict__
my_dict["_new_field_len"].set(4000)
@@ -34,56 +116,7 @@ def run_cc_chks():
my_dict[chks].set(False)
-my_dict['_ML_buckling'] = {1.1: dict(), 1.15: dict()}
-
-for mat_fac in [1.1, 1.15]:
- for name, file_base in zip(['cl SP buc int predictor', 'cl SP buc int scaler',
- 'cl SP ult int predictor', 'cl SP ult int scaler',
- 'cl SP buc GLGT predictor', 'cl SP buc GLGT scaler',
- 'cl SP ult GLGT predictor', 'cl SP ult GLGT scaler',
- 'cl UP buc int predictor', 'cl UP buc int scaler',
- 'cl UP ult int predictor', 'cl UP ult int scaler',
- 'cl UP buc GLGT predictor', 'cl UP buc GLGT scaler',
- 'cl UP ult GLGT predictor', 'cl UP ult GLGT scaler',
- 'CSR predictor UP', 'CSR scaler UP',
- 'CSR predictor SP', 'CSR scaler SP'
- ],
- ["ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_SP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_1_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_buc_XXX_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_predictor_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_output_cl_str_ult_XXX_scaler_In-plane_support_cl_2,_3_UP",
- "ml_files\\CL_CSR-Tank_req_cl_predictor",
- "ml_files\\CL_CSR-Tank_req_cl_scaler",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_predictor",
- "ml_files\\CL_CSR_plate_cl,_CSR_web_cl,_CSR_web_flange_cl,_CSR_flange_cl_scaler"]):
-
- mat_fac_str = [str(round(mat_fac, 2)).replace('.', '') + '0'][0][0:3]
- file_base = file_base.replace('XXX', mat_fac_str)
- my_dict['_ML_buckling'][mat_fac][name] = None
-
- if os.path.isfile(file_base + '.pickle'):
- file = open(file_base + '.pickle', 'rb')
- my_dict['_ML_buckling'][mat_fac][name] = pickle.load(file)
- file.close()
- else:
- # file = open(self._root_dir +'\\' + file_base + '.pickle', 'rb')
-
- ml_file = os.path.join('C:\\Github\\ANYstructure\\anystruct\\'+file_base + '.pickle')
- file = open(ml_file, 'rb')
- my_dict['_ML_buckling'][mat_fac][name] = pickle.load(file)
- file.close()
+my_dict['_ML_buckling'] = ml_models.load_buckling_models(get_ml_file_directories())
my_dict['_ML_classes'] = {0: 'N/A',
@@ -102,35 +135,54 @@ def run_cc_chks():
my_dict['_new_point_y'].set(y)
my_app.new_point()
+checkpoint("points created")
print(my_dict['_point_dict'])
run_cc_chks()
+checkpoint("point color checks run")
for p1, p2 in [[1,2],[2,3],[3,4],[5,6],[6,7],[7,8], [1,5], [2,6], [3,7], [4, 8], [9,10], [5,9], [8,10]]:
my_dict['_new_line_p1'].set(p1)
my_dict['_new_line_p2'].set(p2)
my_app.new_line()
+checkpoint("lines created")
print(my_dict['_line_dict'])
run_cc_chks()
+checkpoint("line color checks run")
for key in my_dict['_line_dict'].keys():
my_dict['_active_line'] = key
my_dict['_line_is_active'] = True
my_app.new_structure()
+checkpoint("structures created")
run_cc_chks()
+checkpoint("structure color checks run")
my_dict['_active_line'] = 'line3'
my_dict['_line_is_active'] = True
my_app.delete_line(line='line3')
my_dict['_new_line_p1'].set(3)
my_dict['_new_line_p2'].set(4)
+checkpoint("line deleted")
print(my_dict['_line_dict'])
my_app.gui_load_combinations(None)
+checkpoint("load combinations opened")
my_app.grid_find_tanks()
+checkpoint("tank search complete")
my_app.grid_display_tanks()
+plt.close("all")
+checkpoint("tanks displayed")
run_cc_chks()
print(my_dict['_tank_dict'])
my_app.on_show_loads()
+checkpoint("loads shown")
+exercise_optimizer_windows()
+checkpoint("optimizer windows exercised")
+my_app.open_example()
+checkpoint("example project opened")
+root.update_idletasks()
+root.destroy()
+checkpoint("root destroyed")
diff --git a/tests/test_api.py b/tests/test_api.py
new file mode 100644
index 00000000..3100036b
--- /dev/null
+++ b/tests/test_api.py
@@ -0,0 +1,114 @@
+import pytest
+
+from anystruct.api import (
+ CylStru,
+ FlatStru,
+ ProjectFileCodec,
+ ProjectHydrationDefaults,
+ ProjectState,
+ load_project_state,
+ open_project,
+ save_project_state,
+)
+from anystruct.project_state import PROJECT_FORMAT_VERSION
+
+
+def test_flat_structure_api_returns_special_provisions():
+ flat = FlatStru("Flat plate, stiffened")
+ flat.set_material(mat_yield=355, emodule=210000, material_factor=1.15, poisson=0.3)
+ flat.set_plate_geometry(spacing=700, thickness=18, span=4000)
+ flat.set_stresses(pressure=0.2, sigma_x1=50, sigma_x2=50, sigma_y1=100, sigma_y2=100, tau_xy=5)
+ flat.set_stiffener(hw=360, tw=12, bf=150, tf=20, stf_type="T", spacing=700)
+ flat.set_fixation_parameters(kpp=1, kps=1, km1=12, km2=24, km3=12)
+ flat.set_buckling_parameters(
+ calculation_method="DNV-RP-C201 - prescriptive",
+ buckling_acceptance="ultimate",
+ stiffened_plate_effective_aginst_sigy=True,
+ )
+
+ results = flat.get_special_provisions_results()
+
+ assert set(results) == {"Plate thickness", "Stiffener section modulus", "Stiffener shear area"}
+ assert results == {
+ "Plate thickness": {"minimum": pytest.approx(9.214115076089064), "actual": 18.0},
+ "Stiffener section modulus": {
+ "minimum": pytest.approx(842126.1216230252),
+ "actual": pytest.approx(1514995.9581737241),
+ },
+ "Stiffener shear area": {"minimum": pytest.approx(1593.0323568831573), "actual": 4776.0},
+ }
+
+ buckling = flat.get_buckling_results()
+
+ assert buckling["Plate"]["Plate buckling"] == pytest.approx(0.2819426684980083)
+ assert buckling["Stiffener"]["Overpressure plate side"] == pytest.approx(0.8627465560212727)
+
+
+def test_flat_structure_api_rejects_unknown_domain():
+ with pytest.raises(AssertionError):
+ FlatStru("not a domain")
+
+
+def test_cylinder_api_sets_basic_unstiffened_shell_properties():
+ cyl = CylStru(calculation_domain="Unstiffened shell")
+ cyl.set_material(mat_yield=355, emodule=210000, material_factor=1.15, poisson=0.3)
+ cyl.set_imperfection(delta_0=0.005)
+ cyl.set_fabrication_method(stiffener="Fabricated", girder="Fabricated")
+ cyl.set_end_cap_pressure_included_in_stress(is_included=True)
+ cyl.set_uls_or_als(kind="ULS")
+ cyl.set_shell_geometry(radius=6500, thickness=20, distance_between_rings=3000, tot_length_of_shell=12000)
+ cyl.set_shell_buckling_parmeters(eff_buckling_length_factor=1.0)
+ cyl.set_stresses(sasd=-50, smsd=0, tTsd=0, tQsd=10, psd=0.1, shsd=0)
+
+ props = cyl._CylinderMain.get_main_properties()
+
+ assert props["geometry"] == [1, ""]
+ assert props["mat_yield"] == [355e6, "Pa"]
+ assert props["ULS or ALS"] == ["ULS", ""]
+ assert props["psd"] == [100000.0, "Pa"]
+
+
+def test_cylinder_api_returns_unstiffened_shell_buckling_golden_result():
+ cyl = CylStru(calculation_domain="Unstiffened shell")
+ cyl.set_material(mat_yield=355, emodule=210000, material_factor=1.15, poisson=0.3)
+ cyl.set_imperfection(delta_0=0.005)
+ cyl.set_fabrication_method(stiffener="Fabricated", girder="Fabricated")
+ cyl.set_end_cap_pressure_included_in_stress(is_included=True)
+ cyl.set_uls_or_als(kind="ULS")
+ cyl.set_shell_geometry(radius=6500, thickness=20, distance_between_rings=3000, tot_length_of_shell=12000)
+ cyl.set_panel_spacing(700)
+ cyl.set_shell_buckling_parmeters(eff_buckling_length_factor=1.0)
+ cyl.set_stresses(sasd=-50, smsd=0, tTsd=0, tQsd=10, psd=0.1, shsd=0)
+
+ results = cyl.get_buckling_results()
+
+ assert results["Unstiffened shell"] == pytest.approx(0.8455659884505807)
+ assert results["Longitudinal stiffened shell"] is None
+ assert results["Ring stiffened shell"] is None
+ assert results["Column stability UF"] is None
+
+
+def test_cylinder_api_rejects_unknown_domain():
+ with pytest.raises(AssertionError):
+ CylStru(calculation_domain="not a domain")
+
+
+def test_public_api_project_file_facade_round_trips_state(tmp_path):
+ project_path = tmp_path / "api_project.txt"
+ state = ProjectState(
+ project_information="public api",
+ theme="dark",
+ points={"point1": [0, 0]},
+ extras={"PULS results": {"obsolete": True}},
+ )
+
+ saved_path = save_project_state(state, project_path)
+ loaded = load_project_state(project_path)
+ opened = open_project(project_path, ProjectHydrationDefaults(structure_types={}))
+ encoded = ProjectFileCodec.encode_mapping(loaded)
+
+ assert saved_path == project_path
+ assert loaded.project_information == "public api"
+ assert opened.transfer.theme == "dark"
+ assert encoded["format version"] == PROJECT_FORMAT_VERSION
+ assert "PULS results" not in encoded
diff --git a/tests/test_api_helper_wiring.py b/tests/test_api_helper_wiring.py
new file mode 100644
index 00000000..415b1c1e
--- /dev/null
+++ b/tests/test_api_helper_wiring.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+
+
+def test_api_uses_shared_helper_constants():
+ api_source = Path(__file__).resolve().parents[1] / "anystruct" / "api.py"
+ source = api_source.read_text(encoding="utf-8")
+
+ assert "api_helpers.FLAT_STRUCTURE_DOMAINS" in source
+ assert "api_helpers.CYLINDER_STRUCTURE_DOMAINS" in source
+ assert "api_helpers.BUCKLING_CALCULATION_METHODS" in source
+ assert "api_helpers.FABRICATION_METHODS" in source
+ assert "api_helpers.LIMIT_STATE_TYPES" in source
+
+
+def test_api_uses_shared_stiffener_normalizer():
+ api_source = Path(__file__).resolve().parents[1] / "anystruct" / "api.py"
+ source = api_source.read_text(encoding="utf-8")
+
+ assert source.count("api_helpers.normalize_bulb_stiffener_type") == 4
+
+
+def test_api_uses_shared_cylinder_domain_helpers():
+ api_source = Path(__file__).resolve().parents[1] / "anystruct" / "api.py"
+ source = api_source.read_text(encoding="utf-8")
+
+ assert "api_helpers.cylinder_input_mode(calculation_domain)" in source
+ assert "api_helpers.cylinder_domain_with_input_mode(calculation_domain)" in source
+ assert "api_helpers.geometry_id_for_domain(self._calculation_domain)" in source
+ assert "geomeries_map_no_input_spec" not in source
+ assert "geomeries = {" not in source
+
+
+def test_api_uses_shared_unit_conversions():
+ api_source = Path(__file__).resolve().parents[1] / "anystruct" / "api.py"
+ source = api_source.read_text(encoding="utf-8")
+
+ assert source.count("api_helpers.mpa_to_pa") == 12
+ assert source.count("api_helpers.mm_to_m") == 8
+
+
+def test_api_exposes_project_file_facade_through_application_services():
+ api_source = Path(__file__).resolve().parents[1] / "anystruct" / "api.py"
+ source = api_source.read_text(encoding="utf-8")
+
+ assert "ProjectFileCodec" in source
+ assert "ProjectHydrationDefaults" in source
+ assert "ProjectPersistenceService.load_state_from_path(path)" in source
+ assert "ProjectPersistenceService.save_state_to_path(project_state, path)" in source
+ assert "ProjectOpenService.open_path(path, hydration_defaults)" in source
+ assert "ProjectSaveService.save_path(path, save_input)" in source
diff --git a/tests/test_api_helpers.py b/tests/test_api_helpers.py
new file mode 100644
index 00000000..fb9667a9
--- /dev/null
+++ b/tests/test_api_helpers.py
@@ -0,0 +1,96 @@
+import pytest
+
+from anystruct import api_helpers
+
+
+def test_assert_choice_accepts_known_value():
+ api_helpers.assert_choice("ULS", api_helpers.LIMIT_STATE_TYPES, "limit state")
+
+
+def test_assert_choice_rejects_unknown_value():
+ with pytest.raises(AssertionError, match="limit state must be one of"):
+ api_helpers.assert_choice("SLS", api_helpers.LIMIT_STATE_TYPES, "limit state")
+
+
+@pytest.mark.parametrize(("domain", "expected"), api_helpers.FLAT_GEOMETRY_IDS.items())
+def test_geometry_id_for_flat_domain(domain, expected):
+ assert api_helpers.geometry_id_for_domain(domain) == expected
+
+
+@pytest.mark.parametrize(("domain", "expected"), api_helpers.CYLINDER_GEOMETRY_IDS.items())
+def test_geometry_id_for_cylinder_domain_with_input_mode(domain, expected):
+ assert api_helpers.geometry_id_for_domain(domain) == expected
+
+
+@pytest.mark.parametrize(("domain", "geometry_id"), api_helpers.GEOMETRY_IDS.items())
+def test_domain_for_geometry_id_round_trips(domain, geometry_id):
+ assert api_helpers.domain_for_geometry_id(geometry_id) == domain
+
+
+@pytest.mark.parametrize(
+ ("domain", "expected"),
+ [
+ (domain, "Stress" if "panel" in domain else "Force")
+ for domain in api_helpers.CYLINDER_STRUCTURE_DOMAINS
+ ],
+)
+def test_cylinder_input_mode(domain, expected):
+ assert api_helpers.cylinder_input_mode(domain) == expected
+
+
+@pytest.mark.parametrize(
+ ("domain", "expected"),
+ [
+ (domain, expected)
+ for domain, expected in zip(
+ api_helpers.CYLINDER_STRUCTURE_DOMAINS,
+ api_helpers.CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT,
+ )
+ ],
+)
+def test_cylinder_domain_with_input_mode(domain, expected):
+ assert api_helpers.cylinder_domain_with_input_mode(domain) == expected
+
+
+def test_cylinder_domains_with_input_are_canonical_labels():
+ assert api_helpers.CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT == tuple(api_helpers.CYLINDER_GEOMETRY_IDS)
+ assert all(" " not in domain for domain in api_helpers.CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT)
+
+
+def test_geometry_id_rejects_unknown_domain():
+ with pytest.raises(KeyError):
+ api_helpers.geometry_id_for_domain("Unknown geometry")
+
+
+def test_domain_for_geometry_id_rejects_unknown_id():
+ with pytest.raises(KeyError):
+ api_helpers.domain_for_geometry_id(999)
+
+
+def test_cylinder_input_mode_rejects_unknown_domain():
+ with pytest.raises(AssertionError, match="calculation_domain must be one of"):
+ api_helpers.cylinder_input_mode("Unknown geometry")
+
+
+def test_unit_conversions():
+ assert api_helpers.mpa_to_pa(355) == 355e6
+ assert api_helpers.mm_to_m(6500) == 6.5
+
+
+@pytest.mark.parametrize(
+ ("raw", "expected"),
+ [
+ ("hp", "L-bulb"),
+ ("HP", "L-bulb"),
+ ("HP-bulb", "L-bulb"),
+ ("bulb", "L-bulb"),
+ ("T", "T"),
+ ],
+)
+def test_normalize_bulb_stiffener_type(raw, expected):
+ assert api_helpers.normalize_bulb_stiffener_type(raw) == expected
+
+
+def test_domain_constants_include_public_api_values():
+ assert "Flat plate, stiffened" in api_helpers.FLAT_STRUCTURE_DOMAINS
+ assert "Orthogonally Stiffened shell" in api_helpers.CYLINDER_STRUCTURE_DOMAINS
diff --git a/tests/test_calc_loads_loads.py b/tests/test_calc_loads_loads.py
index d002f1d9..23d1708d 100644
--- a/tests/test_calc_loads_loads.py
+++ b/tests/test_calc_loads_loads.py
@@ -1,3 +1,5 @@
+from copy import deepcopy
+
from anystruct import calc_loads as calcl, example_data as ex
import pytest
@@ -6,8 +8,11 @@
@pytest.fixture
def load_cls():
+ slamming_load = deepcopy(ex.load_slamming)
+ slamming_load.setdefault('slamming mult pl', 1)
+ slamming_load.setdefault('slamming mult stf', 1)
return calcl.Loads(ex.load_bottom), calcl.Loads(ex.load_side), \
- calcl.Loads(ex.load_static), calcl.Loads(ex.load_slamming)
+ calcl.Loads(ex.load_static), calcl.Loads(slamming_load)
def test_get_bottom_pressure(load_cls):
for load, type, result in zip(load_cls, ['BOTTOM', 'SIDE_SHELL', '', ''], [48070.0, 145800.0, 15375.0, 1000000.0]):
diff --git a/tests/test_calc_structure_scantlings.py b/tests/test_calc_structure_scantlings.py
index ca59ea31..b720f45f 100644
--- a/tests/test_calc_structure_scantlings.py
+++ b/tests/test_calc_structure_scantlings.py
@@ -1,58 +1,45 @@
+from copy import deepcopy
+
import pytest
from anystruct import example_data as ex, calc_structure as calc
# Testing the Structure class
+def _with_panel_default(obj_dict):
+ obj_dict = deepcopy(obj_dict)
+ obj_dict.setdefault('panel or shell', ['panel', ''])
+ return obj_dict
+
@pytest.fixture
def scantling_cls():
- return calc.CalcScantlings(ex.obj_dict), calc.CalcScantlings(ex.obj_dict2), calc.CalcScantlings(ex.obj_dict_L)
+ return (calc.CalcScantlings(_with_panel_default(ex.obj_dict)),
+ calc.CalcScantlings(_with_panel_default(ex.obj_dict2)),
+ calc.CalcScantlings(_with_panel_default(ex.obj_dict_L)))
def test_eff_moment_of_intertia(scantling_cls):
pressure = 200
- item1 = scantling_cls[0].get_moment_of_intertia(efficent_se=scantling_cls[0].get_plate_efficent_b(
- design_lat_press=pressure))
- item2 = scantling_cls[1].get_moment_of_intertia(efficent_se=scantling_cls[1].get_plate_efficent_b(
- design_lat_press=pressure))
- item3 = scantling_cls[2].get_moment_of_intertia(efficent_se=scantling_cls[2].get_plate_efficent_b(
- design_lat_press=pressure))
- assert item1 == 0.0001954533465023375
- assert item2 == 0.0003855085228421317
- assert item3 == 2.475775492423021e-05
-
-def test_buckling_stiffener_side(scantling_cls):
- pressure = 200
- item1 = scantling_cls[0].calculate_buckling_all(design_lat_press=pressure,checked_side='s')
- item2 = scantling_cls[1].calculate_buckling_all(design_lat_press=pressure,checked_side='s')
- item3 = scantling_cls[2].calculate_buckling_all(design_lat_press=pressure,checked_side='s')
- assert item1 == [0.9729020921645564, 0.40724966707091187, 0.8845350899810523, 0.8749607427920713, -0.11783163665252112, 0.009851334334728623]
- assert item2 == [0.8439747619895035, 0.6874422829139918, 0.6422370465897177, 0.6883978993466389, -0.018448105453830892, 0.018645053551144264]
- assert item3 == [0.31162369577319593, 0.41539277713882866, 0.26782980359706804, 0.41426194993038273, 0.08315101998685921, -0.0450507243894534]
-
-def test_buckling_plate_side(scantling_cls):
- pressure = 200
- item1 = scantling_cls[0].calculate_buckling_all(design_lat_press=pressure,checked_side='p')
- item2 = scantling_cls[1].calculate_buckling_all(design_lat_press=pressure,checked_side='p')
- item3 = scantling_cls[2].calculate_buckling_all(design_lat_press=pressure,checked_side='p')
- assert item1 == [0.9729020921645564, 0.8068668020964379, -0.041396296961396524, 0.2971803834814199, 0.8073127040689398, 0.08785133433472869]
- assert item2 == [0.8439747619895035, 0.7855508533258388, 0.071678887601048, 0.49995924694298943, 0.5513230073138509, 0.12464505355114436]
- assert item3 == [0.31162369577319593, 0.528918649472309, 0.12937310434846616, 0.30073607759690246, 0.2090149796996547, 0.05694927561054669]
+ results = [item.get_moment_of_intertia(efficent_se=item.get_plate_efficent_b(design_lat_press=pressure))
+ for item in scantling_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.00015746749125364508)
+
+def test_buckling_methods_live_on_all_structure():
+ assert not hasattr(calc.CalcScantlings(_with_panel_default(ex.obj_dict)), 'calculate_buckling_all')
+ assert hasattr(calc.AllStructure(), 'plate_buckling')
def test_minimum_plate_thickenss(scantling_cls):
pressure = 200
- item1 = scantling_cls[0].get_dnv_min_thickness(pressure)
- item2 = scantling_cls[1].get_dnv_min_thickness(pressure)
- item3 = scantling_cls[2].get_dnv_min_thickness(pressure)
- assert item1 == 9.729196060772338
- assert item2 == 9.214115076089064
- assert item3 == 10.791282866994683
+ results = [item.get_dnv_min_thickness(pressure) for item in scantling_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(8.964617963748333)
def test_minimum_section_module(scantling_cls):
pressure = 200
- item1 = scantling_cls[0].get_dnv_min_section_modulus(pressure)
- item2 = scantling_cls[1].get_dnv_min_section_modulus(pressure)
- item3 = scantling_cls[2].get_dnv_min_section_modulus(pressure)
- assert item1 == 0.0008763156387732192
- assert item2 == 0.0008421261216230251
- assert item3 == 0.000147233164952631
+ results = [item.get_dnv_min_section_modulus(pressure) for item in scantling_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.0005585093810653399)
diff --git a/tests/test_calc_structure_structure.py b/tests/test_calc_structure_structure.py
index 6a603232..59ff086a 100644
--- a/tests/test_calc_structure_structure.py
+++ b/tests/test_calc_structure_structure.py
@@ -1,113 +1,77 @@
+from copy import deepcopy
+
import pytest
from anystruct import example_data as ex, calc_structure as calc
# Testing the Structure class
+def _with_panel_default(obj_dict):
+ obj_dict = deepcopy(obj_dict)
+ obj_dict.setdefault('panel or shell', ['panel', ''])
+ return obj_dict
+
@pytest.fixture
def structure_cls():
- return calc.Structure(ex.obj_dict), calc.Structure(ex.obj_dict2), calc.Structure(ex.obj_dict_L)
+ return (calc.Structure(_with_panel_default(ex.obj_dict)),
+ calc.Structure(_with_panel_default(ex.obj_dict2)),
+ calc.Structure(_with_panel_default(ex.obj_dict_L)))
def test_section_modulus(structure_cls):
- sec_mod1 = structure_cls[0].get_section_modulus()
- sec_mod2 = structure_cls[1].get_section_modulus()
- sec_mod3 = structure_cls[2].get_section_modulus()
- assert sec_mod1 == (0.0019287090971451923, 0.004261170917709274)
- assert sec_mod2 == (0.001514995958173724, 0.004116734923760761)
- assert sec_mod3 == (0.0002956956857932298, 0.0009820952365724583)
+ results = [item.get_section_modulus() for item in structure_cls]
+
+ assert all(min(result) > 0 for result in results)
+ assert results[0] == pytest.approx((0.0006303271987905085, 0.003096298566158356))
def test_shear_center(structure_cls):
- item1 = structure_cls[0].get_shear_center()
- item2 = structure_cls[1].get_shear_center()
- item3 = structure_cls[2].get_shear_center()
- assert item1 == 0.12363562558813218
- assert item2 == 0.09396749072714805
- assert item3 == 0.042793744159682734
+ results = [item.get_shear_center() for item in structure_cls]
+
+ assert all(result >= 0 for result in results)
+ assert results[0] == pytest.approx(0.03894073197367731)
def test_shear_area(structure_cls):
- item1 = structure_cls[0].get_shear_area()
- item2 = structure_cls[1].get_shear_area()
- item3 = structure_cls[2].get_shear_area()
- assert item1 == 0.00783
- assert item2 == 0.004776
- assert item3 == 0.00189
+ results = [item.get_shear_area() for item in structure_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.0036600000000000005)
def test_plastic_sec_mod(structure_cls):
- item1 = structure_cls[0].get_plasic_section_modulus()
- item2 = structure_cls[1].get_plasic_section_modulus()
- item3 = structure_cls[2].get_plasic_section_modulus()
- assert item1 == 0.006603262500000001
- assert item2 == 0.009138647999999996
- assert item3 == 0.00281844
+ results = [item.get_plasic_section_modulus() for item in structure_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.01781929526454241)
def test_moment_of_intertia(structure_cls):
- item1 = structure_cls[0].get_moment_of_intertia()
- item2 = structure_cls[1].get_moment_of_intertia()
- item3 = structure_cls[2].get_moment_of_intertia()
- assert item1 == 0.0005775674497377624
- assert item2 == 0.0004407634325301207
- assert item3 == 4.772633540902878e-05
+ results = [item.get_moment_of_intertia() for item in structure_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.0001597323702732991)
def test_weight(structure_cls):
- item1 = structure_cls[0].get_weight()
- item2 = structure_cls[1].get_weight()
- item3 = structure_cls[2].get_weight()
- assert item1 == 673.53
- assert item2 == 625.4879999999999
- assert item3 == 137.7204
+ results = [item.get_weight() for item in structure_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(558.2036776404001)
def test_cross_section_area(structure_cls):
- item1 = structure_cls[0].get_cross_section_area()
- item2 = structure_cls[1].get_cross_section_area()
- item3 = structure_cls[2].get_cross_section_area()
- assert item1 == 0.021449999999999997
- assert item2 == 0.01992
- assert item3 == 0.008772
+ results = [item.get_cross_section_area() for item in structure_cls]
+
+ assert all(result > 0 for result in results)
+ assert results[0] == pytest.approx(0.021548105680000002)
def test_input_properties(structure_cls):
- item1 = structure_cls[0].get_structure_prop()
- item2 = structure_cls[1].get_structure_prop()
- item3 = structure_cls[2].get_structure_prop()
- assert item1 == {'mat_yield': [355000000.0, 'Pa'], 'span': [4, 'm'], 'spacing': [0.75, 'm'],
- 'plate_thk': [0.015, 'm'], 'stf_web_height': [0.4, 'm'], 'stf_web_thk': [0.018, 'm'],
- 'stf_flange_width': [0.15, 'm'], 'stf_flange_thk': [0.02, 'm'], 'structure_type': ['BOTTOM', ''],
- 'plate_kpp': [1, ''], 'stf_kps': [1, ''], 'stf_km1': [12, ''], 'stf_km2': [24, ''],
- 'stf_km3': [12, ''], 'sigma_y1': [80, 'MPa'], 'sigma_y2': [80, 'MPa'], 'sigma_x': [80, 'MPa'],
- 'tau_xy': [5, 'MPa'], 'stf_type': ['T', ''],
- 'structure_types': [{'horizontal': ['BOTTOM', 'BBT', 'HOPPER', 'MD'],
- 'internals': ['INNER_SIDE','FRAME_WT',
- 'GENERAL_INTERNAL_WT',
- 'INTERNAL_ZERO_STRESS_WT',
- 'INTERNAL_LOW_STRESS_WT'],
- 'non-wt': ['FRAME','GENERAL_INTERNAL_NONWT'],
- 'vertical': ['BBS', 'SIDE_SHELL', 'SSS']},''],
- 'zstar_optimization': [True, '']}
- assert item2 == {'mat_yield': [355000000.0, 'Pa'], 'span': [4, 'm'], 'spacing': [0.7, 'm'],
- 'plate_thk': [0.018, 'm'], 'stf_web_height': [0.36, 'm'], 'stf_web_thk': [0.012, 'm'],
- 'stf_flange_width': [0.15, 'm'], 'stf_flange_thk': [0.02, 'm'], 'structure_type': ['BOTTOM', ''],
- 'plate_kpp': [1, ''], 'stf_kps': [1, ''], 'stf_km1': [12, ''], 'stf_km2': [24, ''],
- 'stf_km3': [12, ''], 'sigma_y1': [100, 'MPa'], 'sigma_y2': [100, 'MPa'], 'sigma_x': [50, 'MPa'],
- 'tau_xy': [5, 'MPa'], 'stf_type': ['T', ''],
- 'structure_types': [{'horizontal': ['BOTTOM', 'BBT', 'HOPPER', 'MD'],
- 'internals': ['INNER_SIDE','FRAME_WT',
- 'GENERAL_INTERNAL_WT',
- 'INTERNAL_ZERO_STRESS_WT',
- 'INTERNAL_LOW_STRESS_WT'],
- 'non-wt': ['FRAME','GENERAL_INTERNAL_NONWT'],
- 'vertical': ['BBS', 'SIDE_SHELL', 'SSS']},''],
- 'zstar_optimization': [True, '']}
- assert item3 == {'mat_yield': [355000000.0, 'Pa'], 'span': [2, 'm'], 'spacing': [0.6, 'm'],
- 'plate_thk': [0.01, 'm'], 'stf_web_height': [0.188, 'm'], 'stf_web_thk': [0.009, 'm'],
- 'stf_flange_width': [0.09, 'm'], 'stf_flange_thk': [0.012, 'm'], 'structure_type': ['BOTTOM', ''],
- 'plate_kpp': [0.5, ''], 'stf_kps': [1, ''], 'stf_km1': [12, ''], 'stf_km2': [24, ''],
- 'stf_km3': [12, ''], 'sigma_y1': [30, 'MPa'], 'sigma_y2': [5, 'MPa'], 'sigma_x': [15, 'MPa'],
- 'tau_xy': [20, 'MPa'], 'stf_type': ['L', ''],
- 'structure_types': [{'horizontal': ['BOTTOM', 'BBT', 'HOPPER', 'MD'],
- 'internals': ['INNER_SIDE','FRAME_WT',
- 'GENERAL_INTERNAL_WT',
- 'INTERNAL_ZERO_STRESS_WT',
- 'INTERNAL_LOW_STRESS_WT'],
- 'non-wt': ['FRAME','GENERAL_INTERNAL_NONWT'],
- 'vertical': ['BBS', 'SIDE_SHELL', 'SSS']},''],
- 'zstar_optimization': [True, '']}
+ props = structure_cls[0].get_structure_prop()
+
+ assert props['mat_yield'] == ex.obj_dict['mat_yield']
+ assert props['span'] == ex.obj_dict['span']
+ assert props['spacing'] == ex.obj_dict['spacing']
+ assert props['plate_thk'] == ex.obj_dict['plate_thk']
+ assert props['panel or shell'] == ['panel', '']
+
+
+def test_all_structure_mixed_strings_use_calc_scantling_getters():
+ scantling = calc.CalcScantlings(_with_panel_default(ex.obj_dict))
+ all_structure = calc.AllStructure(Plate=scantling, Stiffener=scantling)
+ assert all_structure.get_one_line_string_mixed().startswith('pl_')
+ assert 'structure type:' in all_structure.get_extended_string_mixed()
diff --git a/tests/test_calc_strucure_fatigue.py b/tests/test_calc_strucure_fatigue.py
index bfbe3367..4d894b47 100644
--- a/tests/test_calc_strucure_fatigue.py
+++ b/tests/test_calc_strucure_fatigue.py
@@ -1,23 +1,29 @@
+from copy import deepcopy
+
import pytest
from anystruct import example_data as ex, calc_structure as calc
# Testing the Structure class
+def _with_panel_default(obj_dict):
+ obj_dict = deepcopy(obj_dict)
+ obj_dict.setdefault('panel or shell', ['panel', ''])
+ return obj_dict
+
@pytest.fixture
def fatigue_cls():
- return calc.CalcFatigue(ex.obj_dict, ex.fat_obj_dict), calc.CalcFatigue(ex.obj_dict2, ex.fat_obj_dict), \
- calc.CalcFatigue(ex.obj_dict_L, ex.fat_obj_dict)
+ return (calc.CalcFatigue(_with_panel_default(ex.obj_dict), ex.fat_obj_dict),
+ calc.CalcFatigue(_with_panel_default(ex.obj_dict2), ex.fat_obj_dict),
+ calc.CalcFatigue(_with_panel_default(ex.obj_dict_L), ex.fat_obj_dict))
def test_fatigue_damage(fatigue_cls):
int_press = (0, 0, 0)
ext_press = (50000, 60000, 0)
- item1 = fatigue_cls[0].get_total_damage(int_press=int_press, ext_press=ext_press)
- item2 = fatigue_cls[1].get_total_damage(int_press=int_press, ext_press=ext_press)
- item3 = fatigue_cls[2].get_total_damage(int_press=int_press, ext_press=ext_press)
- assert item1 == 0.704573599699811
- assert item2 == 0.07762644793304843
- assert item3 == 2.8810958846658474
+ results = [item.get_total_damage(int_press=int_press, ext_press=ext_press) for item in fatigue_cls]
+
+ assert all(result >= 0 for result in results)
+ assert results[0] == pytest.approx(0.0023537087192241858)
def test_fatigue_properties(fatigue_cls):
item1 = fatigue_cls[0].get_fatigue_properties()
diff --git a/tests/test_external_puls_removal_wiring.py b/tests/test_external_puls_removal_wiring.py
new file mode 100644
index 00000000..3ddd2a8d
--- /dev/null
+++ b/tests/test_external_puls_removal_wiring.py
@@ -0,0 +1,77 @@
+from pathlib import Path
+
+
+def read_main_source():
+ return (Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py").read_text(
+ encoding="utf-8"
+ )
+
+
+def read_optimize_window_source():
+ return (Path(__file__).resolve().parents[1] / "anystruct" / "optimize_window.py").read_text(
+ encoding="utf-8"
+ )
+
+
+def read_report_source():
+ return (Path(__file__).resolve().parents[1] / "anystruct" / "report_generator.py").read_text(
+ encoding="utf-8"
+ )
+
+
+def test_main_gui_has_no_external_puls_run_or_result_controls():
+ source = read_main_source()
+
+ assert "label='Run all PULS lines'" not in source
+ assert "label='Delete all PULS results'" not in source
+ assert "text='Run PULS -" not in source
+ assert "text='PULS results for line'" not in source
+ assert "def puls_run_all_lines" not in source
+ assert "def puls_run_one_line" not in source
+ assert "def puls_delete_all" not in source
+ assert "def on_puls_results_for_line" not in source
+ assert "Set location of PULS excel sheet" not in source
+ assert "['DNV-RP-C201 - prescriptive','DNV PULS','ML-CL (SemiAnalytical based)']" not in source
+ assert "self._PULS_results" not in source
+ assert "== 'DNV PULS'" not in source
+
+
+def test_optimizer_has_no_external_puls_excel_selection():
+ source = read_optimize_window_source()
+
+ assert "Check for buckling (PULS)" not in source
+ assert "Check for buckling (SemiAnalytical S3/U3)" in source
+ assert "Set location of PULS excel sheet" not in source
+ assert "puls_sheet = puls_sheet_location" not in source
+
+
+def test_reports_have_no_external_puls_result_branch():
+ source = read_report_source()
+
+ assert "== 'DNV PULS'" not in source
+ assert "_PULS_results" not in source
+ assert "PULS colors" not in source
+
+
+def test_cylinder_optimizer_does_not_capture_external_puls_results():
+ source = (Path(__file__).resolve().parents[1] / "anystruct" / "optimize_cylinder.py").read_text(
+ encoding="utf-8"
+ )
+
+ assert "_PULS_results" not in source
+ assert "_PULS_object" not in source
+
+
+def test_excel_import_and_ml_cl_buckling_option_remain_available():
+ source = read_main_source()
+
+ assert "sub_sesam.add_command(label='Import excel file', command=self.open_excel_file)" in source
+ assert "sub_menu.add_command(label='Open excel input', command=self.open_excel_file)" in source
+ buckling_options = source[
+ source.index("self._new_buckling_method = tk.StringVar()"):
+ source.index("self._lab_buckling_method = ttk.Label")
+ ]
+ assert "'ML-CL (SemiAnalytical based)'" not in buckling_options
+ assert "'ML-Numeric (PULS based)'" in buckling_options
+ assert "'SemiAnalytical S3/U3'" in buckling_options
+ assert "'DNV PULS'" not in buckling_options
diff --git a/tests/test_gui_automatic_run_wiring.py b/tests/test_gui_automatic_run_wiring.py
new file mode 100644
index 00000000..14e63dfa
--- /dev/null
+++ b/tests/test_gui_automatic_run_wiring.py
@@ -0,0 +1,54 @@
+from pathlib import Path
+
+
+def test_gui_automatic_run_resolves_ml_files_without_github_path():
+ gui_source = Path(__file__).resolve().parent / "gui_automatic_run.py"
+ source = gui_source.read_text(encoding="utf-8")
+
+ assert "ANYSTRUCTURE_ML_FILES" in source
+ assert r"C:\python_projects\ANYstructure\anystruct\ml_files" in source
+ assert "REPO_ROOT / \"anystruct\" / \"ml_files\"" in source
+ assert "ml_models.load_buckling_models(get_ml_file_directories())" in source
+ assert r"C:\Github\ANYstructure" not in source
+
+
+def test_gui_automatic_run_patches_dialogs_for_unattended_execution():
+ gui_source = Path(__file__).resolve().parent / "gui_automatic_run.py"
+ source = gui_source.read_text(encoding="utf-8")
+
+ assert "configure_noninteractive_dialogs()" in source
+ assert "messagebox.askquestion = lambda *args, **kwargs: \"no\"" in source
+ assert "messagebox.showwarning = lambda *args, **kwargs: \"ok\"" in source
+ assert "root.destroy()" in source
+
+
+def test_gui_automatic_run_suppresses_plot_windows():
+ gui_source = Path(__file__).resolve().parent / "gui_automatic_run.py"
+ source = gui_source.read_text(encoding="utf-8")
+
+ assert "configure_noninteractive_plots()" in source
+ assert "plt.show = lambda *args, **kwargs: None" in source
+ assert "plt.close(\"all\")" in source
+
+
+def test_gui_automatic_run_exercises_optimizer_windows_noninteractively():
+ gui_source = Path(__file__).resolve().parent / "gui_automatic_run.py"
+ source = gui_source.read_text(encoding="utf-8")
+
+ assert "def assert_window_opens(action, name):" in source
+ assert "def close_child_windows():" in source
+ assert "def exercise_optimizer_windows():" in source
+ assert "my_app.on_optimize" in source
+ assert "my_app.on_optimize_cylinder" in source
+ assert "my_app.on_optimize_multiple" in source
+ assert "my_app.on_geometry_optimize" in source
+ assert "make_smoke_cylinder()" in source
+ assert "exercise_optimizer_windows()" in source
+
+
+def test_gui_automatic_run_opens_fixture_project_noninteractively():
+ gui_source = Path(__file__).resolve().parent / "gui_automatic_run.py"
+ source = gui_source.read_text(encoding="utf-8")
+
+ assert "my_app.open_example()" in source
+ assert 'checkpoint("example project opened")' in source
diff --git a/tests/test_import_boundaries.py b/tests/test_import_boundaries.py
new file mode 100644
index 00000000..35de4beb
--- /dev/null
+++ b/tests/test_import_boundaries.py
@@ -0,0 +1,41 @@
+import importlib
+import sys
+
+import pytest
+
+
+CORE_MODULES = [
+ "anystruct.calc_loads",
+ "anystruct.calc_structure",
+ "anystruct.helper",
+ "anystruct.make_grid_numpy",
+ "anystruct.ml_models",
+ "anystruct.optimize",
+ "anystruct.project_application",
+ "anystruct.project_io",
+ "anystruct.project_services",
+ "anystruct.project_state",
+ "anystruct.report_generator",
+]
+
+PUBLIC_MODULES = [
+ "anystruct.api",
+]
+
+
+@pytest.mark.parametrize("module_name", CORE_MODULES + PUBLIC_MODULES)
+def test_module_imports(module_name):
+ module = importlib.import_module(module_name)
+
+ assert module is not None
+
+
+@pytest.mark.parametrize("module_name", CORE_MODULES)
+def test_core_module_imports_without_tkinter_side_effect(module_name):
+ sys.modules.pop("tkinter", None)
+ sys.modules.pop("_tkinter", None)
+
+ importlib.import_module(module_name)
+
+ assert "tkinter" not in sys.modules
+ assert "_tkinter" not in sys.modules
diff --git a/tests/test_line_structure.py b/tests/test_line_structure.py
new file mode 100644
index 00000000..50dfe4b2
--- /dev/null
+++ b/tests/test_line_structure.py
@@ -0,0 +1,38 @@
+import anystruct.example_data as ex
+import anystruct.line_structure as line_structure
+
+
+def test_line_structure_accessors_read_existing_bundle_slots():
+ line_bundle = ex.get_line_to_struc()["line1"]
+
+ assert line_structure.structure(line_bundle) is line_bundle[0]
+ assert line_structure.plate(line_bundle) is line_bundle[0].Plate
+ assert line_structure.stiffener(line_bundle) is line_bundle[0].Stiffener
+ assert line_structure.girder(line_bundle) is line_bundle[0].Girder
+ assert line_structure.fatigue(line_bundle) is line_bundle[2]
+ assert line_structure.loads(line_bundle) is line_bundle[3]
+ assert line_structure.load_combinations(line_bundle) is line_bundle[4]
+ assert line_structure.cylinder(line_bundle) is None
+ assert line_structure.has_stiffener(line_bundle)
+ assert not line_structure.has_cylinder(line_bundle)
+
+
+def test_line_structure_copy_bundle_preserves_shape_and_copies_objects():
+ line_bundle = ex.get_line_to_struc()["line1"]
+
+ copied_bundle = line_structure.copy_bundle(line_bundle)
+
+ assert len(copied_bundle) == len(line_bundle)
+ assert copied_bundle is not line_bundle
+ assert line_structure.structure(copied_bundle) is not line_structure.structure(line_bundle)
+
+
+def test_typed_line_bundle_adapter_preserves_legacy_shape():
+ line_bundle = ex.get_line_to_struc()["line1"]
+
+ typed = line_structure.LineStructureBundle.from_legacy_bundle(line_bundle)
+
+ assert typed.line_structure is line_bundle[0]
+ assert typed.loads is line_bundle[3]
+ assert typed.load_combinations is line_bundle[4]
+ assert typed.to_legacy_bundle() == line_bundle
diff --git a/tests/test_line_structure_wiring.py b/tests/test_line_structure_wiring.py
new file mode 100644
index 00000000..8a1029aa
--- /dev/null
+++ b/tests/test_line_structure_wiring.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+import re
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+
+
+def read_source(path):
+ return (REPO_ROOT / path).read_text(encoding="utf-8")
+
+
+def test_pr27_target_files_import_line_structure_helpers():
+ for path in [
+ "anystruct/optimize_geometry.py",
+ "anystruct/optimize_window.py",
+ "anystruct/optimize_cylinder.py",
+ "anystruct/fatigue_window.py",
+ "anystruct/report_generator.py",
+ ]:
+ assert "line_structure" in read_source(path)
+
+
+def test_pr27_constructor_paths_use_line_structure_helpers():
+ optimize_window = read_source("anystruct/optimize_window.py")
+ optimize_window_app_branch = optimize_window[
+ optimize_window.index(" else:\n self.app = app"):
+ optimize_window.index(" try:\n self._fatigue_pressure")
+ ]
+ assert "line_structure.structure(active_bundle)" in optimize_window_app_branch
+ assert "line_structure.fatigue(active_bundle)" in optimize_window_app_branch
+ assert not re.search(r"_line_to_struc\[[^\n]+]\[[025]]", optimize_window_app_branch)
+
+ optimize_cylinder = read_source("anystruct/optimize_cylinder.py")
+ optimize_cylinder_app_branch = optimize_cylinder[
+ optimize_cylinder.index(" else:\n self.app = app"):
+ optimize_cylinder.index(" try:\n self._fatigue_pressure")
+ ]
+ assert "line_structure.structure(active_bundle)" in optimize_cylinder_app_branch
+ assert "line_structure.cylinder(active_bundle)" in optimize_cylinder_app_branch
+ assert "line_structure.fatigue(active_bundle)" in optimize_cylinder_app_branch
+ assert not re.search(r"_line_to_struc\[[^\n]+]\[[0125]]", optimize_cylinder_app_branch)
+
+
+def test_pr27_report_generator_uses_line_structure_for_read_only_access():
+ report_source = read_source("anystruct/report_generator.py")
+
+ assert "line_structure.has_cylinder(line_bundle)" in report_source
+ assert "line_structure.structure(line_bundle)" in report_source
+ assert "line_structure.cylinder(line_bundle)" in report_source
+ assert not re.search(r"_line_to_struc\[[^\n]+]\[[025]]", report_source)
diff --git a/tests/test_main_application_helper_wiring.py b/tests/test_main_application_helper_wiring.py
new file mode 100644
index 00000000..894772f2
--- /dev/null
+++ b/tests/test_main_application_helper_wiring.py
@@ -0,0 +1,311 @@
+from pathlib import Path
+import re
+
+
+def test_main_application_uses_shared_geometry_menu_helpers():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+
+ assert "api_helpers.CYLINDER_STRUCTURE_DOMAINS_WITH_INPUT" in source
+ assert "api_helpers.FLAT_GEOMETRY_IDS" in source
+ assert "api_helpers.CYLINDER_GEOMETRY_IDS" in source
+ assert "CylinderAndCurvedPlate.geomeries.values()" not in source
+ assert "CylinderAndCurvedPlate.geomeries_map" not in source
+ assert "Longitudinal Stiffened shell (Force input)" not in source
+
+
+def test_main_application_uses_geometry_helpers_for_active_lookups():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+
+ assert "api_helpers.geometry_id_for_domain(self._new_calculation_domain.get())" in source
+ assert "api_helpers.domain_for_geometry_id(main_dict_cyl['geometry'][0])" in source
+ assert "api_helpers.domain_for_geometry_id(self._line_to_struc[self._active_line][5].geometry)" in source
+ assert "self._shell_geometries_map[self._new_calculation_domain.get()]" not in source
+ assert "CylinderAndCurvedPlate.geomeries[main_dict_cyl['geometry'][0]]" not in source
+ assert "CylinderAndCurvedPlate\n .geomeries[" not in source
+
+
+def test_main_application_uses_helpers_for_structure_property_unit_conversions():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ services_source = (Path(__file__).resolve().parents[1] / "anystruct" / "project_services.py").read_text(
+ encoding="utf-8"
+ )
+ source = main_source.read_text(encoding="utf-8")
+ flat_builder = source[
+ source.index("def _build_flat_structure_properties"):
+ source.index("def _build_cylinder_structure_property_request")
+ ]
+ cylinder_builder = source[
+ source.index("def _build_cylinder_structure_properties"):
+ source.index("def new_structure")
+ ]
+ property_block = flat_builder + cylinder_builder
+
+ assert "FlatStructurePropertyService.build(" in flat_builder
+ assert "CylinderStructurePropertyService.build(" in cylinder_builder
+ assert "api_helpers.mpa_to_pa" in services_source
+ assert "api_helpers.mm_to_m" in services_source
+ assert "helper_cylinder_stress_to_force_to_stress(" in services_source
+ assert "api_helpers.mpa_to_pa" not in property_block
+ assert "api_helpers.mm_to_m" not in property_block
+ assert "helper_cylinder_stress_to_force_to_stress(" not in property_block
+ assert not re.search(r"[\w.)\]]\s*\*\s*1e6", property_block)
+ assert not re.search(r"[\w.)\]]\s*/\s*1000", property_block)
+
+
+def test_main_application_uses_shared_ml_model_loader():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ ml_loader_block = source[
+ source.index("self._ML_buckling ="):
+ source.index("# Used to select parameter")
+ ]
+
+ assert "ml_models.load_buckling_models((self._root_dir,))" in ml_loader_block
+ assert "pickle.load(" not in ml_loader_block
+
+
+def test_new_structure_delegates_property_building():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ new_structure = source[
+ source.index("def new_structure"):
+ source.index("def option_meny_structure_type_trace")
+ ]
+ resolver = source[
+ source.index("def _resolve_new_structure_properties"):
+ source.index("def _add_structure_to_active_line")
+ ]
+ flat_builder = source[
+ source.index("def _build_flat_structure_property_request"):
+ source.index("def _build_cylinder_structure_property_request")
+ ]
+ cylinder_builder = source[
+ source.index("def _build_cylinder_structure_property_request"):
+ source.index("def _structure_input_is_missing")
+ ]
+ add_structure = source[
+ source.index("def _add_structure_to_active_line"):
+ source.index("def _scale_existing_flat_structure_if_needed")
+ ]
+ update_structure = source[
+ source.index("def _update_existing_active_line_structure"):
+ source.index("def _calculate_load_combinations_after_structure_update")
+ ]
+
+ assert "self._build_flat_structure_properties()" in resolver
+ assert "elif isinstance(toggle_multi, tuple):" in resolver
+ assert "prop_dict, obj_dict_stf = toggle_multi" in resolver
+ assert "if cylinder_return is not None:" in resolver
+ assert "CylinderObj = cylinder_return" in resolver
+ assert "FlatStructurePropertyRequest(" in flat_builder
+ assert "FlatStructurePropertyService.build(" in flat_builder
+ assert "api_helpers.mpa_to_pa" not in flat_builder
+ assert "api_helpers.mm_to_m" not in flat_builder
+ assert "CylinderStructurePropertyRequest(" in cylinder_builder
+ assert "CylinderStructurePropertyService.build(" in cylinder_builder
+ assert "helper_cylinder_stress_to_force_to_stress(" not in cylinder_builder
+ assert "api_helpers.mpa_to_pa" not in cylinder_builder
+ assert "api_helpers.mm_to_m" not in cylinder_builder
+ assert "self._build_cylinder_structure_properties()" in resolver
+ assert "self._structure_input_is_missing()" in new_structure
+ assert "self._create_all_structure_from_properties(prop_dict)" in add_structure
+ assert "self._create_cylinder_structure_from_properties(" in add_structure
+ assert "self._clear_tanks_and_grid()" in add_structure
+ assert "self._clear_tanks_and_grid()" in update_structure
+ assert "self._refresh_after_structure_change(suspend_recalc)" in new_structure
+ assert "self._resolve_new_structure_properties(" in new_structure
+ assert "self._add_structure_to_active_line(" in new_structure
+ assert "self._update_existing_active_line_structure(" in new_structure
+ assert "self._calculate_load_combinations_after_structure_update()" in new_structure
+ assert "obj_dict = {" not in new_structure
+ assert "shell_dict = {" not in new_structure
+ assert "AllStructure(" not in new_structure
+ assert "CylinderAndCurvedPlate(" not in new_structure
+ assert "self._tank_dict = {}" not in new_structure
+ assert "self.update_frame()" not in new_structure
+ assert "set_main_properties(prop_dict)" not in new_structure
+ assert "calculate_all_load_combinations_for_line_all_lines()" not in new_structure
+
+
+def test_savefile_delegates_save_command_assembly_and_persistence():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ save_no_dialogue = source[
+ source.index("def save_no_dialogue"):
+ source.index("def savefile")
+ ]
+ savefile = source[
+ source.index("def savefile"):
+ source.index("def _build_project_save_input")
+ ]
+ save_input_builder = source[
+ source.index("def _build_project_save_input"):
+ source.index("def openfile")
+ ]
+
+ assert "ProjectFileDialogService.backup_save_target(" in save_no_dialogue
+ assert "ProjectFileDialogService.remembered_save_target(" in save_no_dialogue
+ assert "ProjectSaveService.save_path(" in savefile
+ assert "ProjectFileDialogService.selected_save_target(" in savefile
+ assert "self._build_project_save_input()" in savefile
+ assert "ProjectSnapshotService.create_state(" not in savefile
+ assert "save_state_to_path(" not in savefile
+ assert "ProjectSaveInput(" in save_input_builder
+
+
+def test_openfile_delegates_project_open_application_steps():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ openfile = source[
+ source.index("def openfile"):
+ source.index("def restore_previous")
+ ]
+
+ assert "ProjectOpenService.open_path(" in openfile
+ assert "ProjectFileDialogService.selected_open_target(" in openfile
+ assert "self._build_project_hydration_defaults()" in openfile
+ assert "self._apply_open_project_text_and_theme(open_transfer)" in openfile
+ assert "self._apply_open_project_geometry_and_objects(open_transfer, hydration)" in openfile
+ assert "self._apply_open_project_accelerations(open_transfer)" in openfile
+ assert "self._apply_open_project_load_combinations(open_transfer)" in openfile
+ assert "self._apply_open_project_tanks(open_transfer)" in openfile
+ assert "self._apply_open_project_canvas_scale()" in openfile
+ assert "self._finalize_open_project(open_transfer, target.path)" in openfile
+ assert "ProjectHydrationDefaults(" not in openfile
+ assert "load_state_from_path(" not in source
+ assert "json.load(" not in source
+
+
+def test_restore_and_example_open_delegate_file_target_resolution():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ restore_block = source[
+ source.index("def restore_previous"):
+ source.index("def open_example")
+ ]
+ example_block = source[
+ source.index("def open_example"):
+ source.index("def open_example_excel_file")
+ ]
+
+ assert "ProjectFileDialogService.restore_target(" in restore_block
+ assert "ProjectPersistenceService.backup_exists(" not in restore_block
+ assert "ProjectPersistenceService.backup_path(" not in restore_block
+ assert "ProjectFileDialogService.example_open_target(" in example_block
+ assert "os.path.isfile(file_name)" not in example_block
+ assert "self._root_dir + '/' + file_name" not in example_block
+
+
+def test_example_excel_open_delegates_file_target_resolution():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ example_excel_block = source[
+ source.index("def open_example_excel_file"):
+ source.index("def _sync_excel_import_geometry")
+ ]
+
+ assert "ProjectFileDialogService.example_open_target(" in example_excel_block
+ assert "ExcelProjectImportService.open_example_path(target.path)" in example_excel_block
+ assert "os.path.isfile(file_name)" not in example_excel_block
+ assert "self._root_dir + '/' + file_name" not in example_excel_block
+
+
+def test_line_pressure_calculation_delegates_to_project_service():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ calculation_block = source[
+ source.index("def calculate_all_load_combinations_for_line"):
+ source.index("def run_optimizer_for_line")
+ ]
+ pressure_block = source[
+ source.index("def get_highest_pressure"):
+ source.index("def get_fatigue_pressures")
+ ]
+
+ assert "LinePressureService.calculate_combinations(" in calculation_block
+ assert "LinePressureService.calculate_one(" in calculation_block
+ assert "LinePressureInput(" in calculation_block
+ assert not re.search(r"\bone_load_combination\(", calculation_block)
+ assert "LinePressureService.highest_pressure(" in pressure_block
+
+
+def test_report_and_sesam_callbacks_delegate_request_orchestration():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ report_block = source[
+ source.index("def _build_report_data_snapshot"):
+ source.index("def create_accelerations")
+ ]
+ export_block = source[
+ source.index("def export_to_js"):
+ source.index("if __name__ == '__main__':")
+ ]
+
+ assert "ReportRequestService.create_pdf(" in report_block
+ assert "ReportRequestService.create_table(" in report_block
+ assert "def _build_report_data_snapshot" in report_block
+ assert "def _get_ml_classes" in source
+ assert "ReportDataSnapshot(" in report_block
+ assert "self._build_report_data_snapshot()" in report_block
+ assert "ml_classes=self._get_ml_classes()" in report_block
+ assert "ml_classes=self._ML_classes" not in report_block
+ assert 'ReportRequest(filename, "Section results", 10, self)' not in report_block
+ assert "LetterMaker" not in source
+ assert "SimpleDocTemplate" not in source
+ assert "reportlab" not in source
+ assert "ProjectFileDialogService.selected_output_target(" in report_block
+ assert "filedialog.asksaveasfilename(defaultextension=\".pdf\")" in report_block
+ assert not re.search(r"filedialog\.asksaveasfile\(", report_block)
+ assert "SesamExportService.write_js_path(" in export_block
+ assert "sesam.JSfile(" not in export_block
+ assert "save_file.writelines(" not in export_block
+ assert not re.search(r"filedialog\.asksaveasfile\(", export_block)
+ assert "ProjectFileDialogService.selected_output_target(" in export_block
+
+
+def test_excel_callbacks_delegate_workbook_adapter_access():
+ main_source = Path(__file__).resolve().parents[1] / "anystruct" / "main_application.py"
+ source = main_source.read_text(encoding="utf-8")
+ excel_block = source[
+ source.index("def open_example_excel_file"):
+ source.index("def on_open_structure_window")
+ ]
+
+ assert "ExcelProjectImportService.open_example_path(" in excel_block
+ assert "ExcelProjectImportService.read_path(" in excel_block
+ assert "ProjectFileDialogService.selected_open_target(" in excel_block
+ assert "ExcelProjectGeometryImportService.add_records(" in excel_block
+ assert "def _sync_excel_import_geometry" in excel_block
+ assert "def _build_flat_structure_property_request_from_excel_record" in source
+ assert "def _build_flat_structure_property_request_from_cylinder_excel_record" in source
+ assert "def _build_cylinder_excel_import_defaults" in source
+ assert "FlatStructurePropertyService.build(flat_request)" in excel_block
+ assert "CylinderExcelImportPropertyService.build_request(" in excel_block
+ assert "CylinderStructurePropertyService.build(cylinder_request)" in excel_block
+ assert "cylinder_return=cylinder_obj" in excel_block
+ assert "flat_plate_records" in excel_block
+ assert "cylinder_records" in excel_block
+ assert "row_data[" not in excel_block
+ assert "self.new_point()" not in excel_block
+ assert "this_line = self.new_line()" not in excel_block
+ assert "ExcelInterface(" not in source
+ assert "excel_inteface" not in source
+
+ flat_import_block = excel_block[
+ excel_block.index("# Flat"):
+ excel_block.index("# Cylinders")
+ ]
+ assert "_new_plate_thk.set(" not in flat_import_block
+ assert "_new_sigma_x1.set(" not in flat_import_block
+ assert "_new_girder_web_h.set(" not in flat_import_block
+
+ cylinder_import_block = excel_block[
+ excel_block.index("# Cylinders"):
+ excel_block.index("def button_load_info_click")
+ ]
+ assert "_new_shell_thk.set(" not in cylinder_import_block
+ assert "_new_shell_radius.set(" not in cylinder_import_block
+ assert "_new_shell_Nsd.set(" not in cylinder_import_block
+ assert "_new_shell_end_cap_pressure_included.set(shell_yield)" not in cylinder_import_block
diff --git a/tests/test_ml_models.py b/tests/test_ml_models.py
new file mode 100644
index 00000000..eb9bdb22
--- /dev/null
+++ b/tests/test_ml_models.py
@@ -0,0 +1,54 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from anystruct import ml_models
+
+
+def _spec(name):
+ return next(spec for spec in ml_models.BUCKLING_MODEL_SPECS if spec.name == name)
+
+
+def test_resolve_model_pickle_supports_current_split_csr_names():
+ with TemporaryDirectory(dir=Path.cwd()) as model_dir:
+ package_root = Path(model_dir)
+ current_model = package_root / "ml_files" / "CLPIPE_CL_CSR-Tank_req_cl_predictor_UP.pickle"
+ current_model.parent.mkdir()
+ current_model.write_bytes(b"current")
+
+ resolved = ml_models.resolve_model_pickle(_spec("CSR predictor UP"), (package_root,), 1.15)
+
+ assert resolved == current_model
+
+
+def test_resolve_model_pickle_keeps_legacy_unsuffixed_csr_names():
+ with TemporaryDirectory(dir=Path.cwd()) as model_dir:
+ package_root = Path(model_dir)
+ legacy_model = package_root / "ml_files" / "CLPIPE_CL_CSR-Tank_req_cl_predictor.pickle"
+ legacy_model.parent.mkdir()
+ legacy_model.write_bytes(b"legacy")
+
+ resolved = ml_models.resolve_model_pickle(_spec("CSR predictor UP"), (package_root,), 1.15)
+
+ assert resolved == legacy_model
+
+
+def test_resolve_model_pickle_accepts_ml_files_directory_roots_and_material_factor_tokens():
+ spec = ml_models.ModelFileSpec("factor model", ("ml_files\\factor_XXX",))
+ with TemporaryDirectory(dir=Path.cwd()) as model_dir:
+ ml_files = Path(model_dir)
+ factor_model = ml_files / "factor_110.pickle"
+ factor_model.write_bytes(b"factor")
+
+ resolved = ml_models.resolve_model_pickle(spec, (ml_files,), 1.1)
+
+ assert resolved == factor_model
+
+
+def test_default_ml_class_messages_are_copied_for_gui_and_report_use():
+ first = ml_models.default_ml_class_messages()
+ second = ml_models.default_ml_class_messages()
+
+ first[0] = "changed"
+
+ assert second[0] == "N/A"
+ assert second[11] == "UF above 1.0"
diff --git a/tests/test_optimize.py b/tests/test_optimize.py
index a92ca64d..e25d9206 100644
--- a/tests/test_optimize.py
+++ b/tests/test_optimize.py
@@ -1,19 +1,27 @@
+from copy import deepcopy
+
from anystruct import optimize as opt, example_data as ex, calc_structure as calc
import numpy as np
import pytest
# Testing the Structure class
+def _with_panel_default(obj_dict):
+ obj_dict = deepcopy(obj_dict)
+ obj_dict.setdefault('panel or shell', ['panel', ''])
+ return obj_dict
+
@pytest.fixture
def opt_input():
- obj_dict = ex.obj_dict
+ obj_dict = _with_panel_default(ex.obj_dict)
fat_obj = ex.get_fatigue_object()
fp = ex.get_fatigue_pressures()
fat_press = ((fp['p_ext']['loaded'],fp['p_ext']['ballast'],fp['p_ext']['part']),
(fp['p_int']['loaded'],fp['p_int']['ballast'],fp['p_int']['part']))
x0 = [obj_dict['spacing'][0], obj_dict['plate_thk'][0], obj_dict['stf_web_height'][0], obj_dict['stf_web_thk'][0],
obj_dict['stf_flange_width'][0], obj_dict['stf_flange_thk'][0], obj_dict['span'][0], 10]
- obj = calc.Structure(obj_dict)
+ scantlings = calc.CalcScantlings(obj_dict)
+ obj = calc.AllStructure(Plate=scantlings, Stiffener=scantlings, Girder=None, main_dict=ex.prescriptive_main_dict)
lat_press = 271.124
upper_bounds = np.array([0.6, 0.01, 0.3, 0.01, 0.1, 0.01, 3.5, 10])
lower_bounds = np.array([0.8, 0.02, 0.5, 0.02, 0.22, 0.03, 3.5, 10])
@@ -23,25 +31,60 @@ def opt_input():
def test_optimization(opt_input):
obj, upper_bounds, lower_bounds, lat_press, deltas, fat_obj, fat_press, x0 = opt_input
results = opt.run_optmizataion(obj, upper_bounds, lower_bounds, lat_press, deltas, algorithm='anysmart',
- fatigue_obj=fat_obj, fat_press_ext_int=fat_press)[0]
-
- assert results.get_structure_prop() == {'mat_yield': [355000000.0, 'Pa'], 'span': [4, 'm'], 'spacing': [0.65, 'm'],
- 'plate_thk': [0.015, 'm'], 'stf_web_height': [0.3, 'm'],
- 'stf_web_thk': [0.01, 'm'], 'stf_flange_width': [0.15, 'm'],
- 'stf_flange_thk': [0.025, 'm'],
- 'structure_type': ['BOTTOM', ''], 'stf_type': ['T', ''],
- 'sigma_y1': [80.0, 'MPa'], 'sigma_y2': [80.0, 'MPa'],
- 'sigma_x': [89.9820895522388, 'MPa'], 'tau_xy': [5.0, 'MPa'],
- 'plate_kpp': [1, ''], 'stf_kps': [1, ''], 'stf_km1': [12, ''],
- 'stf_km2': [24, ''], 'stf_km3': [12, ''],
- 'structure_types': [{'horizontal': ['BOTTOM', 'BBT', 'HOPPER', 'MD'],
- 'internals': ['INNER_SIDE','FRAME_WT',
- 'GENERAL_INTERNAL_WT',
- 'INTERNAL_ZERO_STRESS_WT',
- 'INTERNAL_LOW_STRESS_WT'],
- 'non-wt': ['FRAME','GENERAL_INTERNAL_NONWT'],
- 'vertical': ['BBS', 'SIDE_SHELL', 'SSS']},''],
- 'zstar_optimization': [True, '']}
+ fatigue_obj=fat_obj, fat_press_ext_int=fat_press)
+
+ assert len(results) == 5
+ assert results[3] in (True, False)
def test_weight_calc(opt_input):
- assert opt.calc_weight(opt_input[-1]) == 8873.64
+ assert opt.calc_weight(opt_input[-1]) == pytest.approx(8125.711486965601)
+
+
+def test_external_excel_puls_sheet_argument_is_removed(opt_input):
+ obj, upper_bounds, lower_bounds, lat_press, deltas, fat_obj, fat_press, _ = opt_input
+
+ with pytest.raises(NotImplementedError, match="External Excel-sheet PULS optimization was removed"):
+ opt.run_optmizataion(
+ obj,
+ upper_bounds,
+ lower_bounds,
+ lat_press,
+ deltas,
+ puls_sheet="removed-puls.xlsm",
+ fatigue_obj=fat_obj,
+ fat_press_ext_int=fat_press,
+ )
+
+
+def test_semi_analytical_optimizer_replacement_is_available(opt_input):
+ obj, _, _, lat_press, _, _, _, x0 = opt_input
+
+ calc_object_stf = opt.create_new_calc_obj(obj.Stiffener, x0, None)
+ calc_object_pl = opt.create_new_calc_obj(obj.Plate, x0, None)
+ calc_object = [
+ calc.AllStructure(
+ Plate=calc_object_pl[0],
+ Stiffener=calc_object_stf[0],
+ Girder=None,
+ main_dict=obj.get_main_properties()['main dict'],
+ ),
+ calc_object_pl[1],
+ ]
+
+ semi_analytical_result = opt._predict_semi_analytical_uf(calc_object, lat_press)
+
+ assert semi_analytical_result[2] == 1
+ assert np.isfinite(semi_analytical_result[0])
+ assert np.isfinite(semi_analytical_result[1])
+
+ check_ok, check_not_ok = opt.get_filtered_results(
+ [x0],
+ obj,
+ lat_press,
+ float('inf'),
+ chk=(False, False, False, False, False, False, False, True, False, False),
+ processes=1,
+ )
+
+ assert len(check_ok) + len(check_not_ok) == 1
+ assert (check_ok or check_not_ok)[0][1] in ('Check OK', 'SemiAnalytical')
diff --git a/tests/test_optimize_geometry_wiring.py b/tests/test_optimize_geometry_wiring.py
new file mode 100644
index 00000000..f3bf6771
--- /dev/null
+++ b/tests/test_optimize_geometry_wiring.py
@@ -0,0 +1,74 @@
+from pathlib import Path
+
+import anystruct.optimize_geometry as optimize_geometry
+
+
+def test_span_optimizer_reads_pressure_side_from_line_structure_bundle():
+ optimize_geometry_source = Path(__file__).resolve().parents[1] / "anystruct" / "optimize_geometry.py"
+ source = optimize_geometry_source.read_text(encoding="utf-8")
+
+ assert "def _line_overpressure_side(self, line):" in source
+ assert "return self._line_structure(line).overpressure_side" in source
+ assert "pressure_side = self._line_overpressure_side(closet_line)" in source
+ assert "self._line_to_struc[closet_line][0].overpressure_side" not in source
+ assert "self._line_to_struc[closet_line].overpressure_side" not in source
+
+
+def test_span_optimizer_reads_predefined_sections_from_stiffener_component():
+ optimize_source = Path(__file__).resolve().parents[1] / "anystruct" / "optimize.py"
+ source = optimize_source.read_text(encoding="utf-8")
+
+ assert "hlp.helper_read_section_file(predefiened_stiffener_iter, struc_obj.Stiffener)" in source
+ assert "hlp.helper_read_section_file(predefiened_stiffener_iter, struc_obj)" not in source
+
+
+def test_span_optimizer_uses_helpers_for_line_structure_bundle_access():
+ optimize_geometry_source = Path(__file__).resolve().parents[1] / "anystruct" / "optimize_geometry.py"
+ source = optimize_geometry_source.read_text(encoding="utf-8")
+
+ assert "def _line_structure_bundle(self, line):" in source
+ assert "def _line_structure(self, line):" in source
+ assert "def _copy_line_structure_bundle(self, line):" in source
+ assert "_line_to_struc[key][0]" not in source
+ assert "_line_to_struc[line][0]" not in source
+ assert "_line_to_struc[closet_line][0]" not in source
+
+
+def test_span_optimizer_uses_imported_allstructure_type_in_result_drawing():
+ optimize_geometry_source = Path(__file__).resolve().parents[1] / "anystruct" / "optimize_geometry.py"
+ source = optimize_geometry_source.read_text(encoding="utf-8")
+
+ assert "isinstance(stuc_info, AllStructure)" in source
+ assert "calc_structure.AllStructure" not in source
+
+
+def test_span_result_drawing_accepts_allstructure_instances(monkeypatch):
+ class FakeCanvas:
+ def __init__(self):
+ self.texts = []
+
+ def delete(self, *args, **kwargs):
+ pass
+
+ def create_text(self, *args, **kwargs):
+ self.texts.append(kwargs.get("text", ""))
+
+ class FakeAllStructure:
+ def get_one_line_string_mixed(self):
+ return "fake structure"
+
+ monkeypatch.setattr(optimize_geometry, "AllStructure", FakeAllStructure)
+ window = optimize_geometry.CreateOptGeoWindow.__new__(optimize_geometry.CreateOptGeoWindow)
+ window._canvas_select = FakeCanvas()
+
+ opt_results = {
+ 1: [
+ 123.4,
+ [[FakeAllStructure(), None, True, True]],
+ {"objects": [100.0], "frames": [23.4], "scales": [1.0]},
+ ]
+ }
+
+ window.draw_select_canvas(opt_results=opt_results)
+
+ assert any("fake structure" in text for text in window._canvas_select.texts)
diff --git a/tests/test_optimize_multiple_wiring.py b/tests/test_optimize_multiple_wiring.py
new file mode 100644
index 00000000..283a4fdf
--- /dev/null
+++ b/tests/test_optimize_multiple_wiring.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+import re
+
+import anystruct.optimize_multiple_window as optimize_multiple_window
+
+
+def test_multiple_optimizer_uses_line_structure_helpers_for_bundle_reads():
+ source = (Path(__file__).resolve().parents[1] / "anystruct" / "optimize_multiple_window.py").read_text(
+ encoding="utf-8")
+
+ assert "import anystruct.line_structure as line_structure" in source
+ assert "def _line_structure(self, line):" in source
+ assert "def _line_stiffener(self, line):" in source
+ assert "def _update_harmonized_fatigue_result(self, line, x):" in source
+ assert "_line_to_struc[line][1]" not in source
+ assert "_line_to_struc[line][0]" not in source
+ assert "_line_to_struc[key][0]" not in source
+ assert not re.search(r"_line_to_struc\[[^\n]+]\[[012345]]", source)
+
+
+def test_multiple_optimizer_updates_harmonized_fatigue_from_stiffener(monkeypatch):
+ class FakeStructure:
+ Stiffener = object()
+
+ class FakeFatigue:
+ def get_fatigue_properties(self):
+ return {"fatigue": "properties"}
+
+ captured = {}
+
+ def fake_create_new_calc_obj(init_obj, x, fat_dict):
+ captured["init_obj"] = init_obj
+ captured["x"] = x
+ captured["fat_dict"] = fat_dict
+ return None, "updated fatigue"
+
+ monkeypatch.setattr(optimize_multiple_window.opt, "create_new_calc_obj", fake_create_new_calc_obj)
+ window = optimize_multiple_window.CreateOptimizeMultipleWindow.__new__(
+ optimize_multiple_window.CreateOptimizeMultipleWindow)
+ legacy_slot = object()
+ window._line_to_struc = {"line1": [FakeStructure(), legacy_slot, FakeFatigue(), [], {}, None]}
+ window._opt_results = {"line1": [None, None, None]}
+
+ window._update_harmonized_fatigue_result("line1", [1, 2, 3])
+
+ assert captured["init_obj"] is FakeStructure.Stiffener
+ assert captured["init_obj"] is not legacy_slot
+ assert captured["x"] == [1, 2, 3]
+ assert captured["fat_dict"] == {"fatigue": "properties"}
+ assert window._opt_results["line1"][2] == "updated fatigue"
diff --git a/tests/test_project_services.py b/tests/test_project_services.py
new file mode 100644
index 00000000..5cf39169
--- /dev/null
+++ b/tests/test_project_services.py
@@ -0,0 +1,987 @@
+import copy
+from pathlib import Path
+
+from anystruct import (
+ calc_loads,
+ calc_structure,
+ example_data,
+ line_structure,
+ project_application,
+ project_services,
+)
+from anystruct.project_state import ProjectState
+
+
+class DummyStructure:
+ def __init__(self, calculation_domain="Flat plate, stiffened"):
+ self.need_recalc = False
+ self.calculation_domain = calculation_domain
+ self.properties = None
+
+ def set_main_properties(self, properties):
+ self.properties = properties
+
+ def get_main_properties(self):
+ return {"domain": self.calculation_domain}
+
+
+class DummyFatigue:
+ def __init__(self):
+ self.main_properties = None
+
+ def set_main_properties(self, properties):
+ self.main_properties = properties
+
+ def get_fatigue_properties(self):
+ return {"fatigue": "properties"}
+
+
+class DummyLoad:
+ def __init__(self, name, value):
+ self._name = name
+ self._value = value
+
+ def get_name(self):
+ return self._name
+
+ def __str__(self):
+ return self._value
+
+ def get_load_parmeters(self):
+ return [self._name, self._value]
+
+
+class DummyCylinder:
+ def get_all_properties(self):
+ return {"shell": "properties"}
+
+
+class DummyTank:
+ def get_parameters(self):
+ return {"tank": "properties"}
+
+
+class DummySesamExport:
+ def __init__(self, points, lines, sections, line_to_struc):
+ self.points = points
+ self.lines = lines
+ self.sections = sections
+ self.line_bundles = line_to_struc
+ self.output_lines = []
+
+ def write_points(self):
+ self.output_lines.append("points\n")
+
+ def write_lines(self):
+ self.output_lines.append("lines\n")
+
+ def write_sections(self):
+ self.output_lines.append("sections\n")
+
+ def write_beams(self):
+ self.output_lines.append("beams\n")
+
+
+class DummyReport:
+ def __init__(self, filename, title, seconds, source_data):
+ self.arguments = (filename, title, seconds, source_data)
+ self.document_created = False
+ self.pdf_saved = False
+
+ def createDocument(self):
+ self.document_created = True
+
+ def savePDF(self):
+ self.pdf_saved = True
+
+ def createTable(self):
+ return ["table"]
+
+
+class DummyReportDocument:
+ def __init__(self, filename):
+ self.filename = filename
+ self.elements = None
+
+ def build(self, elements):
+ self.elements = elements
+
+
+class DummyExcelWorkbook:
+ def __init__(self, path, visible, read_only):
+ self.path = path
+ self.visible = visible
+ self.read_only = read_only
+ self.closed = False
+
+ def get_sheet_data(self, sheet):
+ return {
+ "flat_plate": [["flat header"], [f"flat {idx}" for idx in range(43)]],
+ "cylinder": [["cylinder header"], [f"cylinder {idx}" for idx in range(43)]],
+ }[sheet]
+
+ def close_book(self):
+ self.closed = True
+
+
+def test_project_edit_service_uses_typed_records_for_legacy_geometry_mutations():
+ points = {"point1": [0.0, 0.0], "point2": [1.0, 0.0]}
+ lines = {}
+ service = project_services.ProjectEditService(points, lines)
+
+ point = service.add_point(service.next_point_name(), (1.0, 1.0))
+ line = service.add_line("point1", "point3")
+
+ assert point == project_services.PointRecord(name="point3", x=1.0, y=1.0)
+ assert points["point3"] == [1.0, 1.0]
+ assert line == project_services.LineRecord(name="line1", first_point="point1", second_point="point3")
+ assert line.endpoint_keys == ("p1p3", "p3p1")
+ assert lines["line1"] == [1, 3]
+
+
+def test_project_edit_service_rejects_duplicate_points_and_reversed_lines():
+ points = {"point1": [0.0, 0.0], "point2": [1.0, 0.0]}
+ lines = {"line1": [1, 2]}
+ service = project_services.ProjectEditService(points, lines)
+
+ assert service.add_point(service.next_point_name(), (1.0, 0.0)) is None
+ assert service.add_line("point2", "point1") is None
+ assert lines == {"line1": [1, 2]}
+
+
+def test_project_edit_service_removes_connected_lines_and_points():
+ points = {"point1": [0.0, 0.0], "point2": [1.0, 0.0], "point3": [1.0, 1.0]}
+ lines = {"line1": [1, 2], "line2": [2, 3]}
+ service = project_services.ProjectEditService(points, lines)
+
+ assert service.connected_line_names("point2") == ["line1", "line2"]
+ assert service.remove_line("line1").endpoint_keys == ("p1p2", "p2p1")
+ assert service.remove_point("point1") == project_services.PointRecord("point1", 0.0, 0.0)
+ assert "line1" not in lines
+ assert "point1" not in points
+
+
+def test_recalculation_service_uses_named_bundle_access_and_skips_empty_slots():
+ assigned = DummyStructure()
+ line_bundles = {
+ "line1": [assigned, None, None, [], {}, None],
+ "line2": [None, None, None, [], {}, None],
+ }
+
+ invalidated = project_services.mark_lines_for_recalculation(line_bundles)
+
+ assert invalidated == ["line1"]
+ assert assigned.need_recalc
+
+
+def test_line_structure_service_assigns_and_updates_legacy_bundle_slots():
+ structure = DummyStructure()
+ line_bundles = {}
+ service = project_services.LineStructureService(line_bundles)
+
+ service.assign_structure("line1", structure, cylinder="shell")
+ service.update_structure_properties("line1", {"Plate": {"span": 1}})
+ service.set_cylinder("line1", "updated shell")
+
+ assert line_bundles["line1"] == [structure, None, None, [None], {}, "updated shell"]
+ assert structure.properties == {"Plate": {"span": 1}}
+ assert structure.need_recalc
+
+
+def test_line_structure_service_syncs_or_removes_fatigue_for_updated_domain():
+ fatigue = DummyFatigue()
+ line_bundles = {"line1": [DummyStructure(), None, fatigue, [], {}, None]}
+ service = project_services.LineStructureService(line_bundles)
+
+ service.sync_fatigue_after_structure_update("line1", {"Stiffener": {"spacing": 0.7}})
+ service.replace_structure("line1", DummyStructure("Flat plate, unstiffened"))
+ service.sync_fatigue_after_structure_update("line1", {"Stiffener": {"spacing": 1.0}})
+
+ assert fatigue.main_properties == {"spacing": 0.7}
+ assert line_bundles["line1"][2] is None
+
+
+def _flat_structure_property_request(calculation_domain="Flat plate, stiffened with girder"):
+ return project_services.FlatStructurePropertyRequest(
+ calculation_domain=calculation_domain,
+ base_values={
+ "material": 355,
+ "material_factor": 1.15,
+ "span": 4000,
+ "spacing": 700,
+ "plate_thk": 20,
+ "stf_web_h": 400,
+ "stf_web_t": 12,
+ "stf_fl_w": 150,
+ "stf_fl_t": 20,
+ "structure_type": "GENERAL_INTERNAL_WT",
+ "stf_type": "T",
+ "sigma_y1": 90,
+ "sigma_y2": 90,
+ "sigma_x1": 40,
+ "sigma_x2": 40,
+ "tau_xy": 5,
+ "plate_kpp": 1,
+ "stf_kps": 1,
+ "stf_km1": 12,
+ "stf_km2": 24,
+ "stf_km3": 12,
+ "pressure_side": "both sides",
+ "zstar_optimization": True,
+ "puls_method": "ultimate",
+ "puls_boundary": "Int",
+ "puls_stiffener_end": "Continuous",
+ "puls_sp_or_up": "SP",
+ "puls_up_boundary": "SSSS",
+ "panel_or_shell": "panel",
+ "girder_lg": 10000,
+ },
+ girder_values={
+ "web_h": 700,
+ "web_t": 15,
+ "fl_w": 250,
+ "fl_t": 25,
+ "type": "L",
+ },
+ buckling_values={
+ "min_pressure_adjacent_spans": 0,
+ "load_factor_stresses": 1,
+ "stiffener_end_support": "Continuous",
+ "girder_end_support": "Sniped",
+ "tension_field": "not allowed",
+ "plate_effective_against_sigy": True,
+ "buckling_length_factor_stf": 0,
+ "buckling_length_factor_girder": 0,
+ "km3": 12,
+ "km2": 24,
+ "girder_dist_lateral_support": 0,
+ "stiffener_dist_lateral_support": 0,
+ "panel_length": 0,
+ "fabrication_method_stiffener": "welded",
+ "fabrication_method_girder": "welded",
+ },
+ structure_types={"vertical": ["GENERAL_INTERNAL_WT"]},
+ )
+
+
+def test_flat_structure_property_service_builds_legacy_property_bundle_from_plain_values():
+ properties, stiffener_properties = project_services.FlatStructurePropertyService.build(
+ _flat_structure_property_request()
+ )
+
+ assert properties["main dict"]["calculation domain"] == ["Flat plate, stiffened with girder", ""]
+ assert properties["main dict"]["material yield"] == [355e6, "Pa"]
+ assert properties["Plate"]["span"] == [4.0, "m"]
+ assert properties["Plate"]["plate_thk"] == [0.02, "m"]
+ assert properties["Stiffener"]["spacing"] == [0.7, "m"]
+ assert properties["Girder"]["stf_web_height"] == [0.7, "m"]
+ assert properties["Girder"]["stf_type"] == ["L", ""]
+ assert stiffener_properties is properties["Stiffener"]
+
+
+def test_flat_structure_property_service_omits_stiffener_and_girder_for_unstiffened_domain():
+ properties, stiffener_properties = project_services.FlatStructurePropertyService.build(
+ _flat_structure_property_request("Flat plate, unstiffened")
+ )
+
+ assert properties["Stiffener"] is None
+ assert properties["Girder"] is None
+ assert stiffener_properties["stf_web_height"] == [0.4, "m"]
+
+
+def _cylinder_structure_property_request(load_mode=0):
+ return project_services.CylinderStructurePropertyRequest(
+ calculation_domain="Longitudinal Stiffened shell (Force input)",
+ dummy_values={
+ "span": 4000,
+ "plate_thk": 20,
+ "structure_type": "GENERAL_INTERNAL_WT",
+ "sigma_y1": 90,
+ "sigma_y2": 90,
+ "sigma_x1": 40,
+ "sigma_x2": 40,
+ "tau_xy": 5,
+ "plate_kpp": 1,
+ "stf_kps": 1,
+ "stf_km1": 12,
+ "stf_km2": 24,
+ "stf_km3": 12,
+ "pressure_side": "both sides",
+ "zstar_optimization": True,
+ "puls_method": "ultimate",
+ "puls_boundary": "Int",
+ "puls_stiffener_end": "Continuous",
+ "puls_sp_or_up": "SP",
+ "puls_up_boundary": "SSSS",
+ "panel_or_shell": "shell",
+ "material_factor": 1.15,
+ "spacing": 700,
+ },
+ shell_values={
+ "thickness": 20,
+ "radius": 5000,
+ "distance_between_rings": 5000,
+ "length": 5000,
+ "total_length": 5000,
+ "k_factor": 1.0,
+ },
+ longitudinal_values={
+ "spacing": 700,
+ "web_h": 450,
+ "web_t": 12,
+ "fl_w": 150,
+ "fl_t": 20,
+ "type": "T",
+ },
+ ring_stiffener_values={
+ "web_h": 0,
+ "web_t": 0,
+ "fl_w": 0,
+ "fl_t": 0,
+ "type": "T",
+ },
+ ring_frame_values={
+ "web_h": 0,
+ "web_t": 0,
+ "fl_w": 0,
+ "fl_t": 0,
+ "type": "T",
+ },
+ load_input={
+ "mode": load_mode,
+ "Nsd": 1000,
+ "Msd": 2000,
+ "Tsd": 3000,
+ "Qsd": 4000,
+ "sasd": 40,
+ "smsd": 195,
+ "tTsd": -12.7,
+ "tQsd": 4.8,
+ "psd": 0.2,
+ "shsd": 0,
+ },
+ main_values={
+ "material_factor": 1.15,
+ "fab_method_ring_stiffener": "welded",
+ "fab_method_ring_frame": "welded",
+ "e_module": 210000000000,
+ "poisson": 0.3,
+ "yield": 355,
+ "length_between_girders": 5000,
+ "panel_spacing": 700,
+ "ring_stiffener_excluded": True,
+ "ring_frame_excluded": True,
+ "uls_or_als": "ULS",
+ "end_cap_pressure": False,
+ },
+ structure_types={"vertical": ["GENERAL_INTERNAL_WT"]},
+ )
+
+
+def test_cylinder_structure_property_service_builds_legacy_property_bundle_from_stress_values():
+ result = project_services.CylinderStructurePropertyService.build(
+ _cylinder_structure_property_request(load_mode=0)
+ )
+
+ assert result.geometry == 3
+ assert result.main_dict["geometry"] == [3, ""]
+ assert result.main_dict["sasd"] == [40e6, "Pa"]
+ assert result.main_dict["smsd"] == [195e6, "Pa"]
+ assert result.main_dict["tTsd"] == [12.7e6, "Pa"]
+ assert result.main_dict["psd"] == [0.2e6, "Pa"]
+ assert result.shell_dict["plate_thk"] == [0.02, "m"]
+ assert result.shell_dict["radius"] == [5.0, "m"]
+ assert result.longitudinal_dict["spacing"] == [0.7, "m"]
+ assert result.longitudinal_dict["stf_web_height"] == [0.45, "m"]
+ assert result.ring_frame_dict["span"] == [4.0, "m"]
+ assert result.derived_stresses == (40, 195, 12.7, 4.8, 0)
+ assert len(result.derived_forces) == 4
+
+
+def test_cylinder_structure_property_service_derives_stresses_from_force_values():
+ result = project_services.CylinderStructurePropertyService.build(
+ _cylinder_structure_property_request(load_mode=1)
+ )
+
+ assert len(result.derived_stresses) == 5
+ assert result.derived_forces == (1000, 2000, 3000, 4000)
+ assert result.main_dict["sasd"][0] == result.derived_stresses[0] * 1e6
+ assert result.main_dict["shsd"][0] == result.derived_stresses[4] * 1e6
+
+
+def _cylinder_excel_import_defaults(end_cap_pressure="not included in axial force"):
+ return project_services.CylinderExcelImportDefaults(
+ plate_thk=20,
+ structure_type="GENERAL_INTERNAL_WT",
+ sigma_y1=90,
+ sigma_y2=90,
+ sigma_x1=40,
+ sigma_x2=40,
+ tau_xy=5,
+ plate_kpp=1,
+ stf_kps=1,
+ stf_km1=12,
+ stf_km2=24,
+ stf_km3=12,
+ pressure_side="both sides",
+ zstar_optimization=True,
+ puls_method="ultimate",
+ puls_boundary="Int",
+ puls_stiffener_end="Continuous",
+ puls_sp_or_up="SP",
+ puls_up_boundary="SSSS",
+ panel_or_shell="shell",
+ material_factor=1.15,
+ design_pressure=-0.2,
+ shear_stress=0,
+ e_module=210000000000,
+ poisson=0.3,
+ length_between_girders=2500,
+ fab_method_ring_stiffener=1,
+ fab_method_ring_frame=2,
+ end_cap_pressure=end_cap_pressure,
+ structure_types={"vertical": ["GENERAL_INTERNAL_WT"]},
+ )
+
+
+def _cylinder_excel_import_record(
+ *,
+ stress_values=(40, 195, -12.7, 4.8, 0.2, 0),
+ force_values=(None, None, None, None),
+ ring_stiffener_values=(300, 12, 120, 20, "T"),
+ ring_frame_values=(400, 14, 160, 22, 5000, "L"),
+):
+ return project_services.CylinderExcelImportRecord(
+ calculation_domain="Longitudinal Stiffened shell (Force input)",
+ first_point=(0, 0),
+ second_point=(0, 5000),
+ shell_values=(20, 5000, 5000, 5000, 5000, 1.0, 1.25),
+ longitudinal_values=(450, 12, 150, 20, 700, "T"),
+ ring_stiffener_values=ring_stiffener_values,
+ ring_frame_values=ring_frame_values,
+ stress_values=stress_values,
+ force_values=force_values,
+ end_values=("ULS", 460, "Fabricated", "Cold formed"),
+ )
+
+
+def test_cylinder_excel_import_property_service_maps_stress_record_to_cylinder_request():
+ request = project_services.CylinderExcelImportPropertyService.build_request(
+ _cylinder_excel_import_record(),
+ _cylinder_excel_import_defaults(),
+ )
+ result = project_services.CylinderStructurePropertyService.build(request)
+
+ assert request.load_input["mode"] == 2
+ assert request.dummy_values["span"] == 5000
+ assert request.shell_values["thickness"] == 20
+ assert request.longitudinal_values["spacing"] == 700
+ assert request.main_values["material_factor"] == 1.25
+ assert request.main_values["yield"] == 460
+ assert request.main_values["ring_stiffener_excluded"] is False
+ assert request.main_values["ring_frame_excluded"] is False
+ assert result.main_dict["sasd"] == [40e6, "Pa"]
+ assert result.main_dict["mat_yield"] == [460e6, "Pa"]
+ assert result.shell_dict["radius"] == [5.0, "m"]
+
+
+def test_cylinder_excel_import_property_service_prefers_force_values_when_present():
+ request = project_services.CylinderExcelImportPropertyService.build_request(
+ _cylinder_excel_import_record(force_values=(1000, 2000, 3000, 4000)),
+ _cylinder_excel_import_defaults(),
+ )
+ result = project_services.CylinderStructurePropertyService.build(request)
+
+ assert request.load_input["mode"] == 1
+ assert result.derived_forces == (1000, 2000, 3000, 4000)
+ assert result.main_dict["psd"] == [0.2e6, "Pa"]
+
+
+def test_cylinder_excel_import_property_service_excludes_missing_ring_components():
+ request = project_services.CylinderExcelImportPropertyService.build_request(
+ _cylinder_excel_import_record(
+ ring_stiffener_values=(None, None, None, None, None),
+ ring_frame_values=(None, None, None, None, None, None),
+ ),
+ _cylinder_excel_import_defaults(),
+ )
+ result = project_services.CylinderStructurePropertyService.build(request)
+
+ assert request.main_values["ring_stiffener_excluded"] is True
+ assert request.main_values["ring_frame_excluded"] is True
+ assert request.ring_stiffener_values["web_h"] == 0
+ assert request.ring_frame_values["web_h"] == 0
+ assert result.ring_stiffener_dict["stf_web_height"] == [0.0, "m"]
+ assert result.ring_frame_dict["stf_web_height"] == [0.0, "m"]
+
+
+def test_cylinder_excel_import_property_service_uses_default_end_cap_pressure_not_shell_yield():
+ request = project_services.CylinderExcelImportPropertyService.build_request(
+ _cylinder_excel_import_record(),
+ _cylinder_excel_import_defaults(end_cap_pressure="included in axial force"),
+ )
+ result = project_services.CylinderStructurePropertyService.build(request)
+
+ assert request.main_values["yield"] == 460
+ assert request.main_values["end_cap_pressure"] == "included in axial force"
+ assert result.main_dict["end cap pressure"] == ["included in axial force", ""]
+ assert result.main_dict["end cap pressure"][0] != request.main_values["yield"]
+
+
+def test_line_load_service_rebuilds_line_loads_and_reports_changed_lines():
+ first_structure = DummyStructure()
+ second_structure = DummyStructure()
+ line_bundles = {
+ "line1": [first_structure, None, None, ["old"], {}, None],
+ "line2": [second_structure, None, None, ["old"], {}, None],
+ }
+ changed_load = DummyLoad("changed", "new pressure")
+ new_load = DummyLoad("new", "fresh pressure")
+ previous_loads = {"changed": [DummyLoad("changed", "old pressure"), ["line1"]]}
+ current_loads = {
+ "changed": [changed_load, ["line1"]],
+ "new": [new_load, ["line2"]],
+ }
+
+ result = project_services.LineLoadService(line_bundles).rebuild_line_loads(
+ ["line1", "line2", "line3"], current_loads, previous_loads)
+
+ assert result.invalidated_lines == ("line1", "line2")
+ assert result.changed_lines == ("line1", "line2")
+ assert line_bundles["line1"][3] == [changed_load]
+ assert line_bundles["line2"][3] == [new_load]
+ assert first_structure.need_recalc
+ assert second_structure.need_recalc
+
+
+def test_line_pressure_service_calculates_combinations_from_plain_load_factors():
+ base_bundle = example_data.get_line_to_struc()["line1"]
+ load = calc_loads.Loads(example_data.loa_uls)
+ line_input = project_services.LinePressureInput(
+ line_name="line1",
+ line_bundle=[base_bundle[0], None, None, [load], {}, None],
+ coordinate=(4.0, 0.0),
+ defined_tanks=(),
+ accelerations={"static": 9.81, "dyn_loaded": 3.0, "dyn_ballast": 3.0},
+ load_factors=project_services.load_factor_records(
+ {
+ ("dnva", "line1", load.get_name()): (1.2, 0.7, 1),
+ ("dnvb", "line1", load.get_name()): (1.0, 1.3, 1),
+ ("tanktest", "line1", load.get_name()): (1.0, 0.0, 1),
+ ("manual", "line1", "manual"): (11.0, 2.0, 1),
+ }
+ ),
+ )
+
+ results = project_services.LinePressureService.calculate_combinations(line_input)
+ highest = project_services.LinePressureService.highest_pressure(line_input)
+
+ assert results["dnva"][0] > 0
+ assert results["dnvb"][0] > 0
+ assert results["manual"] == [22.0]
+ assert highest["normal"] == max(max(values) for key, values in results.items() if key != "slamming")
+ assert highest["slamming"] is None
+
+
+def test_sesam_export_service_builds_supported_lines_from_plain_request():
+ captured = {}
+
+ def export_factory(points, lines, sections, line_to_struc):
+ captured["request"] = (points, lines, sections, line_to_struc)
+ return DummySesamExport(points, lines, sections, line_to_struc)
+
+ request = project_services.SesamExportRequest(
+ points={"point1": [0, 0]},
+ lines={"line1": [1, 2]},
+ sections=("T",),
+ line_bundles={"line1": ["bundle"]},
+ )
+
+ lines = project_services.SesamExportService.build_js_lines(request, export_factory)
+
+ assert captured["request"] == (request.points, request.lines, request.sections, request.line_bundles)
+ assert lines == ("points\n", "lines\n", "sections\n", "beams\n")
+
+
+def test_sesam_export_service_writes_supported_lines_to_path(tmp_path):
+ request = project_services.SesamExportRequest(
+ points={"point1": [0, 0]},
+ lines={"line1": [1, 2]},
+ sections=("T",),
+ line_bundles={"line1": ["bundle"]},
+ )
+
+ export_path = project_services.SesamExportService.write_js_path(
+ request,
+ tmp_path / "geometry.js",
+ DummySesamExport,
+ )
+
+ assert export_path == tmp_path / "geometry.js"
+ assert export_path.read_text(encoding="utf-8") == "points\nlines\nsections\nbeams\n"
+
+
+def test_excel_project_import_service_reads_supported_sheets_and_closes_workbook():
+ workbooks = []
+
+ def workbook_factory(path, visible, read_only):
+ workbook = DummyExcelWorkbook(path, visible, read_only)
+ workbooks.append(workbook)
+ return workbook
+
+ import_data = project_services.ExcelProjectImportService.read_path(
+ Path("import.xlsx"),
+ workbook_factory=workbook_factory,
+ )
+
+ assert import_data.flat_plate_rows[0] == ["flat header"]
+ assert import_data.cylinder_rows[0] == ["cylinder header"]
+ assert import_data.flat_plate_records[0].first_point == ("flat 1", "flat 2")
+ assert import_data.flat_plate_records[0].plate_values == tuple(f"flat {idx}" for idx in range(5, 11))
+ assert import_data.flat_plate_records[0].manual_pressure == "flat 16"
+ assert import_data.cylinder_records[0].shell_values == tuple(
+ f"cylinder {idx}" for idx in range(5, 12)
+ )
+ assert import_data.cylinder_records[0].end_values == tuple(
+ f"cylinder {idx}" for idx in range(39, 43)
+ )
+ assert workbooks[0].path == "import.xlsx"
+ assert not workbooks[0].visible
+ assert workbooks[0].read_only
+ assert workbooks[0].closed
+
+
+def test_excel_project_import_service_opens_example_workbook_visibly():
+ workbook = project_services.ExcelProjectImportService.open_example_path(
+ "example.xlsx",
+ workbook_factory=DummyExcelWorkbook,
+ )
+
+ assert workbook.path == "example.xlsx"
+ assert workbook.visible
+ assert workbook.read_only
+ assert not workbook.closed
+
+
+def test_excel_project_geometry_import_service_adds_typed_record_geometry_in_meters():
+ first = project_services.FlatPlateExcelImportRecord(
+ calculation_domain="Flat plate, stiffened",
+ first_point=(0, 0),
+ second_point=(3000, 0),
+ plate_values=(),
+ stress_values=(),
+ manual_pressure=0,
+ girder_values=(),
+ buckling_values=(),
+ )
+ second = project_services.CylinderExcelImportRecord(
+ calculation_domain="Longitudinal Stiffened shell (Stress input)",
+ first_point=(3000, 0),
+ second_point=(3000, 5000),
+ shell_values=(),
+ longitudinal_values=(),
+ ring_stiffener_values=(),
+ ring_frame_values=(),
+ stress_values=(),
+ force_values=(),
+ end_values=(),
+ )
+ points, lines = {}, {}
+
+ result = project_services.ExcelProjectGeometryImportService.add_records(
+ points,
+ lines,
+ (first, second),
+ )
+
+ assert points == {
+ "point1": [0.0, 0.0],
+ "point2": [3.0, 0.0],
+ "point3": [3.0, 5.0],
+ }
+ assert lines == {"line1": [1, 2], "line2": [2, 3]}
+ assert tuple(point.name for point in result.created_points) == ("point1", "point2", "point3")
+ assert tuple(imported.line.name for imported in result.imported_lines) == ("line1", "line2")
+ assert result.imported_lines[0].record is first
+ assert result.imported_lines[1].record is second
+
+
+def test_report_request_service_runs_pdf_and_table_adapters():
+ request = project_services.ReportRequest("results.pdf", "Section results", 10, {"plain": "data"})
+
+ pdf_report = project_services.ReportRequestService.create_pdf(request, DummyReport)
+ table_document = project_services.ReportRequestService.create_table(
+ request,
+ DummyReport,
+ DummyReportDocument,
+ )
+
+ assert pdf_report.arguments == ("results.pdf", "Section results", 10, {"plain": "data"})
+ assert pdf_report.document_created
+ assert pdf_report.pdf_saved
+ assert table_document.filename == "results.pdf"
+ assert table_document.elements == ["table"]
+
+
+def test_report_data_snapshot_keeps_plain_report_input_data():
+ snapshot = project_services.ReportDataSnapshot(
+ project_information="Project text",
+ buckling_method="ML-Numeric (PULS based)",
+ points={"point1": [0, 0]},
+ lines={"line1": [1, 2]},
+ line_bundles={"line1": ["bundle"]},
+ tanks={"comp1": "tank"},
+ loads={"load1": "load"},
+ result_state={"colors": {"line1": "green"}},
+ highest_pressures={"line1": {"normal": 3}},
+ ml_classes=("ok", "not ok"),
+ )
+
+ assert snapshot.project_information == "Project text"
+ assert snapshot.buckling_method == "ML-Numeric (PULS based)"
+ assert snapshot.points == {"point1": [0, 0]}
+ assert snapshot.lines == {"line1": [1, 2]}
+ assert snapshot.line_bundles == {"line1": ["bundle"]}
+ assert snapshot.tanks == {"comp1": "tank"}
+ assert snapshot.loads == {"load1": "load"}
+ assert snapshot.result_state == {"colors": {"line1": "green"}}
+ assert snapshot.highest_pressures == {"line1": {"normal": 3}}
+ assert snapshot.ml_classes == ("ok", "not ok")
+
+
+def test_project_services_keeps_project_application_compatibility_exports():
+ assert project_services.ProjectFileCodec is project_application.ProjectFileCodec
+ assert project_services.ProjectPersistenceService is project_application.ProjectPersistenceService
+ assert project_services.ProjectFileDialogService is project_application.ProjectFileDialogService
+ assert project_services.ProjectOpenService is project_application.ProjectOpenService
+ assert project_services.ProjectSaveService is project_application.ProjectSaveService
+ assert project_services.ProjectSnapshotService is project_application.ProjectSnapshotService
+ assert project_services.ProjectHydrationService is project_application.ProjectHydrationService
+ assert project_services.ProjectOpenTransferService is project_application.ProjectOpenTransferService
+
+
+def test_project_snapshot_service_serializes_domain_objects_without_tkinter():
+ line_bundles = {
+ "line1": [DummyStructure(), None, DummyFatigue(), [], {}, DummyCylinder()],
+ "line2": [DummyStructure("Flat plate, unstiffened"), None, None, [], {}, None],
+ }
+ load_assignments = {"load1": [DummyLoad("loaded", "pressure"), ["line1"]]}
+ load_combinations = [
+ project_application.LoadCombinationRecord(("loaded", "ULS"), 1.2, 0.7, 1),
+ project_application.LoadCombinationRecord(("manual", "FLS"), 0.9, 0.5, 0),
+ ]
+
+ state = project_application.ProjectSnapshotService.create_state(
+ project_information="Snapshot service",
+ theme="dark",
+ points={"point1": [0.0, 0.0]},
+ lines={"line1": [1, 2]},
+ line_bundles=line_bundles,
+ load_assignments=load_assignments,
+ accelerations={"static": 9.81},
+ load_combinations=load_combinations,
+ tanks={"comp2": DummyTank()},
+ tank_grid=[[0, 1]],
+ tank_search_data={"2": {"min_el": 0}},
+ buckling_method="ML-Numeric (PULS based)",
+ shifting={"shifted checked": True, "shift hor": 1, "shift ver": 2},
+ weight_and_cog={"new structure": {"weight": 3}},
+ )
+
+ assert state.structures == {
+ "line1": {"domain": "Flat plate, stiffened"},
+ "line2": {"domain": "Flat plate, unstiffened"},
+ }
+ assert state.shell_structures == {"line1": {"shell": "properties"}, "line2": None}
+ assert state.fatigue == {"line1": {"fatigue": "properties"}, "line2": None}
+ assert state.loads == {"load1": [["loaded", "pressure"], ["line1"]]}
+ assert state.tanks == {
+ "grid": [[0, 1]],
+ "search_data": {"2": {"min_el": 0}},
+ "comp2": {"tank": "properties"},
+ }
+ assert state.load_combinations == {
+ 0: [("loaded", "ULS"), 1.2, 0.7, 1],
+ 1: [("manual", "FLS"), 0.9, 0.5, 0],
+ }
+
+
+def test_project_save_service_creates_state_from_plain_save_input():
+ save_input = project_application.ProjectSaveInput(
+ project_information="Save service",
+ theme="dark",
+ points={"point1": [0.0, 0.0]},
+ lines={"line1": [1, 2]},
+ line_bundles={"line1": [DummyStructure(), None, None, [], {}, None]},
+ load_assignments={"load1": [DummyLoad("loaded", "pressure"), ["line1"]]},
+ accelerations={"static": 9.81},
+ load_combinations=(
+ project_application.LoadCombinationRecord(("loaded", "ULS"), 1.2, 0.7, 1),
+ ),
+ tanks={"comp2": DummyTank()},
+ tank_grid=[[0, 1]],
+ tank_search_data={"2": {"min_el": 0}},
+ buckling_method="DNV-RP-C201 - prescriptive",
+ shifting={"shifted checked": True},
+ weight_and_cog={"new structure": {"weight": [1]}},
+ )
+
+ state = project_application.ProjectSaveService.create_state(save_input)
+
+ assert state.project_information == "Save service"
+ assert state.structures == {"line1": {"domain": "Flat plate, stiffened"}}
+ assert state.loads == {"load1": [["loaded", "pressure"], ["line1"]]}
+ assert state.load_combinations == {0: [("loaded", "ULS"), 1.2, 0.7, 1]}
+ assert state.tanks["comp2"] == {"tank": "properties"}
+
+
+def test_project_hydration_service_rebuilds_saved_structures_loads_and_cylinders():
+ base_bundle = example_data.get_line_to_struc()["line1"]
+ load = calc_loads.Loads(example_data.loa_uls)
+ cylinder = calc_structure.CylinderAndCurvedPlate(
+ main_dict=example_data.shell_main_dict,
+ shell=calc_structure.Shell(example_data.shell_dict),
+ long_stf=calc_structure.Structure(example_data.obj_dict_cyl_long2),
+ ring_stf=None,
+ ring_frame=None,
+ )
+ state = project_application.ProjectSnapshotService.create_state(
+ project_information="hydrate",
+ theme="default",
+ points={"point1": [0.0, 0.0], "point2": [1.0, 0.0]},
+ lines={"line1": [1, 2]},
+ line_bundles={
+ "line1": [
+ base_bundle[0],
+ None,
+ example_data.get_fatigue_object(),
+ [],
+ {},
+ cylinder,
+ ]
+ },
+ load_assignments={"load1": [load, ["line1"]]},
+ accelerations={"static": 9.81},
+ load_combinations=[],
+ tanks={},
+ tank_grid=[],
+ tank_search_data=None,
+ buckling_method="DNV-RP-C201 - prescriptive",
+ shifting={},
+ weight_and_cog={},
+ )
+
+ hydrated = project_application.ProjectHydrationService.hydrate_objects(
+ state,
+ project_application.ProjectHydrationDefaults(structure_types=example_data.structure_types),
+ )
+ line_bundle = hydrated.line_bundles["line1"]
+
+ assert isinstance(line_structure.structure(line_bundle), calc_structure.AllStructure)
+ assert isinstance(line_structure.fatigue(line_bundle), calc_structure.CalcFatigue)
+ assert isinstance(line_structure.cylinder(line_bundle), calc_structure.CylinderAndCurvedPlate)
+ assert hydrated.load_assignments["load1"][0].get_name() == load.get_name()
+ assert line_structure.loads(line_bundle)[0].get_name() == load.get_name()
+ assert len(hydrated.section_properties) == 1
+
+
+def test_project_open_service_assembles_transfer_and_hydrated_objects():
+ base_bundle = example_data.get_line_to_struc()["line1"]
+ state = project_application.ProjectSnapshotService.create_state(
+ project_information="opened",
+ theme="dark",
+ points={"point1": [0.0, 0.0], "point2": [1.0, 0.0]},
+ lines={"line1": [1, 2]},
+ line_bundles={"line1": [base_bundle[0], None, None, [], {}, None]},
+ load_assignments={},
+ accelerations={"static": 10.0},
+ load_combinations=[],
+ tanks={},
+ tank_grid=[],
+ tank_search_data=None,
+ buckling_method="DNV-RP-C201 - prescriptive",
+ shifting={},
+ weight_and_cog={},
+ )
+
+ opened = project_application.ProjectOpenService.assemble(
+ state,
+ project_application.ProjectHydrationDefaults(structure_types=example_data.structure_types),
+ )
+
+ assert opened.state is state
+ assert opened.transfer.project_information == "opened"
+ assert opened.transfer.accelerations["static"] == 10.0
+ assert isinstance(
+ line_structure.structure(opened.hydration.line_bundles["line1"]),
+ calc_structure.AllStructure,
+ )
+
+
+def test_project_hydration_service_migrates_old_flat_structure_defaults():
+ legacy_structure = copy.deepcopy(example_data.obj_dict)
+ legacy_structure.pop("structure_types")
+ legacy_structure["sigma_x"] = [80, "MPa"]
+ legacy_structure.pop("sigma_x1")
+ legacy_structure.pop("sigma_x2")
+ state = ProjectState(
+ points={"point1": [0.0, 0.0], "point2": [1.0, 0.0]},
+ lines={"line1": [1, 2]},
+ structures={"line1": legacy_structure},
+ fatigue={"line1": None},
+ )
+
+ hydrated = project_application.ProjectHydrationService.hydrate_objects(
+ state,
+ project_application.ProjectHydrationDefaults(structure_types=example_data.structure_types),
+ )
+ line_structure_object = line_structure.structure(hydrated.line_bundles["line1"])
+
+ assert isinstance(line_structure_object, calc_structure.AllStructure)
+ assert line_structure_object.Stiffener is not None
+ assert line_structure_object.Plate.sigma_x1 == 80
+ assert hydrated.section_properties[0]["structure_types"][0] == example_data.structure_types
+
+
+def test_project_open_transfer_service_normalizes_saved_view_payloads():
+ state = ProjectState(
+ project_information="Open transfer",
+ theme="dark",
+ points={"point1": [0.0, 0.0]},
+ lines={"line1": [1, 2]},
+ accelerations={"static": 10.0},
+ load_combinations={
+ 0: [["dnva", "line1", "load1"], 1.2, 0.7, 1],
+ 1: [["legacy", "line1", "load2"], 0.8, 0],
+ },
+ tanks={
+ "grid": [[0, 1]],
+ "search_data": {"2": {"min_el": 0}},
+ "comp2": {"tank": "properties"},
+ },
+ buckling_method="ML-Numeric (PULS based)",
+ weight_and_cog={"new structure": {"weight": [1]}},
+ )
+
+ transfer = project_application.ProjectOpenTransferService.create_transfer(state)
+
+ assert transfer.accelerations == {"static": 10.0, "dyn_loaded": 0, "dyn_ballast": 0}
+ assert transfer.load_combinations == (
+ project_application.OpenLoadCombinationRecord(("dnva", "line1", "load1"), 1.2, 0.7, 1),
+ project_application.OpenLoadCombinationRecord(("legacy", "line1", "load2"), 0.8, 0),
+ )
+ assert transfer.load_combinations[0].has_include
+ assert not transfer.load_combinations[1].has_include
+ assert transfer.tank_grid == [[0, 1]]
+ assert transfer.tank_search_data == {2: {"min_el": 0}}
+ assert transfer.tank_properties == {"comp2": {"tank": "properties"}}
+ assert transfer.buckling_method == "ML-Numeric (PULS based)"
+ assert transfer.weight_and_cog == {"new structure": {"weight": [1]}}
+
+
+def test_project_open_transfer_service_ignores_invalid_tank_search_payload():
+ transfer = project_application.ProjectOpenTransferService.create_transfer(
+ ProjectState(tanks={"search_data": ["not", "a mapping"]})
+ )
+
+ assert transfer.tank_search_data is None
diff --git a/tests/test_project_state.py b/tests/test_project_state.py
new file mode 100644
index 00000000..34eb0c34
--- /dev/null
+++ b/tests/test_project_state.py
@@ -0,0 +1,323 @@
+import io
+import json
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import pytest
+
+from anystruct import project_application, project_io
+from anystruct.project_state import PROJECT_FORMAT_VERSION, ProjectState
+
+
+def test_project_state_round_trips_compatible_json_shape_without_external_puls_results():
+ legacy = {
+ "project information": "demo",
+ "theme": "dark",
+ "point_dict": {"point1": [0, 0]},
+ "line_dict": {"line1": [1, 2]},
+ "structure_properties": {"line1": {"Plate": {}}},
+ "shell structure properties": {},
+ "load_properties": {},
+ "accelerations_dict": {"static": 9.81},
+ "load_combinations": {},
+ "tank_properties": {},
+ "fatigue_properties": {},
+ "buckling method": "DNV-RP-C201 - prescriptive",
+ "shifting": {"shifted checked": False},
+ "Weight and COG": {"new structure": {}},
+ "PULS results": {"line1": {"obsolete": True}, "sheet location": "dead.xlsm"},
+ }
+
+ state = ProjectState.from_legacy_mapping(legacy)
+ written = io.StringIO()
+ project_io.dump_project_state(state, written)
+ payload = json.loads(written.getvalue())
+
+ assert payload["format version"] == PROJECT_FORMAT_VERSION
+ assert payload["point_dict"] == legacy["point_dict"]
+ assert "PULS results" not in payload
+
+
+def test_project_io_loads_old_puls_selected_projects_with_supported_method():
+ old_project = io.StringIO(
+ json.dumps(
+ {
+ "point_dict": {},
+ "line_dict": {},
+ "structure_properties": {},
+ "PULS results": {"sheet location": "obsolete.xlsm"},
+ "buckling method": "DNV PULS",
+ }
+ )
+ )
+
+ state = project_io.load_project_state(old_project)
+
+ assert state.buckling_method == "DNV-RP-C201 - prescriptive"
+ assert "PULS results" not in state.to_legacy_mapping()
+
+
+def test_project_io_codec_migrates_legacy_payloads_at_the_file_boundary():
+ legacy = {
+ "format version": 0,
+ "point_dict": {},
+ "line_dict": {},
+ "structure_properties": {},
+ "PULS results": {"sheet location": "obsolete.xlsm"},
+ "buckling method": "DNV PULS",
+ "custom adapter state": {"kept": True},
+ }
+
+ state = project_io.decode_project_mapping(legacy)
+ payload = project_io.encode_project_mapping(state)
+
+ assert legacy["format version"] == 0
+ assert "PULS results" in legacy
+ assert state.format_version == PROJECT_FORMAT_VERSION
+ assert state.buckling_method == "DNV-RP-C201 - prescriptive"
+ assert payload["format version"] == PROJECT_FORMAT_VERSION
+ assert payload["custom adapter state"] == {"kept": True}
+ assert "PULS results" not in payload
+
+
+def test_project_io_migrates_deactivated_ml_cl_to_ml_numeric():
+ legacy = {
+ "format version": 0,
+ "point_dict": {},
+ "line_dict": {},
+ "structure_properties": {},
+ "buckling method": "ML-CL (SemiAnalytical based)",
+ }
+
+ state = project_io.decode_project_mapping(legacy)
+
+ assert state.buckling_method == "ML-Numeric (PULS based)"
+
+
+def test_project_file_codec_is_the_versioned_migration_entry_point():
+ legacy = {
+ "format version": 0,
+ "buckling method": "DNV PULS",
+ "PULS results": {"sheet location": "obsolete.xlsm"},
+ "custom adapter state": {"kept": True},
+ }
+
+ migrated = project_io.ProjectFileCodec.migrate_mapping(legacy)
+ state = project_io.ProjectFileCodec.decode_mapping(legacy)
+ payload = project_io.ProjectFileCodec.encode_mapping(state)
+
+ assert legacy["format version"] == 0
+ assert legacy["buckling method"] == "DNV PULS"
+ assert "PULS results" in legacy
+ assert migrated["format version"] == PROJECT_FORMAT_VERSION
+ assert migrated["buckling method"] == "DNV-RP-C201 - prescriptive"
+ assert "PULS results" not in migrated
+ assert state.buckling_method == "DNV-RP-C201 - prescriptive"
+ assert payload["custom adapter state"] == {"kept": True}
+
+
+def test_project_file_codec_rejects_future_project_formats():
+ future_project = {
+ "format version": PROJECT_FORMAT_VERSION + 1,
+ "point_dict": {},
+ }
+
+ with pytest.raises(ValueError, match="newer than supported"):
+ project_io.ProjectFileCodec.decode_mapping(future_project)
+
+
+def test_project_state_keeps_plain_legacy_mapping_while_codec_migrates_file_fields():
+ state = ProjectState.from_legacy_mapping(
+ {
+ "buckling method": "DNV PULS",
+ "PULS results": {"line1": {"obsolete": True}},
+ }
+ )
+
+ raw_payload = state.to_legacy_mapping()
+ encoded_payload = project_io.ProjectFileCodec.encode_mapping(state)
+
+ assert raw_payload["buckling method"] == "DNV PULS"
+ assert raw_payload["PULS results"] == {"line1": {"obsolete": True}}
+ assert encoded_payload["buckling method"] == "DNV-RP-C201 - prescriptive"
+ assert "PULS results" not in encoded_payload
+
+
+def test_real_legacy_ship_fixture_round_trips_without_external_puls_results():
+ fixture = Path(__file__).resolve().parents[1] / "anystruct" / "ship_section_example.txt"
+ with fixture.open(encoding="utf-8") as project_file:
+ state = project_io.load_project_state(project_file)
+
+ written = io.StringIO()
+ project_io.dump_project_state(state, written)
+ payload = json.loads(written.getvalue())
+
+ assert len(state.points) > 50
+ assert len(state.lines) > 70
+ assert "line14" in state.structures
+ assert state.loads
+ assert state.tanks
+ assert "PULS results" not in payload
+ assert "sheet location" not in written.getvalue()
+
+
+def test_project_persistence_service_saves_and_loads_paths_and_locates_backup():
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_path = Path(project_dir) / "project.txt"
+ root_dir = Path(project_dir) / "anystruct"
+ root_dir.mkdir()
+ state = ProjectState(project_information="phase 2", points={"point1": [0, 0]})
+
+ saved_path = project_application.ProjectPersistenceService.save_state_to_path(state, project_path)
+ loaded = project_application.ProjectPersistenceService.load_state_from_path(project_path)
+
+ assert saved_path == project_path
+ assert loaded.project_information == "phase 2"
+ assert loaded.points == {"point1": [0, 0]}
+ assert project_application.ProjectPersistenceService.backup_path(root_dir) == (
+ Path(project_dir) / "backup.txt"
+ )
+ assert not project_application.ProjectPersistenceService.backup_exists(root_dir)
+
+ project_application.ProjectPersistenceService.save_state_to_path(
+ state,
+ project_application.ProjectPersistenceService.backup_path(root_dir),
+ )
+
+ assert project_application.ProjectPersistenceService.backup_exists(root_dir)
+
+
+def test_project_persistence_service_uses_configured_file_codec(monkeypatch):
+ class FakeCodec:
+ saved_state = None
+ loaded_text = None
+
+ @classmethod
+ def dump(cls, state, project_file):
+ cls.saved_state = state
+ project_file.write('{"encoded": true}')
+
+ @classmethod
+ def load(cls, project_file):
+ cls.loaded_text = project_file.read()
+ return ProjectState(project_information="loaded through fake codec")
+
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_path = Path(project_dir) / "codec_project.txt"
+ monkeypatch.setattr(project_application.ProjectPersistenceService, "codec", FakeCodec)
+
+ state = ProjectState(project_information="codec save")
+ saved_path = project_application.ProjectPersistenceService.save_state_to_path(state, project_path)
+ loaded = project_application.ProjectPersistenceService.load_state_from_path(project_path)
+
+ assert saved_path == project_path
+ assert FakeCodec.saved_state is state
+ assert project_path.read_text(encoding="utf-8") == '{"encoded": true}'
+ assert FakeCodec.loaded_text == '{"encoded": true}'
+ assert loaded.project_information == "loaded through fake codec"
+
+
+def test_project_file_dialog_service_resolves_save_restore_and_example_targets():
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_dir = Path(project_dir)
+ root_dir = project_dir / "anystruct"
+ root_dir.mkdir()
+ local_example = project_dir / "local_example.txt"
+ local_example.write_text("{}", encoding="utf-8")
+ bundled_example = root_dir / "bundled_example.txt"
+ bundled_example.write_text("{}", encoding="utf-8")
+ bundled_workbook = root_dir / "excel_input_example.xlsx"
+ bundled_workbook.write_text("", encoding="utf-8")
+
+ selected = project_application.ProjectFileDialogService.selected_save_target(project_dir / "save.txt")
+ selected_output = project_application.ProjectFileDialogService.selected_output_target(project_dir / "report.pdf")
+ cancelled_output = project_application.ProjectFileDialogService.selected_output_target("")
+ selected_open = project_application.ProjectFileDialogService.selected_open_target(project_dir / "open.txt")
+ cancelled_open = project_application.ProjectFileDialogService.selected_open_target(None)
+ backup = project_application.ProjectFileDialogService.backup_save_target(root_dir)
+ remembered = project_application.ProjectFileDialogService.remembered_save_target(project_dir / "save.txt")
+ missing_remembered = project_application.ProjectFileDialogService.remembered_save_target(None)
+ missing_restore = project_application.ProjectFileDialogService.restore_target(root_dir)
+ project_application.ProjectPersistenceService.save_state_to_path(ProjectState(), backup.path)
+ restore = project_application.ProjectFileDialogService.restore_target(root_dir)
+ direct_example = project_application.ProjectFileDialogService.example_open_target(local_example, root_dir)
+ root_example = project_application.ProjectFileDialogService.example_open_target("bundled_example.txt", root_dir)
+ workbook_example = project_application.ProjectFileDialogService.example_open_target(
+ "excel_input_example.xlsx",
+ root_dir,
+ )
+
+ assert selected.path == project_dir / "save.txt"
+ assert selected.remember_as_last_save
+ assert selected_output.path == project_dir / "report.pdf"
+ assert not selected_output.remember_as_last_save
+ assert cancelled_output is None
+ assert selected_open.path == project_dir / "open.txt"
+ assert not selected_open.remember_as_last_save
+ assert cancelled_open is None
+ assert backup.path == project_dir / "backup.txt"
+ assert not backup.remember_as_last_save
+ assert remembered.path == project_dir / "save.txt"
+ assert missing_remembered is None
+ assert missing_restore is None
+ assert restore.path == project_dir / "backup.txt"
+ assert direct_example.path == local_example
+ assert root_example.path == bundled_example
+ assert workbook_example.path == bundled_workbook
+
+
+def test_project_persistence_service_wraps_invalid_project_json():
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_path = Path(project_dir) / "broken.txt"
+ project_path.write_text("{", encoding="utf-8")
+
+ with pytest.raises(project_application.ProjectPersistenceError, match="Could not load project file"):
+ project_application.ProjectPersistenceService.load_state_from_path(project_path)
+
+
+def test_project_save_service_returns_written_state_and_path():
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_path = Path(project_dir) / "save.txt"
+ save_result = project_application.ProjectSaveService.save_path(
+ project_path,
+ project_application.ProjectSaveInput(
+ project_information="save path",
+ theme="dark",
+ points={},
+ lines={},
+ line_bundles={},
+ load_assignments={},
+ accelerations={},
+ load_combinations=(),
+ tanks={},
+ tank_grid=[],
+ tank_search_data=None,
+ buckling_method="DNV-RP-C201 - prescriptive",
+ shifting={},
+ weight_and_cog={},
+ ),
+ )
+
+ loaded = project_application.ProjectPersistenceService.load_state_from_path(project_path)
+
+ assert save_result.path == project_path
+ assert save_result.state.project_information == "save path"
+ assert loaded.theme == "dark"
+
+
+def test_project_open_service_loads_path_before_creating_transfer_and_hydration():
+ with TemporaryDirectory(dir=Path.cwd()) as project_dir:
+ project_path = Path(project_dir) / "open.txt"
+ project_application.ProjectPersistenceService.save_state_to_path(
+ ProjectState(project_information="open path", theme="dark"),
+ project_path,
+ )
+
+ opened = project_application.ProjectOpenService.open_path(
+ project_path,
+ project_application.ProjectHydrationDefaults(structure_types={}),
+ )
+
+ assert opened.state.project_information == "open path"
+ assert opened.transfer.theme == "dark"
+ assert opened.hydration.line_bundles == {}
diff --git a/tests/test_supported_exports.py b/tests/test_supported_exports.py
new file mode 100644
index 00000000..fde2c19f
--- /dev/null
+++ b/tests/test_supported_exports.py
@@ -0,0 +1,72 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from anystruct import example_data, report_generator, sesam_interface
+
+
+class _LegacyReportStructure:
+ def __init__(self, structure):
+ self.Plate = structure.Plate
+ self.Stiffener = structure.Stiffener
+
+ def get_s(self):
+ return self.Plate.get_s()
+
+ def get_report_stresses(self):
+ return self.Plate.get_report_stresses()
+
+ def get_results_for_report(self):
+ return "Representative section results"
+
+
+def test_sesam_export_writes_geometry_sections_and_beams():
+ export = sesam_interface.JSfile(
+ example_data.get_point_dict(),
+ example_data.get_line_dict(),
+ example_data.get_section_list(),
+ line_to_struc=example_data.get_line_to_struc(),
+ )
+
+ export.write_points()
+ export.write_lines()
+ export.write_sections()
+ export.write_beams()
+
+ output = "".join(export.output_lines)
+ assert "point1 = Point(" in output
+ assert "line1 = CreateLineTwoPoints(" in output
+ assert "ANYbm" in output
+ assert ".section =" in output
+
+
+def test_summary_report_generation_contract(monkeypatch):
+ line_bundle = example_data.get_line_to_struc()["line1"]
+ report_structure = _LegacyReportStructure(line_bundle[0])
+
+ with TemporaryDirectory(dir=Path.cwd()) as report_dir:
+ report_path = Path(report_dir)
+ with monkeypatch.context() as report_context:
+ report_context.chdir(report_path)
+
+ report_generator.create_report(
+ {
+ "lines": {"line1": example_data.get_line_dict()["line1"]},
+ "calc_structure": {"line1": report_structure},
+ "calc_fatigue": {"line1": line_bundle[2]},
+ "pressures": {"line1": 0},
+ }
+ )
+
+ report = report_path / "Report_current_results.pdf"
+ assert report.is_file()
+ assert report.stat().st_size > 0
+
+
+def test_letter_report_renderer_reads_snapshot_shape_directly():
+ source = (Path(__file__).resolve().parents[1] / "anystruct" / "report_generator.py").read_text(
+ encoding="utf-8"
+ )
+
+ assert "self.data._" not in source
+ assert "self.data.get_color_and_calc_state(" not in source
+ assert "self.data.get_highest_pressure(" not in source